FileProvider使用

目录


1.背景
2.问题
3.FileProvider
4.事例
5.原理

文章最后有代码链接

1.背景

targetSdkVersion:25
模拟器:genymotion api7.0

2.问题

Android7.0开始,应用私有目录被限制访问,官方做了如下限制:
1.私有文件的文件权限不应再由所有者放宽,使用MODE_WORLD_READABLE/MODE_WORLD_WRITEABLE将抛出异常
2.向应用外传递file://URI会出发FileUriExposedException

3.FileProvider

当targetSdkVersion>=24时,会存在上述问题,可能涉及到的场景有:拍照,程序安装等。
同时,官方在v4包(api=22开始)中引入FileProvider类用于程序间私有文件的共享。该类继承自ContentProvider,使用时需要在清单文件中注册。

1.使用方法
  • 注册
    在清单文件中通过标签注册,参考代码对属性进行说明:
属性名意义
android:name组件的路径统一:android.support.v4.content.FileProvider
android:authorities可以理解为标识符,完全自定义推荐以包名+”.fileprovider”方式命名,增加辨别性
android:exproted该组件是否可以被外部程序访问“false”,没必要
android:grantUriPermissions是否允许为文件设置临时权限“true”

注意:当自定义类继承自FileProvider时,需要更改name属性值为该类的相对路径…

<manifest>
    ...
    <application>
        ...
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.mydomain.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            ...
        </provider>
        ...
    </application>
</manifest>
  • 设置可访问的共享文件
    FileProvider只能对声明的文件夹下的文件生成uri,该文件夹的声明是在xml中使用标签完成的,下面的例子就是声明私有文件目录下images/下的文件可以临时访问(文件在res/xml/目录下),下面时一个简单的样式:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    ...
</paths>

因为的子标签可以有多种,这里对所有进行说明:

标签名作用
< files-path>相当于Context.getFilesDir()
< cache-path>相当于Context.getFilesDir()
< external-path>相当于Environment.getExternalStorageDiretory()
< external-files-path>相当于Context.getExternalCacheDir()

子标签中属性说明:

属性名意义取值
nameuri路径的分隔符,替换目录的全路径,保证子路径的安全自定义,只体现在uri中,格式为:”my_image“,实际操作无体现,可在后续对比uri中看到该值
path真正的子目录,代表一个目录不可以指定为一个单一文件,也不可以使用通配符,格式为:”images/

实际使用时,一个path的定义格式应该如下:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    <files-path name="my_docs" path="docs/"/>
</paths>
  • 完成配置
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.mydomain.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

说明
name:为固定值android.support.FILE_PROVIDER_PATHS
resource:所对应的xml文件路径

  • 使用
    1、通过路径生成要分享的文件File对象
    2、使用FileProvider生成uri—FileProvider.getUriForFile()
    3、客户端可以使用uri通过ContentResolver.openFileDescriptor获取到一个ParcelFileDescriptor
    事例:
    authrity为”com.mydomain.fileprovider”的FileProvider,获取到文件”default_image.jpg”文件,改文件位于”images”目录下
File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);

note:该代码生成的uri为
content://com.mydomain.fileprovider/my_images/default_image.jpg

  • 临时权限的授予方式
    1、使用Context.grantUriPermission(package,Uri,mode_flags)方法,使用想要的模式。这个方法通过mode_flags方法授予客户端package的临时权限,有两个取值,FLAG_GRANT_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSOIN。该方式允许后,可通过revokeUriPermission终止,或者手机重启后
    2、通过Intent
    1. 通过Intent的setData()方法将该uri放入Intent中
    2. 可以为Intent设置flag,设置一个或两个, FLAG_GRANT_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSOIN
    3. 将Intent发送给其他app,大部分情况,通过setResult()来做
      这种方法获取的权限,当接收的Activity在栈中一直活跃时都会保留,当activity栈finish时,权限会自动移除。被允许的activity所在的app的其他组件也会被允许该权限。

4.事例

1.场景

将apk放在内部存储空间内,使用Intent进行安装

2.主要代码
  • 将apk写入
public class App extends Application {
    public static final String TAG = "App";
    private static String path;

    @Override
    public void onCreate() {
        super.onCreate();
        writeApkToInner("app-debug.apk");
    }

    public static String getPath() {
        return path;
    }

    private void writeApkToInner(String s) {
        File dir = getCacheDir();
        if (!dir.exists()) dir.mkdir();
        File[] apks = dir.listFiles();
        for (File f : apks) {
            f.delete();
        }
        File apk = new File(dir, "test.apk");
        path = apk.getAbsolutePath();
        InputStream is = null;
        OutputStream os = null;
        try {
            is = getResources().getAssets().open(s);
            os = new FileOutputStream(apk);
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = is.read(buffer)) > 0) {
                os.write(buffer, 0, len);
                os.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null)
                    os.close();
                if (is != null)
                    is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        Log.e(TAG, "write into inner successful");
    }
}
  • 安装代码
 Intent intent = new Intent(Intent.ACTION_VIEW);
        Uri uriForFile = FileProvider.getUriForFile(this, "com.zzc.sample.fileprovider.fileprovider", new File(App.getPath()));
        Log.e(TAG, "uri" + uriForFile.toString());
        intent.setDataAndType(uriForFile, "application/vnd.android.package-archive");
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        startActivity(intent);
  • 清单文件
 <application
        android:name=".App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.zzc.sample.fileprovider.fileprovider"
            android:grantUriPermissions="true"
            >
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/files_provider">

            </meta-data>
        </provider>
    </application>
  • path文件
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <cache-path
        name="zzc_files"
        path="apks/"/>
</paths>
  • uri对比
    content://com.zzc.sample.fileprovider.fileprovider/zzc_files/test.apk
    file:///data/user/0/com.zzc.sample.fileprovider/cache/apks/test.apk

5.原理

1.FileProvider内部
  1. PathStrategy
    文件和uri之间的对应策略,不依赖动态,保证所有生成的uri能在进程被杀死并在之后的启动中保持一致。
  2. query方法
    该方法会能获取文件名和文件大小的Cursor
  3. openFile方法
    该方法的主要作用是将文件的文件描述符FileDescriptor封装为ParcelFileDescriptor作为该方法的返回值,实现Parcelable接口,可在进程间传输
2.自定义处理

该模式依然采用ContentProvider-ContentResolver工作
1.客户端(文件被分享者)主要代码
- 清单文件

<activity android:name=".ResolverActivity">
            <intent-filter>
                <action android:name="com.zzc.sample.client.SHARE"/>
                <data android:mimeType="zzc/client"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
  • ResolverActivity
Intent intent = getIntent();
        if (intent != null) {
            Uri data = intent.getData();
            ContentResolver resolver = getContentResolver();
            BufferedReader br = null;
            try {
                ParcelFileDescriptor parcelFileDescriptor = resolver.openFileDescriptor(data, "r");
                FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
                br = new BufferedReader(new FileReader(fd));
                Log.e(TAG, "content---" + br.readLine());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (br != null) {
                        br.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

2.服务端(文件分享者)主要代码
- 写入Context.getCacheDir()/txts/hello.txt文件

private void writeFile(String s) {
        File dir = getCacheDir();
        if (!dir.exists()) dir.mkdir();
        dir = new File(dir, "txts");
        if (!dir.exists()) dir.mkdir();
        File file = new File(dir, s);
        filePath = file.getAbsolutePath();

        if (!file.exists()) {
            OutputStreamWriter osw = null;
            try {
                osw = new OutputStreamWriter(new FileOutputStream(file));
                osw.write("hello world");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (osw != null) {
                        osw.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 授予临时权限
Uri data = FileProvider.getUriForFile(this, "com.zzc.sample.fileprovider.fileprovider", new File(App.getFilePath()));
        Intent intent = new Intent("com.zzc.sample.client.SHARE");
        intent.setDataAndType(data, "zzc/client");
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        startActivity(intent);

3.输出
content---hello world

如果文章有不足之处,欢迎指出
官网链接
代码下载

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值