Android在应用中拍照并管理图片的策略分析

139 阅读1分钟

实现方案

问题如下: Android 系统中拍照后,想将Bitmap存储在应用自己的目录下面,比如应用包名为com.xxxx.xxxx,那么想将图片Bitmap存储在 data/data/com.xxxx/files 目录下,这样用户就无法删除这些图片了。当想要选择这些图片时想通过以下代码,Matisse.from(this) .choose(MimeType.ofImage()) // 只选图片 .countable(true) // 显示选中数量 .maxSelectable(100) // 最多选9张 .imageEngine(GlideEngine()) // 使用Glide加载引擎 .forResult(REQUEST_CODE_CHOOSE) 这段代码可能需要改造一下,只访问data/data/com.xxxx/files目录下的图片,具体要如何实现呢,如果想将图片Bitmap存储在 data/data/com.xxxx/files 目录下进行管理有什么缺陷,有什么更好的策略和实现方案,主要是为了管理图片,不让用户随意删除图片

将图片保存到应用私有目录

fun saveBitmapToPrivateDir(context: Context, bitmap: Bitmap, filename: String): Boolean {
    return try {
        context.openFileOutput(filename, Context.MODE_PRIVATE).use { stream ->
            bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream)
            true
        }
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

自定义图片选择器(替代Matisse)

class PrivateImageAdapter(private val context: Context) : 
    RecyclerView.Adapter<PrivateImageAdapter.ViewHolder>() {

    private val imageUris = mutableListOf<Uri>()

    init {
        loadPrivateImages()
    }

    private fun loadPrivateImages() {
        val filesDir = context.filesDir
        filesDir.listFiles { _, name -> 
            name.endsWith(".jpg") || name.endsWith(".png") 
        }?.forEach { file ->
            imageUris.add(FileProvider.getUriForFile(
                context,
                "${context.packageName}.fileprovider",
                file
            ))
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_image, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        Glide.with(holder.itemView)
            .load(imageUris[position])
            .into(holder.imageView)
    }

    override fun getItemCount() = imageUris.size

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val imageView: ImageView = view.findViewById(R.id.image_view)
    }
}

存储策略缺陷分析

  1. 空间限制:内部存储空间有限,不适合大量图片存储
  2. 数据丢失:应用卸载时数据会被清除
  3. 分享限制:需要通过FileProvider生成临时URI进行分享

改进方案

// 使用外部私有目录(更推荐)
fun getAppSpecificStorage(context: Context, subDir: String): File {
    return File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), subDir).apply {
        mkdirs()
    }
}

// 带自动清理的存储方案
private const val MAX_STORAGE_SIZE = 1024 * 1024 * 500L // 500MB

fun manageStorageSpace(storageDir: File) {
    var totalSize = storageDir.walk().sumBy { it.length().toInt() }
    
    storageDir.listFiles()?.sortedBy { it.lastModified() }?.forEach {
        if (totalSize > MAX_STORAGE_SIZE) {
            totalSize -= it.length()
            it.delete()
        }
    }
}

安全增强方案

// AES加密存储
fun saveEncryptedBitmap(context: Context, bitmap: Bitmap, filename: String, key: SecretKey) {
    val cipher = Cipher.getInstance("AES/GCM/NoPadding").apply {
        init(Cipher.ENCRYPT_MODE, key, GCMParameterSpec(128, ByteArray(12)))
    }
    
    context.openFileOutput(filename, Context.MODE_PRIVATE).use { stream ->
        val encryptedBytes = cipher.doFinal(bitmap.toByteArray())
        stream.write(encryptedBytes)
    }
}

// 图片元数据管理
class ImageMetadata(
    val uri: Uri,
    val createdTime: Long,
    val size: Long,
    val tags: List<String>
)

class ImageMetadataManager(context: Context) {
    private val database = Room.databaseBuilder(
        context,
        AppDatabase::class.java, "image-metadata"
    ).build()

    suspend fun addMetadata(metadata: ImageMetadata) {
        database.metadataDao().insert(metadata)
    }
    
    suspend fun queryByTag(tag: String) = database.metadataDao().getByTag(tag)
}

方案对比

方案安全性存储空间数据持久性实现复杂度
内部私有目录★★★★★★★☆☆☆★☆☆☆☆★★☆☆☆
外部私有目录★★★☆☆★★★★☆★★★☆☆★★☆☆☆
加密存储+内部目录★★★★★★★☆☆☆★☆☆☆☆★★★★☆
云同步+本地缓存★★★★☆★★★★★★★★★★★★★★★

推荐方案

  1. 使用getExternalFilesDir()创建应用专属外部存储目录
  2. 结合定期自动清理机制管理存储空间
  3. 重要图片使用AES加密存储
  4. 使用Room数据库管理图片元数据
  5. 实现自定义图片选择器替代Matisse

优势

  • 用户无法通过常规文件管理访问外部私有目录
  • 加密保障数据安全
  • 自动清理防止存储空间不足
  • 数据库提供高效查询能力
  • 完全控制图片访问权限

注意事项

  1. 在AndroidManifest.xml中添加权限声明
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" 
    android:maxSdkVersion="28" />
  1. 配置FileProvider
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>
  1. 创建file_paths.xml
<paths>
    <external-files-path
        name="private_images"
        path="Pictures/" />
</paths>