Preface
Leak Canary is a small tool for Android memory detection provided by Square. It can help us locate code hidden BUG quickly and reduce the chance of OOM.
Here is the git address link: https://github.com/square/leakcanary
Side Topic: Square is really a conscientious company, providing many well-known components. Subsequently, the well-known components on the market will be sorted out. For example, Facebook's Open Source Components... Now let's talk about Square's Open Source Components.
OKHttp is an open source and stable Http communication dependency library. It feels better than HttpUrlConnection. okhttp is now officially recognized by Google. Okie OKHttp relies on this library dagger fast dependency injection framework. Now it's maintained by google. Now it should be Dagger 2. Official address: https://google.github.io/dagger/ picasso, a picture cache library, can download and cache pictures Retrofit is an encapsulation of the Http network request framework of RESTFUL (Baidu). Based on OKHttp, retrofit is the encapsulation of the interface. In essence, it uses OKHttp to make network requests. leakcanary is a small tool to detect memory. This is what I'm talking about in this article. otto Android Event Bus, which reduces code coupling, can be compared with EventBus. ...
Back to the text, let's start with the use of Leak Canary.
Use LeakCanary
Actually, it can be referred to. Introduction to leakcanary's Sample
- First of all, it's referenced in 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' }
- Adding methods to onCreate in Application
public class ExampleApp extends Application{ @Override public void onCreate() { super.onCreate(); // LeakCanary initialization LeakCanary.install(this); } }
- Adding memory leak code to App, this paper writes a System Clock. sleep (20000) referring to the example in sample.
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(); } }
- Running, you will find a LeakCanary icon. Later, we will introduce how this icon appears. When there is a memory leak, a memory leak notification will be displayed in the notification bar. Clicking on the notification will enter the specific problem of memory leak.LeakCanary Icon
According to the icon, we can see that the memory leak is in AsyncTask, and we can modify the memory according to AsyncTask.
After explaining how to use it, let's start with Leak Canary.
Leak Canary
Code directory structure
. ├── AbstractAnalysisResultService.java - ActivityRefWatcher. Java - Activity monitors their life cycles - Android Debugger Control. Java - Android Debugger Control Switch is to judge Debug. is Debugger Connected () - Android ExcludedRefs. Java -- Memory leak base class - Android HeapDumper. Java -- Generating. hrpof classes - Android WatchExecutor. Java - Android monitors threads and delays execution for 5s - DisplayLeakService.java -- Displays memory leaks in the notification bar, implements AbstractAnalysis ResultService. Java -- LeakCanary.java -- Provides install(this) methods for classes ├── ServiceHeapDumpListener.java - internal -- This folder is used to display memory leaks (interface related) - DisplayLeak Activity. Java -- Activeness for Memory Leak Display ├── DisplayLeakAdapter.java ├── DisplayLeakConnectorView.java ├── FutureResult.java - HeapAnalyrService. Java is a Service launched by another process to receive data and send it to the interface ├── LeakCanaryInternals.java ├── LeakCanaryUi.java └── MoreDetailsView.java
LeakCanary.install(this)
In fact, LeakCanary offers only a way out.
LeakCanary.install(this);
Start from here, corresponding to the 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 Analyr process is in progress if (isInAnalyzerProcess(application)) { return RefWatcher.DISABLED; } // Activity Allows Display of Memory Leakage enableDisplayLeakActivity(application); HeapDump.Listener heapDumpListener = new ServiceHeapDumpListener(application, listenerServiceClass); RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs); ActivityRefWatcher.installOnIcsPlus(application, refWatcher); return refWatcher; }
Why Leak Canary requires more than 4.0
<mark> You can see from the annotations that LeakCanary is a method for more than 4.0
references (on ICS+).
Why use more than 4.0?
ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
This method tells us that this is used for Ics + (i.e. over version 4.0), so what is the specific use of this kind of activity RefWatcher?
@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. registerActivity Lifecycle Callbacks (lifecycle Callbacks); this method is used on Android 4.0 to observe the life cycle of Activity. As you can see from the above code, LeakCanary monitors the destruction of Activity
ActivityRefWatcher.this.onActivityDestroyed(activity);
How LeakCanary's icon appears
public static void setEnabled(Context context, final Class<?> componentClass, final boolean enabled) { final Context appContext = context.getApplicationContext(); // time-consuming operation 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); } }); }
Called when the install method executes
// Activity Allows Display of Memory Leakage enableDisplayLeakActivity(application);
This method executes the method setEnable shown above. The core method is packageManager. setComponentEnabled Setting.
This method can be used to hide / display application icons
Specific reference can be made. android disables or opens four components setComponentEnabled Setting
How Leak Canary Captures Memory Leaks
The. hprof file is generated by Debug.dumpHprofData() method, and then the open source library HAHAHA (open source address: https://github.com/square/haha ) Parse the. hprof file 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() { 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(); } }); } }
Detection opportunity
The RefWatch.watch method is executed when Activity is destroyed, and then memory detection is performed.
Here we see a relatively small usage, IdleHandler, the principle of IdleHandler is to give users a hook when they are idle waiting for messages. The Android Watch Executor will dispatch a background task when the main thread is idle, which will be executed after the DELAY_MILLIS time. Leak Canary 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() { 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; } }); } }
How Fragment uses LeakCanary
If we want to detect Fragment's memory, we can save the returned RefWatcher in Application and watch it in Fragment's onDestroy.
public abstract class BaseFragment extends Fragment { @Override public void onDestroy() { super.onDestroy(); RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity()); refWatcher.watch(this); } }
Reference resources LeakCanary Open Source Project
Other References
Research on Leak Canary Memory Leak Monitoring Principle
Analysis of LeakCanary Source Code for Android Memory Leak Checking Tool