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

Android应用空闲RAM占用超300MB,修复泄漏后仍异常,LeakCanary报错求解决

问题描述

我发现我的应用内存占用过高,使用Profiler检测后发现,应用刚启动、闪屏页展示时,尚未打开主页面或其他任何Activity,内存占用就已经超过150MB。

更新:我已经修复了所有已知内存泄漏,但在真机上仅浏览带有RecyclerView的MainActivity时,应用RAM占用仍接近300+MB。目前LeakCanary持续报错,提示存在内存泄漏问题,烦请各位帮忙告知解决方法:

  • MainActivity内存状态截图:MainActivity
  • 点击任意RecyclerView条目后的内存状态截图:itemclicked
  • LeakCanary泄漏报告截图:LeakCanary
相关WorkDetailsActivity代码
public class WorkDetailsActivity extends AppCompatActivity {

    ArrayList<String> imagesFromURL = new ArrayList<String>();
    ActivityWorkDetailsBinding binding;
    DatabaseReference databaseReference;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityWorkDetailsBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        String workUid_details = getIntent().getExtras().getString("UID_Details");
        String title = getIntent().getExtras().getString("name");
        String description = getIntent().getExtras().getString("description");
        String location = getIntent().getExtras().getString("location");
        String path = getIntent().getExtras().getString("path");


        databaseReference = FirebaseDatabase.getInstance().getReference().child("Work").child(path);
        databaseReference.child(workUid_details).addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                binding.workDetailsTitle.setText(title);
                binding.workDetailsDescription.setText(description);
                binding.workDetailsLocation.setText(location);
                binding.getUIDDetails.setText(workUid_details);

                for (DataSnapshot dataSnapshot : snapshot.child("images").getChildren()) {
                    String value = String.valueOf(dataSnapshot.child("image").getValue());
                    imagesFromURL.add(value);
                    //Log.i("Value", String.valueOf(imagesFromURL));
                }
                initRecyclerView();
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {
            }
        });
    }

    private void initRecyclerView(){
        binding.workDetailsImage.setNestedScrollingEnabled(false);
        binding.workDetailsImage.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
        binding.workDetailsImage.setHasFixedSize(true);
        PrivateRecyclerAdapter adapter = new PrivateRecyclerAdapter(this, imagesFromURL);
        binding.workDetailsImage.setAdapter(adapter);
        binding.progressBar.setVisibility(View.VISIBLE);

       /* //Add Divider between recyclerView items
        DividerItemDecoration itemDecorator = new DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL);
        binding.workDetailsImage.addItemDecoration(itemDecorator);

        final int radius = getResources().getDimensionPixelSize(R.dimen.radius);
        final int dotsHeight = getResources().getDimensionPixelSize(R.dimen.dots_height);
        final int color = ContextCompat.getColor(this, R.color.green);
        binding.workDetailsImage.addItemDecoration(new DotsIndicatorDecoration(radius, radius * 2, dotsHeight, color, color));
        binding.workDetailsImage.setOnFlingListener(null);
        new PagerSnapHelper().attachToRecyclerView(binding.workDetailsImage);*/
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        imagesFromURL = null;
        binding.workDetailsImage.setAdapter(null);
        binding.workDetailsTitle.setText(null);
        binding.workDetailsLocation.setText(null);
        binding.workDetailsDescription.setText(null);
        binding.getUIDDetails.setText(null);
        binding.workDetailsImage.setAdapter(null);
    }
}
修复方案
  1. 移除Firebase事件监听
    当前代码的核心泄漏点是匿名ValueEventListener没有主动移除,该内部类持有Activity引用,Firebase内部会一直持有监听实例,导致Activity销毁后无法被回收。修改方法:
  • 新增成员变量存储监听实例
private ValueEventListener workValueListener;
  • onCreate中给变量赋值后再添加监听
workValueListener = new ValueEventListener() {
    // 原有onDataChange、onCancelled逻辑保持不变
};
databaseReference.child(workUid_details).addValueEventListener(workValueListener);
  • onDestroy中新增移除监听的逻辑
if (databaseReference != null && workValueListener != null) {
    databaseReference.removeEventListener(workValueListener);
}
  1. 优化图片加载逻辑
    RecyclerView滑动后内存上涨到300MB+基本是图片加载框架的配置问题:
  • 检查图片框架(如Glide、Fresco)的内存缓存配置,建议设置最大缓存为App可用内存的1/5~1/4
  • 在WorkDetailsActivity的onDestroy中添加当前页面图片内存清理逻辑,以Glide为例:
Glide.with(this).clear(binding.workDetailsImage);
  • 列表加载图片时根据控件尺寸做采样压缩,避免加载过大分辨率的图片到内存。
  1. 冗余代码清理
    onDestroy中重复调用了两次binding.workDetailsImage.setAdapter(null),可删除其中一次,无需手动给TextView设置null,仅需清理持有Context的长生命周期引用即可。

  2. 启动内存过高排查
    闪屏页内存超过150MB需排查Application初始化逻辑,检查是否存在不必要的第三方SDK同步初始化、是否内置了过大的资源文件或so库,可通过Profiler的内存分配追踪定位占用内存最高的资源/类进行针对性优化。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 09:18:03