如何在Android各个版本上实现应用内安装APK。
一.Android 7.0以下
首先在android7.0以下,采用普通的方式就可以了:
public static void startInstall(Context context, String path) {
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(Uri.parse("file://" + path), "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}
二.Android 7.0
其次android7.0,这里要说的要比较多一点:
如果用之前的方法,那么程序就会报错:Caused by: android.os.FileUriExposedException:
这是因为Android7.0引入了“私有目录被限制访问”
,“StrictMode API 政策”。
这些更改在为用户带来更加安全的操作系统的同时也为开发者带来了一些新的任务。如何让APP能够适应这些改变而不是崩溃,是摆在每一位Android开发者身上的责任。
私有目录被限制访问:
是指在Android7.0中为了提高私有文件的安全性,面向 Android N 或更高版本的应用私有目录将被限制访问。
StrictMode API 政策
是指禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常。
上面用到的代码中的Uri.fromFile 其实就是生成一个file://URL。一旦我们通过这种办法打开其它程序(这里打开系统包安装器)就认为file:// URI类型的 Intent 离开你的应用。这样程序就会发生异常,并出现 FileUriExposedException 异常。要在应用间共享文件,应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。
FileProvider的使用
1.在AndroidManifest.xml清单文件中注册provider,因为provider也是Android四大组件之一,可以简单把它理解为向外提供数据的组件。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities= "com.install.test"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
2.配置xml文件下的file_paths文件内容:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<!--
external-path: 该方式提供在外部存储区域根目录下的文件。
它对应Environment.getExternalStorageDirectory返回的路径
external-files-path: Context.getExternalFilesDir(null)
external-cache-path: Context.getExternalCacheDir(String)
-->
<external-path name="download" path="" />
</paths>
</resources>
3.apk文件的安装
@RequiresApi(api = Build.VERSION_CODES.N)
public static void startInstallN(Context context, String path) {
String authority = "com.install.test";
try {
//参数1 上下文, 参数2 在AndroidManifest中的android:authorities值, 参数3 共享的文件
Uri apkUri = FileProvider.getUriForFile(context, authority, new File(path));
Intent install = new Intent(Intent.ACTION_VIEW);
//由于没有在Activity环境下启动Activity,设置下面的标签
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
context.startActivity(install);
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "startInstallN: " + e.getMessage());
}
}
三.Android 8.0
最后android8.0
android8.0的诸多新特性中有一个非常重要的特性:未知来源应用权限
以前安装未知来源应用的时候一般会弹出一个弹窗让用户去设置允许还是拒绝,并且设置为允许之后,所有的未知来源的应用都可以被安装。
android8.0的变化是,未知应用安装权限的开关被除掉,取而代之的是未知来源应用的管理列表,需要在里面打开每个应用的未知来源的安装权限。Google这么做是为了防止一开始正经的应用后来开始通过升级来做一些不合法的事情,侵犯用户权益。
当你的应用直接适配到android8.0之后,内部启动应用安装是会被阻塞的,如果不处理好这个未知来源的权限,会导致应用根本无法更新,只能去应用市场重新下载。
那么对于Android开发者来说适配Android 8.0(仅限应用版本更新方面)需要做哪些工作呢?
1.在清单文件中增加请求安装权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
2.在代码里面对权限进行处理
boolean isGranted = getPackageManager().canRequestPackageInstalls();
如果haveInstallPermission 为 true,则说明你的应用有安装未知来源应用的权限,你直接执行安装应用的操作即可。
如果haveInstallPermission 为 false,则说明你的应用没有安装未知来源应用的权限,则无法安装应用。由于这个权限不是运行时权限,所以无法再代码中请求权限,还是需要用户跳转到设置界面中自己去打开权限。
a. 弹出dialog,告知用户 “安装应用需要打开未知来源权限,请去设置中开启权限”
b. 然后用户点击确定之后跳转到未知来源应用权限管理列表:
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
startActivityForResult(intent, UNKNOWN_CODE);
// 在onActivityResult中去接收结果:
if (resultCode == RESULT_OK && requestCode == InstallUtil.UNKNOWN_CODE) {
startInstallO();//再次执行安装流程,包含权限判等
}
四.InstallUtil全部代码
public class InstallUtil {
private static final int UNKNOWN_CODE = 2020;
public static void installApk(Context context, String path) {
//8.0及以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startInstallO(context, path);
//7.0及以上
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
startInstallN(context, path);
//7.0 以下
} else {
startInstall(context, path);
}
}
/**
* android1.x-6.x
*
* @param path 文件的路径
*/
public static void startInstall(Context context, String path) {
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(Uri.parse("file://" + path), "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}
/**
* android7.x
*
* @param path 文件路径
*/
@RequiresApi(api = Build.VERSION_CODES.N)
public static void startInstallN(Context context, String path) {
String authority = "com.install.test";
try {
//参数1 上下文, 参数2 在AndroidManifest中的android:authorities值, 参数3 共享的文件
Uri apkUri = FileProvider.getUriForFile(context, authority, new File(path));
Intent install = new Intent(Intent.ACTION_VIEW);
//由于没有在Activity环境下启动Activity,设置下面的标签
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
context.startActivity(install);
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "startInstallN: " + e.getMessage());
}
}
private static final String TAG = "InstallUtil";
/**
* android8.x
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private static void startInstallO(final Context context, String path) {
boolean isGranted = context.getPackageManager().canRequestPackageInstalls();
if (isGranted)
startInstallN(context, path);//安装应用的逻辑(写自己的就可以)
else
new AlertDialog.Builder(context)
.setCancelable(false)
.setTitle("安装应用需要打开未知来源权限,请去设置中开启权限")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
Activity act = (Activity) context;
act.startActivityForResult(intent, UNKNOWN_CODE);
}
}).show();
}
}