美文网首页
LeakCanary源码分析2

LeakCanary源码分析2

作者: 陆元伟 | 来源:发表于2019-07-15 17:38 被阅读0次

LeakCanary是Square公司基于MAT开源的一个内存泄漏检测工具,在发生内存泄漏的时候LeakCanary会自动显示泄漏信息。这样我们可以在手机上面方便的查看app的内存泄露信息。下面我们探究它的原理

首先调用入口

@Override

public void onCreate() {

super.onCreate();
    //1 首先检查当前进程和分析内存分析进程是否在同一进程
    if (LeakCanary.isInAnalyzerProcess(this)) {
    
    // This process is dedicated to LeakCanary for heap analysis.
    
    // You should not init your app in this process.
    
    return;
    
    }
    
    enabledStrictMode();
    
    LeakCanary.install(this);

}

LeakCanaryisInAnalyzerProcess方法

public static boolean isInAnalyzerProcess(Context context) {

    return isInServiceProcess(context, HeapAnalyzerService.class);

}

public static boolean isInServiceProcess(Context context, Class serviceClass) {

    PackageManager packageManager = context.getPackageManager();
    
    PackageInfo packageInfo;
    
    try {
    
    packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
    
    } catch (Exception e) {
    
        CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
        return false;
    }
    //获取app的进程名
    
    String mainProcess = packageInfo.applicationInfo.processName;
    
    ComponentName component = new ComponentName(context, serviceClass);
    
    ServiceInfo serviceInfo;
    
    try {
    
    serviceInfo = packageManager.getServiceInfo(component, 0);//获取serviceClass服务信息
    
    } catch (PackageManager.NameNotFoundException ignored) {
    
    // Service is disabled.
    
    return false;

    }

    //如果服务在当前主进程
    
    if (serviceInfo.processName.equals(mainProcess)) {
    
        CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);
        
        // Technically we are in the service process, but we're not in the service dedicated process.
        
        return false;
    
    }

    //获取当前进程的pid
    
    int myPid = android.os.Process.myPid();
    
    ActivityManager activityManager =
    
    (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    
    ActivityManager.RunningAppProcessInfo myProcess = null;

    List runningProcesses =
    
    activityManager.getRunningAppProcesses();   

    if (runningProcesses != null) {

        for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
    
            if (process.pid == myPid) {
            
            myProcess = process;
            
            break;
            
            }
    
        }

    }

    if (myProcess == null) {
    
    CanaryLog.d("Could not find running process for %d", myPid);
    
    return false;
    
    }

    return myProcess.processName.equals(serviceInfo.processName);

}

前面是检查当前app进程是否和内存分析服务的进程是否在同一进程,如果是,则不安装内

存检测。为什么是这样子的呢?

查看内存分析的服务,发现前面有一段注释

/*** 
This service runs in a separate process to avoid slowing down the app process or 
making it run* out of memory.*/public final  
classHeapAnalyzerServiceextendsIntentService
**/

意思是这个服务运行在单独的进程避免使当前app运行慢或者内存溢出.由此可见内存分析服务分析过程中占用资源多

接下来是如果判断不在当前进程中,则进行安装.代码如下

public final class LeakCanary {


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

}

首先创建一个AndoidRefWatchBuilder,这里用到创建者模式.

/** Builder to create a customized {@linkRefWatcher} with appropriate Android defaults. 
*/
public static AndroidRefWatcher Builder refWatcher(Context context) {

return new AndroidRefWatcherBuilder(context);

}

然后用AndoidRefWatchBuilder创建了一个AndroidRefWatcher对象,把接收分析堆的结果的服务类的class设置进去,这里为什么要传递个服务类型呢?因为分析堆内存的服务不在当前进程,所以通过服务之间来实现两进程中的通信

public AndroidRefWatcher Builder listenerServiceClass(Class listenerServiceClass) {
    
    return heapDumpListener(new ServiceHeapDumpListener(context,listenerServiceClass));
    
}

AbstractAnalysisResultService又继承了IntentService

public abstract class AbstractAnalysisResultService extends IntentService {

    private static finalStringHEAP_DUMP_EXTRA="heap_dump_extra";
    
    private static finalStringRESULT_EXTRA="result_extra";
    
    ServiceHeapDumpListener代码如下:
    
    public ServiceHeapDumpListener(Context context,Class listenerServiceClass) {
    
        setEnabled(context,listenerServiceClass, true);
        setEnabled(context,HeapAnalyzerService.class, true);
        
        this.listenerServiceClass=checkNotNull(listenerServiceClass,"listenerServiceClass");
        
        this.context=checkNotNull(context,"context").getApplicationContext();
    
}

里面的setEnabled方法设置该服务可用(以前我们写设置服务不可用,直接简单粗暴的调用stopService方法,看大神写的代码才发现大神不愧是大神,考虑的那么细致).

public static void setEnabled(Context context, finalClass componentClass,

final booleanenabled) {

    final Context appContext = context.getApplicationContext();
    executeOnFileIoThread(new Runnable() {
        @Override 
        public voidrun() {
            setEnabledBlocking(appContext,componentClass,enabled);
        }
    });
}
    
public static void setEnabledBlocking(Context appContext,Class componentClass,
    boolean enabled) {
    ComponentName component =newComponentName(appContext,componentClass);
    PackageManager packageManager = appContext.getPackageManager();
    intnewState = enabled ?COMPONENT_ENABLED_STATE_ENABLED:COMPONENT_ENABLED_STATE_DISABLED;
    // Blocks on IPC.
    packageManager.setComponentEnabledSetting(component,newState,DONT_KILL_APP);
}

继续往下看

/*** Creates a {@linkRefWatcher} 
that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {

    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
    
    .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
    
    .buildAndInstall();

}

创建了一个ExcludedRefs.Builder的建造者。

public static ExcludedRefs.BuildercreateAppDefaults() {
    return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));

}

添加了一个AndroidExcludedRefs.class

public static ExcludedRefs.BuildercreateBuilder(EnumSet refs) {

    ExcludedRefs.Builder excluded = ExcludedRefs.builder();
    
    for(AndroidExcludedRefs ref : refs) {
        if(ref.applies) {
        
        ref.add(excluded);
        ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
        
        }
    
    }
    
    returnexcluded;

}

把所有要分析的AndroidExcludedRefs根据当前手机SDK版本和ROM作了过滤,注释里面也说得很清楚,随着时间的推移,很多SDK 和厂商 ROM 中的内存泄露问题已经被尽快修复了

/***
 This class is a work in progress. You can help by reporting leak traces that seem to be caused
* by the Android SDK, here: https://github.com/square/leakcanary/issues/new
* ** We filter on SDK versions and Manufacturers because many of those leaks are specific to a given
* * manufacturer implementation, they usually share their builds across multiple models, and the
* * leaks eventually get fixed in newer versions.
* ** Most app developers should use {@link#createAppDefaults()}. However, you can also pick the
* * leaks you want to ignore by creating an {@linkEnumSet} that matches your needs and calling
* * {@link#createBuilder(EnumSet)}
* */@SuppressWarnings({"unused","WeakerAccess"})// Public API.

public enum AndroidExcludedRefs

继续往往下看..

/*** Creates a {@linkRefWatcher} instance and starts watching activity references (on ICS+).*/
public RefWatcher buildAndInstall() {

    RefWatcher refWatcher = build();
    
    if(refWatcher !=DISABLED) {
    
        LeakCanary.enableDisplayLeakActivity(context);
        
        ActivityRefWatcher.install((Application)context,refWatcher);
    
    }
    
    return refWatcher;

}

这个和前面的设置服务调用的是同一个方法

public static void enable DisplayLeakActivity(Context context) {

    setEnabled(context,DisplayLeakActivity.class, true);
    
}
    
public static void install(Application application,RefWatcher refWatcher) {
    
    new ActivityRefWatcher(application,refWatcher).watchActivities();
    
}
    
public void watchActivities() {
    
    // Make sure you don't get installed twice.
    
    stopWatchingActivities();
    
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);

}

这里注册了一个activity生命周期的回调,因为主要是对activity进行检测

private finalApplication.ActivityLifecycleCallbackslifecycleCallbacks=

new Application.ActivityLifecycleCallbacks() {

    @Override
    public void onActivityCreated(Activity activity,Bundle savedInstanceState) {
    
    }
    
    @Override
    public void onActivityStarted(Activity activity) {
    
    }
    
    @Override
    public void onActivityResumed(Activity activity) {
    
    }
    
    @Override
    public void onActivityPaused(Activity activity) {
    
    }
    
    @Override
    public void onActivityStopped(Activity activity) {
    
    }
    
    @Override
    public void onActivitySaveInstanceState(Activity activity,Bundle outState) {
    
    }
    
    @Override
    public void onActivityDestroyed(Activity activity) {

        ActivityRefWatcher.this.onActivityDestroyed(activity);

    }

};

这个回调有什么用呢?这个回调可就厉害了,app里面所有activity什么周期的都会在回调,可看到这里的回调方法都对应的activity的生命周期方法。举个栗子,在app里面随便在哪里调用startActivity. Activity执行onCreate方法时会回调onActivityCreated()方法。以后写退出app的辅助类是,就不用在BaseActivity里面的onCreate()方法里面添加当前activity,在onDestory里面移除当前activity。当然这只是这个回调在其他地方的用处。扯远了.

最最关键的代码来了.将要销毁的activity添加到观察里面

void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
}

调用

/*** Identical to {@link#watch(Object, String)} with an empty string reference name.**@see#watch(Object, String)*/
public void watch(Object watchedReference) {
    watch(watchedReference,"");

}

最终调用重载方法watch。生成一个随机的UUID当做key。把key添加到retainedKeys set集合里面,把key和要检测的对象封装成KeyedWeakReference,而KeyedWeakReference继承了WeakReference,是个弱引用。添加到弱引用中,并不会垃圾回收器对它的回收

public void watch(Object watchedReference,String referenceName) {
    //注释1 ,如果没有启用,则直接返回,一般是debug版本启用。release版本不启动
    if(this==DISABLED) {
        return;
    }
    //检查对象是否为Null
    checkNotNull(watchedReference,"watchedReference");
    
    checkNotNull(referenceName,"referenceName");
    
    //通过两个差值计算某些代码执行时间,比System.currentTimeMillis要精确
    
    final longwatchStartNanoTime = System.nanoTime();
    //生成一个随机uuid作为key
    String key = UUID.randomUUID().toString();
    
    retainedKeys.add(key);
    
    final KeyedWeakReference reference =new KeyedWeakReference(watchedReference,key,referenceName,queue);
    ensureGoneAsync(watchStartNanoTime,reference);

}

特定作了一个实验

longstartcurrentTimeMillis=System.currentTimeMillis();//精确到毫秒

Thread.sleep(100);

System.out.println(System.currentTimeMillis()-startcurrentTimeMillis);

longstartnanoTime=System.nanoTime();//精确到毫微秒

Thread.sleep(100);

System.out.println(System.nanoTime()-startnanoTime);

输出结果

100

100126792

原来它就是可以精确到后面6位数.就和圆周率后面的小数点一样,越多越精确。虽然说毫秒级的误差应该是可以忽略不计的,但也让我们知道了原来还有比毫秒更精确的一个方法的存在,说不定万一哪天就遇到了呢。

继续看代码.

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的execute方法

 @Override 
public void execute(Retryable retryable) {
    //如果在主线程调用
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else {
      postWaitForIdle(retryable, 0);
    }
}

private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
    mainHandler.post(new Runnable() {
      @Override public void run() {
        waitForIdle(retryable, failedAttempts);
      }
    });
}

void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.
    //当主线程的message处理完后,会回调这个接口
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override 
        public boolean queueIdle() {
             postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }
    
查看IdleHandler说明,在looper里面的message暂时处理完了,这个时候会回调这个接口,返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调,
/**
 * Callback interface for discovering when a thread is going to block
 * waiting for more messages.
 */
public static interface IdleHandler {
    /**
     * Called when the message queue has run out of messages and will now
     * wait for more.  Return true to keep your idle handler active, false
     * to have it removed.  This may be called if there are still messages
     * pending in the queue, but they are all scheduled to be dispatched
     * after the current time.
     */
    boolean queueIdle();
}

继续看postToBackgroundWithDelay方法

 private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    //切换到子线程中处理
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        Retryable.Result result = retryable.run();
        //如果需要重新尝试,则继续
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }

继续回到RefWatcher类,ensureGone方法,检测是否内存泄露的核心方法

Retryable.Result ensureGone(final KeyedWeakReference reference, final longwatchStartNanoTime) {

    long gcStartNanoTime = System.nanoTime();
    
    long watchDurationMs =NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    
    //查看弱引用所引用的对象是否被回收了,如果被回收了则retainedKeys集合里面移除当前弱引用的key
    removeWeaklyReachableReferences();
    //如果当前在debug模式,因为我们在debug的时候,一步一步执行代码,所以可能会产生错误的结果,因此在debug模式则重试
    if(debuggerControl.isDebuggerAttached()) {
    
        // The debugger can create false leaks.
        
        return RETRY;
    
    }
    
    //判断弱引用的key是否还在,如果不在了,则说明该对象已经被回收了
    
    if(gone(reference)) {
        return  DONE;
    }
    
    //调用gc方法
    
    gcTrigger.runGc();
    
    //再判断
    
    removeWeaklyReachableReferences();
    
    //如果对象依然存在,则说明该对象已经泄露了,然后获取dumpHeap文件,分析dumHeap文件
    
    if(!gone(reference)) {
    
        long startDumpHeap = System.nanoTime();
        
        long gcDurationMs =NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
        
        File heapDumpFile =heapDumper.dumpHeap();
        
        if(heapDumpFile ==RETRY_LATER) {
            // Could not dump the heap.
            return RETRY;
        
        }
        
        long heapDumpDurationMs =NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
        
        //发送到分析服务进程去分析dumHeap文件
        
        heapdumpListener.analyze(new HeapDump(heapDumpFile,reference.key,reference.name,excludedRefs,watchDurationMs,
        gcDurationMs,heapDumpDurationMs));
        
    }
    return DONE;

}

private void removeWeaklyReachableReferences() {

    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    
    // reachable. This is before finalization or garbage collection has actually happened.
    
    KeyedWeakReference ref;
    
    //如果弱引用所引用的对象没有被垃圾回收器回收了,则queue.poll()返回null,如果回收了,则返回该对象
    
    while((ref = (KeyedWeakReference)queue.poll()) !=null) {
    
        retainedKeys.remove(ref.key);
    
    }

}

//检查当前引用的key是否在集合当中

private boolean gone(KeyedWeakReference reference) {

    return !retainedKeys.contains(reference.key);

}

如果在还集合当中,运行gc,gc调用的是Runtime.getRuntime()类的gc方法,通过注释可以看到.Syetem.gc并不会每次调用都会触发gc操作,

@Override
public void runGc() {

    // Code taken from AOSP FinalizationTest:
    
    // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
    
    // java/lang/ref/FinalizationTester.java
    
    // System.gc() does not garbage collect every time. Runtime.gc() is
    // more likely to perfom a gc.
    
    Runtime.getRuntime().gc();
    
    enqueueReferences();
    
    System.runFinalization();

}

最费解的当属弱引用和其引用列队的联合使用,才能判断当前弱引用所应用的对象是否被回收了。

这里特例举个栗子说明弱引用如何判断所引用的对象是否被回收了

Person person=new Person();

ReferenceQueue queue=new ReferenceQueue();

final KeyedWeakReference reference =

new KeyedWeakReference(person,"123","456",queue);

person=null;//显示将person置为null。

System.out.println("queue.poll():"+queue.poll());



System.gc();

try{

    Thread.sleep(500);
    
    }catch(InterruptedException e) {
    
    throw newAssertionError();

}

System.runFinalization();

System.out.println("queue.poll():"+queue.poll());


输出结果
queue.poll():null
finalize.....
queue.poll():com.example.lib.KeyedWeakReference@75b84c92

当我们将person置为null时,垃圾回收器并不会立即回收person对象,所以第一个poll的结果是null.当我们调用gc方法后,垃圾回收器发现person为null了(因为调用gc后垃圾回收器并不会立即执行,所以加延迟),于是就回收person对象,所以第二次打印的结果就不一样了。

通过上次测试发现,如果引用列队里面的弱引用不为null。则表示该弱引用的对象已经被回收了。因此可以判断一个对象是否被回收了。如果没有回收,就造成内存泄露
至于如何读取内存泄露的引用链信息,有兴趣可以自行研究。

通过源码我们可以学到

1 如果一些不太重要的方法,可以放到Looper.myQueue().addIdleHandler接口里面去执行,这里的优先级比message低。
2 利用弱引用和引用队列可以判断一个类是否被回收
3 如果想快点执行垃圾回收,则调用Runtime.getRuntime().gc()

感谢以下文章:leak-canary-read-me

相关文章

  • LeakCanary 原理分析

    本文主要内容 1、Reference 简介 2、LeakCanary 使用 3、LeakCanary 源码分析 L...

  • LeakCanary 2.0源码分析与总结

    本文基于LeakCanary 2.0源码分析LeakCanary - 官方地址LeakCanary - GitHu...

  • LeakCanary源码分析2

    LeakCanary是Square公司基于MAT开源的一个内存泄漏检测工具,在发生内存泄漏的时候LeakCanar...

  • LeakCanary原理

    一、源码 LeakCanary内存分析模块,独立进程,包名:leakcanary,保持app进程独立。在Appli...

  • LeakCanary原理整理

    主要描述LeakCanary的检查原理,具体实现过程,可以查看源码分析,2.x可以对View做分析检查。 XMin...

  • LeakCanary 源码分析

    前言 本篇LeakCanary源码分析,基于1.6.3版本,使用Java编写,而不是最新的2.x版本。 2.x版本...

  • leakcanary源码分析

    leakcanary LeakCananry 是 Square 公司开源的一个针对 Android 的内存泄漏检测...

  • LeakCanary源码分析

    前言 最近高产似母猪,闲下来的时候就喜欢找找源码看。昨天看了下LeakCanary,准备来分析一波。 导入 gra...

  • LeakCanary源码分析

    大家好,我是苍王。 以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。 [Androi...

  • leakcanary源码分析

    在Application中初始化 使用build设计模式,创建RefWatcher对象 设置heap监听 Serv...

网友评论

      本文标题:LeakCanary源码分析2

      本文链接:https://www.haomeiwen.com/subject/olkokctx.html