日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

FileProvider 在 Android N 上的应用

發布時間:2025/3/15 Android 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 FileProvider 在 Android N 上的应用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:才華橫溢的段老師?藍田大營

一、背景

Android 從 N 開始不允許以 file:// 的方式通過 Intent 在兩個 App 之間分享文件,取而代之的是通過 FileProvider 生成 content://Uri 。如果在 Android N 以上的版本繼續使用 file:// 的方式分享文件,則系統會直接拋出異常,導致 App 出現 Crash ,同時會報以下錯誤日志:

FATAL EXCEPTION: mainProcess: com.inthecheesefactory.lab.intent_fileprovider, PID: 28905android.os.FileUriExposedException: file:///storage/emulated/0/.../xxx/xxx.jpg exposed beyond app through ClipData.Item.getUri()at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)at android.net.Uri.checkFileUriExposed(Uri.java:2346)at android.content.ClipData.prepareToLeaveProcess(ClipData.java:832)

當然如果工程的 targetSDK 小于24,暫時還不會遇到這個問題,一旦升級到24及以上,則會立即出現上述問題,所以提早做好預防很有必要,否則等到線上曝出大量的 bug 就很被動了。

二、關于 FileProvider

官方對于 FileProvider 的解釋為:FileProvider 是一個特殊的 ContentProvider 子類,通過 content://Uri 代替 file://Uri 實現不同 App 間的文件安全共享。

當通過包含 Content URI 的 Intent 共享文件時,需要申請臨時的讀寫權限,可以通過 Intent.setFlags() 方法實現。

而 file://Uri 方式需要申請長期有效的文件讀寫權限,直到這個權限被手動改變為止,這是極其不安全的做法。因此 Android 從 N 版本開始禁止通過 file://Uri 在不同 App 之間共享文件。

三、FileProvider 的使用流程

完成整個文件共享的流程,需要配置以下5點:

  • 定義一個 FileProvider
  • 指定有效的文件
  • 為文件生成有效的 Content URI
  • 申請臨時的讀寫權限
  • 發送 Content URI 至其他的 App
  • 1. 定義 FileProvider

    FileProvider 已經把文件生成 Content URI 的工作幫我們做掉了,因此我們只需要在 AndroidManifest.xml 文件中配置 <provider> 元素并提供相應的屬性。

    重要的屬性包括以下四個:

    • 設置 android:name 為android.support.v4.content.FileProvider,這是固定的,不需要手動更改;
    • 設置 android:authorities 為 application id + .provider ;
    • 設置 android:exported 為 false ,表示 FileProvider 不是公開的;
    • 設置 android:grantUriPermissions 為 true 表示允許臨時讀寫文件。

    此處需要特別說明的是

  • android:authorities 最好是 application id 而不能直接用包名硬編碼,因為 Android 系統要求 android:authorities 對于每個 App 而言必須是唯一的。
  • 假如 FileProvider 用在 SDK 中,多個 App 都在調用同一個 SDK,而 SDK 中的 android:authorities 為硬編碼,那么 App 之間的 authorities 就會出現沖突,會報 Install shows error in console: INSTALL FAILED CONFLICTING PROVIDER 的錯誤。
  • 如果 SDK 的 android:authorities 是 application id,那么 authorities 會和宿主 App 的 application id 保持一致,就不會出現 authorities 沖突的問題。
  • 在 Java 代碼中調用 getPackageName() 返回的是 application id ,而非 package name ,要驗證這一點也很容易,在 build.gradle 文件中定義和包名不同的 application id ,打印代碼中 getPackageName() 的返回值,就會發現返回值是 build.gradle 中自定義的 application id ,而非 package name
  • 關于 package name 和 application id 的區別可以參考?ApplicationId 與 PackageName 的區別
  • 以下是一個簡單的示例:

    <manifest>...<application>...<providerandroid:name="android.support.v4.content.FileProvider"android:authorities="${applicationId}.provider"android:exported="false"android:grantUriPermissions="true">...</provider>...</application> </manifest>

    需要說明的是 ${applicationId} 是占位符,Gradle 會替換成我們在 build.gralde 中定義的 applicationId "com.domain.example",如果 build.gradle 文件中沒有定義,那么 application id的默認值是 App 的 package name。

    2. 指定有效的文件

    在生成 Content URI 之前你還需要提前指定文件目錄,通常的做法是在 res 目錄下新建一個 xml 文件夾,然后創建一個 xml 文件,在此文件中指定共享文件的路徑和名字,示例如下:

    <paths xmlns:android="http://schemas.android.com/apk/res/android"><external-path name="my_images" path="images/"/>... </paths>

    其中 name 屬性和 path 屬性必填, name 表示共享文件的名字, path 代表文件路徑。

    • external-path 代表文件位于手機外部存儲空間,訪問效果如同 Environment.getExternalStorageDirectory();
    • files-path 代表文件位于手機內部存儲空間,訪問效果如同 getFilesDir();
    • cache-path 代表文件位于手機內部緩存空間,訪問效果如同 getCacheDir()。

    xml 文件創建完成后,還需要在 manifest 文件的 <provider> 元素下完成相應的配置,假定 xml 文件命名為 file_paths.xml ,示例如下:

    <providerandroid:name="android.support.v4.content.FileProvider"android:authorities="${applicationId}.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths" /> </provider>

    3. 為共享文件生成 Content URI

    文件配置完成后還需要生成可以被其他 App 訪問的 Content URI,可以直接調用 FileProvider 提供的 getUriForFile(File file) 方法,顧名思義,傳入文件名稱就可以得到相應的 Content URI 。需要訪問該文件的 App 可以通過 ContentResolver.openFileDescriptor 得到一個 ParcelFileDescriptor 對象。

    假定你想要共享一個圖片文件,文件存放的位置為手機內部存儲空間下的 images 文件夾,圖片文件名字為 default_name.jpg ,那么生成 Content URI 方式如下:

    File imagePath = new File(getContext().getFilesDir(), "images"); File newFile = new File(imagePath, "default_image.jpg"); Uri contentUri = getUriForFile(getContext(), "com.mydomain.provider", newFile);

    最后生成的 Content URI 為

    content://com.domain.example.provider/images/default_image.jpg.

    4. 申請臨時讀寫文件權限

    上文已經提到 FileProvider 可以申請臨時讀寫文件權限,以增強安全性,所以 Content URI 生成完成后,還需要申請臨時訪問權限。

    通常直接通過 intent.setFlags 即可完成,具體的權限名稱為:Intent.FLAG_GRANT_READ_URI_PERMISSION 和 Intent.FLAG_GRANT_WRITE_URI_PERMISSION。

    5. 發送 Content URI 至其他的 App

    萬事已備,只需要發送出去即可,通常都會使用 startActivityForResult 方法發送,可以在 onActivityResult 中獲取其他 App 的處理結果,完成整個操作閉環。

    三、實用場景——手機照相

    在 Android N 之前的版本調用相機獲取圖片可以用如下代碼實現:

    // 設置照片需要存儲的位置 photoPath = FileUtil.getImageFile().getPath() Intent intent = new Intent();// 指定開啟系統相機的Action intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); intent.addCategory(Intent.CATEGORY_DEFAULT);// 把文件地址轉換成Uri格式 Uri uri = Uri.parse("file://" + photoPath); intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); activity.startActivityForResult(intent, requestCode);

    如果要想在 Android N 及以上版本上不會出錯,則必須將 file:// 形式替換成 content:// ,具體的代碼如下:

    Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);// 系統版本大于N的統一用FileProvider處理 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {// 將文件轉換成content://Uri的形式Uri photoURI = FileProvider.getUriForFile(activity,activity.getPackageName()+ ".provider",new File(photoPath));// 申請臨時訪問權限intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); } else {intent.addCategory(Intent.CATEGORY_DEFAULT);Uri uri = Uri.parse("file://" + photoPath);intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); } activity.startActivityForResult(intent, requestCode);

    需要注意的是 getPackageName() 返回值是 application id,關于 application id 上文已經解釋過,此處不再重復。

    實用場景——微信朋友圈多圖分享

    微信官方不支持朋友圈直接多圖分享,Android 之前的版本由于沒有強制限制 file:// 的使用,所以可以通過訪問微信包名的方式實現朋友圈多圖分享,但是Android N 之后這種“曲線救國”的方式就不行了。

    先來看一下之前如何通過訪問包名實現朋友圈多圖分享,代碼如下:

    Intent intent = new Intent(); intent.setComponent(new ComponentName("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI")); intent.setAction("android.intent.action.SEND_MULTIPLE");// List存儲多張圖片地址 ArrayList<Uri> localArrayList = new ArrayList<>(); for (int i = 0, size = localPicsList.size(); i < size; i++) {localArrayList.add(Uri.parse("file:///" + localPicsList.get(i))); }intent.putParcelableArrayListExtra("android.intent.extra.STREAM", localArrayList); intent.setType("image/*"); intent.putExtra("Kdescription", desc); context.startActivity(intent);

    這種方式可以直接繞過微信官方 SDK 實現多圖分享,無需手動選擇圖片,唯一的問題就是沒有分享結果的回調,也就是說無法判斷是否分享成功,這在大部分情況下依然是一種可以接受的方案。

    但是如果 targetSDK 大于等于24,那么這項功能就無效了,原因就是 Android N 不允許 file://Uri 的方式在不同的 App 間共享文件,但是如果換成 FileProvider 的方式,經試驗發現依然是無效的,所以在 Android N 上無法實現朋友圈直接多圖分享。

    原文地址: https://zhuanlan.zhihu.com/p/26139355

    總結

    以上是生活随笔為你收集整理的FileProvider 在 Android N 上的应用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。