Android7.0、8.0安裝apk以及安裝apk彈出“選擇開啟方式”的解決方案
目錄
最近在做一款APP,做自動更新的時候,安裝apk遇到了一些問題:
-
FileUriExposedException異常;
-
無法跳轉到APP安裝頁面,無法進行版本更新升級;
-
在下載完成,進行安裝的時候,總是彈出“請選擇以下開啟方式”讓使用者選擇一個開啟方式進行安裝,一旦選擇不對則無法安裝。
下面我們逐一分析解決。
Android7.0安裝apk導致的FileUriExposedException異常
問題描述
在Android7.0中為了提高私有檔案的安全性,Google做了私有目錄限制訪問,面向Android-N或者更高版本的私有目錄被限制訪問,有點類似於IOS的沙盒機制,此設定可防止私有檔案的元資料洩漏。禁止像你的應用外公開file://uri,如果某一項包含file://uri型別的Intent被外部執行的時候,比如說安裝App(從file://uri獲取apk檔案進行安裝)、拍照(向file://uri存取照片)就會導致安全異常FileUriExposedException,下面是我的錯誤程式碼以及報錯日誌:
安裝App的程式碼
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + apkfile.toString()),
"application/vnd.android.package-archive");
mContext.startActivity(intent);
解決方案
方案一,使用嚴格模式(在Application中新增以下程式碼)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); }
方案二,使用FileProvider
首先在配置清單檔案(AndroidManifest.xm)中測註冊一個provider
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.houbin.test.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
注意:
- exported:要求必須為false,為true則會報安全異常。
- grantUriPermissions:true,表示授予 URI 臨時訪問許可權。
- authorities 元件標識,按照江湖規矩,都以包名開頭,避免和其它應用發生衝突。
然後資源目錄(res)下建立xml目錄,並在xml目錄下建立一個xml檔案,名字要跟AndroidMaifest.xml檔案中註冊的provider引用的resource保持一致。
建立file_paths.xml檔案:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="files_root"
path="Android/data/com.herenit.webdoc/" />
<external-path
name="external_storage_root"
path="." />
</paths>
<external-path/>代表的根目錄: Environment.getExternalStorageDirectory()
<external-path name="files_root" path="Android/data/com.houbin.test/" />
表示路徑為:Environment.getExternalStorageDirectory()+"Android/data/com.houbin.test/",起名字為files_root。
最後要在程式碼中做Android7.0相容,使用FileProvider代替file://url的Uri
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri contentUri = FileProvider.getUriForFile(mContext,BuildConfig.APPLICATION_ID+ ".fileprovider", apkfile);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
mContext.startActivity(intent);
}
其中BuildConfig.APPLICATION_ID+".fileprovider",表示“APP包名.fileprovider”,即前面AndroidManifest.xml註冊的provider的authorities節點的值com.houbin.test.fileprovider。
注意,在FileProvider.getUriForFile()方法的原始碼已經明確指出,需要Intent設定flag新增許可權FLAG_GRANT_READ_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSION許可權,如果這裡缺少intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);這句程式碼時,還是會丟擲FileUriExposedException異常的。
Android8.0安裝apk無法跳轉到正常的APP安裝頁面
問題描述
當用戶要從除了官方應用商店之外的來源安裝App時(安裝位置來源的APP):
Android8.0(Android-O)之前:可以被安裝,或者需要開啟系統設定當中“安裝未知應用”許可權,或者會有彈窗給使用者一個提示。;Android8.0之後,Google進一步加強了許可權管理,“安裝未知應用”許可權的永久開關被移除掉,每次當用戶安裝位置來源的App時,都需要單獨授權並且對軟體許可權進行手動確認,這樣的設計避免了被引誘安裝帶來的危害。當我們在Android8.0上安裝位置來源Apk時,如果不進行設定或者程式碼控制,是不會跳轉到系統App安裝介面,就會導致App無法安裝。
解決方案
方案一,使用者自己設定
例如在華為8.0系統手機上,使用者可以通過開啟 “設定->安全和隱私->更多安全設定->安裝未知應用”找到要更新的應用,然後點選開啟操作介面,設定“安裝未知應用”狀態為“允許”即可,如下圖所示:
是不是很不方便?如果我們的軟體以這樣的方式給使用者升級更新,那就體驗太差了。請看另一種解決辦法。
方案二,程式碼相容Android8.0
首先在AndroidManifest.xml清單檔案中新增安裝未知來源的許可權
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
然後在程式碼中相容Android8.0
if (android.os.Build.VERSION.SDK_INT >= 26) {
boolean hasInstallPermission = mContext.getPackageManager().canRequestPackageInstalls();
if (!hasInstallPermission) {
//請求安裝未知應用來源的許可權
ActivityCompat.requestPermissions((Activity) mContext, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, 6666);
}
}
這樣在系統安裝App頁面的時候,會彈出選擇框,讓使用者手動選擇是否同意安裝位置來源的應用,這個是無法避免的。如果到這一步,使用者還要選擇“禁止”,那在下實在沒辦法了~ ~ ~
安裝apk時彈出“選擇開啟方式”讓使用者選擇而不是直接跳轉到APP安裝介面
問題描述
當Android系統版本比較高的時候,在有些機型上,會出現這樣的情況,每次安裝下載好的APK檔案時候,不會直接跳轉到系統的安裝介面,而是先彈出一個“開啟方式的彈窗”讓使用者選擇開啟,如果選擇的是打包安裝程式之類的應用來開啟APK,那麼會成功跳轉到系統安裝Apk介面,否則不會安裝成功。
解決方案
遇到這種情況,請檢查一下安裝apk的程式碼,是不是缺少開啟相應Activity的Action?為了保險起見,在寫安裝apk程式碼的時候,要有如下程式碼,否則有可能就會出現上述問題
intent.setAction(Intent.ACTION_VIEW);
安裝apk程式碼示例
/**
* 安裝APK檔案
*/
private void installApk() {
File apkfile = new File(mSavePath, apkName);
if (!apkfile.exists()) {
return;
}
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri contentUri = FileProvider.getUriForFile(mContext, BuildConfig.APPLICATION_ID + ".fileprovider", apkfile);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
//相容8.0
if (android.os.Build.VERSION.SDK_INT >= 26) {
boolean hasInstallPermission = mContext.getPackageManager().canRequestPackageInstalls();
if (!hasInstallPermission) {
//請求安裝未知應用來源的許可權
ActivityCompat.requestPermissions((Activity) mContext, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, 6666);
}
}
} else {
// 通過Intent安裝APK檔案
intent.setDataAndType(Uri.parse("file://" + apkfile.toString()),
"application/vnd.android.package-archive");
}
if (mContext.getPackageManager().queryIntentActivities(intent, 0).size() > 0) {
mContext.startActivity(intent);
}
}
個人總結的一點小東西,歡迎各位大神批評指正