如何在两个片段之间传递位图? (我正在使用 Android 导航组件)

Posted

技术标签:

【中文标题】如何在两个片段之间传递位图? (我正在使用 Android 导航组件)【英文标题】:How to pass Bitmap between two fragment ? ( I am using Android Navigation Component ) 【发布时间】:2022-01-02 20:28:23 【问题描述】:

我正在构建一个照片编辑器。对于绘画、调整、裁剪等,我创建了片段。我面临传递位图和 Uri 我面临下面描述的问题。

首先,我应用了两个方法一个

    首先我传递了一个对象,它是CommonParcelData

@Parcelize data class CommonParcelData( val uri:Uri?=null,val bitmap: Bitmap?=null, val availableData: ActiveNavArgsData, var isResize:Boolean=false ): Parcelable

problem 是当我使用 CommonParcelData 数据类传递数据时,我会显示 errorjava.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 17488344 bytesJavaBinder: !!! FAILED BINDER TRANSACTION !!! 因为我传递了位图对象。然后我选择我的第二种方法。

    第二种方法是我将 edited bitmap 和 android 转换为 uri 并保存在缓存目录中并将 uri 发送到目的地/其他片段,但 这个方法的问题是片段转换非常慢。为了简化,我创建了一个转换将位图保存到存储中并获取 uri 该类是 SaveBitmapInStorage

    类 SaveBitmapInStorage(private val context: Context)

     companion object 
         private val TAG = SaveBitmapInStorage::class.java.simpleName
     
    
     var compressionQuality = 100
         set(value) 
             if (value in 0..100) 
                 field = value
              else 
                 throw Exception("compressionQuality value must be between 0 to 100")
             
         
    
     private var imgExtension = ImgExtension.PNG.name.lowercase()
    
     fun changeImgExtension(imgType: ImgExtension) 
         imgExtension = imgType.name.lowercase()
     
    
     private val dirName = context.getString(R.string.app_name)
    
     private val appName = dirName
    
    
    
     private val folder =
         File(context.getExternalFilesDir(null)?.parentFile?.parentFile?.parentFile?.parentFile?.path + File.separator + appName)
    
    
     /** It must be execute on background thread */
     fun save(bitmap: Bitmap, isSaveInCacheDir: Boolean = false): Uri 
         if (!folder.isDirectory) 
             folder.mkdir()
             Log.d(TAG, " file created :-> $folder")
         
    
    
         val imgFile = if (isSaveInCacheDir) File(context.cacheDir, getCacheImgFile)
         else File(folder, "$appName$System.currentTimeMillis().$imgExtension")
    
    
         try 
    
             val fileOutputStream = FileOutputStream(imgFile)
    
             checkImgFormat(bitmap, fileOutputStream)
    
             fileOutputStream.apply 
                 flush();
                 close()
             
    
    
          catch (e: IOException) 
             e.printStackTrace();
    
          finally 
    
         
    
         return Uri.fromFile(imgFile)
    
    
     
    
    
     private fun checkImgFormat(bitmap: Bitmap, fos: FileOutputStream) 
         when (imgExtension) 
             ImgExtension.PNG.name.lowercase() -> 
                 bitmap.compress(Bitmap.CompressFormat.PNG, compressionQuality, fos)
             
    
             ImgExtension.JPG.name.lowercase() -> 
                 bitmap.compress(Bitmap.CompressFormat.JPEG, compressionQuality, fos)
             
    
    
         
    
     
    
    
     private val getCacheImgFile = "$context.getString(R.string.cache_img_name).$imgExtension"
    
     /** This should be in background thread ... */
     fun savePhotoInCacheDir(bitmap: Bitmap): Uri 
    
         return save(bitmap, true)
    
     
    

在 ViewModel 中

fun savePhotoInCacheStorage(result: (resultUri: Uri) -> Unit) 
        CoroutineScope(Dispatchers.IO).launch 
            val uri = saveBitmapInStorage.savePhotoInCacheDir(_imgSrc.value!!)
            withContext(Dispatchers.Main) 
                result(uri)
            
        

    

在 UI 中意味着在 Fragment 中

binding.editorIncludeId.crop.setOnClickListener 

            mainEditViewModel.savePhotoInCacheStorage 
                val data =CommonParcelData(it,availableData = ActiveNavArgsData.URI)
                val action = MainEditScreenFragmentDirections
                    .mainEditScreenFragmentToCropFragment(data)

                findNavController().navigate(action,getSharedElementExtra())
            


        

【问题讨论】:

【参考方案1】:

你也可以用这种方式传递Bitmap。首先将Bitmap转换为Byte数组,然后将该Byte数组编码为String 像这样,

 val outputStream = ByteArrayOutputStream()
    bmp.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
    val b = outputStream.toByteArray()
val encodedBitmap = Base64.encodeToString(b, Base64.DEFAULT)

然后你可以将它作为 Intent extra 传递。

要将字符串解码回位图,您可以这样做,

val b = Base64.decode(previouslyEncodedImage, Base64.DEFAULT)
val bitmap =  BitmapFactory.decodeByteArray(b, 0, b.length)

previousEncodedImage 是您将从 Intent 中检索到的字符串值。

【讨论】:

是的,它有效。但是我的问题没有解决,因为我在第二个方法中描述了这意味着它将解决JavaBinder: !!! FAILED BINDER TRANSACTION !!! 问题,但两个片段之间的事务时间很慢。

以上是关于如何在两个片段之间传递位图? (我正在使用 Android 导航组件)的主要内容,如果未能解决你的问题,请参考以下文章

kotlin:如何在两个片段之间传递数据

如何在活动和浏览器片段之间传递值? [复制]

如何在 Fragment 之间传递值

在两个以上片段之间传递数据

如何在导航抽屉活动模板中的片段之间传递字符串变量

如何在android中使用imageloader释放位图内存?