大家对FileProvider应该不陌生,但是对于它诞生的原因,内部是如何实现的,又是如何转化为文件的,你又了解多少呢?今天就通过它来了解一下ContentProvider的四大组件之一。在Android7.0中,Android提高了应用程序的隐私性,限制了应用程序之间的文件共享。如果需要在应用程序之间共享,则需要授予对要访问的URI的临时访问权限。官方说明如下:对于针对Android7.0的应用,Android框架强制执行的StrictModeAPI策略禁止在应用之外公开file://URI。如果包含文件URI的Intent离开您的应用程序,则该应用程序会崩溃并出现FileUriExposedException异常。要在应用程序之间共享文件,您应该发送一个content://URI并授予对该URI的临时访问权限。执行此授权的最简单方法是使用FileProvider类。"为什么限制应用之间共享文件?比如应用A有个文件,绝对路径是file:///storage/emulated/0/Download/photo.jpg现在应用A想通过其他应用完成一些需求,比如要拍照,把他的文件路径传给相机应用B,然后应用B拍完照片就会把照片存储在这个绝对路径下,貌似没有问题,但是如果这个应用B是a怎么办“不良应用”?文件路径泄露,即应用隐私。万一这个应用A是“不良应用”呢?可以使用应用B实现存储文件的危险权限,无需申请存储权限。如你可以看到,这个以前过时的解决方案,从自己到对方,并不是一个好的选择。所以谷歌想了一个办法,限制应用程序内部对文件的访问。如果你想共享文件路径,不要分享file://URI文件绝对路径,butsharecontent://URI,这种相对路径,也就是这样的格式:content://com.jimu.test.fileprovider/external/photo.jpg那么其他应用就可以使用这个绝对路径到app里面该文件所属需要文件数据,因此该文件所属的应用程序本身必须具有访问该文件的权限。即应用程序A与应用程序B共享相对路径,应用程序B使用相对路径找到应用程序A,应用程序A读取文件内容返回给应用程序B。配置好FileProvider后,搞清楚要做什么,下一步是如何去做。说到应用之间的通信,你还记得IPC的几种方式吗?FileAIDLContentProviderSocket等。考虑到易用性、安全性、完整性等各方面,谷歌这次选择了ContentProvider作为限制应用文件共享的解决方案。于是,FileProvider诞生了。具体方法是://修改文件URL获取方法UriphotoURI=FileProvider.getUriForFile(context,context.getApplicationContext().getPackageName()+".provider",createImageFile());这样配置之后,就可以生成content://URI,文件内容也可以通过这个URI传输给外部应用。FileProvider的这些配置属性也是ContentProvider的通用配置:android:name是ContentProvider的类路径。android:authorities是唯一的标签,通常是包名+.providerandroid:exported,表示该组件是否可以被其他应用使用。android:grantUriPermissions,表示是否允许临时访问授权文件。需要注意的是android:exported在正常情况下应该为true,因为它应该被外部应用程序使用。但是这里FileProvider设置为false,必须为false。这主要是为了保护应用程序隐私。如果设置为true,那么任何应用程序都可以访问当前应用程序的FileProvider。对于应用文件不是很理想,所以Android7.0及以上版本会允许外部应用通过其他方式安全访问到这个文件,而不是正常的ContentProvider访问方式,后面会提到。正是因为这个属性为true,在Android7.0以下,Android默认把它当做一个普通的ContentProvider,外部无法通过content://URI访问文件。所以一般需要判断系统版本,然后判断传入的Uri是File格式还是content格式。FileProvider源码接着看FileProvider的主要源码:publicclassFileProviderextendsContentProvider{@OverridepublicbooleanonCreate(){returntrue;}@OverridepublicvoidattachInfo(@NonNullContextcontext,@NonNullProviderInfoinfo){super.attachInfo(context,info);//Sanitycheckoursecurityif(info.exportedException)"Providermustnotbeexported");}if(!info.grantUriPermissions){thrownewSecurityException("Providermustgranturipermissions");}mStrategy=getPathStrategy(context,info.authority);}publicstaticUrigetUriForFile(@NonNullContextcontext,@NonNullStringauthority,@NonNullFilefile){finalPathStrategyStrategy(getPathStrategy)context,authority);returnstrategy.getUriForFile(file);}@OverridepublicUriinsert(@NonNullUriuri,ContentValuesvalues){thrownewUnsupportedOperationException("Noexternalinserts");}@Overridepublicintupdate(@NonNullUriuri,ContentValuesvalues,@NullableStringselection,@NullableString[]selectionArgs){thrownewUnsupportedOperationException("Noexternalupdates");}@Overridepublicintdelete(@NonNullUriuri,@NullableStringselection,@NullableString[]selectionArgs){//ContentProviderhasalreadycheckedgrantedpermissionsfinalFilefile=mStrategy.getFileForUri(uri);returnfile.delete()?1:0;}@OverridepublicCursorquery(@NonNullUriuri,@NullableString[]projection,@NullableStringselection,@NullableString[]selectionArgs,@NullableStringsortOrder){//ContentProviderhasalreadycheckedgrantedpermissionsfinalFilefile=mStrategy.getFileForUri(uri);if(projection==null){projection=COLUMNS;}String[]cols=newString[projection.length];Object[]values=newObject[projection.length];inti=0;for(Stringcol:projection){if(OpenableColumns.DISPLAY_NAME.equals(col)){cols[i]=OpenableColumns.DISPLAY_NAME;values[i++]=file.getName();}elseif(OpenableColumns.SIZE.equals(col)){cols[i]=OpenableColumns.SIZE;values[i++]=file.length();}}cols=copyOf(cols,i);values=copyOf(values,i);finalMatrixCursorcursor=newMatrixCursor(cols,1);cursor.addRow(values);returncursor;}@OverridepublicStringgetType(@NonNullUriuri){finalFilefile=mStrategy.getFileForUri(uri);finalintlastDot=file.getName().lastIndexOf('.');if(lastDot>=0){finalStringextension=file.getName().substring(lastDot+1);finalStringmime=MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);if(mime!=null){returnmime;}}返回“应用/octet-stream";}}任何ContentProvider都需要继承ContentProvider类,然后实现这些抽象方法:onCreate,getType,query,insert,delete,update(每个方法中的Uri参数就是我们在getUriForFile之前传递的内容URI方法生成的)分为三部分:在数据调用方面,query、insert、delete、update这四个方法分别是数据的增、删、查、改,是进程间通信的相关方法。其他应用可以通过ContentProvider调用这些方法,完成对本地应用数据的增删改查,从而完成进程间通信功能。具体方法是调用getContentResolver()的相关方法,例如:Cursorcursor=getContentResolver().query(uri,null,null,null,"userid");回过头来看FileProvider:query,查询方法。在此方法中,返回文件的名称和长度。插入,插入方法。什么也没做。删除,删除方法。删除Uri对应的File。更新,更新方法。什么也没做。查看MIME类型的getType方法。该方法主要返回Url所代表的数据的MIME类型。一般使用默认格式:如果是单条记录,则返回以vnd.android.cursor.item/为首的字符串如果是多条记录,则返回以vnd.android.cursor.dir/为首的字符串如何使用它?通过ContentURI对应的ContentProvider配置的getType可以匹配到activity。有点啰嗦,比如Activity和ContentProvider的配置:@OverridepublicStringgetType(@NonNullUriuri){return"type_test";}intent.setData(mContentRUI);startActivity(intent)这样配置后,startActivity会检查mineType和ContentURI对应的ContentProvider的getType是否相同,相同情况下Activity可以正常打开。最后看一下初始化后的onCreate方法。APP启动过程中会自动执行所有ContentProvider的attachInfo方法,最后调用onCreate方法。一般在这个方法中会做一些初始化工作,比如初始化ContentProvider需要的数据库。在FileProvider中,调用attachInfo方法作为初始化工作的入口点。其实它和onCreate方法的作用是一样的,都是App启动时会调用的方法。该方法中还限制了exported属性必须为false,grantUriPermissions属性必须为true。if(info.exported){thrownewSecurityException("Providermustnotbeexported");}if(!info.grantUriPermissions){thrownewSecurityException("Providermustgranturipermissions");}这个初始化方法和特性也被很多第三方库使用,可以无声而unaware初始化工作不需要单独调用第三方库初始化方法。例如,FacebookSDK:getSimpleName();@Override@SuppressWarnings("deprecation")publicbooleanonCreate(){try{FacebookSdk.sdkInitialize(getContext());}catch(Exceptionex){Log.i(TAG,"FailedtoautoinitializetheFacebookSDK",ex);}returnfalse;}//...}这样就不需要单独集成FacebookSDK的初始化方法来实现静默初始化。Jetpack中的AppStartup也考虑到了这些三方库的需求,将三方库的初始化合并,从而多次优化ContentProvider的创建耗时。如何使用内容URI?很多人都知道如何配置FileProvider让别人(比如相机APP)获取我们的ContentURI,但是你知道别人获取ContentURI后是如何获取具体File的吗?其实仔细看的话,可以发现在FileProvider.java中有一句注释:TheclientappthatreceivesthecontentURIcanopenthefileandaccessitscontentsbycalling{@linkandroid.content.ContentResolver#openFileDescriptor(Uri,String)ContentResolver.openFileDescriptor}togeta{@linkParcelFileDescriptor}是openFileDescriptor方法,得到ParcelFileDescriptor类型的数据其实就是一个文件描述符,然后就可以读取文件流了。ParcelFileDescriptorparcelFileDescriptor=getContentResolver().openFileDescriptor(intent.getData(),"r");FileReaderreader=newFileReader(parcelFileDescriptor.getFileDescriptor());BufferedReaderbufferedReader=newBufferedReader(reader);ContentProvider实际在日常工作中使用,主要有以下几种情况多和ContentProvider打交道:和系统的一些App通信,比如获取通讯录,调用拍照等,上面提到的FileProvider也是如此。与自己的APP进行一些交互。比如家里的多个应用之间,可以通过这个进行一些数据交互。三方库的初始化工作。很多第三方库会利用ContentProvider自动初始化的特性,进行静悄悄的、不知不觉的初始化。综上所述,ContentProvider作为四大组件之一,存在感似乎没有其他组件强。但是他还是有自己的职责,就是在保证安全的情况下,应用之间进行通信,也可以作为组件进行扩展,帮助初始化。所以了解他并掌握他也是非常重要的,说不定以后的某个时候你会需要他。不要忽视任何一个知识点。参考https://mp.weixin.qq.com/s/kQmH2GnwW8FK-yNmWcheTAhttps://segmentfault.com/a/1190000021357383https://blog.csdn.net/lmj623565791/article/details/72859156本文转载自微信公众号“码上积木”,可通过以下二维码关注。转载本文请联系代码上的积木公众号。