android安装应用(适用于各个版本)
本篇讲解一下如何在Android各个版本上实现应用内安装APK。
首先在android7.0以下,采用普通的方式就可以了:
/**
*android1.x-6.x
*@param path 文件的路径
*/
public 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.0,这里要说的要比较多一点:
如果用之前的方法,那么程序就会报错
错误信息
Caused by: android.os.FileUriExposedException:
file:///storage/emulated/0/Download/myApp.apk exposed beyond app through Intent.getData()
这是因为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="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<!-- 元数据 -->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
2.指定共享的目录上面配置文件中 android:resource="@xml/file_paths" 指的是当前组件引用 res/xml/file_paths.xml 这个文件。
我们需要在资源(res)目录下创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest注册的provider所引用的resource保持一致即可)的资源文件,
<files-path/>代表的根目录: Context.getFilesDir()
<external-path/>代表的根目录: Environment.getExternalStorageDirectory()
<cache-path/>代表的根目录: getCacheDir()
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<!--
files-path: 该方式提供在应用的内部存储区的文件/子目录的文件。
它对应Context.getFilesDir返回的路径:eg:”/data/data/com.***.***/files”。
cache-path: 该方式提供在应用的内部存储区的缓存子目录的文件。
它对应Context.getCacheDir返回的路:eg:“/data/data/com.***.***/cache”;
external-path: 该方式提供在外部存储区域根目录下的文件。
它对应Environment.getExternalStorageDirectory返回的路径
external-files-path: Context.getExternalFilesDir(null)
external-cache-path: Context.getExternalCacheDir(String)
-->
<external-path name="download" path="" />
</paths>
</resources>
上述代码中path="",是有特殊意义的,它代表根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了。
如果你将path设为path="pictures",那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。
3.使用FileProvider上述准备工作做完之后,现在我们就可以使用FileProvider了。我们需要将上述安装APK代码修改为如下
/**
* android7.x
* @param path 文件路径
*/
public void startInstallN(Context context, String path) {
//参数1 上下文, 参数2 在AndroidManifest中的android:authorities值, 参数3 共享的文件
Uri apkUri = FileProvider.getUriForFile(context, Constants.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");
startActivity(install);
}
因为是6.0以上的版本,所以一定不要忘记提前申请存储卡操作权限[WRITE_EXTERNAL_STORAGE]
最后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);
c. 在onActivityResult中去接收结果:
if (resultCode == RESULT_OK && requestCode == InstallUtil.UNKNOWN_CODE) {
startInstallO();//再次执行安装流程,包含权限判等
}
android8.0全部代码
/**
* android8.x
* @param path 文件路径
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void startInstallO(Activity act, String path) {
boolean isGranted = mAct.getPackageManager().canRequestPackageInstalls();
if (isGranted) startInstallN(act, path);//安装应用的逻辑(写自己的就可以)
else new AlertDialog.Builder(act)
.setCancelable(false)
.setTitle("安装应用需要打开未知来源权限,请去设置中开启权限")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
act.startActivityForResult(intent, UNKNOWN_CODE);
}
})
.show();
}
以上就是所有版本的适配,现在贴出Util
/**
* <pre>
* author: Fan
* time : 2018/1/7 下午4:56
* desc : android安装应用(适用于各个版本)
* </pre>
*/
public class InstallUtil {
private Activity mAct;
private String mPath;//下载下来后文件的路径
public static int UNKNOWN_CODE = 2018;
public InstallUtil(Activity mAct, String mPath) {
this.mAct = mAct;
this.mPath = mPath;
}
public void install(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startInstallO();
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) startInstallN();
else startInstall();
}
/**
* android1.x-6.x
*/
private void startInstall() {
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(Uri.parse("file://" + mPath), "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mAct.startActivity(install);
}
/**
* android7.x
*/
private void startInstallN() {
//参数1 上下文, 参数2 在AndroidManifest中的android:authorities值, 参数3 共享的文件
Uri apkUri = FileProvider.getUriForFile(mAct, Constants.AUTHORITY, new File(mPath));
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");
mAct.startActivity(install);
}
/**
* android8.x
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void startInstallO() {
boolean isGranted = mAct.getPackageManager().canRequestPackageInstalls();
if (isGranted) startInstallN();//安装应用的逻辑(写自己的就可以)
else new AlertDialog.Builder(mAct)
.setCancelable(false)
.setTitle("安装应用需要打开未知来源权限,请去设置中开启权限")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
mAct.startActivityForResult(intent, UNKNOWN_CODE);
}
})
.show();
}
}
public class DownAct extends AppCompatActivity {
private InstallUtil mInstallUtil;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mInstallUtil = new InstallUtil(this, "");
mInstallUtil.install();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == InstallUtil.UNKNOWN_CODE) {
mInstallUtil.install();//再次执行安装流程,包含权限判等
}
}
}