1. 程式人生 > >Android:應用內下載更新app,apk包在Android7.0以上系統安裝失敗

Android:應用內下載更新app,apk包在Android7.0以上系統安裝失敗

最近又更新了一下Android studio的gradle版本

結果。。。

與儲存相關的無一倖免,Android6.0引入的動態許可權控制(Runtime Permissions),Android7.0又引入“私有目錄被限制訪問”,“StrictMode API 政策”。“私有目錄被限制訪問“ 是指在Android7.0中為了提高私有檔案的安全性,面向 Android N 或更高版本的應用私有目錄將被限制訪問。這點類似iOS的沙盒機制。

” StrictMode API 政策” 是指禁止向你的應用外公開 file:// URI。 如果一項包含檔案 file:// URI型別 的 Intent 離開你的應用,應用失敗,並出現 FileUriExposedException 異常。

比如我在應用內跟新app下載後跳轉不到安裝頁面,比如我的掃碼開啟相機以及相機拍照上傳頭像等

解決方法如下:

使用FileProvider

使用FileProvider的大致步驟如下:

第一步:
在AndroidManifest.xml清單檔案中註冊provider,因為provider也是Android四大元件之一,可以簡單把它理解為向外提供資料的元件,這種元件在實際開發中用的頻率並不高,四大元件都可以在清單檔案中進行配置:

<application
   ...>
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="自己包名.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
      
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
</application>

注意:

  • exported:要求必須為false,為true則會報安全異常。
  • grantUriPermissions:true,表示授予 URI 臨時訪問許可權。
  • authorities 元件標識,以包名開頭,避免和其它應用發生衝突。

第二步:指定共享的目錄
上面配置檔案中 android:resource="@xml/file_paths" 指的是當前元件引用 res/xml/file_paths.xml 這個檔案。

我們需要在資源(res)目錄下建立一個xml目錄,然後建立一個名為“file_paths”(名字可以隨便起,只要和在manifest註冊的provider所引用的resource保持一致即可)的資原始檔,內容如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="files_root"
        path="Android/data/com.gantang.gantang/" />
    <external-path
        name="external_storage_root"
        path="." />
</paths>
  • 代表的根目錄: Context.getFilesDir()
  • 代表的根目錄: Environment.getExternalStorageDirectory()
  • 代表的根目錄: getCacheDir()

上述程式碼中path=”“,是有特殊意義的,它程式碼根目錄,也就是說你可以向其它的應用共享根目錄及其子目錄下任何一個檔案了。

如果你將path設為path="pictures",那麼它代表著根目錄下的pictures目錄(eg:/storage/emulated/0/pictures),如果你向其它應用分享pictures目錄範圍之外的檔案是不行的。

第三步:使用FileProvider
上述準備工作做完之後,現在我們就可以使用FileProvider了。
我們需要將上述安裝APK程式碼修改為如下

public static void install(Context context) {
        File file = new File(
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                , "myApp.apk");
        Intent intent = new Intent(Intent.ACTION_VIEW);
        // 由於沒有在Activity環境下啟動Activity,設定下面的標籤
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if(Build.VERSION.SDK_INT>=24) { //判讀版本是否在7.0以上
            //引數1 上下文, 引數2 Provider主機地址 和配置檔案中保持一致   引數3  共享的檔案
            Uri apkUri =
                    FileProvider.getUriForFile(context, "你的包名.fileprovider", file);
            //新增這一句表示對目標應用臨時授權該Uri所代表的檔案
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        }else{
            intent.setDataAndType(Uri.fromFile(file),
                    "application/vnd.android.package-archive");
        }
        context.startActivity(intent);
    }

上述程式碼中主要有兩處改變:
1. 將之前Uri改成了有FileProvider建立一個content型別的Uri。
2. 添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);來對目標應用臨時授權該Uri所代表的檔案。

上述程式碼通過FileProviderUri getUriForFile (Context context, String authority, File file)靜態方法來獲取Uri
該方法中authority引數就是清單檔案中註冊provider時填寫的authority
android:authorities="你的包名.test.fileprovider"。
按照上面步驟修改就可以相容Android7.0了。