当前位置: 移动技术网 > IT编程>开发语言>Java > FileProvider安装应用

FileProvider安装应用

2020年10月30日  | 移动技术网IT编程  | 我要评论
一、权限相关1、“未知来源”应用ndroid8.0及其以上系统,为了申请“未知来源”,需要在清单文件添加权限<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>然后代码中动态申请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代码也可以看到有如下对应关系

其实,只要提供的路径以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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网