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);
}
LeakCanary
的isInAnalyzerProcess
方法
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
网友评论