You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android关闭Fragment时后台线程引发内存溢出的修复方案

解决Fragment中AsyncTask后台加载视频时关闭页面引发的内存溢出与空指针问题

首先看你的报错信息,核心问题有两个:Fragment销毁后AsyncTask仍在执行导致的空指针,以及一次性加载大量视频缩略图引发的内存溢出。咱们一步步来解决:

一、先解决空指针问题(NullPointerException)

报错里的ContextCompat.checkSelfPermission调用时getActivity()返回null,因为你在Fragment已经关闭(脱离Activity)后,AsyncTask的onPostExecute还在执行,此时Fragment的上下文已经失效了。

解决方案:

  1. 给AsyncTask添加弱引用,避免持有Fragment强引用
    把AsyncTask改成持有Fragment的WeakReference,这样Fragment销毁时不会被AsyncTask卡住导致内存泄漏,同时能在执行回调时判断Fragment是否存活。
  2. 在Fragment销毁时取消AsyncTask
    在Fragment的生命周期方法里取消正在运行的AsyncTask,防止后台任务继续执行。
  3. 在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。

解决方案:

  1. 不要提前加载所有缩略图,改为在Adapter中按需加载
    只在RecyclerView的Item显示时才加载对应视频的缩略图,使用成熟的图片加载库(比如Glide)来处理Bitmap的缓存和回收,避免手动管理内存出错。
  2. 优化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

火山引擎 最新活动