如何在房间持久性库中插入图像?

51

我正在为我的 Android 应用程序使用 Room 持久性库,现在我需要在我的数据库中插入图像。我已经成功地为原始数据类型定义了 @Entity,并且通过转换器类存储了所有对象、日期和时间。现在我需要存储图像。我不明白如何定义列信息和实体,以及如何插入该数据并从表中读取数据。

单个行插入的数据的最大大小是多少?Android SQLite 中一个字段的最小和最大数据大小是多少?


4
你可以使用"blob"在Room中存储图像。 - Reena
请按照此链接 https://developer.android.com/topic/libraries/architecture/room.html。 - Ankita
@Ankita 在 Room Persistence 中,我们如何在文档中使用它,他们正在忽略 BitMap。 - Prince Kumar
2
@CommonsWare如果您不建议将图像存储在数据库中,您会建议什么?将图像存储在外部存储中? - Aniruddha
1
@Aniruddha:你使用内部存储还是外部存储取决于用户是否需要独立访问这些图片。 - CommonsWare
8
@CommonsWare 如果您能解释为什么不建议将图像存储在数据库中,那么这对学习目的来说是最好的。 - Aniruddha
3个回答

61

通常不建议将图像数据存储到数据库中。但是,如果项目需要,您可以这样做。

图像数据通常使用BLOB数据类型存储到数据库中,Room还提供对BLOB数据类型的支持。文档

您可以按照下面所示声明实体类来存储图像数据。

@Entity(tableName = "test")
public class Test{

@PrimaryKey
@ColumnInfo(name = "_id")
private int id;

@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
private byte[] image;
}

2
谢谢,它正在工作。我需要添加编码器和解码器,将原始图像转换为byte[],并将byte[]转换为原始图像。 - Prince Kumar
1
@PrinceKumar 没错,你需要编码器和解码器来进行位图到byte[]的相互转换。 - Pinakin Kansara
1
@JhonFredyTrujilloOrtega,如果你不想使用blob,那么先压缩你的图像,然后将其转换为“base 64”字符串并存储在数据库中。不建议存储"blob"或"base 64"。最好的方法是将文件存储在本地文件系统中,并在数据库中添加引用条目。具体取决于你的需求。 - Pinakin Kansara
1
似乎这个解决方案并没有真正使用BLOB。这个解决方案让应用程序完全在内存中持有字节数组,而真正的BLOB使用方式(例如在SQLite或MySQL中)会使用InputStream进行写入和OutputStream进行读取。因此,如果字节数组太大,每次只会在内存中保留一部分缓冲区。顺便说一下,BitmapFactory支持使用InputStream工作。ROOM数据库是否缺少这个功能? - Elad Lavi
1
使用(typeAffinity = ColumnInfo.BLOB)有什么优缺点?我已经可以在Room中保存字节数组而不必使用这种类型。 - Fatih
显示剩余2条评论

20

正如Pinakin所提到的,不推荐将图像存储在数据库中,最好使用文件路径,但如果需要存储图像,我建议将图像压缩至2 MB以下(这里有一个示例),以避免应用程序崩溃。Room支持用于图像的BLOB。

Kotlin中的实体类:

ImageTest.kt

 @Entity    
 class ImageTest {

        @PrimaryKey(autoGenerate = true)

        var id: Int = 1

        @ColumnInfo(typeAffinity = ColumnInfo.BLOB)
        var data: ByteArray? = null
      }
 @Dao
 interface ImageTestDao {

       @Insert(onConflict = OnConflictStrategy.REPLACE)
       fun upsertByReplacement(image: List<ImageTest>)

       @Query("SELECT * FROM image")
       fun getAll(): List<ImageTest>

       @Query("SELECT * FROM image WHERE id IN (:arg0)")
       fun findByIds(imageTestIds: List<Int>): List<ImageTest>

       @Delete
       fun delete(imageTest: ImageTest)
   }
 import android.arch.persistence.room.Database
 import android.arch.persistence.room.RoomDatabase
 import android.arch.persistence.room.TypeConverters

   @Database(entities = arrayOf(ImageTest::class), version = 1)
   @TypeConverters(DataConverters::class)
   abstract class Database : RoomDatabase() {
    abstract fun getImageTestDao(): ImageTestDao
   }
    
在DatabaseHelper中,类似于以下内容:

  class DatabaseHelper(context: Context) {

   init {
        DatabaseHelper.context = WeakReference(context)
        }

   companion object {
    
   private var context: WeakReference<Context>? = null
   private const val DATABASE_NAME: String = "image_test_db"
   private var singleton: Database? = null

   private fun createDatabase(): Database {
       return Room.databaseBuilder(context?.get() ?:
               throw IllegalStateException("initialize by calling  
               constructor before calling DatabaseHelper.instance"),
               Database::class.java,
               DATABASE_NAME)
               .build()
   }


   val instance: Database
       @Synchronized get() {
           if (null == singleton)
               singleton = createDatabase()

           return singleton as Database
       }

     fun setImage(img: Bitmap){
     val dao = DatabaseHelper.instance.getImageTestDao() 
     val imageTest = ImageTest()
     imageTest.data = getBytesFromImageMethod(image)//TODO
     dao.updsertByReplacement(imageTest)

     fun getImage():Bitmap?{
     val dao = DatabaseHelper.instance.getImageTestDao() 
     val imageByteArray = dao.getAll()
     return loadImageFromBytes(imageByteArray[0].data)
     //change accordingly 
     }

如果我错了,请纠正我。希望这对某些人有所帮助。


感谢您对每行代码的解释和易于理解的Java项目解决方案。 - Prince Kumar

10

将图像保存为文件并将文件路径Uri保存到Room

如在CameraX图像捕获用例中所示,当成功拍摄照片时,可以安全地检索到文件路径引用Uri savedUri

然后,可以使用savedUri.toString()将Uri转换为字符串,并保存到Room中。

  • 重要的是要确保Room文件引用在文件移动或删除时也得到更新。
  • 在Room中保存的图像字符串可能需要被转换回Uri,以便与Glide等图像库一起显示,方法是使用Uri.parse(someString)

在CameraX示例中,可以在onImageSaved中安全地获取图像路径的Uri。

  • 然后,最好在ViewModel或其他单独处理业务逻辑而不是视图逻辑的位置使用Kotlin Coroutines或RxJava在主线程之外将其保存到Room中。

开始学习CameraX > 5. 实施ImageCapture用例

private fun takePhoto() {
   // Get a stable reference of the modifiable image capture use case
   val imageCapture = imageCapture ?: return

   // Create time-stamped output file to hold the image
   val photoFile = File(
       outputDirectory,
       SimpleDateFormat(FILENAME_FORMAT, Locale.US
       ).format(System.currentTimeMillis()) + ".jpg")

   // Create output options object which contains file + metadata
   val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

   // Set up image capture listener, which is triggered after photo has
   // been taken
   imageCapture.takePicture(
       outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
           override fun onError(exc: ImageCaptureException) {
               Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
           }

           override fun onImageSaved(output: ImageCapture.OutputFileResults) {
               val savedUri = Uri.fromFile(photoFile)
               val msg = "Photo capture succeeded: $savedUri"
               Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
               Log.d(TAG, msg)
           }
       })
}

这种策略在Reddit中的在Room数据库中保存图像中有详细说明。

云存储

创建一个文件来保存图像,并将文件路径保存在Room中,可以覆盖本地存储。为了确保图像能够跨多个设备保存,或者当数据缓存和数据被清除时能够下载并与本地存储同步,需要一种形式的云存储


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接