preface
LeakCanary is a small tool provided by Square for Android to detect memory. It can help us quickly locate code hidden bugs and reduce the chance of OOM.
Here is the git address link: github.com/square/leak…
Side note :Square is really a conscience company, offering a lot of well-known components. The following will sort out the well-known components on the market. Like Facebook’s open source components… Let’s take a look at what open source components Square has
OKHttp is an open source stable Http communication dependency library that feels better than HttpUrlConnection. OKHttp is now officially approved by Google. Okie OKHttp relies on the library dagger fast dependency injection framework. It is now maintained by Google and should be dagger2. Official website: HTTPS://google.github.io/dagger/Retrofit is an encapsulation of a RESTFUL Http web request framework based on OKHttp. Retrofit is designed to encapsulate interfaces. The otto Android EventBus is the same as the otto Android EventBus, which reduces the code coupling and can be compared to EventBus.Copy the code
Back to the main text, now to the use of LeakCanary
Using LeakCanary
In fact, you can refer to the Sample of Leakcanary
- The first reference is in build.gradle
dependencies { compile fileTree(dir: 'libs', include: [' *. Jar ']) androidTestCompile (' com. Android. Support. The test. The espresso: espresso - core: 2.2.2 ', {exclude group: 'com.android.support', module: 'the support - annotations'}) compile' com. Android. Support: appcompat - v7:25.2.0 'compile 'com. Android. Support. The constraint, the 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'}Copy the code
- Add a method to Application onCreate
public class ExampleApp extends Application{
@Override
public void onCreate(a) {
super.onCreate();
// LeakCanary is initialized
LeakCanary.install(this); }}Copy the code
- 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(a) {
// 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(); }}Copy the code
- Run and you will see an icon for LeakCanary. We’ll explain how this icon appears later. When a memory leak occurs, a memory leak notification is displayed in the notification bar. Clicking on the notification will take you to the specific problem.
LeakCanary icon
According to the icon, we can see the memory leak in the AsyncTask, according to the AsyncTask memory modification
Having explained how to use this, let’s start with LeakCanary.
LeakCanary,
Code directory structure
. ├ ─ ─ AbstractAnalysisResultService. Java ├ ─ ─ ActivityRefWatcher. Java - Activity monitoring, Monitoring its lifecycle ├ ─ ─ AndroidDebuggerControl. Java, Android Debug control switch, Is to determine the Debug. IsDebuggerConnected () ├ ─ ─ AndroidExcludedRefs. Java memory leak - base class ├ ─ ─ AndroidHeapDumper. Java - generated. Hrpof class ├ ─ ─ AndroidWatchExecutor. Java -- -- Android monitoring thread, delay5S executive ├ ─ ─ DisplayLeakService. Java -- -- show notification bar memory leaks, implements the AbstractAnalysisResultService. Java ├ ─ ─ LeakCanary. Java - foreign class, provide the install (this) method ├ ─ ─ ServiceHeapDumpListener. Java └ ─ ─ internal - this folder is used to display the memory leak (interface) related to the condition of ├ ─ ─ DisplayLeakActivity. Java - memory leak show Activity ├ ─ ─ DisplayLeakAdapter. Java ├ ─ ─ DisplayLeakConnectorView. Java ├ ─ ─ FutureResult. Java ├ ─ ─ Java A Service started in another process to receive data and send it to the interface ├─ LeakCanaryInternals. Java ├─ leakcanaryui.java ├─ MoreDetailsView.javaCopy the code
External method LeakCanary. Install (this)
In fact, LeakCanary provides only one method
LeakCanary.install(this);Copy the code
Start from here, corresponding source code
/**
* 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) {
// Determine whether the Analyzer is in process
if (isInAnalyzerProcess(application)) {
return RefWatcher.DISABLED;
}
// Allows memory leaks to be displayed
enableDisplayLeakActivity(application);
HeapDump.Listener heapDumpListener =
new ServiceHeapDumpListener(application, listenerServiceClass);
RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
return refWatcher;
}Copy the code
Why does LeakCanary require above 4.0
<mark> You can see from the comments that this LeakCanary is used for methods above 4.0
references (on ICS+).Copy the code
Why use above 4.0?
ActivityRefWatcher.installOnIcsPlus(application, refWatcher);Copy the code
The ActivityRefWatcher class is used for Ics+(version 4.0 and above)
@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(a) {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities(a) { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks); }}Copy the code
application.registerActivityLifecycleCallbacks(lifecycleCallbacks); This method is used on Android4.0 to observe the Activity lifecycle. As you can see from the code above, LeakCanary listens for the Activity’s destruction operation
ActivityRefWatcher.this.onActivityDestroyed(activity);Copy the code
How does LeakCanary show the LeakCanry icon
public static void setEnabled(Context context, finalClass<? > componentClass,final boolean enabled) {
final Context appContext = context.getApplicationContext();
// Time consuming operation
executeOnFileIoThread(new Runnable() {
@Override
public void run(a) {
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); }}); }Copy the code
Called when install’s method executes
// Allows memory leaks to be displayed
enableDisplayLeakActivity(application);Copy the code
This method performs the setEnable shown above method. The core of the method is packageManager setComponentEnabledSetting. This method can be used to hide/show application icon Specific can consult android setComponentEnabledSetting disable or open four components
[Key point] LeakCanary How do I catch a memory leak
Use the debug.dumphprofData () method to generate a. Hprof file, parse the. Hprof file using the open-source library HAHA(open source address :github.com/square/haha), and send it to DisplayLeakActivity for display
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(a) {
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
returnNO_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(a) {
LeakCanaryInternals.executeOnFileIoThread(new Runnable() {
@Override public void run(a) {
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(a) {
return new File(storageDirectory(), "suspected_leak_heapdump.hprof");
}
private void showToast(final FutureResult<Toast> waitingForToast) {
mainHandler.post(new Runnable() {
@Override public void run(a) {
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(a) {
waitingForToast.set(toast);
return false; }}); }}); }private void cancelToast(final Toast toast) {
mainHandler.post(new Runnable() {
@Override public void run(a) { toast.cancel(); }}); }}Copy the code
Testing time
The refwatch. watch method is executed when the Activity is destroyed, and then memory checks are performed
The IdleHandler principle is to hook the user when the messageQueue is idle for a message. AndroidWatchExecutor will send out a background task when the main thread is idle, which will be executed after DELAY_MILLIS time. LeakCanary is set to 5 seconds.
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(a) { ensureGone(reference, watchStartNanoTime); }}); }Copy the code
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(a) {
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(a) { executeDelayedAfterIdleUnsafe(command); }}); }}private boolean isOnMainThread(a) {
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(a) {
backgroundHandler.postDelayed(runnable, DELAY_MILLIS);
return false; }}); }}Copy the code
How does Fragment use LeakCanary
If we want to detect the Fragment’s memory, we can save the returned RefWatcher in the Application. We can watch it in the Fragment’s onDestroy.
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy(a) {
super.onDestroy();
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
refWatcher.watch(this); }}Copy the code
See the LeakCanary open source project
Other references
LeakCanary Memory leak monitoring principle
Android memory leak check tool LeakCanary