Android內(nèi)存優(yōu)化那些事

相信小伙伴們在開發(fā)過程中,最經(jīng)常使用的是LeakCanary開源框架來監(jiān)控內(nèi)存泄漏存在的問題點,這樣效率比較高,而且迅速發(fā)現(xiàn)問題點,其次做相應的優(yōu)化處理,但LeakCanary畢竟是開源框架,由于特殊原因,可能不允許使用LeakCanary開源框架來分析潛在的內(nèi)存泄漏。因此,我們經(jīng)常使用內(nèi)存分析工具來檢測潛在的內(nèi)存泄漏的場景。
我們采用Memory Profiler和 MAT分析工具來檢測應用是否存在內(nèi)存泄漏。
先來源碼:
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 = "子線程發(fā)消息";
            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、匿名內(nèi)部類引起的內(nèi)存泄漏的例子,這次先不說引起內(nèi)存泄漏的原因,使用Memory Profiler工具來如何進行分析。


圖片1.png

從上圖可以看出,MemoryMonitorActivity界面已經(jīng)退出或者銷毀了,但MemoryMonitorActivity實例仍然存在,而且它實例對象本身大小為0.69K,MemoryMonitorActivity包括引用大小為142.7K,這就是典型了activity的內(nèi)存泄漏,存在內(nèi)存浪費。但通過上面,我們還是無法定位造成activity的內(nèi)存泄漏真正的原因是什么呢。接下來繼續(xù)看下圖:


圖片2.png

圖片3.png

通過上面可以看出:

1.Context持有外部MemoryMonitorActivity引用,導致GC無法回收。
2.runnable持有外部MemoryMonitorActivity引用,從源碼可以看出runnable是一個內(nèi)部類,持有外部引用MemoryMonitorActivity的類,導致GC不能回收。
3.Handler持有外部MemoryMonitorActivity引用,導致GC無法回收。
以上三點,activity存在內(nèi)存泄漏真正原因就是持有引用,GC沒有及時回收,存在內(nèi)存浪費。
那接下來怎么優(yōu)化它呢,別著急。我們知道如何使用Memory Profiler來檢測應用是否存在內(nèi)存泄漏。接下來我們使用MAT來分析下內(nèi)存占用情況。


圖片4.png

我們點擊Top Consumers看看,如下:
圖片5.png

圖片6.png

圖片7.png

從上圖可以看出,由于MemoryMonitorActivity界面已經(jīng)銷毀了,仍然存在很多實例,因此它已經(jīng)存在內(nèi)存泄露了。我們需要熟悉Memory Profiler和 MAT工具的使用,可以進行綜合分析來定位內(nèi)存泄露的真正原因。
接下來我們就需要優(yōu)化代碼了。
優(yōu)化后源碼如下:

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);
    }

    /**
     * 匿名內(nèi)部類持有外部類,需要實例化一個Runnable對象,在onDestroy方法runnable設置null即可
     */
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            Message message = new Message();
            message.what=1;
            message.obj = "子線程發(fā)消息";
            myHandler.sendMessage(message);
        }
    };

    /**
     * 使用靜態(tài)handler的弱引用方式來避免內(nèi)存泄露
     */
    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生命周期一樣長的進行避免內(nèi)存泄露
              instance = new SingleManager(context.getApplicationContext());
          }
          return instance;
      }

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

圖片9.png

經(jīng)過代碼優(yōu)化,通過Memory Profiler和 MAT工具分析結果,MemoryMonitorActivity不存在內(nèi)存泄露了。
作為Android開發(fā)者,我們需要必備這個基本技能,需要熟悉Memory Profiler和 MAT內(nèi)存工具的使用,開發(fā)過程中會經(jīng)常使用到的。搜索并關注公眾號“Android技術迷”,可查看更多的相關文章。感謝各位關注。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Android開發(fā)過程當中,軟件卡頓、軟件黑屏退出等等現(xiàn)象都跟內(nèi)存相關,安卓軟件與ios軟件體驗同樣是流暢度差距很...
    王冥閱讀 577評論 0 0
  • 前言 成為一名優(yōu)秀的Android開發(fā),需要一份完備的知識體系,在這里,讓我們一起成長為自己所想的那樣~。 本篇是...
    zhx喜籽閱讀 1,013評論 0 4
  • 16宿命:用概率思維提高你的勝算 以前的我是風險厭惡者,不喜歡去冒險,但是人生放棄了冒險,也就放棄了無數(shù)的可能。 ...
    yichen大刀閱讀 8,205評論 0 4
  • 公元:2019年11月28日19時42分農(nóng)歷:二零一九年 十一月 初三日 戌時干支:己亥乙亥己巳甲戌當月節(jié)氣:立冬...
    石放閱讀 7,531評論 0 2
  • 今天上午陪老媽看病,下午健身房跑步,晚上想想今天還沒有斷舍離,馬上做,衣架和旁邊的的布衣架,一看亂亂,又想想自己是...
    影子3623253閱讀 3,076評論 3 8

友情鏈接更多精彩內(nèi)容