首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >拍照时的TransactionTooLargeException

拍照时的TransactionTooLargeException
EN

Stack Overflow用户
提问于 2021-06-12 19:22:51
回答 1查看 2K关注 0票数 1

当我创建一个启动相机应用程序来拍照时,我的应用程序出了问题,我的应用程序崩溃了,我收到了以下错误:

代码语言:javascript
运行
复制
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字符串,以便存储在数据库中,并在需要查看时返回到位图。在播放了一段时间的代码之后,我尝试将转换器从数据库中移除,并将其函数实现到我的视图模型和片段中。无论图片是否被替换,该应用程序现在都能正常工作。

我现在怀疑我实现转换器的方式出了什么问题,但我不确定它会是什么。请看下面的代码。

片段

代码语言:javascript
运行
复制
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:

代码语言:javascript
运行
复制
@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()
    }
}

数据库:

代码语言:javascript
运行
复制
@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))
            }
        }
    }
}

转换器

代码语言:javascript
运行
复制
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:

代码语言:javascript
运行
复制
@Entity(tableName = "entry_table")
@Parcelize
data class Entry(
    val pictures: Bitmap?,
    @PrimaryKey(autoGenerate = true) val id: Int = 0
) : Parcelable

为了让它发挥作用,我做了一些改变:

在ViewModel中,修改后的onPhotoRetrieved函数将图像转换为字符串

代码语言:javascript
运行
复制
    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中的位图函数中。

代码语言:javascript
运行
复制
val imageBytes: ByteArray = Base64.decode(viewModel.entryPictures.toString(), Base64.DEFAULT)
val result = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
ivPicture.setImageBitmap(result)

还将val图片的类型更改为String?而不是位图?在我的数据类中,并在我的数据库中注释掉@Type转换器。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-06-13 03:55:52

Fix

简而言之,您需要重新考虑在数据库中保存图像,并考虑将图像的路径(或其适当的部分,以唯一标识图像)存储在适当的位置上。

另一种选择,但可能仍然相当昂贵的资源明智。可以考虑存储可管理的图像块(可能考虑100 k块)。例如如何在安卓SQLite中使用比CursorWindow的限制更大的图像?

另一种选择是在数据库中存储较小的图像(如果有人认为它们是照片),但将较大的图像存储为路径。例如如何在sqlite数据库中插入图像?

The Issue

您所遇到的是与图像大小有关的其他问题的先驱(双关意:)。

也就是说,正如TransactionTooLargeException解释的那样,您已经超过了包裹的1 1Mb限制,其中包括:-

Binder事务缓冲区有一个有限的固定大小,目前为1MB,它由进程的所有正在进行中的事务共享。因此,即使大多数单个事务的大小适中,也可以在有许多正在进行的事务时抛出此异常。

您的包裹(图像)似乎是14763232,即14 be。

即使你增加了包裹的大小,你也可以点击安卓的SQLite实现,因此房间问题有光标大小的限制,或者游标中减少的行数效率低下。

在创建新行或在没有图片的行中拍照时,我可以将照片保存到我的数据库中,没有任何问题。

当插入时,限制并不存在,因为您是在个人基础上插入的。当提取数据时(通常/通常),在单个请求中提取数据组并使用中间缓冲区(即游标是缓冲区)时,限制。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67952311

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档