通过意图或从图库将捕获的图像保存到房间数据库中

问题描述 投票:0回答:1

我一直在尝试将捕获的图像保存到房间数据库中,并稍后将其与其他详细信息一起上传到服务器。图像采集代码如下

val activityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                if (result.resultCode == Activity.RESULT_OK) {
                    val data: Intent? = result.data
                    val uri: Uri? = if (data?.data != null){ data.data }else{ billUri }
                    if (uri != null) {

                        chequeBitmap = uriToBitmap(this, uri)

                        binding.chequeimage.setImageBitmap(chequeBitmap)
                        binding.addimage.visibility = View.GONE

                        binding.chequeimage.setOnClickListener {
                            // Clear the image and show the add image button
                            binding.chequeimage.setImageResource(R.drawable.image_upload)
                            binding.addimage.visibility = View.VISIBLE
                        }
                    } else {
                        Toast.makeText(this, "No Image Found", Toast.LENGTH_SHORT).show()
                    }
                }

        }

        binding.addimage.setOnClickListener {
            val intent = Intent(Intent.ACTION_GET_CONTENT)
            intent.type = "image/*"
            intent.putExtra(MediaStore.EXTRA_MEDIA_TITLE, "Gallery")
            
            val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

            val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmm_ss",
            Locale.getDefault()).format(Date())
            val storageDir: File? = getExternalFilesDir("DO_NOT_DELETE")
            val photoFile: File? = File.createTempFile(
                "Bill_${timeStamp}_", /* prefix */
                ".jpg", /* suffix */
                storageDir /* directory */
            )
            photoFile?.let { file ->
                val billURI: Uri = FileProvider.getUriForFile(this, "$packageName.provider", file)
                billUri = billURI
                captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, billURI)
            }

当我将其上传到数据库时,它上传成功,但由于 sqlite 数据库中的行大小较大,应用程序无法读取它。然后应用程序永久崩溃,直到数据库被删除。

我正在尝试将图像作为 ByteArray 保存到房间数据库中

data class DepositData(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    var bill: String,
    val payimage: ByteArray
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as DepositData

        return payimage.contentEquals(other.payimage)
    }

    override fun hashCode(): Int {
        return payimage.contentHashCode()

我希望图像大小应减少到 1.5 MB(最大)并保存到房间数据库中,而不会破坏图像质量或其清晰度。以前我曾经将图像捕获为缩略图,但它对于清晰的图像没有用处。加载时图像模糊。我不想将图像 URI 保存到房间中,因为用户可以删除它,并且应用程序可能因此崩溃。

要将其上传到数据库,我使用以下代码:

binding.save.setOnClickListener {
    val payImage: ByteArray = bitmapToByteArray(chequeBitmap)!!

    val data = DepositData(0, bill = bill, payimage = payImage)
    collectionViewModel.insertRecord(data)
}
android kotlin android-room android-camera-intent
1个回答
0
投票

当我将其上传到数据库时,它上传成功,但由于 sqlite 数据库中的行大小较大,应用程序无法读取它

这不一定是真的,因为可以规避底层 CursorWindow 的限制。

    Room 使用的 SQLite API 将数据提取到游标中,游标利用数据行的缓冲。该缓冲区是一个 CursorWindow,它必须能够缓冲至少一整行。如果行的大小超过缓冲区大小,则会发生异常。
因此,减少提取行的大小可以克服该限制。

可以通过从太大的 BLOB 中提取可管理数量的数据然后进行组合来减少数据量。

  • 从下面的演示中,插入的图像太大:-

    • 读取 Sqlite 表“AltImage”时出错:行太大,无法放入 CursorWindow requiredPos=0,totalRows=1
  • 然后可以提取部分图像,例如作者:-

  • enter image description here

      可以利用递归 CTE 来提取所有部分,然后驱动一个函数将所有部分组合为一个。
另一种方法是将数据存储在可管理的块/块中,然后进行组合。这可能是更简单的方法,更类似于 Room 的面向对象方法,因为您可以拥有父 Image 对象以及底层块对象。

就图像/媒体而言,建议的方法是存储一种然后提取媒体的方法,这很可能位于应用程序的数据内。

演示

以下是一个简单的演示,在资产文件夹中有一个较大的图像(3MB),然后通过两种方法将其存储在数据库中。

    方法 1,基于单个
  • AltImage @Entity
     带注释的对象(SQLite 表),该对象具有 
    idimage 字段 (BLOB)
  • 方法2,是将数据切分成块,有2个
  • @Entity
    带注释的对象(SQLite表);
    
      包含实际 ByteStream/ByteArray/BLOB 之外的所有信息的
    • ImageParent,并且,
    • DataChunk 与其父对象/表具有引用/映射/关系。
因此,这两种方法的 3 个

@Entity

 带注释的对象是:-

@Entity data class AltImage( @PrimaryKey val id: Long?=null, val image: ByteArray ) @Entity data class ImageParent( @PrimaryKey val id: Long?=null, val timestamp: Long=System.currentTimeMillis(), @ColumnInfo(index = true) val imageName: String ) @Entity data class DataChunk( @PrimaryKey val id: Long?=null, @ColumnInfo(index = true) val mapTo_ImageParent: Long, val chunk: ByteArray )
为了促进第二种(块)方法,还有一个 

FullImage 对象,可以帮助使用 @Embedded

@Relation
 注释提取相关数据。该类还有几个函数可以帮助处理块(拆分和组合):-

data class FullImage( @Embedded val imageParent: ImageParent, @Relation( entity = DataChunk::class, parentColumn = "id", entityColumn = "mapTo_ImageParent" ) val dataChunkList: List<DataChunk> ) { companion object { fun getArrayOfChunks(fullImage: ByteArray): ArrayList<ByteArray> { val rv: ArrayList<ByteArray> = ArrayList() var offset=0 while(offset < fullImage.size) { Log.d("APPINFO","getArrayOfChunks Loop, Offset is ${offset} FullImageSize = ${fullImage.size}") if (fullImage.size - (offset + CHUNK_SIZE) >0) { rv.add(fullImage.copyOfRange(offset, offset + CHUNK_SIZE)) } else { rv.add(fullImage.copyOfRange(offset,offset + (fullImage.size-offset))) } offset+= CHUNK_SIZE } return rv } } fun getCombinedChunks(): ByteArray { val rv: ArrayList<Byte> = ArrayList() for (c in dataChunkList) { for (b in c.chunk) rv.add(b) } return rv.toByteArray() } }
至于访问数据库,单个 

@Dao

 带注释的 
AllDAOs 接口是:-

@Dao interface AllDAOs { @Insert(onConflict = OnConflictStrategy.IGNORE) fun insert(imageParent: ImageParent): Long @Insert(onConflict = OnConflictStrategy.IGNORE) fun insert(dataChunk: DataChunk): Long @Insert(onConflict = OnConflictStrategy.IGNORE) fun insert(altImage: AltImage) @Query("SELECT * FROM imageparent") fun getAllFullImages(): List<FullImage> }
将它们放在一起,然后是一个 

@Database

 带注释的类:-

@Database(entities = [ImageParent::class,DataChunk::class,AltImage::class], exportSchema = false, version = 1) abstract class TheDatabase: RoomDatabase() { abstract fun getAllDAOs(): AllDAOs companion object { private var instance: TheDatabase?=null fun getInstance(context: Context): TheDatabase { if (instance==null) { instance=Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db") .allowMainThreadQueries() /* for brevity of the DEMO */ .build() } return instance as TheDatabase } } }
实际使用上面的活动代码:-

const val CHUNK_SIZE=1024 * 4 class MainActivity : AppCompatActivity() { lateinit var db: TheDatabase lateinit var dao: AllDAOs override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val theImage = this.assets.open("theimage.JPG").readBytes() Log.d("APPINFO","Image length=${theImage.size}") db = TheDatabase.getInstance(this) dao = db.getAllDAOs() val altImageId = dao.insert(AltImage(null,theImage.copyOf())) val ipId = ImageParent(id =dao.insert(ImageParent(imageName = "TheImage")), imageName = "TheImage") for(dc in FullImage.getArrayOfChunks(theImage)) { dao.insert(DataChunk(null, mapTo_ImageParent = ipId.id!!,dc)) } val extracted = dao.getAllFullImages() for (e in extracted) { Log.d("APPINFO"," Image has ${e.dataChunkList.size} chunks of (${CHUNK_SIZE}) (note last chuck may be smaller) bytearray size is ${e.getCombinedChunks().size}") } } }
运行上述命令(从全新安装)时,日志中将显示以下结果:-

2024-05-07 10:28:01.983 D/APPINFO: Image length=3422566 2024-05-07 10:28:02.137 D/APPINFO: getArrayOfChunks Loop, Offset is 0 FullImageSize = 3422566 2024-05-07 10:28:02.153 D/APPINFO: getArrayOfChunks Loop, Offset is 4096 FullImageSize = 3422566 2024-05-07 10:28:02.153 D/APPINFO: getArrayOfChunks Loop, Offset is 8192 FullImageSize = 3422566 2024-05-07 10:28:02.153 D/APPINFO: getArrayOfChunks Loop, Offset is 12288 FullImageSize = 3422566 2024-05-07 10:28:02.153 D/APPINFO: getArrayOfChunks Loop, Offset is 16384 FullImageSize = 3422566 2024-05-07 10:28:02.153 D/APPINFO: getArrayOfChunks Loop, Offset is 20480 FullImageSize = 3422566 2024-05-07 10:28:02.153 D/APPINFO: getArrayOfChunks Loop, Offset is 24576 FullImageSize = 3422566 2024-05-07 10:28:02.153 D/APPINFO: getArrayOfChunks Loop, Offset is 28672 FullImageSize = 3422566 2024-05-07 10:28:02.153 D/APPINFO: getArrayOfChunks Loop, Offset is 32768 FullImageSize = 3422566 .... 2024-05-07 10:28:02.218 D/APPINFO: getArrayOfChunks Loop, Offset is 3403776 FullImageSize = 3422566 2024-05-07 10:28:02.218 D/APPINFO: getArrayOfChunks Loop, Offset is 3407872 FullImageSize = 3422566 2024-05-07 10:28:02.218 D/APPINFO: getArrayOfChunks Loop, Offset is 3411968 FullImageSize = 3422566 2024-05-07 10:28:02.218 D/APPINFO: getArrayOfChunks Loop, Offset is 3416064 FullImageSize = 3422566 2024-05-07 10:28:02.218 D/APPINFO: getArrayOfChunks Loop, Offset is 3420160 FullImageSize = 3422566 2024-05-07 10:28:03.997 W/CursorWindow: Window is full: requested allocation 4096 bytes, free space 3884 bytes, window size 2097152 bytes 2024-05-07 10:28:04.538 D/APPINFO: Image has 836 chunks of (4096) (note last chunk may be smaller) bytearray size is 3422566

    可以看出,提取的图像长度 3422566 与图像本身的长度相匹配。
  • 我认为,被捕获/捕获的窗口已满是由于 CursorWindow 可以容纳的行数的初始确定造成的。
除了之前在尝试访问 AltImage 表时显示的 Window Full 之外。应用程序检查还显示:-

  • enter image description here
  • 即插入的单个 ImageParent 行
和:-

  • enter image description here
通过:-

  • enter image description here

  • 即有 836 个 4K 块,因此最多为 3,424,256 字节,最后一个为 2046 字节,根据:-

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.