Фильтр намерений Android: связать приложение с расширением файла


93

У меня есть собственный тип / расширение файла, с которым я хочу связать свое приложение.

Насколько мне известно, элемент данных создан для этой цели, но я не могу заставить его работать. http://developer.android.com/guide/topics/manifest/data-element.html Согласно документации и множеству сообщений на форуме, это должно работать следующим образом:

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:mimeType="application/pdf" />
</intent-filter>

Что ж, не работает. Что я сделал не так? Я просто хочу объявить свой собственный тип файла.


Добавить android: label = "@ string / app_name" в фильтр намерений
Prashant

Ответы:


117

Вам нужно несколько фильтров намерений для решения различных ситуаций, с которыми вы хотите справиться.

Пример 1, обработка HTTP-запросов без mimetypes:

  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.BROWSABLE" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="http" />
    <data android:host="*" />
    <data android:pathPattern=".*\\.pdf" />
  </intent-filter>

Обработка с миметическими типами, где суффикс не имеет значения:

  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.BROWSABLE" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="http" />
    <data android:host="*" />
    <data android:mimeType="application/pdf" />
  </intent-filter>

Обработка намерений из приложения файлового браузера:

  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="file" />
    <data android:host="*" />
    <data android:pathPattern=".*\\.pdf" />
  </intent-filter>

Я подумал, что host = "*" можно опустить, но он стал слишком широким.
Чай Phyrum

1
@Phyrum Tea: есть ли способ связать расширение файла с ДРУГИМ приложением с помощью моего приложения? Мое приложение генерирует файлы .list, которые я хочу сообщить Android, что они являются текстовыми файлами, и открыть их с помощью текстового редактора (мое приложение НЕ является текстовым редактором).
Луис А. Флорит

@ LuisA.Florit вы пробовали называть свои файлы * .txt? Это может сработать.
Гавриил

пользовательское расширение не работает без MimeType. Если я указываю тип mimetype, как /, мое приложение отображается, когда я нажимаю на уведомление Gmail.
madan V

1
Не работает .. Мне надоело, теперь поиск за последние 2 дня не работал.
Ахмад Арслан

49

Другие решения не работали для меня надежно, пока я не добавил:

android:mimeType="*/*" 

До этого в некоторых приложениях работало, в некоторых нет ...

полное решение для меня:

<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <data android:scheme="file"  android:host="*" android:pathPattern=".*\\.EXT" android:mimeType="*/*"  />
</intent-filter>

1
Вот это да! это помогло мне. Похоже, что документация на андроид неверна. Все (схема, хост, путь [Pattern], mimeType) должны быть объявлены для работы
Гавриил

Если вы хотите быть полными, у вас должны быть оба правила с « mimeTypeи». См. Developer.android.com/guide/components/…
Эндрю Сан

1
Что делать, если вы хотите открыть файловое приложение из Gmail?
Игорь Ганапольский

32

Ответы Phyrum Tea и yuku уже очень информативны.

Хочу добавить, что начиная с Android 7.0 Nougat изменился способ обмена файлами между приложениями:

Из официальных изменений Android 7.0 :

Для приложений, ориентированных на Android 7.0, платформа Android применяет политику StrictMode API, которая запрещает раскрывать URI file: // за пределами вашего приложения. Если намерение, содержащее URI файла, покидает ваше приложение, приложение выдает ошибку с исключением FileUriExposedException.

Чтобы обмениваться файлами между приложениями, вы должны отправить URI content: // и предоставить временное разрешение на доступ к URI. Самый простой способ предоставить это разрешение - использовать класс FileProvider. Для получения дополнительной информации о разрешениях и совместном использовании файлов см. Совместное использование файлов.

Если у вас есть собственный файл, заканчивающийся без определенного mime-type(или, я думаю, даже с одним), вам, возможно, придется добавить второе schemeзначение к your, intent-filterчтобы он FileProvidersтоже работал .

Пример:

<intent-filter>
    <action android:name="android.intent.action.VIEW" />

    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="file" />
    <data android:scheme="content" />
    <data android:mimeType="*/*" />
    <!--
        Work around Android's ugly primitive PatternMatcher
        implementation that can't cope with finding a . early in
        the path unless it's explicitly matched.
    -->
    <data android:host="*" />
    <data android:pathPattern=".*\\.sfx" />
    <data android:pathPattern=".*\\..*\\.sfx" />
    <data android:pathPattern=".*\\..*\\..*\\.sfx" />
    <data android:pathPattern=".*\\..*\\..*\\..*\\.sfx" />
    <!-- keep going if you need more -->

</intent-filter>

Здесь важно добавить

<data android:scheme="content" />

к фильтру.

Мне было трудно узнать об этом небольшом изменении, из-за которого моя активность не открывалась на устройствах Android 7.0, в то время как на старых версиях все было хорошо. Надеюсь, это кому-то поможет.


Без этого совета и дополнительной информации я не смог бы зарегистрировать свое приложение в проводниках. Это очень важно, и за него следует проголосовать
u2tall

Вы сэкономили день! Большой tnx. Это должен быть принятый ответ!
Андрис

⚠️ Если вы попытаетесь получить файл, предоставленный через схему содержимого, например File(uri.path), он выйдет из строя из-за No such file or directory- придется обрабатывать этот сценарий по-другому при обновлении для поддержки Nougat +!
Jordan H

20

Мои выводы:

Вам нужно несколько фильтров, чтобы иметь дело с различными способами получения файла. т.е. с помощью вложения Gmail, с помощью файлового менеджера, по HTTP, по FTP ... Все они отправляют очень разные намерения.

И вам нужно отфильтровать намерение, которое запускает вашу активность в коде активности.

В приведенном ниже примере я создал поддельный файл типа new.mrz. И я получил его из вложения Gmail и файлового проводника.

Код активности добавлен в onCreate ():

        Intent intent = getIntent();
        String action = intent.getAction();

        if (action.compareTo(Intent.ACTION_VIEW) == 0) {
            String scheme = intent.getScheme();
            ContentResolver resolver = getContentResolver();

            if (scheme.compareTo(ContentResolver.SCHEME_CONTENT) == 0) {
                Uri uri = intent.getData();
                String name = getContentName(resolver, uri);

                Log.v("tag" , "Content intent detected: " + action + " : " + intent.getDataString() + " : " + intent.getType() + " : " + name);
                InputStream input = resolver.openInputStream(uri);
                String importfilepath = "/sdcard/My Documents/" + name; 
                InputStreamToFile(input, importfilepath);
            }
            else if (scheme.compareTo(ContentResolver.SCHEME_FILE) == 0) {
                Uri uri = intent.getData();
                String name = uri.getLastPathSegment();

                Log.v("tag" , "File intent detected: " + action + " : " + intent.getDataString() + " : " + intent.getType() + " : " + name);
                InputStream input = resolver.openInputStream(uri);
                String importfilepath = "/sdcard/My Documents/" + name; 
                InputStreamToFile(input, importfilepath);
            }
            else if (scheme.compareTo("http") == 0) {
                // TODO Import from HTTP!
            }
            else if (scheme.compareTo("ftp") == 0) {
                // TODO Import from FTP!
            }
        }

Фильтр вложений Gmail:

        <intent-filter android:label="@string/app_name">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="content" />
            <data android:mimeType="application/octet-stream" />
        </intent-filter>
  • ЖУРНАЛ: обнаружено намерение содержимого: android.intent.action.VIEW: content: //gmail-ls/l.foul@gmail.com/messages/2950/attachments/0.1/BEST/false: application / octet-stream: new. MRZ

Фильтр проводника файлов:

        <intent-filter android:label="@string/app_name">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="file" />
            <data android:pathPattern=".*\\.mrz" />
        </intent-filter>
  • ЖУРНАЛ: обнаружено намерение файла: android.intent.action.VIEW: file: ///storage/sdcard0/My%20Documents/new.mrz: null: new.mrz

HTTP-фильтр:

        <intent-filter android:label="@string/rbook_viewer">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="http" />
            <data android:pathPattern=".*\\.mrz" />
        </intent-filter>

Частные функции, использованные выше:

private String getContentName(ContentResolver resolver, Uri uri){
    Cursor cursor = resolver.query(uri, null, null, null, null);
    cursor.moveToFirst();
    int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
    if (nameIndex >= 0) {
        return cursor.getString(nameIndex);
    } else {
        return null;
    }
}

private void InputStreamToFile(InputStream in, String file) {
    try {
        OutputStream out = new FileOutputStream(new File(file));

        int size = 0;
        byte[] buffer = new byte[1024];

        while ((size = in.read(buffer)) != -1) {
            out.write(buffer, 0, size);
        }

        out.close();
    }
    catch (Exception e) {
        Log.e("MainActivity", "InputStreamToFile exception: " + e.getMessage());
    }
}

Должна ли активность быть лаунчера или основного типа? Потому что я попробовал фильтр вложений Gmail, но он не работает.
Амит

15

В pathPattern

<data android:pathPattern=".*\\.pdf" />

не работает, если путь к файлу содержит одну или несколько точек перед ".pdf".

Это будет работать:

<data android:pathPattern=".*\\.pdf" />
<data android:pathPattern=".*\\..*\\.pdf" />
<data android:pathPattern=".*\\..*\\..*\\.pdf" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.pdf" />

Добавьте больше, если хотите поддерживать больше точек.


1
На данный момент это единственный вариант для
входа в

Я использую тот же код, но IDE предупреждает меня, см. Этот вопрос stackoverflow.com/questions/35833776/…
AndreaF

5

Я пытался заставить это работать целую вечность и пробовал в основном все предлагаемые решения, но до сих пор не могу заставить Android распознавать определенные расширения файлов. У меня есть фильтр намерений с "*/*"типом mimetype, который, похоже, работает, и файловые браузеры теперь перечисляют мое приложение как вариант для открытия файлов, однако теперь мое приложение отображается как вариант для открытия ЛЮБОГО ВИДА файла, хотя Я указал определенные расширения файлов с помощью тега pathPattern. Это заходит так далеко, что даже когда я пытаюсь просмотреть / отредактировать контакт в моем списке контактов, Android спрашивает меня, хочу ли я использовать свое приложение для просмотра контакта, и это лишь одна из многих ситуаций, когда это происходит, ОЧЕНЬ ОЧЕНЬ раздражая.

В конце концов я нашел это сообщение групп Google с аналогичным вопросом, на который ответил настоящий разработчик фреймворка Android. Она объясняет, что Android просто ничего не знает о расширениях файлов, только MIME-типы ( https://groups.google.com/forum/#!topic/android-developers/a7qsSl3vQq0 ).

Итак, из того, что я видел, пробовал и читал, Android просто не может отличить расширения файлов, а тег pathPattern - это, по сути, гигантская трата времени и энергии. Если вам повезло, что вам нужны файлы только определенного типа mime (например, текст, видео или аудио), вы можете использовать фильтр намерения с типом mime. Однако если вам нужно конкретное расширение файла или mime-тип, неизвестный Android, вам не повезло.

Если я ошибаюсь в чем-либо из этого, скажите мне, до сих пор я читал все сообщения и пробовал все предлагаемые решения, которые мог найти, но ни одно не сработало.

Я мог бы написать еще одну или две страницы о том, насколько распространены подобные вещи в Android и насколько неприятен опыт разработчиков, но я спасу вас от своих гневных разглагольствований;). Надеюсь, я избавил кого-то от неприятностей.


5

Маркус Рессель прав. Android 7.0 Nougat больше не разрешает обмен файлами между приложениями с использованием URI файла. Необходимо использовать URI контента. Однако URI контента не позволяет использовать общий путь к файлу, только mime-тип. Таким образом, вы не можете использовать URI контента, чтобы связать приложение с собственным расширением файла.

Drobpox имеет интересное поведение на Android 7.0. Когда он встречает неизвестное расширение файла, он, кажется, формирует намерение URI файла, но вместо запуска намерения он вызывает операционную систему, чтобы узнать, какие приложения могут принять намерение. Если есть только одно приложение, которое может принять этот URI файла, оно затем отправляет явный URI контента непосредственно этому приложению. Таким образом, для работы с Dropbox вам не нужно менять фильтры намерений в вашем приложении. Он не требует фильтра намерений URI контента. Просто убедитесь, что приложение может получать URI контента, а ваше приложение с собственным расширением файла будет работать с Dropbox, как это было до Android 7.0.

Вот пример моего кода загрузки файла, измененного для приема URI контента:

Uri uri = getIntent().getData();
if (uri != null) {
    File myFile = null;
    String scheme = uri.getScheme();
    if (scheme.equals("file")) {
        String fileName = uri.getEncodedPath();
        myFile = new File(filename);
    }
    else if (!scheme.equals("content")) {
        //error
        return;
    }
    try {
        InputStream inStream;
        if (myFile != null) inStream = new FileInputStream(myFile);
        else inStream = getContentResolver().openInputStream(uri);
        InputStreamReader rdr = new InputStreamReader(inStream);
        ...
    }
}

2

Попробуйте, это поможет вам. Вместо pdf вы также можете использовать другие расширения. Сначала вам нужно добавить разрешение на чтение внешнего хранилища в файле androidmanifest.xml .

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Затем в файле androidmanifest в теге Activity вы добавляете фильтр намерений, как показано ниже.

            <action android:name="android.intent.action.SEND" />

            <action android:name="android.intent.action.VIEW" />

             <category android:name="android.intent.category.DEFAULT" />

            <data android:mimeType= "application/pdf" />

            <data android:host="*" />

        </intent-filter>

Наконец, в вашем коде вы получите путь к файлу pdf, как показано ниже:

Intent intent=getIntent();

if(intent!=null) {          

        String action=intent.getAction();

        String type=intent.getType();

        if(Intent.ACTION_VIEW.equals(action) && type.endsWith("pdf")) {

            // Get the file from the intent object

            Uri file_uri=intent.getData();

            if(file_uri!=null)

                filepath=file_uri.getPath();

            else

                filepath="No file";

        }

        else if(Intent.ACTION_SEND.equals(action) && type.endsWith("pdf")){

            Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);

            filepath = uri.getPath();

        }

Для этого Intent.ACTION_VIEW, когда невозможно получить путь к файлу, возникает ошибка: java.io.FileNotFoundException: Permission denied. Только из каких-то конкретных приложений, не каждый раз. любые решения?
Hardik Joshi

@HardikJoshi Возможно, приложения не устанавливают разрешение GRANT_READ_URI_PERMISSION.
Mira_Cole

1

Попробуйте добавить

<action android:name="android.intent.action.VIEW"/>

Теперь, когда он работает с заводскими типами файлов, такими как application / pdf, как мне объявить свой собственный тип файла? И когда я говорю тип файла, я имею в виду mimeType;)
Тамас

Какой файл вы хотите, чтобы этот mimetype перехватил? Кроме того, открывается ли этот файл из браузера или файлового менеджера или отправляется из другого приложения, которое вы создали?
magaio

может быть из браузера, почтового клиента, файлового менеджера или где угодно .. Или мое собственное приложение ofc :) расширение файла является настраиваемым, указанным клиентом.
Tamas

Ну, я все еще застрял ... Кто-нибудь может помочь?
Tamas

@ Тамас, ты все это разобрал. Я застрял на этом!
StuStirling 03

1

Для вложения Gmail вы можете использовать:

<intent-filter android:label="@string/app_name">
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <data android:scheme="content" />
  <data android:mimeType="application/pdf" /> <!-- .pdf -->
  <data android:mimeType="application/msword" /> <!-- .doc / .dot -->
  <data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" /> <!-- .docx -->
  <data android:mimeType="application/vnd.ms-excel" />  <!-- .xls / .xlt / .xla -->
  <data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />  <!-- .xlsx -->
  <data android:mimeType="application/vnd.ms-powerpoint" />  <!-- .ppt / .pps / .pot / .ppa -->
  <data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation" /> <!-- .pptx -->
  <data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.slideshow" /> <!-- .ppsx -->
  <data android:mimeType="application/zip" /> <!-- .zip -->
  <data android:mimeType="image/jpeg" /> <!-- .jpeg -->
  <data android:mimeType="image/png" /> <!-- .png -->
  <data android:mimeType="image/gif" /> <!-- .gif -->
  <data android:mimeType="text/plain" /> <!-- .txt / .text / .log / .c / .c++ / ... -->

Добавьте столько типов пантомимы, сколько необходимо. Мне они нужны только для моего проекта.


1
         <!--
            Works for Files, Drive and DropBox
        -->
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="file" />
            <data android:mimeType="*/*" />
            <data android:host="*" />
            <data android:pathPattern=".*\\.teamz" />
        </intent-filter>

        <!--
            Works for Gmail
        -->
        <intent-filter>
            <action android:name="android.intent.action.VIEW"/>
            <category android:name="android.intent.category.BROWSABLE" />
            <category android:name="android.intent.category.DEFAULT"/>
            <data android:host="gmail-ls" android:scheme="content" android:mimeType="application/octet-stream"/>
        </intent-filter>

Обратите внимание, что это приведет к тому, что ваше приложение откроет все вложения файлов Gmail, нет способа обойти это


Это будет обрабатывать каждый файл, отправленный пользователю по электронной почте, а не только .teamz
Игорь Ганапольский

1

Тем, у кого возникли проблемы с другими приложениями File Manager \ Explorer, как ответили @yuku и @ phyrum-tea

Это работает с приложением файлового менеджера LG по умолчанию.

     <intent-filter android:label="@string/app_name_decrypt">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="file" />
            <data android:pathPattern=".*\\.lock" />
            <data android:pathPattern=".*\\..*\\.lock" />
            <data android:pathPattern=".*\\..*\\..*\\.lock" />
        </intent-filter>

но не мог работать с ES File Explorer и другими файловыми менеджерами, поэтому я добавил

 android:mimeType="*/*"

тогда он работает с ES Explorer, но файловый менеджер LG не может определить тип файла, поэтому мое решение

     <intent-filter android:label="@string/app_name_decrypt">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="file" />
            <data android:pathPattern=".*\\.lock" />
            <data android:pathPattern=".*\\..*\\.lock" />
            <data android:pathPattern=".*\\..*\\..*\\.lock" />
        </intent-filter>
        <intent-filter android:label="@string/app_name_decrypt">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="file"/>
            <data android:scheme="content" />
            <data android:mimeType="*/*" />
            <data android:pathPattern=".*\\.lock" />
            <data android:pathPattern=".*\\..*\\.lock" />
            <data android:pathPattern=".*\\..*\\..*\\.lock" />
        </intent-filter>

1

URI контента ftw и фильтр намерений в манифесте ... если ваши файлы имеют настраиваемое расширение .xyz, добавьте соответствующий тип mime:

        <intent-filter>
            <action android:name="android.intent.action.VIEW" />

            <category android:name="android.intent.category.DEFAULT" />

            <data
                android:host="*"
                android:mimeType="application/xyz"
                android:scheme="content" />
        </intent-filter>

Некоторые приложения, такие как электронная почта, по-видимому, преобразовывают расширение в mime-тип. Теперь я могу щелкнуть вложение в электронном письме и открыть его в моем приложении.


1

Прочтите открывающий файл в котлине:

private fun checkFileOpening(intent: Intent) {
    if (intent.action == Intent.ACTION_VIEW && (intent.scheme == ContentResolver.SCHEME_FILE
                    || intent.scheme == ContentResolver.SCHEME_CONTENT)) {

        val text = intent.data?.let {
            contentResolver.openInputStream(it)?.bufferedReader()?.use(BufferedReader::readText) 
        }
    }
}

-1

Поместите этот фильтр намерений внутри тега активности в манифесте, который вы хотите открыть, касаясь файла:

<intent-filter android:priority="999">
    <action android:name="android.intent.action.VIEW" />

    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <category android:name="android.intent.category.OPENABLE" />

    <data android:host="*" />
    <data android:mimeType="application/octet-stream" />
    <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.yourextension" />
    <data android:pathPattern=".*\\..*\\..*\\..*\\.yourextension" />
    <data android:pathPattern=".*\\..*\\..*\\.yourextension" />
    <data android:pathPattern=".*\\..*\\.yourextension" />
    <data android:pathPattern=".*\\.yourextension" />
    <data android:scheme="content" />
</intent-filter>

-1

// Я пробовал этот код. И это хорошо работает. Вы можете использовать этот код, чтобы принять файл PDF.

<intent-filter>
   <action android:name="android.intent.action.SEND" />
   <category android:name="android.intent.category.DEFAULT" />
   <data android:mimeType="application/pdf" />
   <data android:pathPattern=".*\\.pdf" />
   <data android:pathPattern=".*\\..*\\.pdf" />
   <data android:pathPattern=".*\\..*\\..*\\.pdf" />
   <data android:pathPattern=".*\\..*\\..*\\..*\\.pdf" />
</intent-filter>
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.