欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

FileProvider安装应用

程序员文章站 2022-07-03 16:51:21
一、权限相关1、“未知来源”应用ndroid8.0及其以上系统,为了申请“未知来源”,需要在清单文件添加权限然后代码中动态申请public void setInstallPermission(){ boolean haveInstallPermission; if(Build.VERSION.SDK_INT...

一、权限相关

1、“未知来源”应用

ndroid8.0及其以上系统,为了申请“未知来源”,需要在清单文件添加权限

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

然后代码中动态申请

public void setInstallPermission(){
    boolean haveInstallPermission;
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        //先判断是否有安装未知来源应用的权限
        haveInstallPermission = getPackageManager().canRequestPackageInstalls();
        if(!haveInstallPermission){
            //弹框提示用户手动打开
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("安装权限");
            builder.setMessage("需要打开允许来自此来源,请去设置中开启此权限");
            builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                        //此方法需要API>=26才能使用
                        toInstallPermissionSettingIntent();
                    }
                }
            });

            builder.setCancelable(false);
            builder.setIcon(R.mipmap.ic_launcher);
            AlertDialog dialog = builder.create();
            dialog.show();
        }
    }
}

    /**
     * 开启安装未知来源权限
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    private void toInstallPermissionSettingIntent() {
        Uri packageURI = Uri.parse("package:"+getPackageName());
        Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,packageURI);
        startActivityForResult(intent, INSTALL_PERMISS_CODE);
    }

2、访问存储权限

当然文件读写权限也需要的,在清单文件添加权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

然后代码中动态申请

    private void grantPermission() {
        if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED
        || ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE},STORAGE_PERMISS_CODE);
        }
    }

二、FileProvider使用

android7.0以及以后,谷歌为了安全考虑,禁止应用明文跨进程访问uri,FileProvider应运而生。

老版本可能会用到v4兼容包,在AndroidManife.xml中使用android.support.v4.content.FileProvider,同时build.gradle中添加相关依赖,比如下面

    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:recyclerview-v7:27.1.1'
    implementation 'com.android.support:exifinterface:27.1.1'
    implementation 'com.android.support:support-v4:27.1.1'

android X的话使用androidx.core.content.FileProvider,后续以android X为例吧。AndroidManife.xml中添加FileProvider如下

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.shan.fileprovider.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths" />
        </provider>
provider_paths.xml中内容是
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!--根节点/-->
    <root-path name="root" path="." />

    <!--相当于Context.getFilesDir()-->
    <files-path name="files" path="." />

    <!--相当于Context. getCacheDir()-->
    <cache-path name="cache" path="." />

    <!--相当于Environment.getExternalStorageDirectory()-->
    <external-path name="external" path="." />


    <!--相当于Context.getExternalFilesDir(String) Context.getExternalFilesDir(null).-->
    <external-files-path name="external_files" path="." />

    <!--相当于Context.getExternalCacheDir()-->
    <external-cache-path name="external_cache" path="." />

    <!--相当于Context.getExternalMediaDirs()-->
    <external-media-path name="name" path="." />
</paths>

上面的provider_paths.xml应该是比较万能了。

然后代码中调用安装程序

    private void installApp(Context content) {
        File fileS = new File("/storage/emulated/0/aa/apks/UU加速器.apk");
     //   File fileS = new File("/storage/emulated/0/Android/data/com.shan.fileprovider/files/aa/apks/UU加速器.apk");
       // File fileS = new File("data/data/com.shan.fileprovider/files/aa/apks/UU加速器.apk");
        Log.d(TAG, "installAp,"+fileS+",, exist="+fileS.exists());
        Uri data;
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
            // 给目标应用一个临时授权
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            data = FileProvider.getUriForFile(content, BuildConfig.APPLICATION_ID + ".fileprovider", fileS);
            Log.d(TAG, "installApp: 1");
        } else {
            data = Uri.fromFile(fileS);
            Log.d(TAG, "installApp: 11");
        }
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setDataAndType(data, "application/vnd.android.package-archive");
        startActivity(intent);
    }

三、踩坑

如果安装时跳出“解析软件包出现问题”,那么可能就相应路径下的apk文件不存在,或者没有声明STORAGE的读写权限。

如果程序直接挂掉,并且打印

     Caused by: java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/aa/apks/UU加速器.apk
        at androidx.core.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:744)
        at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:418)
        at com.shan.fileprovider.MainActivity.installApp(MainActivity.java:57)

此时应该是你的provider_paths.xml路径配错了,可以看到是FileProvider的getUriForFile方法报错,跟进FileProvider.java源码看到

        public Uri getUriForFile(File file) {
            String path;
            try {
                path = file.getCanonicalPath();
            } catch (IOException e) {
                throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
            }

            // Find the most-specific root path
            Map.Entry<String, File> mostSpecific = null;
            for (Map.Entry<String, File> root : mRoots.entrySet()) {
                final String rootPath = root.getValue().getPath();
                if (path.startsWith(rootPath) && (mostSpecific == null
                        || rootPath.length() > mostSpecific.getValue().getPath().length())) {
                    mostSpecific = root; //1处报错,说明mostSpecific为null,那么肯定是path.startsWith(rootPath)不满足条件导致
                }
            }

            if (mostSpecific == null) {
                throw new IllegalArgumentException(
                        "Failed to find configured root that contains " + path); //1 这里报错
            }

            // Start at first char of path under root
            final String rootPath = mostSpecific.getValue().getPath();
            if (rootPath.endsWith("/")) {
                path = path.substring(rootPath.length());
            } else {
                path = path.substring(rootPath.length() + 1);
            }

            // Encode the tag and path separately
            path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
            return new Uri.Builder().scheme("content")
                    .authority(mAuthority).encodedPath(path).build();
        }

path.startsWith(rootPath)为什么为false呢?

path是文件实际路径,rootPath是mRoots遍历得到,rootPath实际上是provider_paths.xml配置的路径path的路径,可以从FileProvider的parsePathStrategy方法中找到mRoots赋值地方。

    private static final File DEVICE_ROOT = new File("/");
    private static final String TAG_ROOT_PATH = "root-path";
    private static final String TAG_FILES_PATH = "files-path";
    private static final String TAG_CACHE_PATH = "cache-path";
    private static final String TAG_EXTERNAL = "external-path";
    private static final String TAG_EXTERNAL_FILES = "external-files-path";
    private static final String TAG_EXTERNAL_CACHE = "external-cache-path";
    private static final String TAG_EXTERNAL_MEDIA = "external-media-path";

    private static PathStrategy parsePathStrategy(Context context, String authority)
            throws IOException, XmlPullParserException {
        final SimplePathStrategy strat = new SimplePathStrategy(authority);

        final ProviderInfo info = context.getPackageManager()
                .resolveContentProvider(authority, PackageManager.GET_META_DATA);
        if (info == null) {
            throw new IllegalArgumentException(
                    "Couldn't find meta-data for provider with authority " + authority);
        }

        final XmlResourceParser in = info.loadXmlMetaData(
                context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS);
        if (in == null) {
            throw new IllegalArgumentException(
                    "Missing " + META_DATA_FILE_PROVIDER_PATHS + " meta-data");
        }

        int type;
        while ((type = in.next()) != END_DOCUMENT) { //遍历path.xml中定义的节点
            if (type == START_TAG) {
                final String tag = in.getName();

                final String name = in.getAttributeValue(null, ATTR_NAME);
                String path = in.getAttributeValue(null, ATTR_PATH);

                File target = null;
                if (TAG_ROOT_PATH.equals(tag)) {
                    target = DEVICE_ROOT;
                } else if (TAG_FILES_PATH.equals(tag)) {
                    target = context.getFilesDir();
                } else if (TAG_CACHE_PATH.equals(tag)) {
                    target = context.getCacheDir();
                } else if (TAG_EXTERNAL.equals(tag)) {
                    target = Environment.getExternalStorageDirectory();
                } else if (TAG_EXTERNAL_FILES.equals(tag)) {
                    File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null);
                    if (externalFilesDirs.length > 0) {
                        target = externalFilesDirs[0];
                    }
                } else if (TAG_EXTERNAL_CACHE.equals(tag)) {
                    File[] externalCacheDirs = ContextCompat.getExternalCacheDirs(context);
                    if (externalCacheDirs.length > 0) {
                        target = externalCacheDirs[0];
                    }
                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
                        && TAG_EXTERNAL_MEDIA.equals(tag)) {
                    File[] externalMediaDirs = context.getExternalMediaDirs();
                    if (externalMediaDirs.length > 0) {
                        target = externalMediaDirs[0];
                    }
                }

                if (target != null) {
                    strat.addRoot(name, buildPath(target, path)); //将path.xml中定义的path所对应的路径加入到集合
                }
            }
        }

        return strat;
    }

debug代码也可以看到有如下对应关系

FileProvider安装应用

其实,只要提供的路径以path对应路径开头就不会出现Failed to find configured root that contains报错,简单来说<root-path name="root" path="." />是最具通用性,root-path是根目录,其他path满足的话root-path肯定也满足,而且root-path可以兼容外置sd卡。

 

 

 

 

 

本文地址:https://blog.csdn.net/u013795543/article/details/109392062

相关标签: android笔记