You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android相机拍照RecyclerView展示:主线程负载过高及应用崩溃问题

问题描述

1. 核心异常

  • RecyclerView展示拍摄图片时频繁卡顿,日志输出:

    06-08 17:22:44.318 14283 14283 I Choreographer: Skipped 40 frames! The application may be doing too much work on its main thread.

  • 前台启动相机拍照时,应用有30%概率被系统杀死并自动重建,已拍摄的图片路径丢失。

2. 相关代码

SampleFragment.kt 拍照逻辑

fun onBtnTakePicture() {
        val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        outputFileName = "${System.currentTimeMillis()}.jpg"
        val photo = File(context!!.filesDir, outputFileName)
        val outputFileUri = FileProvider.getUriForFile(context!!, AUTHORITY_APP_FILES,photo)
        cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri)
        cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
        startActivityForResult(cameraIntent, CAMERA_REQUEST_CODE)
    }

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if(resultCode == AppCompatActivity.RESULT_OK){
            if(requestCode == CAMERA_REQUEST_CODE){
                    //To add to db, we need to add this in view model
                    transactionViewModel.addImagePathInList(outputFileName)

                    //to show in the UI, we need to add it into this list
                    myCardsList.add(FeatureCarouselCardData(outputFileName))

                    featureCardsView.notifyAdapaterAboutDataSetChange()
            }
        }
    }

private lateinit var featureCardsView: FeatureCarouselView

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
..........
..........
featureCardsView = binding.featureCardsView
featureCardsView.showFeatureCarouselCards(myCardsList)
..........
..........
}

FeatureCarouselView.kt 创建Adapter代码

fun showFeatureCarouselCards(featureCarouselCards: List<FeatureCarouselCardData>){
        this.featureCarouselCards = featureCarouselCards

        featureCarouselAdapter = FeatureCarouselAdapter(context, featureCarouselCards)
        adapter = featureCarouselAdapter

    }

FeatureCarouselAdapter.kt onBindViewHolder方法

override fun onBindViewHolder(holder: FeatureCarouselViewHolder, position: Int) {
        val currentItem = featureCarouselCards[position]
        var bitmap = mContext.resources.getDrawable(R.drawable.ic_file_not_found).toBitmap()
        Log.d(LOG_TAG, "currentItem in onBindViewHolder : "+currentItem)
        try {
            Log.d(LOG_TAG, "try")
            val photo = File(mContext.filesDir, currentItem.fileName)
            val outputFileUri = FileProvider.getUriForFile(mContext, AUTHORITY_APP_FILES,photo)
            bitmap = MediaStore.Images.Media.getBitmap(mContext.contentResolver, outputFileUri)
        }catch (e: FileNotFoundException) {
            Log.d(LOG_TAG, "catch filenotfound")
            e.printStackTrace()
        } catch (e: IOException) {
            Log.d(LOG_TAG, "catch ioexception")
            e.printStackTrace()
        }
        holder.imageView.setImageBitmap(bitmap)
    }

3. 疑问与尝试

  • 误以为Glide/Picasso必须依赖INTERNET权限,想保持应用离线状态,询问替代解决方案。
  • 更新:尝试用Glide加载本地图片,但卡顿问题依旧,代码如下:
    Glide.with(mContext)
                  .load(outputFileUri)
                  .into(holder.imageView)
    
    希望排查根本原因。

解决方案与问题分析

一、主线程卡顿问题

1. 原代码致命问题

onBindViewHolder中直接在主线程调用MediaStore.Images.Media.getBitmap()加载6-7MB的原图,会阻塞UI线程;RecyclerView滚动时反复触发该方法,累积耗时直接导致帧丢失。

2. 离线使用Glide的可行性纠正

Glide/Picasso不需要强制申请INTERNET权限,它们原生支持本地文件、Uri加载,只要不调用网络加载接口,完全可以离线使用,你的之前的误解不成立。

3. 用Glide仍卡顿的核心原因

  • 未做图片缩放裁剪:直接加载6-7MB原图会占用大量内存,即使Glide在后台加载,过高的内存占用会触发频繁GC,间接导致卡顿。必须根据ImageView尺寸缩放图片:
    Glide.with(mContext)
          .load(outputFileUri)
          .fitCenter() // 或centerCrop,根据布局需求选择
          .into(holder.imageView)
    
    Glide会自动识别ImageView的实际尺寸,生成适配的缩略图,大幅降低内存占用。
  • Adapter刷新方式低效:每次新增图片调用notifyDataSetChanged()会强制RecyclerView重绑所有Item,应替换为notifyItemInserted(myCardsList.size - 1),仅刷新新增的单个Item。
  • 未开启ViewHolder复用优化:在Adapter中设置setHasStableIds(true),并重写getItemId()返回唯一值,帮助RecyclerView更高效地复用ViewHolder。

二、应用被杀死重建丢数据问题

1. 核心原因

启动相机Activity时,系统可能因内存紧张杀死你的应用进程;如果未持久化图片路径数据,Fragment重建后内存中的数据会全部丢失。

2. 解决办法

  • 用Bundle保存临时状态:在Fragment的onSaveInstanceState中保存图片路径列表,重建时恢复:
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putStringArrayList("savedImagePaths", ArrayList(myCardsList.map { it.fileName }))
    }
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // ...
        savedInstanceState?.let {
            val savedPaths = it.getStringArrayList("savedImagePaths")
            savedPaths?.forEach { path ->
                myCardsList.add(FeatureCarouselCardData(path))
            }
            featureCardsView.notifyAdapaterAboutDataSetChange()
        }
        // ...
    }
    
  • 持久化数据到本地:在onActivityResult中,除了更新内存列表,立刻将图片路径写入数据库或SharedPreferences,确保数据永久保存,重建后可从本地读取。
  • 正确使用ViewModel:确保transactionViewModel通过ViewModelProvider获取,而非每次创建新实例:
    val transactionViewModel = ViewModelProvider(this)[TransactionViewModel::class.java]
    
    ViewModel生命周期长于Fragment,即使Fragment重建,ViewModel中的数据也会保留。

内容的提问来源于stack exchange,提问作者Rohit Singla

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 22:54:58