Android内存优化那些事

相信小伙伴们在开发过程中,最经常使用的是LeakCanary开源框架来监控内存泄漏存在的问题点,这样效率比较高,而且迅速发现问题点,其次做相应的优化处理,但LeakCanary毕竟是开源框架,由于特殊原因,可能不允许使用LeakCanary开源框架来分析潜在的内存泄漏。因此,我们经常使用内存分析工具来检测潜在的内存泄漏的场景。
我们采用Memory Profiler和 MAT分析工具来检测应用是否存在内存泄漏。
先来源码:
MemoryMonitorActivity:

public class MemoryMonitorActivity extends BaseActivity{

    @BindView(R.id.btnMemoryThrashing)
    Button btnMemoryThrashing;
    @BindView(R.id.btnHanlder)
    Button btnHanlder;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.memory_monitor_activity);
        ButterKnife.bind(this);
    }
    @OnClick({R.id.btnMemoryThrashing,R.id.btnHanlder})
    public void onViewClicked(View view) {
        switch (view.getId()){
            case R.id.btnMemoryThrashing:
                SingleManager.getInstance(this).get();
                break;
            case R.id.btnHanlder:
               sendMsg();
                break;
        }
    }

    private void sendMsg(){
        mHandler.postDelayed(runnable,5000);
    }
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            Message message = new Message();
            message.what=1;
            message.obj = "子线程发消息";
            mHandler.sendMessage(message);
        }
    };

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
             switch (msg.what){
                 case 1:
                     Toast.makeText(MemoryMonitorActivity.this,"主线程接收: "+msg.obj,Toast.LENGTH_LONG).show();
                     break;
             }
        }
    };
}

SingleManager:

public class SingleManager {

      private static SingleManager instance = null;

      private  Context context = null;

      private SingleManager(Context context){
          this.context = context;
      }
      public static SingleManager getInstance(Context context){
          if (instance==null){
              instance = new SingleManager(context);
          }
          return instance;
      }

      public void get(){
          LogUtils.e(" logcat context:  "+ context );
      }
}

从源码可以看出,单例模式、Handler、匿名内部类引起的内存泄漏的例子,这次先不说引起内存泄漏的原因,使用Memory Profiler工具来如何进行分析。


图片1.png

从上图可以看出,MemoryMonitorActivity界面已经退出或者销毁了,但MemoryMonitorActivity实例仍然存在,而且它实例对象本身大小为0.69K,MemoryMonitorActivity包括引用大小为142.7K,这就是典型了activity的内存泄漏,存在内存浪费。但通过上面,我们还是无法定位造成activity的内存泄漏真正的原因是什么呢。接下来继续看下图:


图片2.png

图片3.png

通过上面可以看出:

1.Context持有外部MemoryMonitorActivity引用,导致GC无法回收。
2.runnable持有外部MemoryMonitorActivity引用,从源码可以看出runnable是一个内部类,持有外部引用MemoryMonitorActivity的类,导致GC不能回收。
3.Handler持有外部MemoryMonitorActivity引用,导致GC无法回收。
以上三点,activity存在内存泄漏真正原因就是持有引用,GC没有及时回收,存在内存浪费。
那接下来怎么优化它呢,别着急。我们知道如何使用Memory Profiler来检测应用是否存在内存泄漏。接下来我们使用MAT来分析下内存占用情况。


图片4.png

我们点击Top Consumers看看,如下:
图片5.png

图片6.png

图片7.png

从上图可以看出,由于MemoryMonitorActivity界面已经销毁了,仍然存在很多实例,因此它已经存在内存泄露了。我们需要熟悉Memory Profiler和 MAT工具的使用,可以进行综合分析来定位内存泄露的真正原因。
接下来我们就需要优化代码了。
优化后源码如下:

MemoryMonitorActivity:

public class MemoryMonitorActivity extends BaseActivity{

    @BindView(R.id.btnMemoryThrashing)
    Button btnMemoryThrashing;
    @BindView(R.id.btnHanlder)
    Button btnHanlder;

    private MyHandler myHandler;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.memory_monitor_activity);
        ButterKnife.bind(this);
        myHandler = new MyHandler(this);
    }
    @OnClick({R.id.btnMemoryThrashing,R.id.btnHanlder})
    public void onViewClicked(View view) {
        switch (view.getId()){
            case R.id.btnMemoryThrashing:
                SingleManager.getInstance(this).get();
                break;
            case R.id.btnHanlder:
                sendMsg();
                break;
        }
    }

    private void sendMsg(){
        myHandler.postDelayed(runnable,5000);
    }

    /**
     * 匿名内部类持有外部类,需要实例化一个Runnable对象,在onDestroy方法runnable设置null即可
     */
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            Message message = new Message();
            message.what=1;
            message.obj = "子线程发消息";
            myHandler.sendMessage(message);
        }
    };

    /**
     * 使用静态handler的弱引用方式来避免内存泄露
     */
    private static class MyHandler extends Handler{
        private final WeakReference<MemoryMonitorActivity> memoryMonitorActivity;
        public MyHandler(MemoryMonitorActivity activity){
            memoryMonitorActivity = new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            MemoryMonitorActivity activity = memoryMonitorActivity.get();
            super.handleMessage(msg);
            if (activity==null)return;
            switch (msg.what){
                case 1:
                    Toast.makeText(activity,"主线程接收: "+msg.obj,Toast.LENGTH_LONG).show();
                    break;
            }
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (runnable!=null){
            runnable=null;
        }
        if (myHandler!=null){
            myHandler.removeCallbacksAndMessages(null);
            myHandler=null;
        }
    }
}

SingleManager:

public class SingleManager {

      private static SingleManager instance = null;

      private  Context context = null;

      private SingleManager(Context context){
          this.context = context;
      }
      public static SingleManager getInstance(Context context){
          if (instance==null){
              // FIXME: 2021/10/30 使用context.getApplicationContext()和app生命周期一样长的进行避免内存泄露
              instance = new SingleManager(context.getApplicationContext());
          }
          return instance;
      }

      public void get(){
          LogUtils.e(" logcat context:  "+ context );
      }
}
图片8.png

图片9.png

经过代码优化,通过Memory Profiler和 MAT工具分析结果,MemoryMonitorActivity不存在内存泄露了。
作为Android开发者,我们需要必备这个基本技能,需要熟悉Memory Profiler和 MAT内存工具的使用,开发过程中会经常使用到的。搜索并关注公众号“Android技术迷”,可查看更多的相关文章。感谢各位关注。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android开发过程当中,软件卡顿、软件黑屏退出等等现象都跟内存相关,安卓软件与ios软件体验同样是流畅度差距很...
    王冥阅读 534评论 0 0
  • 前言 成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。 本篇是...
    zhx喜籽阅读 936评论 0 4
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,144评论 0 4
  • 公元:2019年11月28日19时42分农历:二零一九年 十一月 初三日 戌时干支:己亥乙亥己巳甲戌当月节气:立冬...
    石放阅读 6,953评论 0 2
  • 今天上午陪老妈看病,下午健身房跑步,晚上想想今天还没有断舍离,马上做,衣架和旁边的的布衣架,一看乱乱,又想想自己是...
    影子3623253阅读 2,956评论 3 8