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会自动识别ImageView的实际尺寸,生成适配的缩略图,大幅降低内存占用。Glide.with(mContext) .load(outputFileUri) .fitCenter() // 或centerCrop,根据布局需求选择 .into(holder.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获取,而非每次创建新实例:
ViewModel生命周期长于Fragment,即使Fragment重建,ViewModel中的数据也会保留。val transactionViewModel = ViewModelProvider(this)[TransactionViewModel::class.java]
内容的提问来源于stack exchange,提问作者Rohit Singla
相关产品推荐
相关产品推荐

