Android关闭Fragment时后台线程引发内存溢出的修复方案
解决Fragment中AsyncTask后台加载视频时关闭页面引发的内存溢出与空指针问题
首先看你的报错信息,核心问题有两个:Fragment销毁后AsyncTask仍在执行导致的空指针,以及一次性加载大量视频缩略图引发的内存溢出。咱们一步步来解决:
一、先解决空指针问题(NullPointerException)
报错里的ContextCompat.checkSelfPermission调用时getActivity()返回null,因为你在Fragment已经关闭(脱离Activity)后,AsyncTask的onPostExecute还在执行,此时Fragment的上下文已经失效了。
解决方案:
- 给AsyncTask添加弱引用,避免持有Fragment强引用
把AsyncTask改成持有Fragment的WeakReference,这样Fragment销毁时不会被AsyncTask卡住导致内存泄漏,同时能在执行回调时判断Fragment是否存活。 - 在Fragment销毁时取消AsyncTask
在Fragment的生命周期方法里取消正在运行的AsyncTask,防止后台任务继续执行。 - 在onPostExecute中先判断Fragment状态
执行UI操作前,先检查Fragment是否还附着在Activity上。
修改后的PlayerFragment相关代码:
public class PlayerFragment extends Fragment { // 把AsyncTask声明为成员变量,方便后续取消 private LoadVideoData loadVideoTask; // ...其他成员变量 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // ...原有代码 loadVideoTask = new LoadVideoData(this); // 传入Fragment的弱引用 loadVideoTask.execute(); return view; } @Override public void onDestroy() { super.onDestroy(); // 取消AsyncTask,避免后台任务继续执行 if (loadVideoTask != null && !loadVideoTask.isCancelled()) { loadVideoTask.cancel(true); } // 清空数据,释放内存 if (videoData != null) { for (VideoModel model : videoData) { if (model.getImageBitmap() != null) { model.getImageBitmap().recycle(); } } videoData.clear(); } } // 修改AsyncTask,使用WeakReference public class LoadVideoData extends AsyncTask<Void, Void, ArrayList<VideoModel>> { private final WeakReference<PlayerFragment> fragmentRef; public LoadVideoData(PlayerFragment fragment) { fragmentRef = new WeakReference<>(fragment); } @Override protected void onPreExecute() { PlayerFragment fragment = fragmentRef.get(); if (fragment == null || !fragment.isAdded()) { return; } fragment.progressDialog.show(); } @Override protected ArrayList<VideoModel> doInBackground(Void... voids) { // 检查任务是否被取消,如果取消就停止执行 if (isCancelled()) { return null; } PlayerFragment fragment = fragmentRef.get(); if (fragment == null) { return null; } return fragment.dataForAdapter.getVideoData(); } @Override protected void onPostExecute(ArrayList<VideoModel> videoModels) { PlayerFragment fragment = fragmentRef.get(); // 检查Fragment是否存活,任务是否被取消 if (fragment == null || fragment.isDetached() || isCancelled()) { return; } if (fragment.progressDialog.isShowing()) { fragment.progressDialog.dismiss(); } if (videoModels == null || videoModels.isEmpty()){ fragment.noVideoText.setVisibility(View.VISIBLE); } else { fragment.setRecyclerView(videoModels); fragment.noVideoText.setVisibility(View.INVISIBLE); } } } // 修改setRecyclerView方法,先检查上下文是否有效 private void setRecyclerView(ArrayList<VideoModel> videoModels) { Context context = getContext(); if (context == null) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 1); } else { recyclerViewPart(videoModels); } } else { recyclerViewPart(videoModels); } } }
二、解决内存溢出(Out Of Memory Error)问题
你的GetDataForAdapter里在后台线程同步加载所有视频的缩略图,每个Bitmap都会占用大量内存,一次性加载几十上百个很容易触发OOM。
解决方案:
- 不要提前加载所有缩略图,改为在Adapter中按需加载
只在RecyclerView的Item显示时才加载对应视频的缩略图,使用成熟的图片加载库(比如Glide)来处理Bitmap的缓存和回收,避免手动管理内存出错。 - 优化Cursor的资源释放
把cursor.close()放在finally块里,确保即使发生异常也能关闭Cursor,避免资源泄漏。
修改后的GetDataForAdapter代码:
public class GetDataForAdapter { private Context context; private ContentResolver contentResolver; public GetDataForAdapter(Context context) { this.context = context; contentResolver = context.getContentResolver(); } public ArrayList<VideoModel> getVideoData() { ArrayList<VideoModel> models = new ArrayList<>(); Cursor cursor = null; try { String[] projection = {MediaStore.Video.Media._ID, MediaStore.Video.Media.SIZE, MediaStore.Video.Media.DATA, MediaStore.Video.Media.DISPLAY_NAME}; cursor = contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projection, null, null, MediaStore.Video.Media.DATE_TAKEN); if (cursor != null && cursor.moveToFirst()) { int id = cursor.getColumnIndex(MediaStore.Video.Media._ID); int name = cursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME); int data = cursor.getColumnIndex(MediaStore.Video.Media.DATA); do { VideoModel model = new VideoModel(); model.setName(cursor.getString(name)); model.setUrl(cursor.getString(data)); model.setId(cursor.getInt(id)); // 这里不再加载Bitmap,只存视频的ID和路径 models.add(model); Log.e("video file name", cursor.getString(data)); } while (cursor.moveToNext() && !Thread.currentThread().isInterrupted()); // 检查线程是否被中断 } else { Log.e("No media file", "Present in the selected directory"); } } catch (Exception e) { e.printStackTrace(); } finally { // 确保Cursor被关闭 if (cursor != null) { cursor.close(); } } return models; } }
然后修改VideoFilesAdapters,在ViewHolder中用Glide加载视频缩略图:
// 在Adapter的onBindViewHolder方法中 @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { VideoModel model = videoModels.get(position); holder.videoName.setText(model.getName()); // 用Glide加载视频缩略图 Glide.with(context) .asBitmap() .load(Uri.fromFile(new File(model.getUrl()))) .placeholder(R.drawable.default_thumbnail) // 设置占位图 .into(holder.videoThumbnail); }
额外优化建议
- 替换AsyncTask:AsyncTask在Android 11及以上已经被废弃,建议改用
Coroutine或者WorkManager来处理后台任务,更灵活且避免AsyncTask的内存泄漏问题。 - 权限处理优化:在Fragment启动前先检查权限,避免在AsyncTask完成后再请求权限,提升用户体验。
内容的提问来源于stack exchange,提问作者Kisan Thapa




