当我创建一个启动相机应用程序来拍照时,我的应用程序出了问题,我的应用程序崩溃了,我收到了以下错误:
2021-06-11 18:07:46.914 7506-7506/com.package.app E/JavaBinder: !!! FAILED BINDER TRANSACTION !!! (parcel size = 14763232)
...
2021-06-11 18:07:49.567 7506-7506/com.package.app E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.package.app, PID: 7506
java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 14763232 bytes
at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:161)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: android.os.TransactionTooLargeException: data parcel size 14763232 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:510)
at android.app.IActivityTaskManager$Stub$Proxy.activityStopped(IActivityTaskManager.java:4524)
at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:145)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
这些照片,在相机应用程序中拍摄并返回到我的应用程序后,保存在一个房间数据库中。有趣的是,只有在我试图添加/替换照片的数据库行中已经保存了一张照片时,问题才会发生。在创建新行或在没有图片的行中拍照时,我可以将照片保存到我的数据库中,没有任何问题。
My数据库有一个TypeConverter,它将位图转换为base64字符串,以便存储在数据库中,并在需要查看时返回到位图。在播放了一段时间的代码之后,我尝试将转换器从数据库中移除,并将其函数实现到我的视图模型和片段中。无论图片是否被替换,该应用程序现在都能正常工作。
我现在怀疑我实现转换器的方式出了什么问题,但我不确定它会是什么。请看下面的代码。
片段
lateinit var currentPhotoPath: String
@AndroidEntryPoint
class Fragment : Fragment(R.layout.fragment) {
private val viewModel: ViewModel by viewModels()
private var _binding: FragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply {
ivPicture.setImageBitmap(viewModel.entryPictures)
fab.setOnClickListener {
viewModel.onSaveClick()
}
}
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.event.collect { event ->
when (event) {
ViewModel.Event.NavigateToPhotoActivity -> {
dispatchTakePictureIntent()
}
}
}
}
setHasOptionsMenu(true)
}
private val REQUEST_IMAGE_CAPTURE = 23
private fun dispatchTakePictureIntent() {
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
val packageManager = requireContext().packageManager
takePictureIntent.resolveActivity(packageManager)?.also {
val photoFile: File? = try {
createImageFile()
} catch (ex: IOException) {
Toast.makeText(activity, "Error Creating File", Toast.LENGTH_LONG).show()
null
}
photoFile?.also {
val photoURI: Uri = FileProvider.getUriForFile(
requireContext(),
"com.package.app.fileprovider",
it
)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
requireActivity().startActivityFromFragment(this, takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
}
}
@Throws(IOException::class)
private fun createImageFile(): File {
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val storageDir: File? = context?.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${timeStamp}_", /* prefix */
".jpg", /* suffix */
storageDir /* directory */
).apply {
// Save a file: path for use with ACTION_VIEW intents
currentPhotoPath = absolutePath
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
lifecycleScope.launch {
val takenImage = BitmapFactory.decodeFile(currentPhotoPath)
viewModel.onPhotoRetrieved(takenImage)
binding.ivPicture.apply {
visibility = View.VISIBLE
setImageBitmap(takenImage)
}
}
} else {
Toast.makeText(activity, "Error Retrieving Image", Toast.LENGTH_LONG).show()
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_fragment, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.icon_photo -> {
viewModel.onTakePhotoSelected()
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
ViewModel:
@HiltViewModel
class ViewModel @Inject constructor(
private val dao: EntryDao,
private val state: SavedStateHandle
) : ViewModel() {
val entry = state.get<Entry>("entry")
var entryPictures = entry?.pictures
private val eventChannel = Channel<Event>()
val event = eventChannel.receiveAsFlow()
fun onSaveClick() {
if (entry != null) {
val updatedEntry = entry.copy(
pictures = entryPictures
)
updatedEntry(updatedEntry)
} else {
val newEntry = Entry(
pictures = entryPictures
)
createEntry(newEntry)
}
}
private fun createEntry(entry: Entry) = viewModelScope.launch {
dao.insert(entry)
}
private fun updatedEntry(entry: Entry) = viewModelScope.launch {
dao.update(entry)
}
fun onTakePhotoSelected() = viewModelScope.launch {
eventChannel.send(Event.NavigateToPhotoActivity)
}
fun onPhotoRetrieved(bitmap: Bitmap) = viewModelScope.launch {
entryPictures = bitmap
}
sealed class Event {
object NavigateToPhotoActivity : Event()
}
}
数据库:
@Database(entities = [Entry::class], version = 1)
@TypeConverters(Converters::class)
abstract class Database : RoomDatabase() {
abstract fun entryDao(): EntryDao
class Callback @Inject constructor(
private val database: Provider<com.mayuram.ascend.data.Database>,
@ApplicationScope private val applicationScope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val dao = database.get().entryDao()
applicationScope.launch {
dao.insert(Entry(null))
dao.insert(Entry(null))
dao.insert(Entry(null))
dao.insert(Entry(null))
}
}
}
}
转换器
class Converters {
@Suppress("DEPRECATION")
@TypeConverter
fun bitmapToString(bitmap: Bitmap?): String {
val outputStream = ByteArrayOutputStream()
if (android.os.Build.VERSION.SDK_INT >= 30) {
bitmap?.compress(Bitmap.CompressFormat.WEBP_LOSSY, 50, outputStream)
} else {
bitmap?.compress(Bitmap.CompressFormat.WEBP, 50, outputStream)
}
val imageBytes: ByteArray = outputStream.toByteArray()
return Base64.encodeToString(imageBytes, Base64.DEFAULT)
}
@TypeConverter
fun stringToBitmap(string: String): Bitmap? {
val imageBytes: ByteArray = Base64.decode(string, Base64.DEFAULT)
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
}
}
DataClass:
@Entity(tableName = "entry_table")
@Parcelize
data class Entry(
val pictures: Bitmap?,
@PrimaryKey(autoGenerate = true) val id: Int = 0
) : Parcelable
为了让它发挥作用,我做了一些改变:
在ViewModel中,修改后的onPhotoRetrieved函数将图像转换为字符串
fun onPhotoRetrieved(bitmap: Bitmap) = viewModelScope.launch {
val outputStream = ByteArrayOutputStream()
if (android.os.Build.VERSION.SDK_INT >= 30) {
bitmap.compress(Bitmap.CompressFormat.WEBP_LOSSY, 50, outputStream)
} else {
bitmap.compress(Bitmap.CompressFormat.WEBP, 50, outputStream)
}
val imageBytes: ByteArray = outputStream.toByteArray()
val result = Base64.encodeToString(imageBytes, Base64.DEFAULT)
entryPictures = result
}
在片段中,将转换字符串添加到onViewCreated中的位图函数中。
val imageBytes: ByteArray = Base64.decode(viewModel.entryPictures.toString(), Base64.DEFAULT)
val result = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
ivPicture.setImageBitmap(result)
还将val图片的类型更改为String?而不是位图?在我的数据类中,并在我的数据库中注释掉@Type转换器。
发布于 2021-06-13 03:55:52
Fix
简而言之,您需要重新考虑在数据库中保存图像,并考虑将图像的路径(或其适当的部分,以唯一标识图像)存储在适当的位置上。
另一种选择,但可能仍然相当昂贵的资源明智。可以考虑存储可管理的图像块(可能考虑100 k块)。例如如何在安卓SQLite中使用比CursorWindow的限制更大的图像?
另一种选择是在数据库中存储较小的图像(如果有人认为它们是照片),但将较大的图像存储为路径。例如如何在sqlite数据库中插入图像?
The Issue
您所遇到的是与图像大小有关的其他问题的先驱(双关意:)。
也就是说,正如TransactionTooLargeException解释的那样,您已经超过了包裹的1 1Mb限制,其中包括:-
Binder事务缓冲区有一个有限的固定大小,目前为1MB,它由进程的所有正在进行中的事务共享。因此,即使大多数单个事务的大小适中,也可以在有许多正在进行的事务时抛出此异常。
您的包裹(图像)似乎是14763232,即14 be。
即使你增加了包裹的大小,你也可以点击安卓的SQLite实现,因此房间问题有光标大小的限制,或者游标中减少的行数效率低下。
在创建新行或在没有图片的行中拍照时,我可以将照片保存到我的数据库中,没有任何问题。
当插入时,限制并不存在,因为您是在个人基础上插入的。当提取数据时(通常/通常),在单个请求中提取数据组并使用中间缓冲区(即游标是缓冲区)时,限制。
https://stackoverflow.com/questions/67952311
复制相似问题