LeakCanary流程介紹分析

LeakCanary芭商,在開(kāi)發(fā)階段,可以用來(lái)檢測(cè)內(nèi)存泄露,項(xiàng)目地址:https://github.com/square/leakcanary

1.png

2.png

具體操作:1再愈、在Application 的onCreate()初始化,會(huì)ActivityLifecycleCallbacks監(jiān)聽(tīng)所有的activity的onDestroy护戳,
2翎冲、把a(bǔ)ctivity生成對(duì)應(yīng)key的放到集合Set<String> retainedKeys,并生成帶Key的弱引用媳荒,GC在回收的時(shí)候抗悍,如果是弱引用,會(huì)把這個(gè)回收對(duì)象放到ReferenceQueue<T> queue钳枕,如果poll出來(lái)有對(duì)象缴渊,說(shuō)明這個(gè)activity對(duì)象已回收,移除集合包含鱼炒,若沒(méi)有衔沼,判斷集合是否包含key,若有,則就懷疑是泄漏了指蚁,需要二次確認(rèn)菩佑,進(jìn)行手動(dòng)Runtime.getRuntime().gc(),
3凝化、泄露對(duì)象稍坯,生成HPROF文件,分析這個(gè)快照文件有沒(méi)有存在帶這個(gè)key值的泄漏對(duì)象搓劫,如果沒(méi)有劣光,那么沒(méi)有泄漏,
否則找出最短路徑糟把,打印給我們绢涡,就找到這個(gè)泄漏對(duì)象了。

一遣疯、初始化LeakCanary

public class ExampleApplication extends Application {
  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}

public final class LeakCanary {
    public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }
}

refWatcher()生成AndroidRefWatcherBuilder類(lèi)雄可,.buildAnInstall(),會(huì)生成RefWatcher缠犀,并把相應(yīng)的對(duì)象如(GcTrigger数苫、AndroidWatchExecutor、AndroidHeapDumper辨液、ServiceHeapDumpListener虐急、AndroidExcludedRefs等)。
且Application.registerActivityLifecycleCallbacks這個(gè)方法滔迈,用來(lái)統(tǒng)一管理所有activity的生命周期止吁,LeakCanary就是在onDestory()方法實(shí)現(xiàn)監(jiān)控的.

public final class ActivityRefWatcher {
       ...
  public static void install(Application application, RefWatcher refWatcher) {
    new ActivityRefWatcher(application, refWatcher).watchActivities();
  }
  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new Application.ActivityLifecycleCallbacks() {
       ...
        @Override public void onActivityDestroyed(Activity activity) {
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };
  private final Application application;
  private final RefWatcher refWatcher;
  void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }
  public void watchActivities() {
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }
}

二、泄露判斷
在RefWatcher中watch函數(shù)中:

public final class RefWatcher {
  private final WatchExecutor watchExecutor;
  private final DebuggerControl debuggerControl;
  private final GcTrigger gcTrigger;
  private final HeapDumper heapDumper;
  private final Set<String> retainedKeys;
  private final ReferenceQueue<Object> queue;
  private final HeapDump.Listener heapdumpListener;
  private final ExcludedRefs excludedRefs;
  public void watch(Object watchedReference, String referenceName) {
    ...
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    ensureGoneAsync(watchStartNanoTime, reference);
  }
  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

其中retainedKeys是用來(lái)保存key的集合燎悍,KeyedWeakReference是帶有key的弱引用對(duì)象敬惦,queue是ReferenceQueue,用來(lái)GC時(shí)候谈山,如果弱引用的對(duì)象被回收了俄删, 系統(tǒng)會(huì)把這個(gè)回收對(duì)象放到queue里面。這樣就可以判斷acitivity是否泄漏了奏路。

 private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }
  private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
public final class AndroidWatchExecutor implements WatchExecutor {
  ...
  @Override public void execute(Retryable retryable) {
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else {
      postWaitForIdle(retryable, 0);
    }
  }
  void waitForIdle(final Retryable retryable, final int failedAttempts) {
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }
}

watchExecutor是AndroidWatchExecutor畴椰,在waitForIdle中有一個(gè)Looper.myQueue().addIdleHandler,這個(gè)是在當(dāng)前線程中取出MessageQueue對(duì)象鸽粉,添加一個(gè)idleHandler斜脂,在Looper,loop()循環(huán)取出Message時(shí),如果沒(méi)有message對(duì)象來(lái)潜叛,即Looper處在空閑時(shí)秽褒,會(huì)實(shí)行這個(gè)idleHandler壶硅,相當(dāng)于在UI線程中頁(yè)面的操作如onDestroy威兜、onStop等跟AMS通訊完成后销斟,沒(méi)有handler.post消息message過(guò)來(lái)時(shí),就實(shí)行了idleHandler椒舵。

 public final class Looper {
    public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        ...
        for (;;) {
            Message msg = queue.next(); // might block
            ...
        }
    }   
}
public final class MessageQueue {
   Message next() {
       ...
        for (;;) {
            ...
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                ...(return msg)
            }
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
        }
    }
}

接著實(shí)行核心方法ensureGone:

  @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    removeWeaklyReachableReferences();//看下檢測(cè)的弱引用回收了沒(méi)
    ...
    if (gone(reference)) {//好蚂踊,回收了,那么這個(gè)activity沒(méi)有泄漏
      return DONE;
    }
    gcTrigger.runGc();//還是沒(méi)有回收笔宿,手動(dòng)GC一下
    removeWeaklyReachableReferences();//再看看對(duì)象回收沒(méi)有
    if (!gone(reference)) {//還沒(méi)回收犁钟,懷疑是內(nèi)存泄漏了,dump內(nèi)存快照.hprof下來(lái)分析
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      File heapDumpFile = heapDumper.dumpHeap();//生產(chǎn)快照f(shuō)ile
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }
  private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }
  private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }
}

三泼橘、hrop內(nèi)存快照生成
解析hprof文件中涝动,是先把這個(gè)文件封裝成snapshot,然后根據(jù)弱引用和前面定義的key值,確定泄漏的對(duì)象炬灭,最后找到最短泄漏路徑醋粟,作為結(jié)果反饋出來(lái):
File heapDumpFile = heapDumper.dumpHeap();
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
生產(chǎn)file,進(jìn)入ServiceHeapDumpListener的analyze函數(shù):

@Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }

緊接著調(diào)用HeapAnalyzerService.runAnalysis:

public final class HeapAnalyzerService extends IntentService {
  public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }
}

startService:DisplayLeakService 中:

public class DisplayLeakService extends AbstractAnalysisResultService {

  @Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = leakInfo(this, heapDump, result, true);
    ...
    if (shouldSaveResult) {
      heapDump = renameHeapdump(heapDump);
      resultSaved = saveResult(heapDump, result);
    }
    PendingIntent pendingIntent;
    ...
   pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);

  
     int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
    showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
    afterDefaultHandling(heapDump, result, leakInfo);
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末重归,一起剝皮案震驚了整個(gè)濱河市米愿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鼻吮,老刑警劉巖育苟,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異椎木,居然都是意外死亡违柏,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)香椎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)勇垛,“玉大人,你說(shuō)我怎么就攤上這事士鸥∠泄拢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵烤礁,是天一觀的道長(zhǎng)讼积。 經(jīng)常有香客問(wèn)我,道長(zhǎng)脚仔,這世上最難降的妖魔是什么勤众? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮鲤脏,結(jié)果婚禮上们颜,老公的妹妹穿的比我還像新娘吕朵。我一直安慰自己,他們只是感情好窥突,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布努溃。 她就那樣靜靜地躺著,像睡著了一般阻问。 火紅的嫁衣襯著肌膚如雪梧税。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天称近,我揣著相機(jī)與錄音第队,去河邊找鬼。 笑死刨秆,一個(gè)胖子當(dāng)著我的面吹牛凳谦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播衡未,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼尸执,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了眠屎?” 一聲冷哼從身側(cè)響起剔交,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎改衩,沒(méi)想到半個(gè)月后岖常,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡葫督,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年竭鞍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橄镜。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡偎快,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出洽胶,到底是詐尸還是另有隱情晒夹,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布姊氓,位于F島的核電站丐怯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏翔横。R本人自食惡果不足惜读跷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望禾唁。 院中可真熱鬧效览,春花似錦无切、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至矛洞,卻和暖如春洼哎,著一層夾襖步出監(jiān)牢的瞬間烫映,已是汗流浹背沼本。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锭沟,地道東北人抽兆。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像族淮,于是被迫代替她去往敵國(guó)和親辫红。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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

  • 升級(jí)了macOS Sierra上XAMPP就沒(méi)法運(yùn)行了祝辣, 啟動(dòng)Apache和MySQL的時(shí)候都會(huì)碰到這樣的錯(cuò)誤 我...
    babesamhsu閱讀 492評(píng)論 0 0
  • 最近忙里偷閑看了幾集《錦繡未央》贴妻,人物造型男帥女美,成功引起了我的關(guān)注蝙斜。對(duì)唐嫣的演技不予置評(píng)名惩,畢竟羅晉演的拓跋浚有...
    星期八的夜閱讀 4,471評(píng)論 59 97
  • 七夕,傳統(tǒng)的情人節(jié)孕荠,走在街上娩鹉,看到了好幾個(gè)捧著花的女孩子,一個(gè)個(gè)笑靨如花稚伍。想起十年前的今日弯予,你第一次送我花的時(shí)候,...
    傾心漫話閱讀 432評(píng)論 2 1
  • 版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載垦搬。 PS:轉(zhuǎn)載請(qǐng)注明出處作者: TigerChain地址: ht...
    TigerChain閱讀 3,335評(píng)論 4 2