在Android11/Andorid10分区存储中,两个应用之间如何共享文件呢?比如说我的应用生成了一个jpeg图片,想分享到微信,该怎么搞?
有三种方案:
生成到公共目录下,通过File接口分享(微信支持)
生成到公共目录下,通过MediaStore接口进行分享(微信不支持)
生成到私有目录下,通过FileProvider进行分享(微信支持)
本篇代码比较多,比起废话,Show code更靠谱。
生成到公共目录下,File接口分享
对于Android10的分区存储不支持此方案,而Android11的分区存储是支持的。
源应用把内容写入到外部公共目录下,例子为asset下的文件复制到公共目录Pictures/下。
//通过File的方式分享
private fun fileShare(): String {
val file = File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES
),
"friends_file.jpg"
)
if (!file.parentFile.exists()) {
file.mkdirs()
}
val fileOutputStream = FileOutputStream(file)
val inputStream = this.assets.open("friends.jpg")
val byteArray = ByteArray(1024)
try {
fileOutputStream.use { outputStream ->
inputStream.use { inputStream ->
while (true) {
val readLen = inputStream.read(byteArray)
if (readLen == -1) {
break
}
outputStream.write(byteArray, 0, readLen)
}
}
}
} catch (e: Throwable) {
Log.e("wfeii", "fileShare:$e")
}
return file.path
}
复制代码
目标应用获取外部公共目录文件,并读取内容到自己的外部存储中。
private fun dealFilePath(intent: Intent?) {
if (intent == null) {
return
}
val filePath = intent.getStringExtra(FILE_PATH)
if (filePath == null) {
return
}
val fileInputStream = FileInputStream(filePath)
val file = File(getExternalFilesDir(""), "mediaStore_file.jpg")
if (!file.parentFile.exists()) {
file.parentFile.createNewFile()
}
val fileOutputStream = FileOutputStream(file)
val byteArray = ByteArray(1024)
try {
fileInputStream.use { fileInputStream ->
fileOutputStream.use { fileOutputStream ->
while (true) {
val readLen = fileInputStream.read(byteArray)
if (readLen == -1) {
break
}
fileOutputStream.write(byteArray, 0, readLen)
}
}
}
} catch (t: Throwable) {
Log.e("wfeii", "dealFilePath:$t")
}
Toast.makeText(this, "复制成功", Toast.LENGTH_LONG).show()
}
复制代码
注意:目标应用需要有READ_EXTERNAL_STORAGE权限。
生成到公共目录下,通过MediaStore接口分享
源应用把内容写入到外部公共目录下,例子为asset下的文件写入到Image的公共目录下。
//通过MediaStore存储到公共目录再分享
private fun mediaStore(): String? {
val resolver = applicationContext.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "friends.jpg")
}
val uri = resolver
.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
if (uri != null) {
val outputStream = resolver.openOutputStream(uri)
if (outputStream == null) {
return null
}
val inputStream = this.assets.open("friends.jpg")
val byteArray = ByteArray(1024)
try {
inputStream.use { input ->
outputStream.use { output ->
while (true) {
val readLen = input.read(byteArray)
if (readLen == -1) {
break
}
outputStream.write(byteArray, 0, readLen)
}
}
}
} catch (e: Throwable) {
Log.e("wfeii", "mediaStore e:$e")
}
}
Log.e("wfeii", "mediaStore:$uri")
return uri?.toString()
}
复制代码
目标应用获取外部公共目录文件,并读取内容到自己的外部存储中。
private fun dealMediaStoreUrl(intent: Intent?) {
if (intent == null) {
return
}
val mediaStoreUrl = intent.getStringExtra(MEDIA_STORE)
if (mediaStoreUrl == null) {
return
}
val readOnlyMode = "r"
val fileDescriptor =
contentResolver.openFileDescriptor(
Uri.parse(mediaStoreUrl),
readOnlyMode
)?.fileDescriptor
if (fileDescriptor == null) {
return
}
val fileInputStream: InputStream = FileInputStream(fileDescriptor)
val file = File(getExternalFilesDir(""), "mediaStore.jpg")
val fileOutputStream = FileOutputStream(file)
val byteArray = ByteArray(1024)
try {
fileInputStream.use { fileDescriptor ->
fileOutputStream.use { fileOutputStream ->
while (true) {
val readLen = fileInputStream.read(byteArray)
if (readLen == -1) {
break
}
fileOutputStream.write(byteArray, 0, readLen)
}
}
}
} catch (e: Throwable) {
Log.e("wfeii", "dealMediaStoreUrl:$e")
}
Toast.makeText(this, "复制成功", Toast.LENGTH_LONG).show()
}
复制代码
注意:目标应用需要有READ_EXTERNAL_STORAGE权限。
生成到私有目录下,通过FileProvider分享
目标应用在manifest中声明FileProvider
android:name="androidx.core.content.FileProvider"
android:authorities="com.kuaima.sharefileforandroid11.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths" />
复制代码
使用androidx时,android:name声明为"androidx.core.content.FileProvider"。未使用androidx时,android:name声明为"android.support.v4.content.FileProvider"。
android:authorities声明为域名为${yourApplicationId}+FileProvider。
android:exported="false" 不能修改为true,为true时就会抛出SecurityException("Provider must not be exported")。
android:grantUriPermissions="true" 不能修改为false,为false时会抛出SecurityException("Provider must grant uri permissions")。
meta-data中android:name声明为"android.support.FILE_PROVIDER_PATHS"。
meta-data中android:resource用于声明那些目录可以通过FileProvider访问。
定义FileProvider可以访问的目录。在file_provider_paths.xml中我们定义如下:
name="my_images"
path="images/" />
复制代码
external-files-path指定共享的目录
name表示共享的url的path
path指定的是共享目录下那个文件夹
下表是url,路径直接的对应关系:
根据File获取url并写入内容
private fun fileProviderShare(): String? {
val imagePath = File(this.getExternalFilesDir(null), "images")!!
val file = File(imagePath, "friends_file_provider.jpg")
if (!file.parentFile.exists()) {
file.parentFile.mkdirs()
}
//更具获取Url
val contentUri: Uri =
FileProvider.getUriForFile(this, "com.kuaima.sharefileforandroid11.fileProvider", file)
val fileOutputStream = contentResolver.openOutputStream(contentUri)
if (fileOutputStream == null) {
return null
}
val inputStream = this.assets.open("friends.jpg")
val byteArray = ByteArray(1024)
try {
fileOutputStream.use { outputStream ->
inputStream.use { inputStream ->
while (true) {
val readLen = inputStream.read(byteArray)
if (readLen == -1) {
break
}
outputStream.write(byteArray, 0, readLen)
}
}
}
} catch (e: Throwable) {
Log.e("wfeii", "fileProviderShare e:$e")
}
Log.e("wfeii", "fileProviderShare:$contentUri")
return contentUri.toString()
}
复制代码
授权临时权限
grantUriPermission(
"com.kuaima.destinationapp",
Uri.parse(fileProviderPath),
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
复制代码
目标应用读取文件
private fun dealFileProviderPath(intent: Intent?) {
if (intent == null) {
return
}
val fileProviderUri = intent.getStringExtra(FILE_PROVIDER_URI)
if (fileProviderUri == null) {
return
}
Log.e("wfeii", "dealFileProviderPath fileProviderUri:$fileProviderUri")
val readOnlyMode = "r"
val fileDescriptor =
contentResolver.openFileDescriptor(
Uri.parse(fileProviderUri), readOnlyMode
)?.fileDescriptor
if (fileDescriptor == null) {
return
}
val inputStream = FileInputStream(fileDescriptor)
val file = File(getExternalFilesDir(""), "file_provider_path.jpg")
val fileOutputStream = FileOutputStream(file)
val byteArray = ByteArray(1024)
try {
inputStream.use { fileDescriptor ->
fileOutputStream.use { fileOutputStream ->
while (true) {
val readLen = inputStream.read(byteArray)
if (readLen == -1) {
break
}
fileOutputStream.write(byteArray, 0, readLen)
}
}
}
} catch (t: Throwable) {
Log.e("wfeii", "t")
}
Toast.makeText(this, "复制成功", Toast.LENGTH_LONG).show()
}
复制代码
在分区存储中,这些就是应用间共享文件的常用的三种方案。
参考文档
限于个人水平,有错误请指出,大家共同学习进步!
扫码关注公众号,查看更多内容。