Android 7.0: install .apk
最後是試出來了,中間有幾個問題,就是安裝是從內部 cache dir 會有問題,安裝程式會顯示:
there was a problem parsing the package
改成 external cache dir 就OK。所以 xml 檔一定要使用下面這一組:
<external-path name="external_files" path="." />
Android Manifest中加入許可權
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
取得 SD Card 許可權:
//Android 6.0以上需要判斷使用者是否願意開啟儲存(WRITE_EXTERNAL_STORAGE)的許可權 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { CheckStoragePermission(); }
private void CheckStoragePermission() { //Android 6.0檢查是否開啟儲存(WRITE_EXTERNAL_STORAGE)的許可權,若否,出現詢問視窗 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {//Can add more as per requirement ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 20); } } @Override //Android 6.0以上 接收使用者是否允許使用儲存許可權 public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case 20: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { } else { CheckStoragePermission(); } return; } } } 附註:我測試了多臺Android 6.0 + Android 7.0 裝置,發現提升許可權非必需,完全沒有提升在各裝置都可以正常執行。
附上我在使用中的 install apk function:
public void installApk(Context context, File apkFile) { Intent intent = new Intent(Intent.ACTION_VIEW); //Log.d(TAG, "filepath:" + apkFile.getAbsolutePath()); //如果沒有設定SDCard寫許可權,或者沒有sdcard,apk檔案儲存在記憶體中,需要授予許可權才能安裝 try { String[] command = {"chmod", "777", apkFile.toString()}; ProcessBuilder builder = new ProcessBuilder(command); builder.start(); } catch (IOException ignored) { } if(Build.VERSION.SDK_INT>=24) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri uri = FileProvider.getUriForFile(context, "tw.thinkingsoftware.geofence.provider", apkFile); //intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); //intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.setDataAndType(uri, "application/vnd.android.package-archive"); //Log.d(TAG, "uri:" + uri.getPath()); } else { Uri uri = Uri.fromFile(apkFile); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(uri, "application/vnd.android.package-archive"); //Log.d(TAG, "uri:" + uri.getPath()); } context.startActivity(intent); }
1 manifest 中新增程式碼
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
2 res/xml/provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="publicDir" path="/"/>
</paths>
3 install
public static void installApk(Context context, File apkFile) {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", apkFile);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
uri = Uri.fromFile(apkFile);
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intent);
}
you need this url
the code solution my problem. hope help you
使用Intent安裝APK方法(相容Android N)
Android N之前,通過Intent安裝一個APK程式碼差多不是下面這個樣子的:
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
File apkFile = new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk";
install.setDataAndType(Uri.fromFile(apkFile)), "application/vnd.android.package-archive");
startActivity(install)
上面程式碼在Android N(API 26)之前是沒有問題的,但是從Android 7.0開始,系統修改了安全機制: 限定應用在預設情況下只能訪問自身應用資料。所以當我們想通過File物件訪問其它package資料時,就需要藉助於ContentProvider、FileProvider這些元件,否則會報FileUriExposedException
異常。
相應的,顯然上面的程式碼在Android N中會有FileUriExposedException
的問題,所以我們需要藉助FileProvider做出下面的修改。
使用FileProvider的步驟
新增Provider到AndroidManifest.xml
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="應用包名.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
建立@xml/file_paths對應的資原始檔
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="download"
path="" />
<external-files-path
name="Download"
path="" />
</paths>
paths裡面用來放你需要訪問的非本應用路徑,只有通過這裡申明後,才能在程式中使用不報錯。
- files-path 對應
Context.getFilesDir()
- cache-path 對應
getCacheDir()
- external-path 對應
Environment.getExternalStorageDirectory()
- external-files-path 對應
Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)
- external-cache-path 對應
Context.getExternalCacheDir()
具體其中的path和name就需要你自己來根據需求確定了。
應用程式碼
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
File apkFile = new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(context, "應用報名.fileProvider", apkFile);
install.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
install.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
}
startActivity(install);
如何在Android7.0系統下通過Intent安裝apk
Android系統升級到7.0之後,安全性提高了不少,過去我們通常是使用這樣的程式碼進行apk的安裝操作。
1 2 3 |
Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive"); context.startActivity(intent); |
但是在Android7.0的系統上,執行這段程式碼,會報如下錯誤。
Caused by: android.os.FileUriExposedException
原因是,安卓官方為了提高私有檔案的安全性,面向 Android 7.0 或更高版本的應用私有目錄被限制訪問 (0700)。此設定可防止私有檔案的元資料洩漏,如它們的大小或存在性.
傳遞軟體包網域外的 file:// URI 可能給接收器留下無法訪問的路徑。因此,嘗試傳遞 file:// URI 會觸發 FileUriExposedException。分享私有檔案內容的推薦方法是使用 FileProvider。
1.定義一個FileProvider
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<manifest> ... <application> ... <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.mydomain.fileprovider" android:exported="false" android:grantUriPermissions="true"> ... </provider> ... </application> </manifest> |
2.新增可用許可權的檔案目錄
在res目錄下,增加xml資料夾,並新建一個名為 file_paths.xml
的檔案。檔案內容格式如下:
1 2 3 4 |
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="name1" path="test1" /> ... </paths> |
標籤下面必須包含至少包含以下標籤中的一個或者多個。
files-path
1
|
<files-path name="name1" path="test1" />
|
表示Context.getFilesDir()目錄或者其子目錄。
示例 : /data/data/com.chen.gradle/files/test1
cache-path
1
|
<cache-path name="name2" path="test2" />
|
表示Context.getCacheDir()目錄或者其子目錄。
示例 : /data/data/com.chen.gradle/cache/test2
external-path
1
|
<external-path name="name3" path="test3" />
|
表示Environment.getExternalStorageDirectory()目錄或者其子目錄。
示例 : /storage/emulated/0/test3
external-files-path
1
|
<external-files-path name="name4" path="test4" />
|
表示Context.getExternalFilesDir(null)目錄或者其子目錄。
示例 : /storage/emulated/0/Android/data/com.chen.gradle/files/test4
external-cache-path
1
|
<external-cache-path name="name5" path="test5" />
|
表示Context.getExternalCacheDir()目錄或者其子目錄。
示例 : /storage/emulated/0/Android/data/com.chen.gradle/cache/test5
3.增加到provider
通過<meta-data>
標籤將上面的filepath新增到provider當中。
1 2 3 4 5 6 7 8 9 |
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.mydomain.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> |
4.通過provider生成Uri
1 2 3 4 |
File imagePath = new File(Context.getFilesDir(), "test1"); File newFile = new File(imagePath, "default_image.jpg"); Uri contentUri = FileProvider.getUriForFile(getContext(), "com.mydomain.fileprovider", newFile); |
5.賦予臨時許可權給Uri
1
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
最終安裝apk的程式碼變成這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public static void installApk(Context context, String apkPath) { if (context == null || TextUtils.isEmpty(apkPath)) { return; } File file = new File(apkPath); Intent intent = new Intent(Intent.ACTION_VIEW); //判讀版本是否在7.0以上 if (Build.VERSION.SDK_INT >= 24) { //provider authorities Uri apkUri = FileProvider.getUriForFile(context, "com.mydomain.fileprovider", file); //Granting Temporary Permissions to a 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); } |
參考文獻
相關文章: