亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何理解透過FileProvider再看ContentProvider

發布時間:2021-10-18 14:37:51 來源:億速云 閱讀:107 作者:iii 欄目:移動開發

本篇內容主要講解“如何理解透過FileProvider再看ContentProvider”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“如何理解透過FileProvider再看ContentProvider”吧!

前言

在Android7.0,Android提高了應用的隱私權,限制了在應用間共享文件。如果需要在應用間共享,需要授予要訪問的URI臨時訪問權限。

以下是官方說明:

對于面向 Android 7.0 的應用,Android 框架執行的 StrictMode API 政策禁止在您的應用外部公開 file:// URI。如果一項包含文件 URI 的 intent 離開您的應用,則應用出現故障,并出現 FileUriExposedException 異常。要在應用間共享文件,您應發送一項 content:// URI,并授予 URI 臨時訪問權限。進行此授權的最簡單方式是使用 FileProvider 類。”

為什么限制在應用間共享文件

打個比方,應用A有一個文件,絕對路徑為file:///storage/emulated/0/Download/photo.jpg

現在應用A想通過其他應用來完成一些需求,比如拍照,就把他的這個文件路徑發給了照相應用B,然后應用B照完相就把照片存儲到了這個絕對路徑。

看起來似乎沒有什么問題,但是如果這個應用B是個“壞應用”呢?

  • 泄漏了文件路徑,也就是應用隱私。

如果這個應用A是“壞應用”呢?

  • 自己可以不用申請存儲權限,利用應用B就達到了存儲文件的這一危險權限。

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

所以Google就想了一個辦法,把對文件的訪問限制在應用內部。

  • 如果要分享文件路徑,不要分享file:// URI這種文件的絕對路徑,而是分享content:// URI,這種相對路徑,也就是這種格式:content://com.jimu.test.fileprovider/external/photo.jpg

  • 然后其他應用可以通過這個絕對路徑來向文件所屬應用 索要 文件數據,所以文件所屬的應用本身必須擁有文件的訪問權限。

也就是應用A分享相對路徑給應用B,應用B拿著這個相對路徑找到應用A,應用A讀取文件內容返給應用B。

配置FileProvider

搞清楚了要做什么事,接下來就是怎么做。

涉及到應用間通信的問題,還記得IPC的幾種方式嗎?

  • 文件

  • AIDL

  • ContentProvider

  • Socket

  • 等等。

從易用性,安全性,完整度等各個方面考慮,Google選擇了ContentProvider為這次限制應用分享文件的 解決方案。于是,FileProvider誕生了。

具體做法就是:

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

這樣配置之后,就能生成content:// URI,并且也能通過這個URI來傳輸文件內容給外部應用。

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

  • android:name,是ContentProvider的類路徑。

  • android:authorities,是唯一標示,一般為包名+.provider

  • android: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的主要源碼:

public class FileProvider extends ContentProvider {      @Override     public boolean onCreate() {         return true;     }      @Override     public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {         super.attachInfo(context, info);          // Sanity check our security         if (info.exported) {             throw new SecurityException("Provider must not be exported");         }         if (!info.grantUriPermissions) {             throw new SecurityException("Provider must grant uri permissions");         }          mStrategy = getPathStrategy(context, info.authority);     }       public static Uri getUriForFile(@NonNull Context context, @NonNull String authority,             @NonNull File file) {         final PathStrategy strategy = getPathStrategy(context, authority);         return strategy.getUriForFile(file);     }       @Override     public Uri insert(@NonNull Uri uri, ContentValues values) {         throw new UnsupportedOperationException("No external inserts");     }       @Override     public int update(@NonNull Uri uri, ContentValues values, @Nullable String selection,             @Nullable String[] selectionArgs) {         throw new UnsupportedOperationException("No external updates");     }       @Override     public int delete(@NonNull Uri uri, @Nullable String selection,             @Nullable String[] selectionArgs) {         // ContentProvider has already checked granted permissions         final File file = mStrategy.getFileForUri(uri);         return file.delete() ? 1 : 0;     }      @Override     public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,             @Nullable String[] selectionArgs,             @Nullable String sortOrder) {         // ContentProvider has already checked granted permissions         final File file = mStrategy.getFileForUri(uri);          if (projection == null) {             projection = COLUMNS;         }          String[] cols = new String[projection.length];         Object[] values = new Object[projection.length];         int i = 0;         for (String col : projection) {             if (OpenableColumns.DISPLAY_NAME.equals(col)) {                 cols[i] = OpenableColumns.DISPLAY_NAME;                 values[i++] = file.getName();             } else if (OpenableColumns.SIZE.equals(col)) {                 cols[i] = OpenableColumns.SIZE;                 values[i++] = file.length();             }         }          cols = copyOf(cols, i);         values = copyOf(values, i);          final MatrixCursor cursor = new MatrixCursor(cols, 1);         cursor.addRow(values);         return cursor;     }      @Override     public String getType(@NonNull Uri uri) {   final File file = mStrategy.getFileForUri(uri);          final int lastDot = file.getName().lastIndexOf('.');         if (lastDot >= 0) {             final String extension = file.getName().substring(lastDot + 1);             final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);             if (mime != null) {                 return mime;             }         }         return "application/octet-stream";     }   }

任何一個ContentProvider都需要繼承ContentProvider類,然后實現這幾個抽象方法:

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

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

我們分三部分說說:

數據調用方面

其中,query,insert,delete,update四個方法就是數據的增刪查改,也就是進程間通信的相關方法。

其他應用可以通過ContentProvider來調用這幾個方法,來完成對本地應用數據的增刪查改,從而完成進程間通信的功能。

具體方法就是調用getContentResolver()的相關方法,例如:

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

再回去看看FileProvider:

  • query,查詢方法。在該方法中,返回了File的name和length。

  • insert,插入方法。沒有做任何事。

  • delete,刪除方法。刪除Uri對應的File。

  • update,更新方法。沒有做任何事。

MIME類型

再看getType方法,這個方法主要是返回 Url所代表數據的MIME類型。

一般是使用默認格式:

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

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

具體怎么用呢?可以通過Content URI對應的ContentProvider配置的getType來匹配Activity。

有點拗口,比如Activity和ContentProvider這么配置的:

<activity       android:name=".SecondActivity">       <intent-filter>           <action android:name=""/>           <category android:name=""/>           <data android:mimeType="type_test"/>      </intent-filter>   </activity>
@Override public String getType(@NonNull Uri uri) {     return "type_test"; }   intent.setData(mContentRUI);   startActivity(intent)

這樣配置之后,startActivity就會檢查Activity的mineType 和 Content URI 對應的ContentProvider的getType是否相同,相同情況下才能正常打開Activity。

初始化

最后再看看onCreate方法。

在APP啟動流程中,自動執行所有ContentProvider的attachInfo方法,并最后調用到onCreate方法。一般在這個方法中就做一些初始化工作,比如初始化ContentProvider所需要的數據庫。

而在FileProvider中,調用了attachInfo方法作為了一個初始化工作的入口,其實和onCreate方法的作用一樣,都是App啟動的時候會調用的方法。

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

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

這個初始化方法和特性,也是被很多三方庫所利用,可以進行靜默無感知的初始化工作,而無需單獨調用三方庫初始化方法。比如Facebook SDK:

<provider     android:name="com.facebook.internal.FacebookInitProvider"     android:authorities="${applicationId}.FacebookInitProvider"     android:exported="false" />
public final class FacebookInitProvider extends ContentProvider {     private static final String TAG = FacebookInitProvider.class.getSimpleName();      @Override     @SuppressWarnings("deprecation")     public boolean onCreate() {         try {             FacebookSdk.sdkInitialize(getContext());         } catch (Exception ex) {             Log.i(TAG, "Failed to auto initialize the Facebook SDK", ex);         }         return false;     }      //... }

這樣一寫,就無需單獨集成FacebookSDK的初始化方法了,實現靜默初始化。

而Jetpack中的App Startup也是考慮到這些三方庫的需求,對三方庫的初始化進行了一個合并,從而優化了多次創建ContentProvider的耗時。

拿到Content URI 該怎么使用?

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

其實仔細找找就能發現,在FileProvider.java中有注釋說明:

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

也就是openFileDescriptor方法,拿到ParcelFileDescriptor類型數據,其實就是一個文件描述符,然后就可以讀取文件流了。

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

ContentProvider 實際應用

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

  • 和系統的一些App通信,比如獲取通訊錄,調用拍照等。上述的FileProvider也是屬于這種情況。

  • 與自己的APP有一些交互。比如自家多應用之間,可以通過這個進行一些數據交互。

  • 三方庫的初始化工作。很多三方庫會利用ContentProvider自動初始化這一特性,進行一個靜默無感知的初始化工作。

到此,相信大家對“如何理解透過FileProvider再看ContentProvider”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

前郭尔| 宜章县| 廉江市| 区。| 通州市| 富源县| 南部县| 永德县| 隆回县| 青阳县| 共和县| 普宁市| 奉贤区| 栾城县| 巴东县| 繁峙县| 临高县| 进贤县| 林周县| 平乡县| 枣阳市| 西安市| 杭锦旗| 扎赉特旗| 临邑县| 揭阳市| 大姚县| 宜章县| 南宁市| 茌平县| 寿阳县| 原平市| 柯坪县| 习水县| 新乡市| 项城市| 明水县| 崇礼县| 丰都县| 新竹县| 福州市|