詳細使用說明請參考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檔案。
注意:
-
:要求必須為false,為true則會報安全異常。exported
-
,表示授予 URI 臨時通路權限。grantUriPermissions:true
-
元件辨別,按照江湖規矩,都以包名開頭,避免和其它應用發生沖突。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以下的預設權限問題。