最後是試出來了,中間有幾個問題,就是安裝是從內部 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
https://developer.android.com/reference/android/os/FileUriExposedException.html
https://developer.android.com/reference/android/support/v4/content/FileProvider.html
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); } |
参考文献
相關文章:
下载安装APK(兼容Android7.0)
https://www.jianshu.com/p/577816c3ce93
如何在Android7.0系统下通过Intent安装apk
http://www.czhzero.com/2016/12/21/how-to-install-apk-on-Android7-0/
[ANDROID] DOWNLOADMANAGER下載APK並自動開啟安裝畫面(ANDROID 6.0 , 7.0皆可運作)
https://solinariwu.blogspot.tw/2017/03/android-downloadmanagerapkandroid-60-70.html