【FileProvider】做一个App应用内部安装一个新App(适配Android7/8)

涉及的问题:

1.安装新App

2.读取本地文件

1.在一个App内部启动Android标准安装界面

遇到的问题:

静默安装(需“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进行适配

 

2.使用FileProvider对Android7.0读取本地文件进行适配

主要有四个步骤:

步骤①:新建自定义提供器MyFileProvider,继承自FileProvider【注①】

public class MyFileProvider extends FileProvider {
}

注意:1.类内可以为空;

           2.FileProvider的完整包名为【android.support.v4.content.FileProvider】

步骤②:在AndroidManifest.xml文件...标签下注册该MyFileProvider:


        

注意:1.android:authorities属性必须全局一样,例如主工程和library功能中必须是同一个字符串;(编译可以通过,运行会崩溃)
           2.android:name属性是不能一致的,必须使用不同包名下的FileProvider,继承FileProvider;(不然重复,编译不过去)
           3.android:exported属性true: 本provider可被其他applications使用. false: 本provider不可被其他applications使用. 
           4.android:grantUriPermissions属性必须是true,表示授予 URI 临时访问权限(readPermissionwritePermission, 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文件中添加如下权限


 (处理过程后续补充)

 

3.一个可用的应用内安装新应用的例子

参考: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

发现在这里:

【FileProvider】做一个App应用内部安装一个新App(适配Android7/8)_第1张图片

点进去发现右侧有报错:

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重复的问题

你可能感兴趣的:(Android)