FileProvider使用

来源:互联网 发布:校园网络直播 编辑:程序博客网 时间:2024/05/18 02:31

目录


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()

子标签中属性说明:

属性名 意义 取值 name uri路径的分隔符,替换目录的全路径,保证子路径的安全 自定义,只体现在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

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

原创粉丝点击