最近在做一个项目的时候,遇到关于适配的问题,真的是鼓捣了两个晚上,几乎要心碎了,特来趁还有记忆记录下。
第一个问题是在App刚上线的时候,一般我们都会选择让用户强制更新,so,我们也选择了可以让用户及时享用我们App的变化。形式是通过一个我们常见的下载框,屏幕外不可点击取消,如下图:
弹出框的主要代码appUpdate, 这里采用Retrofit 进行ResponseBody 拦截监听下载进度的,这里着重将这个方面
public void appUpdate() {
// 如果没有权限
if (MPermissionUtils.checkPermissions(HomeHotFragment.this.getContext(), Manifest.permission.READ_EXTERNAL_STORAGE)) {
// 判断是否需要强制更新
if ("Y".equals(mHomeBean.getIsUpdate())) {
File storageDir = new File(Environment.getExternalStorageDirectory().toString(), "Android");
downloadUpdateApkFilePath = storageDir.getPath()
+ File.separator
+ HomeHotFragment.this.getContext().getPackageName()
+ "_" + "Hong" + ".apk";
final ProgressDialog dialog = new ProgressDialog(HomeHotFragment.this.getContext());
dialog.setProgressNumberFormat("%1d KB/%2d KB");
dialog.setTitle("下载");
dialog.setMessage("正在下载,请稍后...");
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setCancelable(false);
dialog.show();
HomeAPIPModel.getInstance().downloadFileProgress(new ProgressListener() {
@Override
public void onProgress(long currentBytes, long contentLength, boolean done) {
dialog.setMax((int) (contentLength / 1024));
dialog.setProgress((int) (currentBytes / 1024));
if (done) {
dialog.dismiss();
}
}
}, mHomeBean.getDownload_url());
}
} else {
Toast.makeText(HomeHotFragment.this.getContext(), "您没有SD权限", Toast.LENGTH_SHORT).show();
}
}
但是,当我们选择安装的时候,在Android 7.0直接显示解析包错误(使用的SM-G9300);在Android 8.0上直接崩掉了,崩掉了,真的是没有一点点防备;(使用的华为荣耀V10)
Android < 7.0
在Android 7.0 之前 我们只需要隐式Intent 启动APK安装即可
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(new File(apkPath)),
"application/vnd.android.package-archive");context.startActivity(intent);
startActivity(intent);
关于Android 7.0
Andorid 现在对用户的隐私权限管理的越来越严格,7.0之后启用私有目录被限制访问,也就是禁止向外公布file://URL, 否则将出现 android.os.FileUriExposedException 异常,需要FileProvider解决
manifest 文件配置
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.pandora.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
exported: 必须是false,不允许被另一个Application的组件启动
authorities:组件标志,一般以包名开头,避免重复
grantUriPermissions:true 权限同意
res文件下新建xml文件,在xml文件下新建file_paths文件,和manifest中配置的文件名称一样
file_paths 指定要共享的文件目录
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path
name="Android"
path="hm_apk" />
<!--<files-path name="apk" path="Android" />-->
<!--<external-path name="cache" path="Cache/" />-->
<!--<!–代表外部存储区域的根目录下的文件 Environment.getExternalStorageDirectory()/DCIM/camerademo目录–>-->
<!--<external-path name="hm_DCIM" path="DCIM/camerademo" />-->
<!--<!–代表外部存储区域的根目录下的文件 Environment.getExternalStorageDirectory()/Pictures/camerademo目录–>-->
<!--<external-path name="hm_Pictures" path="Pictures/camerademo" />-->
<!--<!–代表app 私有的存储区域 Context.getFilesDir()目录下的images目录 /data/user/0/com.hm.camerademo/files/images–>-->
<!--<files-path name="hm_private_files" path="images" />-->
<!--<!–代表app 私有的存储区域 Context.getCacheDir()目录下的images目录 /data/user/0/com.hm.camerademo/cache/images–>-->
<!--<cache-path name="hm_private_cache" path="images" />-->
<!--<!–代表app 外部存储区域根目录下的文件 Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)目录下的Pictures目录–>-->
<!--<!–/storage/emulated/0/Android/data/com.hm.camerademo/files/Pictures–>-->
<!--<external-files-path name="hm_external_files" path="Pictures" />-->
<!--<!–代表app 外部存储区域根目录下的文件 Context.getExternalCacheDir目录下的images目录–>-->
<!--<!–/storage/emulated/0/Android/data/com.hm.camerademo/cache/images–>-->
<!--<external-cache-path name="hm_external_cache" path="" />-->
</paths>
</resources>
在这些配置完之后,我们就可以在代码中真正访问相应的内存空间了。一开始按照相应的配置规则适配7.0的读写权限之后,在下载完App之后,安装的时候还是一直解析包错误,一开始以为包下载出错了,就断点跟踪App的下载过程,最后发现在相应的文件夹下也能看到下载的App,但就是在安装的时候解析包错误。最后找了好久发现是需要在file_paths文件下指定 root-path ,之后的之后就可以覆盖安装成功了。
path=”.”其实是对应外置SD卡的根目录, ,就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path
name="Android"
path="hm_apk" />
<root-path
name="root_path"
path="." />
</paths>
</resources>
之后在APK 进行安装的时候需要使用FiliProvider创建的Uri ,,也就是我们共享的空间位置, 并且添加intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)来对目标应用临时授权该Uri所代表的文件,而且getUriForFile中的authority参数需要填写清单文件中的authorities的值
private void install(String path) {
if (!TextUtils.isEmpty(path)) {
Intent i = new Intent(Intent.ACTION_VIEW);
File apkFile = new File(path);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(
PandoraApplication.getInstance().getApplicationContext(),
PandoraApplication.getInstance().getApplicationContext().getPackageName() + ".fileprovider",
apkFile);
i.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
i.setDataAndType(Uri.fromFile(apkFile),
"application/vnd.android.package-archive");
}
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PandoraApplication.getInstance().getApplicationContext().startActivity(i);
AppManager.getAppManager().AppExit(PandoraApplication.getInstance().getApplicationContext(), false);
}
}
Android 8.0
在7.0上安装成功之后,8.0 安装的时候又莫名其妙的崩溃了,真的想哭了,原来是在手机设置上有个是否已经获取到允许安装未知应用,如果在没有允许的情况下,安装的时候是直接退出的,也不会给用户提示,所以,我们还要判断Android的版本,进行这个权限的申请,这个权限只需要在清单文件中注册即可。
首先在清单文件中进行注册
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
之后我们还可以在代码中再进行配置:
public void checkAndroidO() {
if (Build.VERSION.SDK_INT >= 26) {
boolean b = getPackageManager().canRequestPackageInstalls();
if (b) {
installApk();
} else {
//请求安装未知应用来源的权限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, INSTALL_PACKAGES_REQUESTCODE);
}
} else {
installApk();
}
}
若是没有获得该权限,我们可以使用Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES intent 操作将用户引导至安装未知应用界面。
用户允许该来源之后,我们的处理如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case GET_UNKNOWN_APP_SOURCES:
checkAndroidO();
break;
default:
break;
}
}