Android 安裝時配的幾點適配
最近在做一個專案的時候,遇到關於適配的問題,真的是鼓搗了兩個晚上,幾乎要心碎了,特來趁還有記憶記錄下。
第一個問題是在App剛上線的時候,一般我們都會選擇讓使用者強制更新,so,我們也選擇了可以讓使用者及時享用我們App的變化。形式是通過一個我們常見的下載框,螢幕外不可點選取消,如下圖:
彈出框的主要程式碼appUpdate, 這裡採用Retrofit 進行ResponseBody 攔截監聽下載進度的,這裡著重將這個方面
public void appUpdate() {
// 如果沒有許可權
if (MPermissionUtils.checkPermissions (HomeHotFragment.this.getContext(), Manifest.permission.READ_EXTERNAL_STORAGE)) {
// 判斷是否需要強制更新
if ("Y".equals(mHomeBean.getIsUpdate())) {
File storageDir = new File(Environment.getExternalStorageDirectory().toString(), "Android");
downloadUpdateApkFilePath = storageDir.getPath ()
+ File.separator
+ HomeHotFragment.this.getContext().getPackageName()
+ "_" + "Hong" + ".apk";
final ProgressDialog dialog = new ProgressDialog(HomeHotFragment.this.getContext());
dialog.setProgressNumberFormat ("%1d KB/%2d KB");
dialog.setTitle("下載");
dialog.setMessage("正在下載,請稍後...");
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setCancelable(false);
dialog.show();
HomeAPIPModel.getInstance().downloadFileProgress(new ProgressListener() {
@Override
public void onProgress(long currentBytes, long contentLength, boolean done) {
dialog.setMax((int) (contentLength / 1024));
dialog.setProgress((int) (currentBytes / 1024));
if (done) {
dialog.dismiss();
}
}
}, mHomeBean.getDownload_url());
}
} else {
Toast.makeText(HomeHotFragment.this.getContext(), "您沒有SD許可權", Toast.LENGTH_SHORT).show();
}
}
但是,當我們選擇安裝的時候,在Android 7.0直接顯示解析包錯誤(使用的SM-G9300);在Android 8.0上直接崩掉了,崩掉了,真的是沒有一點點防備;(使用的華為榮耀V10)
Android < 7.0
在Android 7.0 之前 我們只需要隱式Intent 啟動APK安裝即可
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(new File(apkPath)),
"application/vnd.android.package-archive");context.startActivity(intent);
startActivity(intent);
關於Android 7.0
Andorid 現在對使用者的隱私許可權管理的越來越嚴格,7.0之後啟用私有目錄被限制訪問,也就是禁止向外公佈file://URL, 否則將出現 android.os.FileUriExposedException 異常,需要FileProvider解決
manifest 檔案配置
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.pandora.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
exported: 必須是false,不允許被另一個Application的元件啟動
authorities:元件標誌,一般以包名開頭,避免重複
grantUriPermissions:true 許可權同意
res檔案下新建xml檔案,在xml檔案下新建file_paths檔案,和manifest中配置的檔名稱一樣
file_paths 指定要共享的檔案目錄
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path
name="Android"
path="hm_apk" />
<!--<files-path name="apk" path="Android" />-->
<!--<external-path name="cache" path="Cache/" />-->
<!--<!–代表外部儲存區域的根目錄下的檔案 Environment.getExternalStorageDirectory()/DCIM/camerademo目錄–>-->
<!--<external-path name="hm_DCIM" path="DCIM/camerademo" />-->
<!--<!–代表外部儲存區域的根目錄下的檔案 Environment.getExternalStorageDirectory()/Pictures/camerademo目錄–>-->
<!--<external-path name="hm_Pictures" path="Pictures/camerademo" />-->
<!--<!–代表app 私有的儲存區域 Context.getFilesDir()目錄下的images目錄 /data/user/0/com.hm.camerademo/files/images–>-->
<!--<files-path name="hm_private_files" path="images" />-->
<!--<!–代表app 私有的儲存區域 Context.getCacheDir()目錄下的images目錄 /data/user/0/com.hm.camerademo/cache/images–>-->
<!--<cache-path name="hm_private_cache" path="images" />-->
<!--<!–代表app 外部儲存區域根目錄下的檔案 Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)目錄下的Pictures目錄–>-->
<!--<!–/storage/emulated/0/Android/data/com.hm.camerademo/files/Pictures–>-->
<!--<external-files-path name="hm_external_files" path="Pictures" />-->
<!--<!–代表app 外部儲存區域根目錄下的檔案 Context.getExternalCacheDir目錄下的images目錄–>-->
<!--<!–/storage/emulated/0/Android/data/com.hm.camerademo/cache/images–>-->
<!--<external-cache-path name="hm_external_cache" path="" />-->
</paths>
</resources>
在這些配置完之後,我們就可以在程式碼中真正訪問相應的記憶體空間了。一開始按照相應的配置規則適配7.0的讀寫許可權之後,在下載完App之後,安裝的時候還是一直解析包錯誤,一開始以為包下載出錯了,就斷點跟蹤App的下載過程,最後發現在相應的資料夾下也能看到下載的App,但就是在安裝的時候解析包錯誤。最後找了好久發現是需要在file_paths檔案下指定 root-path ,之後的之後就可以覆蓋安裝成功了。
path=”.”其實是對應外接SD卡的根目錄, ,就是說你可以向其它的應用共享根目錄及其子目錄下任何一個檔案了。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path
name="Android"
path="hm_apk" />
<root-path
name="root_path"
path="." />
</paths>
</resources>
之後在APK 進行安裝的時候需要使用FiliProvider建立的Uri ,,也就是我們共享的空間位置, 並且新增intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)來對目標應用臨時授權該Uri所代表的檔案,而且getUriForFile中的authority引數需要填寫清單檔案中的authorities的值
private void install(String path) {
if (!TextUtils.isEmpty(path)) {
Intent i = new Intent(Intent.ACTION_VIEW);
File apkFile = new File(path);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(
PandoraApplication.getInstance().getApplicationContext(),
PandoraApplication.getInstance().getApplicationContext().getPackageName() + ".fileprovider",
apkFile);
i.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
i.setDataAndType(Uri.fromFile(apkFile),
"application/vnd.android.package-archive");
}
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PandoraApplication.getInstance().getApplicationContext().startActivity(i);
AppManager.getAppManager().AppExit(PandoraApplication.getInstance().getApplicationContext(), false);
}
}
Android 8.0
在7.0上安裝成功之後,8.0 安裝的時候又莫名其妙的崩潰了,真的想哭了,原來是在手機設定上有個是否已經獲取到允許安裝未知應用,如果在沒有允許的情況下,安裝的時候是直接退出的,也不會給使用者提示,所以,我們還要判斷Android的版本,進行這個許可權的申請,這個許可權只需要在清單檔案中註冊即可。
首先在清單檔案中進行註冊
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
之後我們還可以在程式碼中再進行配置:
public void checkAndroidO() {
if (Build.VERSION.SDK_INT >= 26) {
boolean b = getPackageManager().canRequestPackageInstalls();
if (b) {
installApk();
} else {
//請求安裝未知應用來源的許可權
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, INSTALL_PACKAGES_REQUESTCODE);
}
} else {
installApk();
}
}
若是沒有獲得該許可權,我們可以使用Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES intent 操作將使用者引導至安裝未知應用介面。
使用者允許該來源之後,我們的處理如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case GET_UNKNOWN_APP_SOURCES:
checkAndroidO();
break;
default:
break;
}
}