成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

透過(guò)FileProvider再看ContentProvider

移動(dòng)開(kāi)發(fā) Android
大家應(yīng)該都熟悉FileProvider吧,但是其誕生的原因,內(nèi)部怎么實(shí)現(xiàn)的,又是怎么轉(zhuǎn)化為文件的,大家有了解多少呢?今天就通過(guò)它重新看看ContentProvider這個(gè)四大組件之一。

[[384130]]

前言

大家應(yīng)該都熟悉FileProvider吧,但是其誕生的原因,內(nèi)部怎么實(shí)現(xiàn)的,又是怎么轉(zhuǎn)化為文件的,大家有了解多少呢?今天就通過(guò)它重新看看ContentProvider這個(gè)四大組件之一。

在Android7.0,Android提高了應(yīng)用的隱私權(quán),限制了在應(yīng)用間共享文件。如果需要在應(yīng)用間共享,需要授予要訪(fǎng)問(wèn)的URI臨時(shí)訪(fǎng)問(wèn)權(quán)限。

以下是官方說(shuō)明:

對(duì)于面向 Android 7.0 的應(yīng)用,Android 框架執(zhí)行的 StrictMode API 政策禁止在您的應(yīng)用外部公開(kāi) file:// URI。如果一項(xiàng)包含文件 URI 的 intent 離開(kāi)您的應(yīng)用,則應(yīng)用出現(xiàn)故障,并出現(xiàn) FileUriExposedException 異常。要在應(yīng)用間共享文件,您應(yīng)發(fā)送一項(xiàng) content:// URI,并授予 URI 臨時(shí)訪(fǎng)問(wèn)權(quán)限。進(jìn)行此授權(quán)的最簡(jiǎn)單方式是使用 FileProvider 類(lèi)。”

為什么限制在應(yīng)用間共享文件

打個(gè)比方,應(yīng)用A有一個(gè)文件,絕對(duì)路徑為file:///storage/emulated/0/Download/photo.jpg

現(xiàn)在應(yīng)用A想通過(guò)其他應(yīng)用來(lái)完成一些需求,比如拍照,就把他的這個(gè)文件路徑發(fā)給了照相應(yīng)用B,然后應(yīng)用B照完相就把照片存儲(chǔ)到了這個(gè)絕對(duì)路徑。

看起來(lái)似乎沒(méi)有什么問(wèn)題,但是如果這個(gè)應(yīng)用B是個(gè)“壞應(yīng)用”呢?

  • 泄漏了文件路徑,也就是應(yīng)用隱私。

如果這個(gè)應(yīng)用A是“壞應(yīng)用”呢?

  • 自己可以不用申請(qǐng)存儲(chǔ)權(quán)限,利用應(yīng)用B就達(dá)到了存儲(chǔ)文件的這一危險(xiǎn)權(quán)限。

可以看到,這個(gè)之前落伍的方案,從自身到對(duì)方,都是不太好的選擇。

所以Google就想了一個(gè)辦法,把對(duì)文件的訪(fǎng)問(wèn)限制在應(yīng)用內(nèi)部。

  • 如果要分享文件路徑,不要分享file:// URI這種文件的絕對(duì)路徑,而是分享content:// URI,這種相對(duì)路徑,也就是這種格式:content://com.jimu.test.fileprovider/external/photo.jpg
  • 然后其他應(yīng)用可以通過(guò)這個(gè)絕對(duì)路徑來(lái)向文件所屬應(yīng)用 索要 文件數(shù)據(jù),所以文件所屬的應(yīng)用本身必須擁有文件的訪(fǎng)問(wèn)權(quán)限。

也就是應(yīng)用A分享相對(duì)路徑給應(yīng)用B,應(yīng)用B拿著這個(gè)相對(duì)路徑找到應(yīng)用A,應(yīng)用A讀取文件內(nèi)容返給應(yīng)用B。

配置FileProvider

搞清楚了要做什么事,接下來(lái)就是怎么做。

涉及到應(yīng)用間通信的問(wèn)題,還記得IPC的幾種方式嗎?

  • 文件
  • AIDL
  • ContentProvider
  • Socket
  • 等等。

從易用性,安全性,完整度等各個(gè)方面考慮,Google選擇了ContentProvider為這次限制應(yīng)用分享文件的 解決方案。于是,F(xiàn)ileProvider誕生了。

具體做法就是:

  1. <!-- 配置FileProvider--> 
  2.  
  3. <provider 
  4.     android:name="androidx.core.content.FileProvider" 
  5.     android:authorities="${applicationId}.provider" 
  6.     android:exported="false" 
  7.     android:grantUriPermissions="true"
  8.     <meta-data 
  9.         android:name="android.support.FILE_PROVIDER_PATHS" 
  10.         android:resource="@xml/provider_paths"/> 
  11. </provider> 
  12.  
  13.  
  14.  
  15. <?xml version="1.0" encoding="utf-8"?> 
  16. <paths xmlns:android="http://schemas.android.com/apk/res/android"
  17.     <external-path name="external" path="."/> 
  18. </paths> 
  1. //修改文件URL獲取方式 
  2.  
  3. Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", createImageFile()); 

這樣配置之后,就能生成content:// URI,并且也能通過(guò)這個(gè)URI來(lái)傳輸文件內(nèi)容給外部應(yīng)用。

FileProvider這些配置屬性也就是ContentProvider的通用配置:

  • android:name,是ContentProvider的類(lèi)路徑。
  • android:authorities,是唯一標(biāo)示,一般為包名+.provider
  • android:exported,表示該組件是否能被其他應(yīng)用使用。
  • android:grantUriPermissions,表示是否允許授權(quán)文件的臨時(shí)訪(fǎng)問(wèn)權(quán)限。

其中要注意的是android:exported正常應(yīng)該是true,因?yàn)橐o外部應(yīng)用使用。

但是FileProvider這里設(shè)置為false,并且必須為false。

這主要為了保護(hù)應(yīng)用隱私,如果設(shè)置為true,那么任何一個(gè)應(yīng)用都可以來(lái)訪(fǎng)問(wèn)當(dāng)前應(yīng)用的FileProvider了,對(duì)于應(yīng)用文件來(lái)說(shuō)不是很可取,所以Android7.0以上會(huì)通過(guò)其他方式讓外部應(yīng)用安全的訪(fǎng)問(wèn)到這個(gè)文件,而不是普通的ContentProvider訪(fǎng)問(wèn)方式,后面會(huì)說(shuō)到。

也正是因?yàn)檫@個(gè)屬性為true,在Android7.0以下,Android默認(rèn)是將它當(dāng)成一個(gè)普通的ContentProvider,外部無(wú)法通過(guò)content:// URI來(lái)訪(fǎng)問(wèn)文件。所以一般要判斷下系統(tǒng)版本再確定傳入的Uri到底是File格式還是content格式。

FileProvider源碼

接著看看FileProvider的主要源碼:

  1. public class FileProvider extends ContentProvider { 
  2.  
  3.     @Override 
  4.     public boolean onCreate() { 
  5.         return true
  6.     } 
  7.  
  8.     @Override 
  9.     public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) { 
  10.         super.attachInfo(context, info); 
  11.  
  12.         // Sanity check our security 
  13.         if (info.exported) { 
  14.             throw new SecurityException("Provider must not be exported"); 
  15.         } 
  16.         if (!info.grantUriPermissions) { 
  17.             throw new SecurityException("Provider must grant uri permissions"); 
  18.         } 
  19.  
  20.         mStrategy = getPathStrategy(context, info.authority); 
  21.     } 
  22.  
  23.  
  24.     public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, 
  25.             @NonNull File file) { 
  26.         final PathStrategy strategy = getPathStrategy(context, authority); 
  27.         return strategy.getUriForFile(file); 
  28.     } 
  29.  
  30.  
  31.     @Override 
  32.     public Uri insert(@NonNull Uri uri, ContentValues values) { 
  33.         throw new UnsupportedOperationException("No external inserts"); 
  34.     } 
  35.  
  36.  
  37.     @Override 
  38.     public int update(@NonNull Uri uri, ContentValues values, @Nullable String selection, 
  39.             @Nullable String[] selectionArgs) { 
  40.         throw new UnsupportedOperationException("No external updates"); 
  41.     } 
  42.  
  43.  
  44.     @Override 
  45.     public int delete(@NonNull Uri uri, @Nullable String selection, 
  46.             @Nullable String[] selectionArgs) { 
  47.         // ContentProvider has already checked granted permissions 
  48.         final File file = mStrategy.getFileForUri(uri); 
  49.         return file.delete() ? 1 : 0; 
  50.     } 
  51.  
  52.     @Override 
  53.     public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, 
  54.             @Nullable String[] selectionArgs, 
  55.             @Nullable String sortOrder) { 
  56.         // ContentProvider has already checked granted permissions 
  57.         final File file = mStrategy.getFileForUri(uri); 
  58.  
  59.         if (projection == null) { 
  60.             projection = COLUMNS; 
  61.         } 
  62.  
  63.         String[] cols = new String[projection.length]; 
  64.         Object[] values = new Object[projection.length]; 
  65.         int i = 0; 
  66.         for (String col : projection) { 
  67.             if (OpenableColumns.DISPLAY_NAME.equals(col)) { 
  68.                 cols[i] = OpenableColumns.DISPLAY_NAME; 
  69.                 values[i++] = file.getName(); 
  70.             } else if (OpenableColumns.SIZE.equals(col)) { 
  71.                 cols[i] = OpenableColumns.SIZE
  72.                 values[i++] = file.length(); 
  73.             } 
  74.         } 
  75.  
  76.         cols = copyOf(cols, i); 
  77.         values = copyOf(values, i); 
  78.  
  79.         final MatrixCursor cursor = new MatrixCursor(cols, 1); 
  80.         cursor.addRow(values); 
  81.         return cursor
  82.     } 
  83.  
  84.     @Override 
  85.     public String getType(@NonNull Uri uri) { 
  86.   final File file = mStrategy.getFileForUri(uri); 
  87.  
  88.         final int lastDot = file.getName().lastIndexOf('.'); 
  89.         if (lastDot >= 0) { 
  90.             final String extension = file.getName().substring(lastDot + 1); 
  91.             final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 
  92.             if (mime != null) { 
  93.                 return mime; 
  94.             } 
  95.         } 
  96.         return "application/octet-stream"
  97.     } 
  98.  
  99.  

任何一個(gè)ContentProvider都需要繼承ContentProvider類(lèi),然后實(shí)現(xiàn)這幾個(gè)抽象方法:

onCreate,getType,query,insert,delete,update。

(其中每個(gè)方法中的Uri參數(shù),就是我們之前通過(guò)getUriForFile方法生成的content URI)

我們分三部分說(shuō)說(shuō):

數(shù)據(jù)調(diào)用方面

其中,query,insert,delete,update四個(gè)方法就是數(shù)據(jù)的增刪查改,也就是進(jìn)程間通信的相關(guān)方法。

其他應(yīng)用可以通過(guò)ContentProvider來(lái)調(diào)用這幾個(gè)方法,來(lái)完成對(duì)本地應(yīng)用數(shù)據(jù)的增刪查改,從而完成進(jìn)程間通信的功能。

具體方法就是調(diào)用getContentResolver()的相關(guān)方法,例如:

  1. Cursor cursor = getContentResolver().query(uri, nullnullnull"userid");  

再回去看看FileProvider:

  • query,查詢(xún)方法。在該方法中,返回了File的name和length。
  • insert,插入方法。沒(méi)有做任何事。
  • delete,刪除方法。刪除Uri對(duì)應(yīng)的File。
  • update,更新方法。沒(méi)有做任何事。

MIME類(lèi)型

再看getType方法,這個(gè)方法主要是返回 Url所代表數(shù)據(jù)的MIME類(lèi)型。

一般是使用默認(rèn)格式:

  • 如果是單條記錄返回以vnd.android.cursor.item/ 為首的字符串
  • 如果是多條記錄返回vnd.android.cursor.dir/ 為首的字符串

具體怎么用呢?可以通過(guò)Content URI對(duì)應(yīng)的ContentProvider配置的getType來(lái)匹配Activity。

有點(diǎn)拗口,比如Activity和ContentProvider這么配置的:

  1. <activity   
  2.     android:name=".SecondActivity">   
  3.     <intent-filter>   
  4.         <action android:name=""/>   
  5.         <category android:name=""/>   
  6.         <data android:mimeType="type_test"/>  
  7.     </intent-filter>   
  8. </activity>   
  1. @Override 
  2. public String getType(@NonNull Uri uri) { 
  3.     return "type_test"
  4.  
  5.  
  6. intent.setData(mContentRUI);   
  7. startActivity(intent) 

這樣配置之后,startActivity就會(huì)檢查Activity的mineType 和 Content URI 對(duì)應(yīng)的ContentProvider的getType是否相同,相同情況下才能正常打開(kāi)Activity。

初始化

最后再看看onCreate方法。

在APP啟動(dòng)流程中,自動(dòng)執(zhí)行所有ContentProvider的attachInfo方法,并最后調(diào)用到onCreate方法。一般在這個(gè)方法中就做一些初始化工作,比如初始化ContentProvider所需要的數(shù)據(jù)庫(kù)。

而在FileProvider中,調(diào)用了attachInfo方法作為了一個(gè)初始化工作的入口,其實(shí)和onCreate方法的作用一樣,都是App啟動(dòng)的時(shí)候會(huì)調(diào)用的方法。

在這個(gè)方法中,也是限制了exported屬性必須為false,grantUriPermissions屬性必須為true。

  1. if (info.exported) { 
  2.     throw new SecurityException("Provider must not be exported"); 
  3. if (!info.grantUriPermissions) { 
  4.     throw new SecurityException("Provider must grant uri permissions"); 

這個(gè)初始化方法和特性,也是被很多三方庫(kù)所利用,可以進(jìn)行靜默無(wú)感知的初始化工作,而無(wú)需單獨(dú)調(diào)用三方庫(kù)初始化方法。比如Facebook SDK:

  1. <provider 
  2.     android:name="com.facebook.internal.FacebookInitProvider" 
  3.     android:authorities="${applicationId}.FacebookInitProvider" 
  4.     android:exported="false" /> 
  1. public final class FacebookInitProvider extends ContentProvider { 
  2.     private static final String TAG = FacebookInitProvider.class.getSimpleName(); 
  3.  
  4.     @Override 
  5.     @SuppressWarnings("deprecation"
  6.     public boolean onCreate() { 
  7.         try { 
  8.             FacebookSdk.sdkInitialize(getContext()); 
  9.         } catch (Exception ex) { 
  10.             Log.i(TAG, "Failed to auto initialize the Facebook SDK", ex); 
  11.         } 
  12.         return false
  13.     } 
  14.  
  15.     //... 

這樣一寫(xiě),就無(wú)需單獨(dú)集成FacebookSDK的初始化方法了,實(shí)現(xiàn)靜默初始化。

而Jetpack中的App Startup也是考慮到這些三方庫(kù)的需求,對(duì)三方庫(kù)的初始化進(jìn)行了一個(gè)合并,從而優(yōu)化了多次創(chuàng)建ContentProvider的耗時(shí)。

拿到Content URI 該怎么使用?

很多人都知道該怎么配置FileProvider讓別人(比如照相APP)來(lái)獲取我們的Content URI,但是你們知道別人拿到Content URI之后又是怎么獲取具體的File的呢?

其實(shí)仔細(xì)找找就能發(fā)現(xiàn),在FileProvider.java中有注釋說(shuō)明:

  1. The client app that receives the content URI can open the file and access its contents by calling 
  2.  {@link android.content.ContentResolver#openFileDescriptor(Uri, String) ContentResolver.openFileDescriptor}  
  3.  to get a {@link ParcelFileDescriptor} 

也就是openFileDescriptor方法,拿到ParcelFileDescriptor類(lèi)型數(shù)據(jù),其實(shí)就是一個(gè)文件描述符,然后就可以讀取文件流了。

  1. ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(intent.getData(), "r"); 
  2. FileReader reader = new FileReader(parcelFileDescriptor.getFileDescriptor()); 
  3. BufferedReader bufferedReader = new BufferedReader(reader); 

ContentProvider 實(shí)際應(yīng)用

在平時(shí)的工作中,主要有以下以下幾種情況和ContentProvider打交道比較多:

  • 和系統(tǒng)的一些App通信,比如獲取通訊錄,調(diào)用拍照等。上述的FileProvider也是屬于這種情況。
  • 與自己的APP有一些交互。比如自家多應(yīng)用之間,可以通過(guò)這個(gè)進(jìn)行一些數(shù)據(jù)交互。
  • 三方庫(kù)的初始化工作。很多三方庫(kù)會(huì)利用ContentProvider自動(dòng)初始化這一特性,進(jìn)行一個(gè)靜默無(wú)感知的初始化工作。

總結(jié)

ContentProvider作為四大組件之一,似乎并沒(méi)有其他組件的存在感那么強(qiáng)。

但是他還是有自己的那一份職責(zé),也就是在保證安全的情況下進(jìn)行應(yīng)用間通信,還可以擴(kuò)展作為幫助初始化的組件。所以了解他,掌握它也是很重要的,沒(méi)準(zhǔn)以后哪個(gè)時(shí)候你就需要他了。

不要忽視任何一個(gè)知識(shí)點(diǎn)。

參考

https://mp.weixin.qq.com/s/kQmH2GnwW8FK-yNmWcheTA 

https://segmentfault.com/a/1190000021357383

https://blog.csdn.net/lmj623565791/article/details/72859156

本文轉(zhuǎn)載自微信公眾號(hào)「碼上積木」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼上積木公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 碼上積木
相關(guān)推薦

2014-07-29 15:57:01

ContentProv

2012-05-01 21:32:39

蘋(píng)果

2009-12-18 11:22:34

Ruby source

2021-01-18 07:31:52

MySQL LeetCode查詢(xún)

2009-07-16 08:50:53

微軟未來(lái)操作系統(tǒng)Windows 7

2013-11-13 10:33:10

KitKatAndroidChromeOS

2023-11-07 11:17:25

Android數(shù)據(jù)共享

2015-12-21 11:11:26

2021-08-02 13:05:49

瀏覽器HTTP前端

2015-03-26 18:52:38

2012-07-31 11:06:48

WebGL

2013-09-25 09:26:03

平臺(tái)軟件企業(yè)虛擬化云網(wǎng)絡(luò)

2010-04-23 10:41:21

鏈路負(fù)載均衡

2010-08-26 14:40:55

隱私保護(hù)

2023-02-17 18:32:42

JavaAIOIO

2016-03-18 13:02:19

2019-07-16 11:06:09

TCP四次揮手半關(guān)閉

2013-09-26 11:05:24

云計(jì)算虛擬化

2011-02-22 09:40:18

HashMap

2013-10-08 11:16:55

谷歌云計(jì)算
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 中文字幕观看 | 男人天堂久久久 | 日本免费在线看 | 欧美最猛性xxxxx亚洲精品 | 免费视频二区 | 亚洲免费在线观看 | 黑人成人网 | 日韩中文字幕在线观看 | av在线视 | 国产日韩欧美一区二区 | 亚洲综合色视频在线观看 | 久久久久电影 | 一区二区三区视频在线观看 | 欧美久久久久久久久 | 亚洲视频免费在线看 | 成人免费观看男女羞羞视频 | 久久久久久久一区 | 中文字幕一区二区三区四区五区 | 久草热视频 | 精品国产视频 | 国产真实乱全部视频 | 在线观看中文字幕 | 秋霞在线一区 | 一区二区三区精品视频 | 成人一区二区在线 | 正在播放亚洲 | 国产精品一区二区视频 | 国产免费一区二区 | 精精国产xxxx视频在线播放 | 亚洲网站免费看 | 久久99深爱久久99精品 | 亚洲欧美一区二区三区在线 | 狼色网| 日韩手机在线看片 | 国产精品美女久久久久久免费 | 日韩精品一区二区三区视频播放 | 欧美日韩美女 | 91福利影院 | 99久久精品一区二区毛片吞精 | 中文字幕在线观看www | 久久最新精品 |