在Android中,如果需要创建一个文件去保存数据,就需要这个文件URI,在Android 7.0之后,直接使用Uri.fromFile()方法会报不安全错误。FileProvider就是提供了一种安全的Uri转化方式。
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.
FileProvider的具体用法在Android的API文档中有具体的介绍,链接在这。这篇文章主要是结合一个相机拍照的例子。
FileProvider的使用步骤
创建FileProvider
由于FileProvider是继承自ContentProvider的,所以需要在AndroidManifest文件中声明。
<application
.../>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}"
android:exported="false"
android:grantUriPermissions="true">
...
</provider>
</application>
其中authorities一般设置为包名,exported必须为false,grantUriPermission为true。
指定名称转化文件
开发者文档上叫 Specifying Available Files
这个文件的作用是指定目标文件的目录的别名,以屏蔽真实的路径。有点凌乱,举例说明。
比如文件的真实路径为
/…/Android/data/data/yourApplicationId/fruit/banana.jpg
这个文件的作用就是为fruit路径设置一个别名,比如叫eatable,这个别名会在Uri中使用。
反应在文件中就是:
res/xml/file_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-cache-path name="eatable" path="fruit" />
</paths>
在写好这个文件之后,回到Manifest中,把该文件配置到FileProvider中的mate-data属性里。并添加android:name="android.support.FILE_PROVIDER_PATHS"属性。
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
还有要注意的地方就是在file_paths.xml文件的<paths>标签下要用什么子标签呢?
这个其实和在代码中怎么获取路径有关系,上面的代码中我用了<external-cache-path>这个子标签,在代码里就要使用Context.getExternalCacheDir()方法获取路径。
下面是开发者文档上抄的对应关系:
子标签 | 对应方法 |
---|---|
files-path | Context.getFilesDir() |
cache-path | getCacheDir() |
external-path | Environment.getExternalStorageDirectory() |
external-files-path | Context#getExternalFilesDir(String) Context.getExternalFilesDir(null) |
external-cache-path | Context.getExternalCacheDir() |
从File中创建Uri
现在就能通过FileProvider获取到安全的Uri了。
// 创建路径和文件的File对象
// 因为xml中用了external-cache-path标签
// 所以这里用Context.getExternalCacheDir()方法获取路径
val fruit = File(enteralCacheDir, "fruit")
val banana = File(fruit, "banana.jpg")
fruit.mkdirs()
if (banana.exists()) {
banana.delete()
}
banana.createNewFile()
// 获取Uri
val eatableUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Android N之后使用FileProvider的getUriForFile()方法来获取Uri
FileProvider.getUriForFile(context!!, MyUtils.PACKAGE_NAME, photo)
} else {
Uri.parse(photo.path)
}
在onCreate()方法里添加这段代码,运行结果如下:
应用Uri
这里结合相机程序来使用:
// 开启相机
private fun openCamera() {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
intent.putExtra(MediaStore.EXTRA_OUTPUT, cameraUri) // 把uri传入
startActivityForResult(intent, REQUEST_CODE_CAMERA)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CODE_CAMERA -> {
showImage(cameraUri) // 返回数据时,相机已经把数据存入Uri所对应的文件了
}
}
}
}
private fun showImage(uri: Uri) {
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
imageView.setImageBitmap(bitmap)
}
运行结果:
参考链接:Android Crop Image Using UCrop Library一个结合UCrop裁剪框架的具体例子。