涉及的问题:
1.安装新App
2.读取本地文件
遇到的问题:
①静默安装(需“root权限”或者“申请为系统应用”,具体再搜集资料,此处略)还是标准安装
②安卓版本适配
如果遇到如下bug:
android.os.FileUriExposedException:
file:///storage/emulated/0/Download/xxx.apk exposed beyond app through Intent.getData()
说明未进行版本适配,这是因为Android7.0引入了:
私有目录被限制访问(在Android7.0中为了提高私有文件的安全性,面向 Android N 或更高版本的应用私有目录将被限制访问).
StrictMode API 政策(禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常).
问题出在:(待补充)
解决方案:使用FileProvider对Android7.0进行适配
主要有四个步骤:
步骤①:新建自定义提供器MyFileProvider,继承自FileProvider【注①】
public class MyFileProvider extends FileProvider {
}
注意:1.类内可以为空;
2.FileProvider的完整包名为【android.support.v4.content.FileProvider】
步骤②:在AndroidManifest.xml文件的
注意:1.android:authorities属性必须全局一样,例如主工程和library功能中必须是同一个字符串;(编译可以通过,运行会崩溃)
2.android:name属性是不能一致的,必须使用不同包名下的FileProvider,继承FileProvider;(不然重复,编译不过去)
3.android:exported属性:true: 本provider可被其他applications使用. false: 本provider不可被其他applications使用.
4.android:grantUriPermissions属性必须是true,表示授予 URI 临时访问权限(readPermission, writePermission, and permissionattributes)
5.android:resource:自定义的xml文件(下面会介绍)
步骤③:在res目录下新建一个xml文件夹,并且新建一个file_path_my.xml文件
具体含义可参考以下文章:
https://blog.csdn.net/z_x_Qiang/article/details/79940700 【标题:Android 开发 之 异常...FileUriExposedException:..】
https://blog.csdn.net/xx326664162/article/details/78298956 【标题:Android N 7.0 应用间共享文件(FileProvider)】
https://blog.csdn.net/yang_song_song/article/details/77164948 【标题:Android7.0解决...FileUriExposedException: ...】
注意: (后续补充)
步骤④:修改适配代码:
File file = new File(filePath, fileName);
Uri uri;
if (Build.VERSION.SDK_INT >= 24) {
uri = MyFileProvider.getUriForFile(context, context.getPackageName()+".myfileprovider", file);
} else {
uri = Uri.fromFile(file);
}
步骤⑤:如果是Android8.0需要对“安装应用未知来源”的权限进行声明和处理
在AndroidManifest.xml文件中添加如下权限
(处理过程后续补充)
参考:https://www.jianshu.com/p/6b7bd2a59096 【标题:android安装应用(适用于各个版本)】
// 待修改
public class InstallUtil {
private Activity mActivity;
private String mPath; // 下载下来后文件的路径
public static int UNKNOWN_CODE = 2019;
private String authority; // 这个应该与AndroidManifest.xml文件中的authorities保持一致
public InstallUtil(Activity mAct, String mPath) {
this.mActivity = mAct;
this.mPath = mPath;
authority = mActivity.getPackageName() + ".downloadfileprovider";
}
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);
mActivity.startActivity(install);
}
/**
* android7.x
*/
private void startInstallN() {
//参数1 上下文, 参数2 在AndroidManifest中的android:authorities值, 参数3 共享的文件
Uri apkUri = FileProvider.getUriForFile(mActivity, /*Constants.AUTHORITY*/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");
mActivity.startActivity(install);
}
/**
* android8.x
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void startInstallO() {
boolean isGranted = mActivity.getPackageManager().canRequestPackageInstalls();
if (isGranted) startInstallN();//安装应用的逻辑(写自己的就可以)
else new AlertDialog.Builder(mActivity)
.setCancelable(false)
.setTitle("安装应用需要打开未知来源权限,请去设置中开启权限")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
mActivity.startActivityForResult(intent, UNKNOWN_CODE);
}
})
.show();
}
}
the end.----------
其他参考文章:
https://yq.aliyun.com/articles/682961 【标题:Android 7.0相机适配及FileProvider重复那些坑】【推荐】
https://www.cnblogs.com/netcorner/p/6542373.html 【标题:安卓7.0遇到 ... exposed beyond app through Intent.getData()】
https://blog.csdn.net/qq_26624143/article/details/83789252 【标题:Android FileProvider配置和当引用包内已经含有FileProvider的多节点解决办法】
=======================================
使用自定义MyFileProvider而不是FileProvider的好处主要是为了避免模块化开发中的FileProvider重复。
在使用“android:name="android.support.v4.content.FileProvider"”时build可能会报错:
Manifest merger failed with multiple errors, see logs
一开始不知道它所谓的logs在哪里,参考文章:https://blog.csdn.net/haha223545/article/details/80985995
发现在这里:
点进去发现右侧有报错:
Merging Errors:
Error: Attribute provider#android.support.v4.content.FileProvider@authorities
value=(com.xxx.xxxx.fileprovider) from AndroidManifest.xml:167:13-72 is also present at AndroidManifest.xml:7:19-70 value=(com.xxx.xxxx.fileProvider).
Suggestion: add 'tools:replace="android:authorities"' to element at AndroidManifest.xml:165:9-173:20 to override. app main manifest (this file), line 166
Error: Attribute meta-data#android.support.FILE_PROVIDER_PATHS@resource
value=(@xml/file_path_download) from AndroidManifest.xml:172:17-59 is also present at AndroidManifest.xml:8:75-109 value=(@xml/file_paths).
Suggestion: add 'tools:replace="android:resource"' to element at AndroidManifest.xml:170:13-172:62 to override. app main manifest (this file), line 171
至于第一个Error,按照她的Suggestion发现第一个Error可以解决,第二个仍然难办。这也是文章https://blog.csdn.net/shenyo/article/details/81198705 中提到的方案。
所以不打算采用上述方案,改用自定义MyFileProvider来避免FileProvider重复的问题。