Android-适配文件存储
本文最后更新于 937 天前

一、迁移

// 简述步骤
// 对sdcard目录下的文件以及文件夹 批量移动到 当前应用的私有目录下,如:files以及cache目录

android_file_oper.png

二、适配操作

  1. AndroidQ及以上使用 MediaStore操作媒体等文件,并且应用自己文件归类保存在 沙盒目录内
    其中注意 Data字段的变化以及操作修改其他应用的权限申请以及捕获SecurityException异常进行用户授权处理
  2. AndroidQ一下就正常文件操作
val appFilePath = getExternalFilesDir(null)?.path?:""
val appImagePath = getExternalFilesDir(Environment.DIRECTORY_DCIM)?.path?:""
val appCustomPath = getExternalFilesDir("Demo")?.path?:""
val appCachePath = getExternalCacheDir()?.path?:""

// 如果应用卸载后又重新安装,删除卸载之前保存的文件就无法直接删除,或者删除其他应用创建的媒体文件也不能直接删除,
// 此时需要申请READ_EXTERNAL_STORAGE权限,删除需要捕获SecurityException异常

// App卸载后,对应的沙盒目录也会被删除,如果APP想要在卸载时保留沙盒目录下的数据,要在AndroidManifest.xml中声明
// android:hasFragileUserData="true",这样在 APP卸载时就会有弹出框提示用户是否保留应用数据

三、MediaStore操作

  1. 通过contentResolve等获取uri,在根据uri获取文件描述符,最后使用FileOutputStream(pfd.fileDescriptor)等流将数据流进行存取等操作
  2. os = resolver.openOutputStream(insertUri)

3.1 ContentValues

/**
 * ```
 * values.put(MediaStore.Images.Media.IS_PENDING, isPending)
 * Android Q , MediaStore中添加 MediaStore.Images.Media.IS_PENDING flag,用来表示文件的 isPending 状态,0是可见,其他不可见
 * ```
 * @param displayName 文件名
 * @param description 描述
 * @param mimeType 媒体类型
 * @param title 标题
 * @param relativePath 相对路径 eg: {Environment.DIRECTORY_PICTURES}/xxx
 * @param isPending 默认0 , 0是可见,其他不可见
 */
// val RELATIVE_PATH = "{Environment.DIRECTORY_PICTURES}${File.separator}img"
/* val values = createContentValues(
            "BitmapImage.png",
            "This is an image",
            "image/png",
            "Image.png",
            RELATIVE_PATH,
            1
        )
*/
fun createContentValues(
    displayName: String? = null, description: String? = null, mimeType: String? = null, title: String? = null,
    relativePath: String? = null, isPending: Int? = 1,
): ContentValues {
    return ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, displayName)
        put(MediaStore.Images.Media.DESCRIPTION, description)
        put(MediaStore.Images.Media.MIME_TYPE, mimeType)
        put(MediaStore.Images.Media.TITLE, title)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaStore.Images.Media.RELATIVE_PATH, relativePath)
            put(MediaStore.Images.Media.IS_PENDING, isPending)
        }
    }
}

3.2 访问其他应用创建文件权限检查

when (FileOperator.getContext()
    .checkUriPermission(uri, android.os.Process.myPid(), android.os.Process.myUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION)) {
    PackageManager.PERMISSION_GRANTED -> {
    }
    PackageManager.PERMISSION_DENIED -> {
        FileOperator.getContext().grantUriPermission(FileOperator.getApplication().packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
}   

3.3 完整例子

四、SAF操作

更多查看源码

4.1 选择文件

fun selectFile(activity: Activity, mimeType: String, requestCode: Int) {
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = mimeType
    }
    activity.startActivityForResult(intent, requestCode)
}


selectFile(activity, "image/*", requestCode)

五、Uri

5.1 文件描述符

通过文件描述符方位uri
 var pfd: ParcelFileDescriptor? = null
    try {
        pfd = contentResolver.openFileDescriptor(queryUri!!, "r")
        if (pfd != null) {
            val bitmap = BitmapFactory.decodeFileDescriptor(pfd.fileDescriptor)
            imageIv.setImageBitmap(bitmap)
        }
    } catch (e: IOException) {
        e.printStackTrace()
    } finally {
        pfd?.close()
    }

在Android Q以下只使用DATA字段,Android Q及以上不使用DATA字段,改为使用RELATEIVE_PATH字段

5.2 检查uri有效性

fun checkUri(uri: Uri?): Boolean {
    if (uri == null) return false
    val resolver = FileOperator.getContext().contentResolver

    //1. Check Uri
    var cursor: Cursor? = null
    val isUriExist: Boolean = try {
        cursor = resolver.query(uri, null, null, null, null)
        //cursor null: content Uri was invalid or some other error occurred
        //cursor.moveToFirst() false: Uri was ok but no entry found.
        (cursor != null && cursor.moveToFirst())
    } catch (t: Throwable) {
        FileLogger.e("1.Check Uri Error: {t.message}")
        false
    } finally {
        try {
            cursor?.close()
        } catch (t: Throwable) {
        }
    }

    //2. Check File Exist
    //如果系统 db 存有 Uri 相关记录, 但是文件失效或者损坏 (If the system db has Uri related records, but the file is invalid or damaged)
    var ins: InputStream? = null
    val isFileExist: Boolean = try {
        ins = resolver.openInputStream(uri)
        // file exists
        true
    } catch (t: Throwable) {
        // File was not found eg: open failed: ENOENT (No such file or directory)
        FileLogger.e("2. Check File Exist Error:{t.message}")
        false
    } finally {
        try {
            ins?.close()
        } catch (t: Throwable) {
        }
    }
    return isUriExist && isFileExist
}

android_mediastore.png

源码地址:FileOperator

iichen:https://iichen.cn/?p=304
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇