其他
别滥用FileProvider了,Android中FileProvider的各种场景应用
本文作者
作者:newki
链接:
https://juejin.cn/post/7140166121595863076
本文由作者授权发布。
也就是说一般使用场景,我们只有在自己App沙盒中的文件,需要给别的App操作的时候,我们才需要使用 FileProvider 。
话不多说,我们从常规的使用与示例上来看看怎么使用,清楚它的一些小细节。
<provider
android:authorities="com.guadou.kt_demo.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path">
</meta-data>
</provider>
属性的一些说明:
authorities 是标记我们这个ContentProvider的唯一标识,是一个用于认证的暗号,我们一般默认使用包名+fileprovider来定义。(能不能使用别的,可以,abcd都行,但是没必要) name 是具体的FileProvider类,如果是系统的,就用上面的这种,如果是自定义的,就写自定义FileProvider的全类名。 exported 是否限制其他应用获取此FileProvider。 grantUriPermissions 是否授权其他应用获取访问Uri权限,一般为true。 meta-data 和下面的 name 都是固定的写法,重点是 resource 需要自己实现规则,定义哪些私有文件会被提供访问。
<?xml version="1.0" encoding="utf-8"?>
<paths>
<root-path name="myroot" path="." />
<external-path name="external_file" path="." />
<files-path name="inner_app_file" path="." />
<cache-path name="inner_app_cache" path="." />
<external-files-path name="external_app_file" path="." />
<external-files-path name="log_file" path="log" />
<external-cache-path name="external_app_cache" path="." />
<external-cache-path name="naixiao_img" path="pos" />
</paths>
root-path 从SD卡开始找 例如 storage/emulated/0/Android/data/com.guadou.kt_demo/cache/pos/naixiao-1122.jpg external-path 从外置SD卡开始 例如 Android/data/com.guadou.kt_demo/cache/pos/naixiao-1122.jpg external-files-path 外置沙盒file目录 例如 pos/naixiao-1122.jpg (真实目录在 Android/data/com.guadou.kt_demo/cache/pos/) external-cache-path 外置沙盒cache目录 例如 naixiao-1122.jpg (真实目录在 Android/data/com.guadou.kt_demo/cache/) files-path 和上面的同理,只是在内置的data/data目录下面 cache-path 和上面的同理,只是在内置的data/data目录下面
<external-cache-path name="external_app_cache" path="." />
<external-cache-path name="naixiao_img" path="pos" />
storage/emulated/0/Android/data/com.guadou.kt_demo/cache/pos/naixiao-1122.jpg
打印Uri:content://com.guadou.kt_demo.fileprovider/naixiao_img/naixiao-1122.jpg
<external-cache-path name="external_app_cache" path="." />
打印Uri:content://com.guadou.kt_demo.fileprovider/external_app_cache/pos/naixiao-1122.jpg
<root-path name="myroot" path="." />
<external-path name="external_file" path="." />
打印Uri:content://com.guadou.kt_demo.fileprovider/external_file/Android/data/com.guadou.kt_demo/cache/pos/naixiao-1122.jpg
<root-path name="myroot" path="." />
打印Uri:content://com.guadou.kt_demo.fileprovider/myroot/storage/emulated/0/Android/data/com.guadou.kt_demo/cache/pos/naixiao-1122.jpg
//测试FileProvider
fun fileProvider1() {
val drawable = drawable(R.drawable.chengxiao)
val bd: BitmapDrawable = drawable as BitmapDrawable
val bitmap = bd.bitmap
FilesUtils.getInstance().saveBitmap(bitmap, "naixiao-1122.jpg")
val filePath = FilesUtils.getInstance().sdpath + "naixiao-1122.jpg"
YYLogUtils.w("文件原始路径:$filePath")
val uri = FileProvider.getUriForFile(commContext(), "com.guadou.kt_demo.fileprovider", File(filePath))
YYLogUtils.w("打印Uri:$uri")
//到系统中找打开对应的文件
openFile(filePath, uri)
}
private fun openFile(path: String, uri: Uri) {
//取得文件扩展名
val extension: String = path.substring(path.lastIndexOf(".") + 1)
//通过扩展名找到mimeType
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
YYLogUtils.w("mimeType: $mimeType")
try {
//构造Intent,启动意图,交由系统处理
startActivity(Intent().apply {
//临时赋予读写权限
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
//表示用其它应用打开
action = Intent.ACTION_VIEW
//给Intent 赋值
setDataAndType(uri, mimeType)
})
} catch (e: Exception) {
e.printStackTrace()
YYLogUtils.e("不能打开这种类型的文件")
}
}
文件原始路径:/storage/emulated/0/Android/data/com.guadou.kt_demo/cache/pos/naixiao-1122.jpg
打印Uri:content://com.guadou.kt_demo.fileprovider/external_app_cache/pos/naixiao-1122.jpg
这... 好像还真行。
其实我们仿造系统的App的做法,我们在自定义的Activity中加入指定Filter即可,比如这里我需要接收图片,那么我定义如下的 intent-filter :
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ReceiveImageActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="content" />
<data android:scheme="file" />
<data android:scheme="http" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_receive_image)
if (intent != null && intent.action == Intent.ACTION_VIEW) {
val uri = intent.data
YYLogUtils.w("uri: $uri")
if (uri != null && uri.scheme != null && uri.scheme == "content") {
val fis = contentResolver.openInputStream(uri)
if (fis != null) {
val bitmap = BitmapFactory.decodeStream(fis)
//展示
if (bitmap != null) {
val ivReveiverShow = findViewById<ImageView>(R.id.iv_reveiver_show)
ivReveiverShow.setImageBitmap(bitmap)
}
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_receive_image)
if (intent != null && intent.action == Intent.ACTION_VIEW) {
val uri = intent.data
YYLogUtils.w("uri: $uri")
if (uri != null && uri.scheme != null && uri.scheme == "content") {
val fis = contentResolver.openInputStream(uri)
if (fis != null) {
val inBuffer = fis.source().buffer()
val outFile = File(getExternalFilesDir("xiaoxiao"), "naixiao5566.jpg")
outFile.sink().buffer().use {
it.writeAll(inBuffer)
inBuffer.close()
}
YYLogUtils.w("存放的路径:${outFile.absolutePath}")
//展示
val ivReveiverShow = findViewById<ImageView>(R.id.iv_reveiver_show)
ivReveiverShow.extLoad(outFile.absolutePath)
}
}
}
}
这...,你别逗我了。
转头一想,好像还真行,有操作空间啊... 既然 FileProvider 是继承自 ContentProvider 。那凭什么我们的App都能获取到别人App的数据库了,不能获取别人的沙盒文件呢?那数据库文件不也存在沙盒中么?
<provider
android:name=".MyFileProvider"
android:authorities="com.guadou.kt_demo.fileprovider"
android:exported="true"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path">
</meta-data>
</provider>
原来 FileProvider的 exported 和 grantUriPermissions 都是指定的写法,不能改变,并且不允许暴露,不允许给别的App主动访问!
public class MyFileProvider extends ContentProvider {
@Override
public void attachInfo(Context context, ProviderInfo info) {
super.attachInfo(context, info);
mStrategy = getPathStrategy(context, info.authority);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
YYLogUtils.w("走到query方法");
final File file = mStrategy.getFileForUri(uri);
YYLogUtils.w("file:" + file);
if (!file.exists()) {
return null;
}
boolean directory = file.isDirectory();
if (directory) {
YYLogUtils.w("说明是文件夹啊!");
File[] files = file.listFiles();
for (File childFile : files) {
if (childFile.isFile()) {
String name = childFile.getName();
String path = childFile.getPath();
long size = childFile.length();
Uri uriForFile = mStrategy.getUriForFile(childFile);
YYLogUtils.w("name:" + name + " path:" + path + " size: " + size +" uriForFile:"+uriForFile);
}
}
//自己遍历封装Cursor实现
return null;
} else {
YYLogUtils.w("说明是文件啊!");
if (projection == null) {
projection = COLUMNS;
}
String[] cols = new String[projection.length];
Object[] values = new Object[projection.length];
int i = 0;
for (String col : projection) {
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
cols[i] = OpenableColumns.DISPLAY_NAME;
values[i++] = file.getName();
} else if (OpenableColumns.SIZE.equals(col)) {
cols[i] = OpenableColumns.SIZE;
values[i++] = file.length();
}
}
cols = copyOf(cols, i);
values = copyOf(values, i);
final MatrixCursor cursor = new MatrixCursor(cols, 1);
cursor.addRow(values);
return cursor;
}
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hongyegroup.receiver">
<queries>
<provider android:authorities="com.guadou.kt_demo.fileprovider" />
</queries>
...
</manifest>
private fun queryFiles() {
val uri = Uri.parse("content://com.guadou.kt_demo.fileprovider/external_app_cache/pos/naixiao-1122.jpg")
val cursor = contentResolver.query(uri, null, null, null, null)
if (cursor != null) {
while (cursor.moveToNext()) {
val fileName = cursor.getString(cursor.getColumnIndex("_display_name"));
val size = cursor.getLong(cursor.getColumnIndex("_size"));
YYLogUtils.w("name: $fileName size: $size")
Toast.makeText(this, "name: $fileName size: $size", Toast.LENGTH_SHORT).show()
}
cursor.close()
} else {
YYLogUtils.w("cursor-result: 为空啊")
Toast.makeText(this, "cursor-result: 为空啊", Toast.LENGTH_SHORT).show()
}
}
val fis = contentResolver.openInputStream(uri)
if (fis != null) {
val inBuffer = fis.source().buffer()
val outFile = File(getExternalFilesDir(null), "abc")
outFile.sink().buffer().use {
it.writeAll(inBuffer)
inBuffer.close()
}
YYLogUtils.w("保存文件成功")
}
private fun queryFiles() {
val uri = Uri.parse("content://com.guadou.kt_demo.fileprovider/external_app_cache/pos/")
val cursor = contentResolver.query(uri, null, null, null, null)
if (cursor != null) {
while (cursor.moveToNext()) {
val fileName = cursor.getString(cursor.getColumnIndex("_display_name"));
val size = cursor.getLong(cursor.getColumnIndex("_size"));
val uri = cursor.getString(cursor.getColumnIndex("uri"));
val fileUri = Uri.parse(uri)
//就可以使用IO或者BitmapFactory来操作流了
YYLogUtils.w("name: $fileName size: $size")
Toast.makeText(this, "name: $fileName size: $size", Toast.LENGTH_SHORT).show()
}
cursor.close()
} else {
YYLogUtils.w("cursor-result: 为空啊")
Toast.makeText(this, "cursor-result: 为空啊", Toast.LENGTH_SHORT).show()
}
}
只要对方封装的Cursor,我们可以把名字,大小,uri等信息都封装到Cursor中,提供给对方获取。
本文全部代码均以开源,源码在此。大家可以点个Star关注一波。
https://gitee.com/newki123456/Kotlin-Room
凡猿修仙传:斩杀ClassNotFoundException when unmarshalling Crash