目录
前言:安卓系统升级到8.0之后,Google将未知应用安装权限的开关除掉了(原先打开这个开关所有的应用都会授予此权限),取而代之的是未知来源应用的管理列表,需要在里面打开每个应用的未知来源的安装权限。如果不处理这个未知来源的权限,那么将会阻塞内部应用启动安装过程,会导致应用根本无法更新,只能去应用市场重新下载。
那么如何处理这个权限呢?
1.应用的安装流程
1.先说说8.0之前的安装流程:
从服务器下载应用到本地 >>开启安装应用代码>>安装完成
2. 8.0之后应用安装流程:
从服务器下载应用到本地 >>代码打开安装位置来源应用界面>>授予权限>>开启安装应用代码>>安装完成
其中 “代码打开安装位置来源应用界面”会阻塞安装应用过程,只有授予了安装未知应用的权限,才能继续执行安装过程。
3.为了模拟从网络上下载应用更新,我直接在内部存储放置了一个应用,点击安装,直接执行8.0的安装流程。
2.首先申请权限
<!--读写内部存储权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--8.0安卓 软件安装需要申请的权限-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
处理sdcard读取权限,代码如下:
@TargetApi(Build.VERSION_CODES.M)
private void checkSdCardPermission() {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
installApk();
} else {
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 100);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 100 & grantResults[0] == PackageManager.PERMISSION_GRANTED) {
installApk();
}
}
重点是该权限:<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
3.点击按钮进行应用安装
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_install).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_install:
checkInstallPermissionAndInstall();
break;
}
}
代码很简单,一个执行安装的按钮
4.检查是否有未知应用来源的权限
public void checkInstallPermissionAndInstall() {
// 如果是8.0系统
if (Build.VERSION.SDK_INT >= 26) {
boolean b = getPackageManager().canRequestPackageInstalls();
// 如果已经打开了安装未知来源的开关
if (b) {
checkSdCardPermission();
} else {
// 请求打开安装未知应用来源的界面,非运行时权限
Uri packageURI = Uri.parse("package:" + getPackageName());
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,packageURI);
startActivityForResult(intent, GET_UNKNOWN_APP_SOURCES);
}
} else {
checkSdCardPermission();
}
}
注释很清楚,这里只说重点:boolean b = getPackageManager().canRequestPackageInstalls();该行代码检查是否有未知应用来源的权限,如果有直接安装,没有就打开未知应用来源的界面。注意,这和运行时权限的申请是不同的!(很多人把运行时权限搞混了!)或者直接在清单文件中添加权限:
<!--安卓8.0打开apk安装更新-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
那么就不需要检查未知来源权限了,直接执行installApk方法
在onActivityResult()中处理回调结果,代码如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case GET_UNKNOWN_APP_SOURCES:
// 用户授予了未知来源应用的权限
if (resultCode == RESULT_OK) {
checkSdCardPermission();
}
break;
default:
break;
}
}
5.执行安装流程
执行安装代码:
private void installApk() {
Uri uri = null;
File file = new File(downLoadUrl);
if (!file.exists()) {
ToastUtil.showToast("文件不存在!");
return;
}
// 安卓7.0及以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(this, getApplicationInfo().packageName + ".FileProvider", file);
} else {
uri = Uri.fromFile(file);
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
startActivity(intent);
}
这里需要说的是,intent.setDataAndType(uri, "application/vnd.android.package-archive");中uri这个参数,同样需要对7.0及以上的系统进行适配,需要设置provider。
我是将应用直接放在了根目录下:
private static final String downLoadUrl = Environment.getExternalStorageDirectory() + "/app-debug.apk";
provider配置如下:
<!--安卓7.0及以上需配置provider -->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths_public" />
</provider>
file_paths_public.xml 文件内容如下:
<paths>
<!--指定共享的文件夹范围-->
<external-path
name="external_storage_root"
path="." />
</paths>
path="." 表示内部存储的根目录
关于provider的配置,可以参见:https://blog.csdn.net/alex01550/article/details/82115074
中provider部分介绍,这里不再赘述。
到这里就基本就完了,但有几个注意点:
1.从内部存储安装应用需要sdcard运行时权限,如果没有该权限会提示解析错误!
2.启动安装界面时 intent.setDataAndType(uri, "application/vnd.android.package-archive"),中uri要记得做7.0适配!
3.在测试apk时,放在内部存储目录的apk,要通过build>>Build Apk(s) 进行构建,否则安装时会提示应用未安装!见下图
所以当你决定要适配到8.0的时候,一定要做其他方面的适配!