天天看點

Android 7.0 安裝apk 廣播失效問題 通過FileProvider在應用間共享檔案 配置根目錄

詳細使用說明請參考hongyang的blog

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

我這裡主要講安裝應用内部路徑apk和外部存儲的apk的失敗原因和解決辦法,這裡面借了一下别人的代碼講述一下我采的坑!!!

如果我們使用Android 7.0或者以上的原生系統,再次運作一下,你會發現應用直接停止運作,抛出了

android.os.FileUriExposedException

Caused by: android.os.FileUriExposedException: 
    file:///storage/emulated/0/20170601-030254.png 
        exposed beyond app through ClipData.Item.getUri()
    at android.os.StrictMode.onFileUriExposed(StrictMode.java:1932)
    at android.net.Uri.checkFileUriExposed(Uri.java:2348)
           

此文主要講一下,關于使用fileprovider的采坑過程。

(1)AndroidManifest中聲明provider

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.xxx.xxxx.fileprovider" 注意這個參數要和代碼中啟動的authorities保持一緻
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>
           

為什麼要聲明呢?因為FileProvider是ContentProvider子類哇~~

注意一點,他需要設定一個meta-data,裡面指向一個xml檔案。

注意:

  • exported

    :要求必須為false,為true則會報安全異常。
  • grantUriPermissions:true

    ,表示授予 URI 臨時通路權限。
  • authorities

     元件辨別,按照江湖規矩,都以包名開頭,避免和其它應用發生沖突。
  • 上面配置檔案中 

    android:resource="@xml/file_paths"

     指的是目前元件引用 

    res/xml/file_paths.xml

     這個檔案。

(2)file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <root-path name="root" path="" />
    <files-path name="files" path="" />
    <cache-path name="cache" path="" />
    <external-path name="external" path="" />
    <external-files-path name="name" path="path" />
     <external-cache-path name="name" path="path" />
</paths>               

為什麼hongyang不寫成這樣? 真是讓人迷惑

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <root-path name="root" path="path" />
    <files-path name="files" path="path" />
    <cache-path name="cache" path="path" />
    <external-path name="external" path="" />
    <external-files-path name="name" path="path" />
     <external-cache-path name="name" path="path" />
</paths>               

在paths節點内部支援以下幾個子節點,分别為:

  • <root-path/>

     代表裝置的根目錄

    new File("/")

    ;
  • <files-path/>

     代表

    context.getFilesDir()

  • <cache-path/>

     代表

    context.getCacheDir()

  • <external-path/>

     代表

    Environment.getExternalStorageDirectory()

  • <external-files-path>

    代表

    context.getExternalFilesDirs()

  • <external-cache-path>

    代表

    getExternalCacheDirs()

每個節點都支援兩個屬性:

的時候代碼沒問題,所有手機運作都沒問題

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="internal_files" path="." />
    <external-files-path name="external_files" path="." />
</paths>
           

路徑内部apkFile = context.getExternalFilesDirs()+"/"xx.apk; 所有版本手機安裝成功

我這裡要将伺服器中的apk下載下傳到應用user/data的私有空間和下載下傳到外部存儲兩種,我的配置檔案如下

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="internal_files" path="download" />
    <external-files-path name="external_files" path="download" />
</paths>                  /**
      * 安裝 apk 檔案
      *
      * @param apkFile
      */
     public void installApk(File apkFile) {
       Intent installApkIntent = new Intent();
       installApkIntent.setAction(Intent.ACTION_VIEW);
       installApkIntent.addCategory(Intent.CATEGORY_DEFAULT);
       installApkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
       if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
         installApkIntent.setDataAndType(FileProvider.getUriForFile(getApplicationContext(),"com.xxx.xxxx.fileprovider", apkFile), "application/vnd.android.package-archive");
         installApkIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
       } else {
         installApkIntent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
       }
 
       if (getPackageManager().queryIntentActivities(installApkIntent, 0).size() > 0) {
         startActivity(installApkIntent);
       }
     }

           

apkFile = context.getFilesDir()+"/download"+xx.apk; 下載下傳到内部存儲的時候發現了新問題,原來在android6.0以下安裝好使的代碼,不好使了。原因是找不到安裝檔案。

apkFile = context.getExternalFilesDirs()+"/download/"+xx.apk; 下載下傳到外部存儲的時候代碼沒問題,所有手機運作都沒問題。

于是修改配置檔案和下載下傳路徑

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="internal_files" path="." />
    <external-files-path name="external_files" path="." />
</paths>
           

路徑内部apkFile = context.getExternalFilesDirs()+"/"xx.apk; 所有版本手機安裝成功

外部apkFile = context.getFilesDir()+"/"+xx.apk; 所有版本手機安裝成功

申請臨時的讀寫權限

生成 Content URI 對象後,需要對其授權通路權限。授權方式有兩種: 

第一種方式,使用 Context 提供的 grantUriPermission(package, Uri, mode_flags) 方法向其他應用授權通路 URI 對象。三個參數分别表示授權通路 URI 對象的其他應用包名,授權通路的 Uri 對象,和授權類型。其中,授權類型為 Intent 類提供的讀寫類型常量:

FLAG_GRANT_READ_URI_PERMISSION

FLAG_GRANT_WRITE_URI_PERMISSION

或者二者同時授權。這種形式的授權方式,權限有效期截止至發生裝置重新開機或者手動調用 revokeUriPermission() 方法撤銷授權時。

第二種方式,配合 Intent 使用。通過 setData() 方法向 intent 對象添加 Content URI。然後使用 setFlags() 或者 addFlags() 方法設定讀寫權限,可選常量值同上。這種形式的授權方式,權限有效期截止至其它應用所處的堆棧銷毀,并且一旦授權給某一個元件後,該應用的其它元件擁有相同的通路權限。

發送 Content URI

擁有授予權限的 Content URI 後,便可以通過 startActivity() 或者 setResult() 方法啟動其他應用并傳遞授權過的 Content URI 資料。當然,也有其他方式提供服務。

如果你需要一次性傳遞多個 URI 對象,可以使用 intent 對象提供的 setClipData() 方法,并且 setFlags() 方法設定的權限适用于所有 Content URIs。

下面的寫法是粘貼的老外一篇文章裡的,應該是斜杠和點的作用是一樣的。              <paths>
    <cache-path name="cache" path="/" />
    <files-path name=”files” path=”/” />
</paths>
           

總結:就是在

installApkIntent.setDataAndType(Uri.fromFile(apkFile), 

"application/vnd.android.package-archive"

);

startActivity(installApkIntent);

android 6.0以下的手機,安裝應用user/data下面的apk時,隻支援目錄為context.getFilesDir()的層級,再深一層,系統都找不到這個下載下傳後的檔案安裝包了。可能是6.0以下的預設權限問題。