安卓使用LeakCanary檢測(cè)代碼內(nèi)存泄漏和BlockCanary優(yōu)化代碼結(jié)構(gòu)

使用LeakCanary檢測(cè)代碼的內(nèi)層泄漏

首先我們看下面的代碼
public class MainActivity extends AppCompatActivity {    
     private Button btn_load;   
     private Handler mHandler = new Handler() {        
            @Override        
            public void handleMessage(Message msg) {    
                   if(msg.what == 0) {                                                    
                      Log.i("handleMessage", "got datas");    
                   }  
             }  
      };    
      @Override    
      protected void onCreate(Bundle savedInstanceState) {                               
                super.onCreate(savedInstanceState); 
                setContentView(R.layout.activity_main);        
                btn_load = (Button)findViewById(R.id.btn_load);
                btn_load.setOnClickListener(new View.OnClickListener() {    
                         Override    
                         public void onClick(View v) { 
                                Log.i("btn_load", "loading datas"); 
                                loadData(); 
                         }
                });
       private void loadData() {    
               new Thread(new Runnable() {        
                   @Override        
                  public void run() {            
                         //do sonething            
                         SystemClock.sleep(10000);            
                        //發(fā)送消息            
                       mHandler.sendEmptyMessageDelayed(0, 20000);      
                  }    
              }).start();}
  • 開(kāi)啟界面后, 立即關(guān)閉舀奶,等待一段時(shí)間后暑竟,出現(xiàn)泄漏,檢查L(zhǎng)eakCanary,獲取以下的結(jié)果:
MainActivity泄漏流程
  • 首先在我們的安卓程序中引入LeakCanary:
    • 在對(duì)應(yīng)安卓模塊的build.gradle文件中導(dǎo)入以下的語(yǔ)句引入相應(yīng)的庫(kù)伪节,并保證leak檢測(cè)只在代碼debug模式下可用光羞,上線后失效
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
  • 創(chuàng)建一個(gè)MyApp 類繼承Application,在onCreate()方法中安裝LeakCanary绩鸣,不要忘了在清單文件中注冊(cè)MYApp
    具體操作如下:
public class MyApp extends Application {   
              @Override   
              public void onCreate() {        
                       super.onCreate();      
                       LeakCanary.install(this);    
              }
}
  • MainActivity泄漏流程分析
  • 觸發(fā)按鈕點(diǎn)擊時(shí)間后怀大, loadData()開(kāi)始執(zhí)行,loadData()方法中開(kāi)啟了一個(gè)子線程呀闻,創(chuàng)建了一個(gè)匿名的線程對(duì)象化借。當(dāng)我們?cè)谠搶?duì)象的run方法沒(méi)有執(zhí)行完之前就關(guān)閉了界面(MainActivity),因?yàn)榫€程對(duì)象是一個(gè)內(nèi)部類對(duì)象,默認(rèn)持有外部類(MainActivity)對(duì)象的引用捡多,從而導(dǎo)致MainActivity關(guān)閉后無(wú)法被gc回收從而造成泄漏
  • 解決方法
    我們自建一個(gè)內(nèi)部靜態(tài)類繼承Thread蓖康,靜態(tài)內(nèi)部類不持有外部類的引用铐炫,從而可以避免以上問(wèn)題,代碼如下
private static class  MyThread extends  Thread {    
          private WeakReference<MainActivity> weak;        
          public MyThread(MainActivity activity) {        
                 weak = new WeakReference<MainActivity>(activity);   
          }    
          @Override    
          public void run() {        
                 //do sonething        
                 SystemClock.sleep(100);        
                //發(fā)送消息       
                if(null != weak && null != weak.get()) { 
                    weak.get().mHandler.sendEmptyMessageDelayed(0, 20000);       
                }
          }                
} 

loadData()中修改如下

new MyThread(this).start();

開(kāi)啟界面后蒜焊, 立即關(guān)閉倒信,等待一段時(shí)間后,又出現(xiàn)泄漏泳梆,檢查L(zhǎng)eakCanary,獲取以下的結(jié)果:


MainActivity泄漏流程

究其原因是和上述線程是一樣的鳖悠,只不過(guò)這次泄漏的是Handler對(duì)象。
所以优妙,我們?cè)俣x一個(gè)Handler的靜態(tài)內(nèi)部類乘综,代碼如下:

private static class  MyHandler extends Handler {    
          private WeakReference<MainActivity> weak;    
          public MyHandler(MainActivity activity) {        
                 weak = new WeakReference<MainActivity>(activity);   
          }    
          @Override    
          public void handleMessage(Message msg) {       
                 if(msg.what == 0) {            
                    Log.i("handleMessage", "got datas");            
                   if(null != weak && null != weak.get()) {    
                        weak.get().textView.setText("goodbye world");           
                   }       
                 }  
          }
}

再次運(yùn)行程序?qū)⒉粫?huì)產(chǎn)生泄漏問(wèn)題

  • 進(jìn)一步優(yōu)化
  • 但界面不可見(jiàn)時(shí), 我們最好把消息隊(duì)列中的message清空套硼,代碼如下:
@Override
protected void onDestroy() {    
         super.onDestroy();    
         mHandler.removeCallbacksAndMessages(null);
}
  • 當(dāng)界面關(guān)閉時(shí)卡辰,我們的子線程還在運(yùn)行,可以通過(guò)觀察LogCat打印日志看出邪意,實(shí)際上九妈,我們?cè)陉P(guān)閉主線程是同時(shí)關(guān)閉子線程,可以如下操作:
    • 定義一個(gè)全局boolbean型變量來(lái)控制MyThread的開(kāi)關(guān)抄罕,在MyThread提供一個(gè)關(guān)閉線程的方法close(), 當(dāng)界面關(guān)閉時(shí)允蚣,調(diào)用該方法mt.close(), 代碼如下:
      定義的全局變量
private MyThread mt;
private boolean isClose;

提供的方法

public void close() {    
       if(null != weak && null != weak.get()) {      
              weak.get().isClose = true;    
       }
}

修改run() 的邏輯

if(null != weak && null != weak.get()) {  
      if(weak.get().isClose) {        
          //直接返回      
           return;   
       }
}

onDestroy()調(diào)用

mt.close();
  • 還想提及的內(nèi)容
    • 我們?cè)趦蓚€(gè)自定義的內(nèi)部類中都有這樣的代碼段
 private WeakReference<MainActivity> weak; 

其作用是為了然我們的靜態(tài)內(nèi)部類可以調(diào)用外部類的非靜態(tài)的字段和方法,從而只有一個(gè)外部類對(duì)象的引用呆贿,但這樣做就又回到導(dǎo)致我們的代碼泄漏的最初的原因嚷兔,怎么辦呢,于是弱引用橫空出世了做入。弱引用的特點(diǎn)是一旦被gc掃描到就會(huì)被立即回收冒晰,而不管是否被引用,這也是為什么每次我們使用時(shí)都要判斷其是否為null的原因竟块。與之對(duì)應(yīng)的還有軟引用(SoftReference), 強(qiáng)引用壶运, 虛引用, 相關(guān)的詳細(xì)說(shuō)明大家自行搜索啊浪秘。


使用BlockCanary優(yōu)化代碼的結(jié)構(gòu)

  • 當(dāng)我們完成我們的app后發(fā)現(xiàn)使用起來(lái)卡頓特別嚴(yán)重耸携,于是需要對(duì)代碼進(jìn)行優(yōu)化棵癣,可是面對(duì)動(dòng)輒幾千行、幾萬(wàn)行的代碼夺衍,讓人無(wú)法下手狈谊,于是BlockCanary出現(xiàn)了。接下來(lái),我為大家演示BlockCanary的用法
  • 第一步河劝, 獲取對(duì)應(yīng)的庫(kù)
    在相應(yīng)的Module的build.gradle中導(dǎo)入如下的語(yǔ)句引入對(duì)應(yīng)的庫(kù)
compile 'com.github.moduth:blockcanary-android:1.2.1'
// 僅在debug包啟用BlockCanary進(jìn)行卡頓監(jiān)控和提示的話壁榕,可以這么用 
debugCompile 'com.github.moduth:blockcanary-android:1.2.1' 
   releaseCompile 'com.github.moduth:blockcanary-no-op:1.2.1'
  • 新建一個(gè)類繼承BlockContextCanary
    實(shí)現(xiàn)各種上下文,像是卡慢報(bào)告閾值赎瞎,log的保存位置牌里,網(wǎng)絡(luò)類型等
public class AppBlockCanaryContext extends BlockCanaryContext {    
          // override to provide context like app qualifier, uid,     network type, block threshold, log save path    
          // this is default block threshold, you can set it by phone's performance    
          @Override    
          public int getConfigBlockThreshold() {       
                return 500;   
          }    
          // if set true, notification will be shown, else only write log file 
          @Override   
          public boolean isNeedDisplay() {       
             return BuildConfig.DEBUG;  
          }   
         // path to save log file (在SD卡目錄下)   
        @Override   
         public String getLogPath() {     
             return "/blockcanary/performance";   
         }
}
  • MyApp中開(kāi)啟檢測(cè), 不要忘了在manifest清單文件中注冊(cè)MyApp
public class MyApp extends Application {    
      @Override    
      public void onCreate() {        
           super.onCreate();       
           BlockCanary.install(this, new AppBlockCanaryContext()).start();  
      }
}  
  • LeakDemo為例我們來(lái)檢測(cè)代碼的卡頓情況,結(jié)果如下:
卡頓檢測(cè)結(jié)果圖

結(jié)果顯示第34行有卡頓情況务甥,我們找到這一行:


卡頓的地方

我們還可以查看更詳細(xì)的信息

卡頓的詳細(xì)信息

獲取到卡頓的代碼位置二庵,我們就可以著手修改代碼和重構(gòu)了



  • 后話
    筆者花了一晚上終于完成了自己的第一篇博文,希望大家多多支持缓呛,筆者還會(huì)繼續(xù)努力催享,為大家奉上更多有趣,有用的文章的哟绊,下次再見(jiàn)咯R蛎睢!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末票髓,一起剝皮案震驚了整個(gè)濱河市攀涵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌洽沟,老刑警劉巖以故,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異裆操,居然都是意外死亡怒详,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門踪区,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)昆烁,“玉大人,你說(shuō)我怎么就攤上這事缎岗【材幔” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵传泊,是天一觀的道長(zhǎng)鼠渺。 經(jīng)常有香客問(wèn)我,道長(zhǎng)眷细,這世上最難降的妖魔是什么拦盹? 我笑而不...
    開(kāi)封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任殊霞,我火速辦了婚禮,結(jié)果婚禮上怪得,老公的妹妹穿的比我還像新娘求摇。我一直安慰自己株旷,他們只是感情好扇雕,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布吓肋。 她就那樣靜靜地躺著签孔,像睡著了一般地熄。 火紅的嫁衣襯著肌膚如雪华临。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天端考,我揣著相機(jī)與錄音雅潭,去河邊找鬼。 笑死却特,一個(gè)胖子當(dāng)著我的面吹牛扶供,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播裂明,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼椿浓,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了闽晦?” 一聲冷哼從身側(cè)響起扳碍,我...
    開(kāi)封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仙蛉,沒(méi)想到半個(gè)月后笋敞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荠瘪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年夯巷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哀墓。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鞭莽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出麸祷,到底是詐尸還是另有隱情澎怒,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布阶牍,位于F島的核電站喷面,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏走孽。R本人自食惡果不足惜惧辈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望磕瓷。 院中可真熱鬧盒齿,春花似錦念逞、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至符匾,卻和暖如春叨咖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背啊胶。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工甸各, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人焰坪。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓趣倾,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親某饰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子誊酌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容