实现方案
问题如下: 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)
}
}
存储策略缺陷分析
- 空间限制:内部存储空间有限,不适合大量图片存储
- 数据丢失:应用卸载时数据会被清除
- 分享限制:需要通过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)
}
方案对比
| 方案 | 安全性 | 存储空间 | 数据持久性 | 实现复杂度 |
|---|---|---|---|---|
| 内部私有目录 | ★★★★★ | ★★☆☆☆ | ★☆☆☆☆ | ★★☆☆☆ |
| 外部私有目录 | ★★★☆☆ | ★★★★☆ | ★★★☆☆ | ★★☆☆☆ |
| 加密存储+内部目录 | ★★★★★ | ★★☆☆☆ | ★☆☆☆☆ | ★★★★☆ |
| 云同步+本地缓存 | ★★★★☆ | ★★★★★ | ★★★★★ | ★★★★★ |
推荐方案:
- 使用
getExternalFilesDir()创建应用专属外部存储目录 - 结合定期自动清理机制管理存储空间
- 重要图片使用AES加密存储
- 使用Room数据库管理图片元数据
- 实现自定义图片选择器替代Matisse
优势:
- 用户无法通过常规文件管理访问外部私有目录
- 加密保障数据安全
- 自动清理防止存储空间不足
- 数据库提供高效查询能力
- 完全控制图片访问权限
注意事项:
- 在AndroidManifest.xml中添加权限声明
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
- 配置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>
- 创建file_paths.xml
<paths>
<external-files-path
name="private_images"
path="Pictures/" />
</paths>