前言
LeakCanary是Square公司提供的用于Android检测内存的小工具,他能帮助我们快速定位代码隐藏的BUG,减少OOM的机会。
此处为git地址链接:https://github.com/square/leakcanary
题外话:
Square真的是家良心公司,提供了很多有名的组件。后续会整理目前市面上有名的组件。比如Facebook的开源组件... 现在先介绍下Square有哪些开源组件
OKHttp 一个开源稳定的Http的通信依赖库,感觉比HttpUrlConnection好用,
okhttp现在已经得到Google官方的认可了。
okie OKHttp依赖这个库
dagger 快速依赖注入框架。现在已经由google公司维护了,
现在应该是dagger2.官网地址:https://google.github.io/dagger/
picasso 一个图片缓存库,可以实现图片的下载和缓存功能
retrofit 是一个RESTFUL(自行百度)的Http网络请求框架的封装,基于OKHttp,retrofit在于对接口的封装,实质是使用OKHttp进行网络请求
leakcanary 一个检测内存的小工具,本文说的就是这个
otto Android事件总线,降低代码的耦合性,可以跟EventBus做比较
...
回到正文,现在开始讲解LeakCanary的使用
使用LeakCanary
其实可以参考leakcanary的Sample介绍
- 首先在build.gradle中引用
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.2.0'
compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha9'
testCompile 'junit:junit:4.12'
// LeakCanary
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
- 在Application的onCreate添加方法
public class ExampleApp extends Application{
@Override
public void onCreate() {
super.onCreate();
// LeakCanary初始化
LeakCanary.install(this);
}
}
- 在App中添加内存泄漏代码,本文就参照sample中的的例子,写了一个SystemClock.sleep(20000);
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button asynTaskBtn = (Button) this.findViewById(R.id.async_task);
asynTaskBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAsyncTask();
}
});
}
private void startAsyncTask() {
// This async task is an anonymous class and therefore has a hidden reference to the outer
// class MainActivity. If the activity gets destroyed before the task finishes (e.g. rotation),
// the activity instance will leak.
new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... params) {
// Do some slow work in background
SystemClock.sleep(20000);
return null;
}
}.execute();
}
}
-
运行,会发现出现一个LeakCanary的图标。后续会介绍这个图标是如何出现的,当出现内存泄漏的时候,会在通知栏显示一条内存泄漏通知,点击通知会进入内存泄漏的具体问题。
根据图标显示我们能够看出内存泄漏在AsyncTask中,根据AsyncTask中进行内存修改
讲解完如何使用,现在开始讲解LeakCanary。
LeakCanary详解
代码目录结构
.
├── AbstractAnalysisResultService.java
├── ActivityRefWatcher.java -- Activity监控者,监控其生命周期
├── AndroidDebuggerControl.java --Android Debug控制开关,就是判断Debug.isDebuggerConnected()
├── AndroidExcludedRefs.java -- 内存泄漏基类
├── AndroidHeapDumper.java --生成.hrpof的类
├── AndroidWatchExecutor.java -- Android监控线程,延迟5s执行
├── DisplayLeakService.java -- 显示通知栏的内存泄漏,实现了AbstractAnalysisResultService.java
├── LeakCanary.java --对外类,提供install(this)方法
├── ServiceHeapDumpListener.java
└── internal --这个文件夹用于显示内存泄漏的情况(界面相关)
├── DisplayLeakActivity.java --内存泄漏展示的Activity
├── DisplayLeakAdapter.java
├── DisplayLeakConnectorView.java
├── FutureResult.java
├── HeapAnalyzerService.java 在另一个进程启动的Service,用于接收数据并发送数据到界面
├── LeakCanaryInternals.java
├── LeakCanaryUi.java
└── MoreDetailsView.java
对外方法LeakCanary.install(this)
实际上LeakCanary对外提供的方法只有
LeakCanary.install(this);
从这里开始切入,对应源码
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {
return install(application, DisplayLeakService.class,
AndroidExcludedRefs.createAppDefaults().build());
}
/**
* Creates a {@link RefWatcher} that reports results to the provided service, and starts watching
* activity references (on ICS+).
*/
public static RefWatcher install(Application application,
Class<? extends AbstractAnalysisResultService> listenerServiceClass,
ExcludedRefs excludedRefs) {
// 判断是否在Analyzer进程
if (isInAnalyzerProcess(application)) {
return RefWatcher.DISABLED;
}
// 允许显示内存泄漏情况Activity
enableDisplayLeakActivity(application);
HeapDump.Listener heapDumpListener =
new ServiceHeapDumpListener(application, listenerServiceClass);
RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
return refWatcher;
}
为什么LeakCanary要求在4.0以上
<mark>通过注释能看出这个LeakCanary是用于4.0以上的方法
references (on ICS+).
为什么要使用在4.0以上呢?
ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
这句方法告诉我们这个是用在Ics+(即4.0版本以上),那这个类ActivityRefWatcher
具体使用来干什么的呢
@TargetApi(ICE_CREAM_SANDWICH) public final class ActivityRefWatcher {
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
if (SDK_INT < ICE_CREAM_SANDWICH) {
// If you need to support Android < ICS, override onDestroy() in your base activity.
return;
}
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
activityRefWatcher.watchActivities();
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
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);
}
};
private final Application application;
private final RefWatcher refWatcher;
/**
* Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
* after they have been destroyed.
*/
public ActivityRefWatcher(Application application, final RefWatcher refWatcher) {
this.application = checkNotNull(application, "application");
this.refWatcher = checkNotNull(refWatcher, "refWatcher");
}
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
}
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);这个方法使用在Android4.0上的,用于观察Activity的生命周期。从上面代码看出,LeakCanary监听Activity的销毁操作
ActivityRefWatcher.this.onActivityDestroyed(activity);
LeakCanary如何出现LeakCanry的图标
public static void setEnabled(Context context, final Class<?> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
// 耗时操作
executeOnFileIoThread(new Runnable() {
@Override
public void run() {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
});
}
在install的方法执行的时候调用了
// 允许显示内存泄漏情况Activity
enableDisplayLeakActivity(application);
这个方法执行了上面显示的方法setEnable.最核心的方法是packageManager.setComponentEnabledSetting。
这个方法可以用来隐藏/显示应用图标
具体可以参照android 禁用或开启四大组件setComponentEnabledSetting
[重点]LeakCanary如何捕获内存泄漏
通过Debug.dumpHprofData()方法生成.hprof文件,然后利用开源库HAHA
(开源地址:https://github.com/square/haha)解析.hprof文件,并发送给DisplayLeakActivity进行展示
public final class AndroidHeapDumper implements HeapDumper {
private static final String TAG = "AndroidHeapDumper";
private final Context context;
private final Handler mainHandler;
public AndroidHeapDumper(Context context) {
this.context = context.getApplicationContext();
mainHandler = new Handler(Looper.getMainLooper());
}
@Override public File dumpHeap() {
if (!isExternalStorageWritable()) {
Log.d(TAG, "Could not dump heap, external storage not mounted.");
}
File heapDumpFile = getHeapDumpFile();
if (heapDumpFile.exists()) {
Log.d(TAG, "Could not dump heap, previous analysis still is in progress.");
// Heap analysis in progress, let's not put too much pressure on the device.
return NO_DUMP;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);
if (!waitingForToast.wait(5, SECONDS)) {
Log.d(TAG, "Did not dump heap, too much time waiting for Toast.");
return NO_DUMP;
}
Toast toast = waitingForToast.get();
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
return heapDumpFile;
} catch (IOException e) {
cleanup();
Log.e(TAG, "Could not perform heap dump", e);
// Abort heap dump
return NO_DUMP;
}
}
/**
* Call this on app startup to clean up all heap dump files that had not been handled yet when
* the app process was killed.
*/
public void cleanup() {
LeakCanaryInternals.executeOnFileIoThread(new Runnable() {
@Override public void run() {
if (isExternalStorageWritable()) {
Log.d(TAG, "Could not attempt cleanup, external storage not mounted.");
}
File heapDumpFile = getHeapDumpFile();
if (heapDumpFile.exists()) {
Log.d(TAG, "Previous analysis did not complete correctly, cleaning: " + heapDumpFile);
heapDumpFile.delete();
}
}
});
}
private File getHeapDumpFile() {
return new File(storageDirectory(), "suspected_leak_heapdump.hprof");
}
private void showToast(final FutureResult<Toast> waitingForToast) {
mainHandler.post(new Runnable() {
@Override public void run() {
final Toast toast = new Toast(context);
toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
toast.setDuration(Toast.LENGTH_LONG);
LayoutInflater inflater = LayoutInflater.from(context);
toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
toast.show();
// Waiting for Idle to make sure Toast gets rendered.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
waitingForToast.set(toast);
return false;
}
});
}
});
}
private void cancelToast(final Toast toast) {
mainHandler.post(new Runnable() {
@Override public void run() {
toast.cancel();
}
});
}
}
检测时机
在Activity销毁的时候会执行RefWatch.watch方法,然后就去执行内存检测
这里又看到一个比较少的用法,IdleHandler,IdleHandler的原理就是在messageQueue因为空闲等待消息时给使用者一个hook。那AndroidWatchExecutor会在主线程空闲的时候,派发一个后台任务,这个后台任务会在DELAY_MILLIS时间之后执行。LeakCanary设置的是5秒。
public void watch(Object watchedReference, String referenceName) {
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
if (debuggerControl.isDebuggerAttached()) {
return;
}
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
watchExecutor.execute(new Runnable() {
@Override public void run() {
ensureGone(reference, watchStartNanoTime);
}
});
}
public final class AndroidWatchExecutor implements Executor {
static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
private static final int DELAY_MILLIS = 5000;
private final Handler mainHandler;
private final Handler backgroundHandler;
public AndroidWatchExecutor() {
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
}
@Override public void execute(final Runnable command) {
if (isOnMainThread()) {
executeDelayedAfterIdleUnsafe(command);
} else {
mainHandler.post(new Runnable() {
@Override public void run() {
executeDelayedAfterIdleUnsafe(command);
}
});
}
}
private boolean isOnMainThread() {
return Looper.getMainLooper().getThread() == Thread.currentThread();
}
private void executeDelayedAfterIdleUnsafe(final Runnable runnable) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
backgroundHandler.postDelayed(runnable, DELAY_MILLIS);
return false;
}
});
}
}
Fragment如何使用LeakCanary
如果我们想检测Fragment的内存的话,可以在Application中将返回的RefWatcher存下来,可以在Fragment的onDestroy中watch它。
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}