TransactionTooLargeException при съемке фотографииAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 TransactionTooLargeException при съемке фотографии

Сообщение Anonymous »

У меня возникла проблема в моем приложении, когда я создал намерение запустить приложение камеры, чтобы сделать снимок, мое приложение аварийно завершает работу, и я получаю следующую ошибку:

Код: Выделить всё

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)
Фотографии после того, как они были сделаны в приложении камеры и возвращены в мое приложение, сохраняются в базе данных комнаты. Интересно, что проблема возникает только в том случае, если в строке базы данных, которую я пытаюсь добавить/заменить фотографию, уже сохранена фотография. При создании новой строки или при съемке фотографии в строке, в которой нет изображения, я могу без проблем сделать фотографию и сохранить ее в своей базе данных.
В базе данных «Моя комната» есть 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")

var entryPictures = entry?.pictures

private val eventChannel = Channel()
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,
@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)

}
}
Класс данных:

Код: Выделить всё

@Entity(tableName = "entry_table")
@Parcelize
data class Entry(
val pictures: Bitmap?,
@PrimaryKey(autoGenerate = true) val id: Int = 0
) : Parcelable
Изменения, которые я внес, чтобы заставить его работать:
В ViewModel изменена функция onPhotoRetieved для преобразования изображения в строку

Код: Выделить всё

    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? вместо растрового изображения? в моем классе данных и закомментировал @TypeConverters в моей базе данных.

Подробнее здесь: https://stackoverflow.com/questions/679 ... king-photo
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»