Jetpack之安全的FileProvider

本文详细介绍了Android中的Jetpack组件FileProvider,其主要目的是提供一种更安全的方式来分享文件。FileProvider通过创建content URI替代file URI,允许为特定应用授予临时访问权限,增强了文件访问的安全性。在AndroidManifest中配置FileProvider,定义权限及访问路径,然后在代码中使用FileProvider.getUriForFile方法获取content URI。文章还深入解析了FileProvider的工作原理,包括如何查找和匹配文件路径,以及如何构建content URI。
摘要由CSDN通过智能技术生成

首先我们要清楚FileProvider出现的意义是什么,官方的解释是:

FileProvider is a special subclass of ContentProvider that facilitates secure sharing of files associated with an app by creating a content:// Uri for a file instead of a file:/// Uri.

A content URI allows you to grant read and write access using temporary access permissions. When you create an Intent containing a content URI, in order to send the content URI to a client app, you can also call Intent.setFlags() to add permissions. These permissions are available to the client app for as long as the stack for a receiving Activity is active. For an Intent going to a Service, the permissions are available as long as the Service is running.

In comparison, to control access to a file:/// Uri you have to modify the file system permissions of the underlying file. The permissions you provide become available to any app, and remain in effect until you change them. This level of access is fundamentally insecure.

The increased level of file access security offered by a content URI makes FileProvider a key part of Android’s security infrastructure.

大概就是FileProvider会创建content://uri的Uri格式来代替file:///uri的格式,至于为什么替换,是为了安全性考虑,file:///必须要修改系统设置访问请求的文件的权限,这样一来其他所有的app都可以访问了,而content://uri可以允许你赋予某个app临时权限来访问(通过Intent的setFlags设置权限,譬如Intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)),而不是面向全体。

现在来看一下如何规范地使用它:

首先需要在AndroidManifest文件中定义,

<manifest>
    ...
    <application>
        ...
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.mydomain.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            ...
        </provider>
        ...
    </application>
</manifest>

name属性是固定的“androidx.core.content.FileProvider”,如果你想重写一些FileProvider的默认方法,可以建一个子类继承自它并使用完整限定类名作为name属性的值;authorities属性的值可以自定义,但规范的写法是包名加上.fileprovider;exported需要设置成false,因为the FileProvider does not need to be public.若是设置成true,也就是失去了安全的意义;最后Set the android:grantUriPermissions attribute to true, to allow you to grant temporary access to files.

接着,我们需要使用xml定义访问路径,比如在res/xml目录中定义一个file_paths.xml:

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

这里的name和path(斜杠可以不加)可以自定义,但是path要和后面代码中的路径最后的子目录

package com.example.tigongzhe; import android.R.integer; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.provider.SyncStateContract.Helpers; import android.text.Selection; import android.util.Log; public class provider extends ContentProvider { private MyOpenHelper myOpenHelper; private SQLiteDatabase sqLiteDatabase; private static final UriMatcher URI_MATCHER=new UriMatcher(UriMatcher.NO_MATCH); private final String TAG="provider"; private static final String authority="com.example.tigongzhe.provider"; static { URI_MATCHER.addURI(authority, "contacter", 1); URI_MATCHER.addURI(authority, "contacter/#", 2); } private static final String _id="id"; private static final String name="name"; private static final String num="num"; @Override public boolean onCreate() { // TODO Auto-generated method stub myOpenHelper=new MyOpenHelper(getContext(), DB_Name, null, version_1); return true; } @Override public String getType(Uri uri) { // TODO Auto-generated method stub int flag=URI_MATCHER.match(uri); switch (flag) { case 2: return "vnd.android.cursor.item/contacter"; case 1: return "vnd.android.dir.item/contacter"; default: throw new IllegalArgumentException("异常参数"); } } @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub sqLiteDatabase=myOpenHelper.getWritableDatabase(); int flag=URI_MATCHER.match(uri); switch (flag) { case 1: sqLiteDatabase.insert(Table_Name, name, values); break; case 2: long id=sqLiteDatabase.insert(Table_Name, name, values); ContentUris.withAppendedId(uri, id); default: break; } return uri; } @Override public Cursor query(Uri uri, String[] arg1, String arg2, String[] arg3, String arg4) { // TODO Auto-generated method stub Cursor cursor; sqLiteDatabase=myOpenHelper.getReadableDatabase(); int flag=URI_MATCHER.match(uri); switch (flag) { case 1: cursor=sqLiteDatabase.query(Table_Name, arg1, arg2, arg3, null, null,arg4); break; case 2: long id=ContentUris.parseId(uri); arg2=(arg2==null||"".equals(arg2.trim()))? _id+"="+id:arg2+"and"+_id+"="+id; cursor=sqLiteDatabase.query(Table_Name, arg1, arg2, arg3, null, null,arg4); default: throw new IllegalArgumentException("参数错误"); } return cursor; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub int num=0; sqLiteDatabase=myOpenHelper.getWritableDatabase(); int flag=URI_MATCHER.match(uri); switch (flag) { case 1: num=sqLiteDatabase.update(Table_Name, values,selection, selectionArgs); break; case 2: long id=ContentUris.parseId(uri); selection=(selection==null||"".equals(selection.trim()))? _id+"="+id:selection+"and"+_id+"="+id; num=sqLiteDatabase.update(Table_Name, values,selection, selectionArgs); default: break; } return num; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub int num=0; sqLiteDatabase=myOpenHelper.getWritableDatabase(); int flag=URI_MATCHER.match(uri); switch (flag) { case 1: num=sqLiteDatabase.delete(Table_Name, selection, selectionArgs); break; case 2: long id=ContentUris.parseId(uri); selection=(selection==null||"".equals(selection.trim()))?_id+"="+id:selection+"and"+_id+"="+id; num=sqLiteDatabase.delete(Table_Name, selection, selectionArgs); default: throw new IllegalArgumentException("异常参数"); } return num; } private final String DB_Name = "mydb.db"; private final String Table_Name="contacter"; private final int version_1=1; private class MyOpenHelper extends SQLiteOpenHelper { public MyOpenHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } /** * @description 当数据表无连接时创建新的表 */ @Override public void onCreate(SQLiteDatabase db) { String sql = " create table if not exists " + Table_Name + "(id INTEGER PRIMARY KEY AUTOINCREMENT," + "name varchar(64),num varchar(64))"; db.execSQL(sql); } /** * @description 当版本更新时触发的方法 */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { String sql = " drop table if exists " + Table_Name; db.execSQL(sql); onCreate(db); } } } +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.tigongzhe" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.tigongzhe.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <provider android:name=".provider" android:authorities="com.example.tigongzhe.provider" android:multiprocess="true" android:exported="true" android:permission="com.example.tigongzhe.permission" ></provider> </application> <permission android:name="com.example.tigongzhe.permission" android:protectionLevel="normal"></permission> </manifest> ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++package com.example.tigongzhe; import android.R.integer; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.provider.SyncStateContract.Helpers; import android.text.Selection; import android.util.Log; public class provider extends ContentProvider { private MyOpenHelper myOpenHelper; private SQLiteDatabase sqLiteDatabase; private static final UriMatcher URI_MATCHER=new UriMatcher(UriMatcher.NO_MATCH); private final String TAG="provider"; private static final String authority="com.example.tigongzhe.provider"; static { URI_MATCHER.addURI(authority, "contacter", 1); URI_MATCHER.addURI(authority, "contacter/#", 2); } private static final String _id="id"; private static final String name="name"; private static final String num="num"; @Override public boolean onCreate() { // TODO Auto-generated method stub myOpenHelper=new MyOpenHelper(getContext(), DB_Name, null, version_1); return true; } @Override public String getType(Uri uri) { // TODO Auto-generated method stub int flag=URI_MATCHER.match(uri); switch (flag) { case 2: return "vnd.android.cursor.item/contacter"; case 1: return "vnd.android.dir.item/contacter"; default: throw new IllegalArgumentException("异常参数"); } } @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub sqLiteDatabase=myOpenHelper.getWritableDatabase(); int flag=URI_MATCHER.match(uri); switch (flag) { case 1: sqLiteDatabase.insert(Table_Name, name, values); break; case 2: long id=sqLiteDatabase.insert(Table_Name, name, values); ContentUris.withAppendedId(uri, id); default: break; } return uri; } @Override public Cursor query(Uri uri, String[] arg1, String arg2, String[] arg3, String arg4) { // TODO Auto-generated method stub Cursor cursor; sqLiteDatabase=myOpenHelper.getReadableDatabase(); int flag=URI_MATCHER.match(uri); switch (flag) { case 1: cursor=sqLiteDatabase.query(Table_Name, arg1, arg2, arg3, null, null,arg4); break; case 2: long id=ContentUris.parseId(uri); arg2=(arg2==null||"".equals(arg2.trim()))? _id+"="+id:arg2+"and"+_id+"="+id; cursor=sqLiteDatabase.query(Table_Name, arg1, arg2, arg3, null, null,arg4); default: throw new IllegalArgumentException("参数错误"); } return cursor; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub int num=0; sqLiteDatabase=myOpenHelper.getWritableDatabase(); int flag=URI_MATCHER.match(uri); switch (flag) { case 1: num=sqLiteDatabase.update(Table_Name, values,selection, selectionArgs); break; case 2: long id=ContentUris.parseId(uri); selection=(selection==null||"".equals(selection.trim()))? _id+"="+id:selection+"and"+_id+"="+id; num=sqLiteDatabase.update(Table_Name, values,selection, selectionArgs); default: break; } return num; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub int num=0; sqLiteDatabase=myOpenHelper.getWritableDatabase(); int flag=URI_MATCHER.match(uri); switch (flag) { case 1: num=sqLiteDatabase.delete(Table_Name, selection, selectionArgs); break; case 2: long id=ContentUris.parseId(uri); selection=(selection==null||"".equals(selection.trim()))?_id+"="+id:selection+"and"+_id+"="+id; num=sqLiteDatabase.delete(Table_Name, selection, selectionArgs); default: throw new IllegalArgumentException("异常参数"); } return num; } private final String DB_Name = "mydb.db"; private final String Table_Name="contacter"; private final int version_1=1; private class MyOpenHelper extends SQLiteOpenHelper { public MyOpenHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } /** * @description 当数据表无连接时创建新的表 */ @Override public void onCreate(SQLiteDatabase db) { String sql = " create table if not exists " + Table_Name + "(id INTEGER PRIMARY KEY AUTOINCREMENT," + "name varchar(64),num varchar(64))"; db.execSQL(sql); } /** * @description 当版本更新时触发的方法 */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { String sql = " drop table if exists " + Table_Name; db.execSQL(sql); onCreate(db); } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值