Android 意图过滤器: 将应用程序与文件扩展名关联

我有一个自定义文件类型/扩展名,我想关联我的应用程序。

据我所知,数据元素就是为此而生的,但我无法让它工作。 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>

好吧,它不工作。我做错了什么? 我只是想声明我自己的文件类型。

80632 次浏览

Try adding

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

You need multiple intent filters to address different situation you want to handle.

Example 1, handle http requests without 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>

Handle with mimetypes, where the suffix is irrelevant:

  <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>

Handle intent from a file browser app:

  <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>

The pathPattern

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

does not work if the file path contains one or more dots before ".pdf".

This will work:

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

Add more if you want to support more dots.

The other solutions did not work reliably for me until I added:

android:mimeType="*/*"

Before that it worked in some applications, in some not...

complete solution for me:

<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>

My findings:

You need several filters to deal with the different ways of retrieving a file. ie, by gmail attachment, by file explorer, by HTTP, by FTP... They all send very different intents.

And you need to filter out the intent that trigger your activity in your activity code.

For the example below, I created a fake file type new.mrz. And I retrieved it from gmail attachment and file explorer.

Activity code added in the 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 attachement filter:

        <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>
  • LOG: Content intent detected: android.intent.action.VIEW : content://gmail-ls/l.foul@gmail.com/messages/2950/attachments/0.1/BEST/false : application/octet-stream : new.mrz

File explorer filter:

        <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>
  • LOG: File intent detected: android.intent.action.VIEW : file:///storage/sdcard0/My%20Documents/new.mrz : null : new.mrz

HTTP filter:

        <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 functions used above:

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());
}
}

I've been trying to get this to work for ages and have tried basicly all the suggested solutions and still cannot get Android to recognise specific file extensions. I have an intent-filter with a "*/*" mimetype which is the only thing that seems to work and file-browsers now list my app as an option for opening files, however my app is now shown as an option for opening ANY KIND of file even though I've specified specific file extensions using the pathPattern tag. This goes so far that even when I try to view/edit a contact in my contacts list Android asks me if I want to use my app to view the contact, and that is just one of many situations where this occurs, VERY VERY annoying.

Eventually I found this google groups post with a similar question to which an actual Android framework engineer replied. She explains that Android simply does not know anything about file-extensions, only MIME-types (https://groups.google.com/forum/#!topic/android-developers/a7qsSl3vQq0).

So from what I've seen, tried and read, Android simply cannot distinguish between file-extensions and the pathPattern tag is basicly a gigantic waste of time and energy. If you are fortunate enough to only need files of a certain mime-type (say text, video or audio), you can use an intent-filter with a mime-type. If you need a specific file-extension or a mime-type not known by Android however then you're out of luck.

If I'm wrong about any of this please tell me, so far I've read every post and tried every proposed solution I could find but none have worked.

I could write another page or two about how common these kinds of things seem to be in Android and how screwed up the developer experience is, but I'll save you my angry rantings ;). Hope I saved someone some trouble.

For Gmail attachment, you can use:

<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++ / ... -->

Add as many mime types as needed. I only need those for my project.

         <!--
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>

Notice that this will get your app open all gmail file attachments, there is no way to work around it

The answeres given by Phyrum Tea and yuku are very informative already.

I want to add that starting with Android 7.0 Nougat there is a change to the way file sharing between apps is handled:

From official Android 7.0 Changes:

For apps targeting Android 7.0, the Android framework enforces the StrictMode API policy that prohibits exposing file:// URIs outside your app. If an intent containing a file URI leaves your app, the app fails with a FileUriExposedException exception.

To share files between applications, you should send a content:// URI and grant a temporary access permission on the URI. The easiest way to grant this permission is by using the FileProvider class. For more information on permissions and sharing files, see Sharing Files.

If you have your own custom file ending without a specific mime-type (or i guess even with one) you may have to add a second scheme value to your intent-filter to make it work with FileProviders too.

Example:

<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>

The important thing here is the addition of

<data android:scheme="content" />

to the filter.

I had a hard time finding out about this little change which kept my activity from opening on Android 7.0 devices while everything was fine on older versions. I hope it helps someone.

Markus Ressel is correct. Android 7.0 Nougat no longer permits file sharing between apps using a file URI. A content URI must be used. However, a content URI does not allow a file path to be shared, only a mime type. So you cannot use a content URI to associate your app with your own file extension.

Drobpox has an interesting behavior on Android 7.0. When it meets an unknown file extension it appears to form a file URI intent but instead of launching the intent it calls the operating system to find out which apps can accept the intent. If there is only one app that can accept that file URI it then sends an explicit content URI directly to that app. So to work with Dropbox you do not need to change the intent filters on your app. It does not require a content URI intent filter. Just make sure the app can receive a content URI and your app with your own file extension will work with Dropbox just like it did before Android 7.0.

Here is an example of my file loading code modified to accept a content 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);
...
}
}

Those who having issue with other File Manager\Explorer apps, as @yuku and @phyrum-tea answered

This works with LG default file manager app

     <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>

but could not work with ES File Explorer and other File Managers so i added

 android:mimeType="*/*"

then it works with ES Explorer but LG file manager could not detect file type so my solution is

     <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>

Content URI ftw, and with the intent filter in the manifest... if your files have a custom extension .xyz, add a matching mime type:

        <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>

Some apps such as email seem to convert the extension into a mime type. Now I can click on the attachment in email and have it open in my app.

You try this it will help for you.Instead of pdf you can use other extensions also. First you have to add read external storage permission in androidmanifest.xml file.

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

Then in the androidmanifest file in the Activity tag, you add an intent-filter as shown below.

            <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>

Finally in your code, you get path of the pdf file as shown below:

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();


}

Put this intent filter inside the activity tag in manifest you'd like to open touching the file:

<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>

// I have tried this code. And it is working well. You can use this code to accept pdf file.

<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>

Read opening file in kotlin:

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)
}
}
}

none of the above solution worked for me, i had to combine all of them to make it work like this:

            <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.DEFAULT"/>
<data android:scheme="content"/>
<data android:scheme="file"/>
<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="content"/>
<data android:scheme="file"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.pdf"/>
</intent-filter>