Original address: http://yifeng.studio/2017/05/03/android-7-0-compat-fileprovider/
Because Android 7.0 or later systems in the domestic mobile phone market is not very high, many Android developers do not do 7.0 adaptation work, and testers can easily ignore this aspect of compatibility issues. This results in the app crash flashback for mobile phone users 7.0 and older when they use the App section feature. Most of these are caused by a URI that is used in the project to the file://type. In this article we will come to a probe.
Android 7.0 Permissions Change
In order to improve the security of the private directory, to prevent the leakage of application information, starting from Android 7.0, the application of private directory access is limited. Specifically, developers can no longer simply access the private directory files of other applications via the file://URI or allow other apps to access their own private directory files.
Note: If you're not sure about the app's private directory, you can read my article: Learn about the file storage directory for Android apps and get the right posture for persistent data.
Also, starting from 7.0, the Strictmode policy in the Android SDK prohibits developers from exposing file://URIs outside the app. Specifically, the program fails when we leave our app with Intent that contains the file://URI in the app.
In development, if we ignore these two rules when using the file://URI, the fileuriexposedexception exception occurs when the user uses the relevant functionality in devices on the 7.0 and later systems, causing the app to crash and Flash back. The alternative solution to these two processes is to use FileProvider
.
Fileprovider
As one of the four components ContentProvider
, it has been playing the role of sharing resources among applications. What we're going to use here FileProvider
is ContentProvider
a special subclass that helps us turn the restricted file://uri into a content://URI that can be licensed for sharing.
The first step is to register a Fileprovider
As one of the four components of the system ContentProvider, its subclasses fileprovider, also need to use elements in the Manifest file to add registration information, and set the relevant property values as required.
1234567891011 |
<APPLICATION> ... <provider android:name= " Android.support.v4.content.FileProvider " android: Authorities= "${applicationid}.yourname" android:exported= "false" android:granturipermissions= "true"; ... </PROVIDER> ... </APPLICATION> |
Where the android:authorities
property value is a Uri string consisting of the ApplicationID value and the custom name in the Build.gradle file (this is customary). Other property values can be used as a fixed value.
Second step, add the shared directory
Create a new XML file in the Res/xml directory to store the directory files that your app needs to share. The content of this XML file is similar to this:
12345 |
<?xml version= "1.0" encoding= "Utf-8"?><paths xmlns:android="http://schemas.android.com/ Apk/res/android "><files-path name=" my_images " path="images/" />... </paths> |
The element must contain one or more child elements. These child elements are used to specify the directory path of the shared file, which must be one of these elements:
<files-path>
: The files/directory under the private directory of the internal storage space, equivalent to the Context.getFilesDir()
directory path obtained;
<cache-path>
: The cache/directory under the private directory of the internal storage space, equivalent to the Context.getCacheDir()
directory path obtained;
<external-path>
: The root of the external storage space, equivalent to the Environment.getExternalStorageDirectory()
directory path obtained;
<external-files-path>
: The files/directory under the Private directory of the external storage space, equivalent to the Context.getExternalFilesDir(null)
directory path obtained;
<external-cache-path>
: The cache/directory under the Private directory of the external storage space, equivalent to Context.getexternalcachedir ();
As you can see, these five seed elements basically cover all directory paths inside and outside the storage space, including the app's private directory. At the same time, each child element has a name and path two properties.
Where the Path property is used to specify the name of the subdirectory that the current child element represents in the directory that needs to be shared. Note: The Path property value cannot use a specific stand-alone file name, only the directory name.
The Name property is used to give an alias to the subdirectory name specified by the Path property. This alias is used instead of the real directory name when the content://URI is subsequently generated. The purpose of this is obviously to improve security.
If the files we need to share are in different subdirectories under the same level directory, you will need to add multiple child elements to specify the file directories to share, or share their common parent directory.
After adding the shared directory, <provider>
use elements in the element <meta-data>
to link the path file in Res/xml with the registered Fileprovider:
123456789 |
<provider android:name= android: Authorities= "${applicationid}.yourname" android:exported= "false" android:granturipermissions= "true"; <meta-data Android:name= "Android.support.FILE_PROVIDER_PATHS" android:resource= "@xml/yourfilename"/> </PROVIDER> |
Step three, generate the Content URI
Before Android 7.0 appeared, we typically used Uri.fromFile()
methods to generate a File URI. Here, we need to FileProvider
generate the Content URI using the public static method provided by the class getUriForFile
. Like what:
12 |
Uri Contenturi = Fileprovider.geturiforfile (this,buildconfig.application_id + ". MyProvider", myFile); |
You need to pass three parameters. The second parameter is the authorities property value set when registering Fileprovider in the Manifest file, the third parameter is the file to be shared, and the file must be located in the second step of the subdirectory we added in the path file.
As an example:
1234567 |
String FilePath = environment.getexternalstoragedirectory () + "/images/" +system.currenttimemillis () +". jpg"; file OutputFile = new File (FilePath); if (!outputfile.getparentfile (). exists ()) {outputfile.getparentfile (). mkdir (); Uri Contenturi = Fileprovider.geturiforfile (this,buildconfig.application_id + ". MyProvider", OutputFile ); |
The resulting Content URI is this:
1 |
Content://com.yifeng.samples.myprovider/my_images/1493715330339.jpg |
Where the host part of the URI is the <provider>
element's authorities property value (ApplicationID + customname), and the path fragment my_images to the subdirectory alias specified in the Res/xml file (the real directory name is: Images).
Fourth step, grant the Content URI access permission
After the Content URI object is generated, it needs to be granted access rights. There are two ways of authorizing:
The first is to use the method provided by the Context grantUriPermission(package, Uri, mode_flags)
to grant access to the URI object to other applications. The three parameters represent the other app package names that grant access to the URI object, the URI object that is authorized to access, and the authorization type. Where the authorization type is the read-write type constant provided by the Intent class:
Or they are authorized at the same time. This form of authorization means that the permission expires until the device restarts or revokeUriPermission()
when the method revocation authorization is invoked manually.
The second way, with the use of Intent. setData()
adds a Content URI to the intent object by method. Then use setFlags()
or addFlags()
method to set the read and write permissions, optional constant value ibid. This form of authorization means that the permissions are valid for the stack destroyed by other applications, and once a component is authorized, the other components of the app have the same access rights.
Fifth step, provide the Content URI to other applications
Once you have a content URI that grants permissions, you startActivity()
can setResult()
start other apps and pass the content URI data that you've authorized, either by means of a method. Of course, there are other ways to provide services.
If you need to pass multiple URI objects at once, you can use the methods provided by the intent object, setClipData()
and setFlags()
the permissions set by the method apply to all Content URIs.
Common usage scenarios
The previous sections are theoretical and are described in the official Fileprovider section of the developer. Next we look at the actual development of an application in the process, will often meet the fileprovider of the use of the scene.
Automatic installation files
When the version update is complete, opening a new version of the APK file for automatic installation should be the most common usage scenario and one of the essential features of each application. A common action is that the notification bar displays the download of the new version complete, the user taps or listens to the download process to automatically open a new version of the APK file. Before adapting to the Android 7.0 version, our code might be like this:
123456 |
File Apkfile = new File (Getexternalfilesdir (environment.directory_downloads), "app_sample.apk"); Intent installintent = new Intent (Intent.action_view); Installintent.addflags (Intent.flag_activity_new_task); Installintent.setdataandtype (Uri.fromFile (apkFile), " Application/vnd.android.package-archive "); StartActivity (installintent); |
You must now use the Content URI instead of the File URI in order to fit a system of version 7.0 and above.
Create a new File_provider_paths.xml file (the file name is freely defined) under the Res/xml directory and add the subdirectory path information:
123456 |
<?xml version= "1.0" encoding= "Utf-8"?><paths xmlns:android="http://schemas.android.com/ Apk/res/android "> <external-files-path name=" My_download " path= "Download"/> </paths> |
Then register the Fileprovider object in the Manifest file and link the path path file above:
1234567891011 |
<provider android:name= "Android.support.v4.content.FileProvider" android:authorities= "Com.yifeng.samples.myprovider" android:exported= "false" android:granturipermissions= "true" > <meta-data Span class= "attr" >android:name= "Android.support.FILE_PROVIDER_PATHS" span class= "tag" > android:resource= "@xml/file_provider_paths"/> </PROVIDER> |
Modify the Java code to generate a Content URI object from the File object and grant access to:
123456789 |
File Apkfile = new File (Getexternalfilesdir (environment.directory_downloads), "app_sample.apk"); Uri Apkuri = Fileprovider.geturiforfile (this,buildconfig.application_id+". MyProvider", apkfile); Intent installintent = new Intent (Intent.action_view); Installintent.addflags (Intent.flag_activity_new_task); Installintent.addflags (Intent.flag_grant_read_uri_ PERMISSION); Installintent.setdataandtype (Apkuri, "application/vnd.android.package-archive"); StartActivity (installintent); |
This completes the 7.0 adaptation of the APK file that is called System functionality in the app.
Call system to take pictures
You also need to pass a Uri object when you call the System capture feature to save the image to the specified directory, which also needs to be adapted to version 7.0. The other steps are no longer mentioned, the core Java code is as follows (the path is different, note add Res/xml in the path file subdirectory):
1234567891011 |
String FilePath = environment.getexternalstoragedirectory () + "/images/" +system.currenttimemillis () +". jpg"; file OutputFile = new File (FilePath); if (!outputfile.getparentfile (). exists ()) {outputfile.getparentfile (). mkdir (); Uri Contenturi = Fileprovider.geturiforfile (this,buildconfig.application_id + ". MyProvider", OutputFile ); Intent Intent = new Intent (mediastore.action_image_capture); Intent.putextra (Mediastore.extra_output, Contenturi); Startactivityforresult (Intent, request_take_picture); |
Call system cropping
The process of calling system clipping involves two Uri objects: Inputuri and Outputuri, which are more complex. Typically, the source that calls the system crop takes a picture of the calling system or selects a system album. The former returns a File URI object, which returns a Content URI object. As a clipping source, all we have to do is to deal with it further. However, it is not difficult to use the method as above getUriForFile()
, it is not hard to understand, because if you choose the picture of the System album, it does not belong to our own application. The correct way to handle this is:
123456789101112131415 |
private Uri getimagecontenturi(String path) { cursor cursor = getcontentresolver (). Query (MediaStore.Images.Media.EXTERNAL_CONTENT_URI,New string[]{mediastore.images.media._id},MediaStore.Images.Media.DATA +"=? ",New String[]{path}, null); if (cursor! = null && cursor.movetofirst ()) { int id = cursor.getint (cursor.getcolumnindex (mediastore.images.media._id));Uri BaseUri = Uri.parse ("Content://media/external/images/media");return Uri.withappendedpath (BaseUri, "" +id); }else {Contentvalues contentvalues =New Contentvalues (1); Contentvalues.put (MediaStore.Images.Media.DATA, path);return Getcontentresolver (). Insert (MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentvalues);}} |
After getting the correct Content URI, pass it to the Intent object as Inputuri:
12345678 |
intent Intent = new Intent ( "Com.android.camera.action.CROP"); Intent.addflags (Intent.flag_grant_write_uri_permission | intent.flag_grant_read_uri_permission); Intent.setdataandtype (Inputuri, Intent.putextra ( "crop", Intent.putextra ( "Aspectx", 1); Intent.putextra ( "Aspecty", 1); Intent.putextra (Mediastore.extra_output, Uri.fromfile (OutputFile)); Startactivityforresult (Intent, request_pick); |
Note: The Outputuri here does not change, still using the Uri.fromFile()
method gets the File URI type! This is a strange point, but it has to be done. In fact, it is problematic to call the system clipping function in this way! Common problems such as: On some models, when the system is called to crop and return to the previous page, the ResultCode value obtained in the Onactivityresult () method is not equal to RESULT_OK. Crop Intent in the official document is no trace, in itself is a non-recommended usage! Instead, we can use some of the open source libraries on GitHub to implement in-app image cropping, such as Ucrop, Cropper, and more.
Historical version Issues
To say so much, there is a concern for everyone is: which has been on the line of the old version of the application did not do 7.0 adaptation work to do? Google has helped us find a solution to this problem in advance.
Do you remember 6.0 runtime permissions issues? If you do not want to handle runtime permissions, simply set the value of Targetsdkversion to 23 in the Build.gradle file.
Similarly, as long as the targetsdkversion value is less than the 24,file URI, it can still appear on devices of version 7.0 and above. However, it is important to note that, as mentioned earlier, the call system clipping feature is special, some problems may occur.
While Google offers this setup targetsdkversion in a way that is compatible with older versions each time a new version of Android is released, it is only a temporary solution, and it is not recommended to use this technique to circumvent the adaptation of a new release. You know, the new API changes must be in the past to solve the system problems, is a progressive performance. Following the specification is the maxim that every developer should bear in mind when developing.
Add: In the one months after the completion of this article, inadvertently found that the blog of the Great God "Hon Yang" also for the 7.0 Fileprovider problem has an article. After the observation, found that the detailed analysis of the article more in place, it is worth the follow-up writing technology blog when the reflection of improvement. Blog Address:
Android 7.0 behavior changes share files between apps via Fileprovider
This article was created by Maple and started Yi Fengfeng's personal blog, sync push the public number: Android Note-taker (Niaotech).
Welcome to all forms of communication and reprint, annotated author and source can be.
This article is entitled: Summary of the Fileprovider section of the Android 7.0 adaptation
This article links to: http://yifeng.studio/2017/05/03/android-7-0-compat-fileprovider/
Science Online, FQ recommendation: http://buytizi.com/?r=225bb619613ec09e
A summary of the Fileprovider section of the Android 7.0 adaptation