Andorid11(30)/Android10(29)分区存储-应用间共享文件

在这里插入图片描述

在Android11/Andorid10分区存储中,两个应用之间如何共享文件呢?比如说我的应用生成了一个jpeg图片,想分享到微信,该怎么搞?

有三种方案:

  1. 生成到公共目录下,通过File接口分享(微信支持)
  2. 生成到公共目录下,通过MediaStore接口进行分享(微信不支持)
  3. 生成到私有目录下,通过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分享

  1. 目标应用在manifest中声明FileProvider
<application>
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.kuaima.sharefileforandroid11.fileProvider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_provider_paths" />
    </provider>
</application>
    1. 使用androidx时,android:name声明为"androidx.core.content.FileProvider"。未使用androidx时,android:name声明为"android.support.v4.content.FileProvider"。
    2. android:authorities声明为域名为${yourApplicationId}+FileProvider。
    3. android:exported=“false” 不能修改为true,为true时就会抛出SecurityException(“Provider must not be exported”)。
    4. android:grantUriPermissions=“true” 不能修改为false,为false时会抛出SecurityException(“Provider must grant uri permissions”)。
    5. meta-data中android:name声明为"android.support.FILE_PROVIDER_PATHS"。
    6. meta-data中android:resource用于声明那些目录可以通过FileProvider访问。
  1. 定义FileProvider可以访问的目录。在file_provider_paths.xml中我们定义如下:

<paths>
    <external-files-path
        name="my_images"
        path="images/" />
</paths>
    1. external-files-path指定共享的目录
    2. name表示共享的url的path
    3. path指定的是共享目录下那个文件夹

下表是url,路径直接的对应关系:

img

  1. 根据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()
}
  1. 授权临时权限
grantUriPermission(
    "com.kuaima.destinationapp",
    Uri.parse(fileProviderPath),
    Intent.FLAG_GRANT_READ_URI_PERMISSION
)
  1. 目标应用读取文件
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()
}

在分区存储中,这些就是应用间共享文件的常用的三种方案。

参考文档

Android 11 中的存储机制更新

访问应用专属文件

共享存储空间

使用存储访问框架打开文件

管理存储的所有文件

FileProvider的使用

代码地址:https://github.com/wfeii/Android11

限于个人水平,有错误请指出,大家共同学习进步!

扫码关注公众号,查看更多内容。
img

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值