LeakCanary 源码分析

LeakCanary是一个检测内存泄漏的工具,使用非常简单。主要用来检测Activity和Fragment内存泄漏,如果发生内存泄漏,直接在用UI显示哪里发生了泄漏并展示对象引用链。

LeakCanary地址:https://github.com/square/leakcanary

LeakCanary的使用

在gradle文件中加入依赖

debugImplementation   'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
 // 如果使用了 support fragment,需要依赖
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'

在Application中加入

public class MyApplication extends Application {
    private RefWatcher refWatcher = null;
    @Override
    public void onCreate() {
        super.onCreate();
        if (!LeakCanary.isInAnalyzerProcess(this)) { // 是否在主进程
            refWatcher = LeakCanary.install(this);
        }
    }
}

如果想监视其他的Object是否内存泄漏了

refWatcher.watch(Object object)

原理

在LeakCanary 的 wiki 就简述了基本的原理:

  1. RefWatcher.watch() 为监视的对象创建一个 KeyedWeakReference;
  2. 然后,后台线程会检查引用是否被清除回收了,如果没有就触发GC;
  3. 如果引用还没被清除,则堆内存dump到文件系统,成一个.hprof文件
  4. HeapAnalyzerService运行在另外一个进程,其中内部的HeapAnalyzer 使用HAHA库解析heap dump
  5. 根据reference key,HeapAnalyzer 找到对应的 KeyedWeakReference,定位内存泄露
  6. HeapAnalyzer 可以找出 GC roots 的最短强引用路径,并确定是否是泄露。如果泄漏,建立导致泄露的引用链。
  7. 将泄漏结果(引用链)传给App进程DisplayLeakService,通知展示

我们主要看的就是LeakCanary.install(this)到底发生了什么?

在解析流程之前先看下涉及到的几个重要的类:

在这里插入图片描述
  1. RefWatcher: 核心类,负责管理和提供入口watch(),由AndroidRefWatcherBuilder创建RefWatcher,建造者模式

  2. WatchExecutor: 负责控制执行检测内存泄漏任务

  3. DebuggerControl:判断是否处于debug

  4. GcTrigger:负责触发一次GC

  5. HeapDump:表示指定时刻的堆栈的快照,AndroidHeapDump为子类

  6. HeapDump.Builder: 负责创建HeapDump

  7. HeapDump.Listener:监听器,当发生内存泄漏的时候,会收到消息,需要触发分析AndroidHeapDump任务

  8. ServiceHeapDumpListener:HeapDump.Listener的实现类,当触发分析任务,调用HeapAnalyzerService执行分析任务

  9. HeapAnalyzerService:是一个Android中四大组件之一的Service,运行在独立的进程,负责执行分析任务和UI通知

  10. HeapAnalyzer:在HeapAnalyzerService内部中,是对DumpHeap分析内存泄漏和找出引用链的工具

  11. retainKeys: 是一个Set<String>,保存着当前还没被回收的Reference的key

  12. ReferenceQueue:引用队列,WeakReference可以关联引用队列,当reference被回收时,会被加入到ReferenceQueue,这样我们就可以判断哪些对象没有被回收了

  13. DisplayLeakService:记录泄漏日志和展示通知的Service

其实,leakCanary的基本原理就是利用ReferenceQueue,在Activity销毁的时候判断对象有没有被加入ReferenceQueue,若没有则说明Activity还在存活,可能存在泄漏。

GC Root的种类

java 使用引用链法来判断一个引用是否该被回收,而出发点就是GC Root

  1. 虚拟机栈的对象引用
  2. 本地方法栈的对象引用
  3. 方法区的常量引用
  4. 方法区的静态对象引用

源码分析

isInAnalyzerProcess()方法是用来判断当前的进程是否是在分析进程,因为多进程中,每个进程都会创建Application,而我们需要在非LeakCanary的进程中检测。

// LeakCanary
public static boolean isInAnalyzerProcess(@NonNull Context context) {
  Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
  // This only needs to be computed once per process.
  if (isInAnalyzerProcess == null) {
    isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
    LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
  }
  return isInAnalyzerProcess;
}

install方法是LeakCanary的总入口

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

这里使用了Build建造者模式,利用AndroidRefWatcherBuilder创建RefWatcher

我们分成几个部分:

  1. refWatch(application) 创建AndroidRefWatcherBuilder
  2. listenerServiceClass(DisplayLeakService.class) 设置监听内存泄漏和分析结果的 Service
  3. excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) 设置忽略的内存泄漏的错误
  4. buildAndInstall() 初始化AndroidRefWatcher
1. refWatch(application)
// LeakCanary
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
  return new AndroidRefWatcherBuilder(context);
}

这里只是创建了AndroidRefWatcherBuilder,最后使用Build模式创建AndroidRefWatcher

2.listenerServiceClass(DisplayLeakService.class)
// AndroidRefWatcherBuilder 
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
    @NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
  enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass); 
  return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}

创建了heapDumpListener负责heapDump的分析和处理

isAssignableFrom() 是object方法,
例如 a.isAssignableFrom(b) 判断a是不是b的父类或接口

如果listenerServiceClass继承或者是DisplayLeakService,则enableDisplayLeakActiviy为true,表示显示LeakCanary界面

// ServiceHeapDumpListener 
public final class ServiceHeapDumpListener implements HeapDump.Listener {

  private final Context context;
  private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;

  public ServiceHeapDumpListener(@NonNull final Context context,
      @NonNull final Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
    this.context = checkNotNull(context, "context").getApplicationContext();
  }

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

这里简单看一下ServiceHeapDumpListener ,主要方法在analyze(),实际上也只是通知了HeapAnalyzerService.runAnalysis()去分析heapDump;

HeapAnalyzerService是运行在独立进程的Service。它是一个IntentService,执行完一次任务就会终止。

3. excludedRefs(AndroidExcludedRefs.createAppDefaults().build())

AndroidExcludedRefs.java类中维护着一系列的特定的内存泄漏类型,在执行内存泄漏和显示的时候也会忽略这些类

4. buildAndInstall()
// AndroidRefWatcherBuilder
public @NonNull RefWatcher buildAndInstall() {
  if (LeakCanaryInternals.installedRefWatcher != null) {
    throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
  }
  RefWatcher refWatcher = build();
  if (refWatcher != DISABLED) {
    if (enableDisplayLeakActivity) {
      LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
    }
    if (watchActivities) {
      ActivityRefWatcher.install(context, refWatcher);
    }
    if (watchFragments) {
      FragmentRefWatcher.Helper.install(context, refWatcher);
    }
  }
  LeakCanaryInternals.installedRefWatcher = refWatcher;
  return refWatcher;
}

这里也主要分成四个部分:
(1). build() 构建初始化RefWatcher
(2). LeakCancaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true) 设置DisplayLeakActivity不显示图标
(3). ActivityRefWatcher.install(context, refWatcher) 监测Activity
(4). FragmentRefWatcher.Helper.install(context, refWatcher) 监测Fragment

(1) build()
// RefWatcherBuilder
public final RefWatcher build() {
  if (isDisabled()) {
    return RefWatcher.DISABLED;
  }
  ...
  return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
      heapDumpBuilder);
}

AndroidRefWatcherBuilder 是 RefWatcherBuilder的子类,build()方法内部负责创建默认的组件。AndroidRefWatcherBuilder 重写了部分默认方法。

// AndroidRefWatcherBuilder
@Override protected @NonNull HeapDumper defaultHeapDumper() {
  LeakDirectoryProvider leakDirectoryProvider =
      LeakCanaryInternals.getLeakDirectoryProvider(context);
  // 创建默认的 AndroidHeapDumper
  return new AndroidHeapDumper(context, leakDirectoryProvider);
}

@Override protected @NonNull DebuggerControl defaultDebuggerControl() {
  // 创建默认的 AndroidDebuggerControl
  return new AndroidDebuggerControl();
}

@Override protected @NonNull HeapDump.Listener defaultHeapDumpListener() {
  // 创建默认的 ServiceHeapDumpListener
  return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}

@Override protected @NonNull WatchExecutor defaultWatchExecutor() {
  // 创建默认的 AndroidWatchExecutor
  return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}

@Override protected @NonNull
List<Class<? extends Reachability.Inspector>> defaultReachabilityInspectorClasses() {
  // 创建默认的 AndroidReachabilityInspectors
  return AndroidReachabilityInspectors.defaultAndroidInspectors();
}
(2) LeakCancaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true)
// LeakCanaryInternals
public static void setEnabledAsync(Context context, final Class<?> componentClass,
    final boolean enabled) {
  final Context appContext = context.getApplicationContext();
  AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
    @Override public void run() {
      setEnabledBlocking(appContext, componentClass, enabled);
    }
  });
}

这里使用了AsyncTask内部的并发线程池 setEnabledBlocking()

// LeakCanaryInternals
public static void setEnabledBlocking(Context appContext, Class<?> componentClass, boolean enabled) {
  ComponentName component = new ComponentName(appContext, componentClass);
  PackageManager packageManager = appContext.getPackageManager();
  int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
  // 设置是否隐藏应用图标
  packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}

如果设置一个app的mainActivity为COMPONENT_ENABLED_STATE_DISABLED状态,则不会再launcher的程序图标中发现该app。

这里设置了DisplayLeakActivity在Launcher程序图标不出现

(3). ActivityRefWatcher.install(context, refWatcher)
// ActivityRefWatcher
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
  Application application = (Application) context.getApplicationContext();
  ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
  // 监听application里面的Activity的生命周期
  application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
    new ActivityLifecycleCallbacksAdapter() {
      @Override public void onActivityDestroyed(Activity activity) {
        refWatcher.watch(activity);
      }
    };

这里才是LeakCanary监测Activity的核心,利用ActivityLifecycleCallbacks监听Activity的生命周期,在Activity销毁的时候,onActivityDestroyed调用refWatcher.watch()

refWatcher.watch()执行了实际的核心工作,暂时先跳过,后续再继续分析refWatcher.watch()

(4). FragmentRefWatcher.Helper.install(context, refWatcher)
// FragmentRefWatcher
final class Helper {
  private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =
      "com.squareup.leakcanary.internal.SupportFragmentRefWatcher";

  public static void install(Context context, RefWatcher refWatcher) {
    List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();

    if (SDK_INT >= O) { // 如果大于Anroid 26,需要增加AndroidOFragmentRefWatcher
      fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
    }
    try {
      // 利用反射添加SupportFragmentRefWatcher
      Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
      Constructor<?> constructor =
          fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
      FragmentRefWatcher supportFragmentRefWatcher =
          (FragmentRefWatcher) constructor.newInstance(refWatcher);
      fragmentRefWatchers.add(supportFragmentRefWatcher);
    } catch (Exception ignored) {
    }

    if (fragmentRefWatchers.size() == 0) {
      return;
    }

    Helper helper = new Helper(fragmentRefWatchers);

    Application application = (Application) context.getApplicationContext();
    application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
  }

以上部分是添加了FragmentRefWatcher,用来监测Fragment,其中SupportFragmentRefWatcher是在leakcanary-support-fragment包通过反射添加进来,需要在build.gradle添加依赖。

// AndroidOFragmentRefWatcher
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
    new FragmentManager.FragmentLifecycleCallbacks() {
      @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
        View view = fragment.getView();
        if (view != null) {
          refWatcher.watch(view);
        }
      }
      @Override
      public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
        refWatcher.watch(fragment);
      }
    };

@Override public void watchFragments(Activity activity) {
  FragmentManager fragmentManager = activity.getFragmentManager();
  fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}

监测Fragment和监测Activity基本是一样的,利用了FragmentLifecycleCallbacks监听Fragment的生命周期,在销毁状态onFragmentViewDestroyed和onFragmentDestroyed分别检测View和Fragment。

refWatcher.watch(Object)

无论是检测Activity还是Fragment,或者是其他类型,都是调用refWatcher.watch(Object)

再看下AnroidRefWatcher重要的组成部分:

在这里插入图片描述
public void watch(Object watchedReference) {
    watch(watchedReference, "");
}

public void watch(Object watchedReference, String referenceName) {
  if (this == DISABLED) {
    return;
  }
  checkNotNull(watchedReference, "watchedReference");
  checkNotNull(referenceName, "referenceName");
  final long watchStartNanoTime = System.nanoTime();
  String key = UUID.randomUUID().toString();
  retainedKeys.add(key);//1
  final KeyedWeakReference reference =
      new KeyedWeakReference(watchedReference, key, referenceName, queue);//2
  ensureGoneAsync(watchStartNanoTime, reference);//3
}

watch()主要的工作:

  1. 为检测的Object创建一个key,并添加进retainedKeys表示Objectha还存活
  2. 为Object创建一个弱应用,关联queue为引用队列
  3. ensureGoneAsync()执行监测任务
// RefWatcher
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  watchExecutor.execute(new Retryable() {
     @Override public Retryable.Result run() {
       return ensureGone(reference, watchStartNanoTime);
     }
  });
}

watchExecutor 是AndroidWatchExecutor

//AndroidWatchExecutor
public void execute(@NonNull Retryable retryable) {
  if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
    // 在主线程
    waitForIdle(retryable, 0);
  } else {
    // 切换到主线程
    postWaitForIdle(retryable, 0);
  }
}

private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
  // 利用Handler切换到主线程
  mainHandler.post(new Runnable() {
    @Override public void run() {
      waitForIdle(retryable, failedAttempts);
    }
  });
}

  private void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // 添加任务到主线程Looper,等待Looper空闲时候执行
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }

private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    // backgroudhandler所在的是HandlerThread
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        Retryable.Result result = retryable.run();
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }

这里利用Looper的IdleHandler,在Looper空闲的时候执行任务。

简单总结下AndroidWatcherExecutor,等待主线程空闲的时候,在子线程执行ensureGone()

// RefWatcher
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
  long gcStartNanoTime = System.nanoTime();
  long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

  // 1. 从retainedKeys移除掉已经被会回收的弱引用的key
  removeWeaklyReachableReferences();

 // 2. 如果是debug模式,会继续重试
  if (debuggerControl.isDebuggerAttached()) {
    // The debugger can create false leaks.
    return RETRY;
  }

 // 3. 若当前引用不在retainedKeys,说明不存在内存泄漏
  if (gone(reference)) {
    return DONE;
  }

 // 4. 触发一次gc
  gcTrigger.runGc();

 // 5.再次从retainedKeys移除掉已经被会回收的弱引用的key
removeWeaklyReachableReferences();

  if (!gone(reference)) {
   // 存在内存泄漏
    long startDumpHeap = System.nanoTime();
    long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
   
   // 6. 创建heapDump文件,还没写入
    File heapDumpFile = heapDumper.dumpHeap();
    if (heapDumpFile == RETRY_LATER) {
      // Could not dump the heap.
      return RETRY;
    }
    long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
    // 7. 创建heapDump
    HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
        .referenceName(reference.name)
        .watchDurationMs(watchDurationMs)
        .gcDurationMs(gcDurationMs)
        .heapDumpDurationMs(heapDumpDurationMs)
        .build();
    // 8.调用heapdumpListener分析
    heapdumpListener.analyze(heapDump);
  }
  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);
  }
}

上面的注释把流程基本阐述清楚了,基本逻辑是

  1. 利用引用队列移除掉已经回收的对象
  2. 可能因为回收不及时,所以再次gc
  3. 若对象还没被回收,则发生内存泄漏
  4. 创建HeapDump,后台service执行分析任务

最后看HeapAnalyzerService分析HeapDump

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

这里的listenerServiceClass 就是从最上面传递过来的DisplayLeakService.class,最后他负责记录日志和展示通知。

runAnalysis() 启动了HeapAnalyzerService,它是一个IntentService运行在独立进程,负责分析HeapDump和显示通知,既然是IntentService,直接看onHandleIntent(),内部又调用了onHandleIntentInForeground()

protected void onHandleIntentInForeground(@Nullable Intent intent) {
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA); // DisplayLeakService.class
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
    // 创建HeapAnalyzer
    HeapAnalyzer heapAnalyzer =
        new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
    // HeapAnanlyzer工具分析
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);
    // 启动DisplayLeakService记录日志和展示通知
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

HeapAnalyzer 可以找出 GC roots 的最短强引用路径,并确定是否是泄露。如果泄漏,建立导致泄露的引用链。

// HeapAnalyzer
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
    @NonNull String referenceKey,
    boolean computeRetainedSize) {
  long analysisStartNanoTime = System.nanoTime();

  if (!heapDumpFile.exists()) {
    Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
    return failure(exception, since(analysisStartNanoTime));
  }

  try {
    listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
    //将heap文件封装成MemoryMappedFileBuffer
    HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
    //创建hprof解析器,解析hprof文件
    HprofParser parser = new HprofParser(buffer);
    listener.onProgressUpdate(PARSING_HEAP_DUMP);
    Snapshot snapshot = parser.parse();
    listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
    // 移除相同GC root
    deduplicateGcRoots(snapshot);
    listener.onProgressUpdate(FINDING_LEAKING_REF);
    // 找出内存泄漏对象
    Instance leakingRef = findLeakingReference(referenceKey, snapshot);
     //检测是否存在泄漏的引用
    if (leakingRef == null) {
      String className = leakingRef.getClassObj().getClassName();
      return noLeak(className, since(analysisStartNanoTime));
    }
     //根据leakingRef寻找引用路径
    return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
  } catch (Throwable e) {
    return failure(e, since(analysisStartNanoTime));
  }
}

再往下就是如何分析HeapDump,不是本文的重点。

总结:

在这里插入图片描述
  1. 无论你是监测Activity还是Fragment亦或是其他Object,最后都是RefWatcher.watch(object)
  2. RefWatcher添加监测的引用, 在主线程 idle 后进行一次强制 gc 后再判断该引用是否在引用队列中,否则就可能是内存泄漏
  3. RefWatcher 通过 HeapDumper 创建HeapDump 和 hprof 文件
  4. HeapAnalyzerService的HeapAnalyzer 分析内存泄漏和找出引用链,最后DisplayLeakService通知
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 一、前言 1.简介 A small leak will sink a great ship —— Benjamin...
    仰简阅读 2,535评论 0 19
  • 转自我的csdn博客,转载请注明出处。 Leakcanary是square推出的内存泄露分析工具,使用很简单,可谓...
    srp123阅读 10,896评论 3 8
  • 大家好,我是苍王。 以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。 [Androi...
    CangWang阅读 4,804评论 5 16
  • 居江南冬天,踏雪寻梅“四海山”, “四海山”观雪,如银涛雪浪 如入蓬莱仙境,似幻随影 从山脚到巅峰一山应四季 大雪...
    象浦阅读 1,268评论 12 41
  • 父亲打来电话,我和往常一样挂了电话,又打了过去。父亲说:“我不愿意到你哥家过年。”原来好强的老爸不愿意再次...
    lizhizhong1764阅读 382评论 0 1