LeakCanary introduction
LeakCanary provides a very convenient way for us to test for memory leaks during development, we don’t need to analyze the cause of memory leaks by memory blocks ourselves, we just need to integrate it into the project and it will help us detect memory leaks and give us a reference chain of memory leaks
integration
- Add dependencies to Gradle without distinction between debug and release
implementation 'com. Squareup. Leakcanary: leakcanary - android: 1.5.1'
Copy the code
- Rewrite the Application
public class App extends Application {
private RefWatcher mRefWatcher;
@Override
public void onCreate(a) {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
mRefWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
App application = (App) context.getApplicationContext();
returnapplication.mRefWatcher; }}Copy the code
- For example, we create a memory leak in the Activity where the Handler sends delayed messages
public class ActivityOne extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_one);
new Handler().postDelayed(new Runnable() {
@Override
public void run(a) {}},100000);
}
@Override
protected void onDestroy(a) {
super.onDestroy(); }}Copy the code
- Then we open the activity and close it, and a Leaks icon appears on the desktop, and we open it
If you want to monitor other memory leaks, for example, if you want to monitor Fragment memory leaks, you can write: actively monitor the object you want to monitor
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy(a) {
super.onDestroy();
RefWatcher refWatcher = App.getRefWatcher(getActivity());
refWatcher.watch(this); }}Copy the code
The principle of overview
By listening to the onDestory of the Activity, manually calling GC, and then through the ReferenceQueue+WeakReference, judge whether the Activity object is reclaimed, and then combined with the HPOF file of dump Heap, analyze the leak location through Haha open source library
Key points
Registers listeners for the Activity’s lifecycle
Through the Application. RegisterActivityLifecycleCallbacks () method to register the Activity lifecycle listener, Every Actvity lifecycle callback to the ActivityLifecycleCallbacks, if an Activity to the onDestory, that means he will no longer exist, and then test whether this Activity is really be destroyed
ReferenceQueue+WeakReference is used to judge whether the object is reclaimed
When WeakReference is created, a ReferenceQueue object can be passed in. If the reference object in WeakReference is reclaimed, then the WeakReference object will be added to the ReferenceQueue. The ReferenceQueue is null to determine whether the ReferenceQueue is reclaimed
Detailed recommendations blog: www.jianshu.com/p/964fbc301…
MessageQueue adds an IdleHandler to get the main thread idle callback
For details, see Android Handler source code Parsing
After calling GC manuallySystem.runFinalization();
This forces a call to the Finalize method of an object that has lost its reference
In accessibility algorithm, inaccessible objects, also do not have to die, when they are in the stage of “probation”, proclaim an object really need at least two death mark phase, if found no reference object, will mark for the first time, and conduct a screening, screening is the condition of whether these objects are necessary to finalize () method, When an object does not override Finalize (), or when Finalize () has already been called, both are considered “not necessary to execute.”
In Apolication, processName can be used to determine whether the task execution process is correct
The process is judged by processName
public static boolean isInServiceProcess(Context context, Class<? extends Service> 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;
}
String mainProcess = packageInfo.applicationInfo.processName;
ComponentName component = new ComponentName(context, serviceClass);
ServiceInfo serviceInfo;
try {
serviceInfo = packageManager.getServiceInfo(component, 0);
} 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;
}
int myPid = android.os.Process.myPid();
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.RunningAppProcessInfo myProcess = null;
List<ActivityManager.RunningAppProcessInfo> 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);
}
Copy the code
Source code analysis
The SDK initialization
mRefWatcher = LeakCanary.install(this);
Copy the code
This is the method that the SDK exposes to the outside, and we use this as the entry point for source analysis
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
Copy the code
The install method first initializes an AndroidRefWatcherBuilder class and then sets DisplayLeakService via the listenerServiceClass method. This class is used to analyze the result information of memory leaks and then send a notification to the user
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
/**
* Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
* overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
*/
public AndroidRefWatcherBuilder listenerServiceClass( Class
listenerServiceClass) {
return heapDumpListener(newServiceHeapDumpListener(context, listenerServiceClass)); }... }public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {.../ * *@see HeapDump.Listener */
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
returnself(); }... }Copy the code
In the AndroidExcludedRefs enumeration class, the ignore list information is defined. If there is a memory leak in the list of classes, it will not be displayed. HeapAnalyzer will also ignore the classes when calculating the GCRoot strong reference path. If you want a class in your project to leak, but you don’t want it to show, you can add the class to it
public enum AndroidExcludedRefs {
// ######## Android SDK Excluded refs ########
ACTIVITY_CLIENT_RECORD__NEXT_IDLE(SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {
@Override void add(ExcludedRefs.Builder excluded) {
excluded.instanceField("android.app.ActivityThread$ActivityClientRecord"."nextIdle")
.reason("Android AOSP sometimes keeps a reference to a destroyed activity as a"
+ " nextIdle client record in the android.app.ActivityThread.mActivities map."
+ " Not sure what's going on there, input welcome."); }}... }Copy the code
Finally, the buildAndInstall method is called, and a RefWatcher object is created and returned. This object is used to detect memory leaks due to unreclaimed objects
/**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall(a) {
RefWatcher refWatcher = build();
if(refWatcher ! = DISABLED) { LeakCanary.enableDisplayLeakActivity(context); ActivityRefWatcher.install((Application) context, refWatcher); }return refWatcher;
}
Copy the code
Since the leak analysis is performed in another process, determine whether the currently started Application is in the leak analysis process. If yes, DISABLED is returned and no subsequent initialization is performed. If it is found to be in the main process of the program, initialization is performed
LeakCanary.enableDisplayLeakActivity(context); The main function is to call PackageManager to set DisplayLeakActivity to available.
public static void enableDisplayLeakActivity(Context context) {
setEnabled(context, DisplayLeakActivity.class, true);
}
public static void setEnabled(Context context, finalClass<? > componentClass,final boolean enabled) {
final Context appContext = context.getApplicationContext();
executeOnFileIoThread(new Runnable() {
@Override public void run(a) { setEnabledBlocking(appContext, componentClass, enabled); }}); }public static void setEnabledBlocking(Context appContext, Class<? > componentClass,boolean enabled) {
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
The LeakCanary profile shows that these files are running in a new process. DisplayLeakActivity defaults to Enable =false so that the launch icon can be hidden at first
<application> <service android:name=".internal.HeapAnalyzerService" android:process=":leakcanary" android:enabled="false"/> <service android:name=".DisplayLeakService" android:process=":leakcanary" android:enabled="false"/> <activity android:theme="@style/leak_canary_LeakCanary.Base" android:name=".internal.DisplayLeakActivity" android:process=":leakcanary" android:enabled="false" android:label="@string/leak_canary_display_activity_label" android:icon="@drawable/leak_canary_icon" android:taskAffinity="com.squareup.leakcanary.${applicationId}"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:theme="@style/leak_canary_Theme.Transparent" android:name=".internal.RequestStoragePermissionActivity" android:process=":leakcanary" android:taskAffinity="com.squareup.leakcanary.${applicationId}" android:enabled="false" android:excludeFromRecents="true" android:icon="@drawable/leak_canary_icon" android:label="@string/leak_canary_storage_permission_activity_label"/> </application>Copy the code
Then ActivityRefWatcher. Install ((Application) context, refWatcher); RefWatcher is passed in as an argument and the Activity lifecycle is monitored
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
public void watchActivities(a) {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities(a) {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
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); }};void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
Copy the code
The listener lifecycleCallbacks will listen to all Activity lifecybacks in your project. OnActivityDestroyed will be called as the Activity is destroyed. LeakCanary picks up the Activity and analyzes it to see if there is a memory leak
Analyzing memory leaks
Here the analysis object whether the memory leak isRefWatcher
Class. Here is a brief introduction to the member variables of this class
- WatchExecutor WatchExecutor: Ensures that the task is running on the main thread, and defaults to a 5s delay for the system GC
- DebuggerControl DebuggerControl: control center
- GcTrigger GcTrigger: An internal call
Runtime.getRuntime().gc()
To manually trigger GC - HeapDumper HeapDumper: Used to create a.hprof file that stores a snapshot of the head heap to see which programs are using most of the memory
- HeapdumpListener: This Listener is told when the analysis result is complete
- ExcludedRefs ExcludedRefs: indicates the whitelist for analyzing memory leaks
As you can see, whenever the Activity is destroyed, RefWatcher’s watch method is called to analyze whether it is a memory leak
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
Copy the code
This code places a random number key in the retainedKeys container to determine whether an object is reclaimed, creates a weak reference, saves the Activity object to be analyzed, and then calls the ensureGoneAsync method
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run(a) {
returnensureGone(reference, watchStartNanoTime); }}); }Copy the code
The watchExecutor is then used to schedule the analysis task, which is mainly to ensure that it is delayed for 5 seconds on the main thread to allow the system time for GC
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
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);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
private void removeWeaklyReachableReferences(a) {
// 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;
while((ref = (KeyedWeakReference) queue.poll()) ! =null) { retainedKeys.remove(ref.key); }}private boolean gone(KeyedWeakReference reference) {
return! retainedKeys.contains(reference.key); }Copy the code
First by removeWeaklyReachableReferences () method, try to get to be analysed from a weak reference queue object, if not the system is empty that recycling, is the key value of retainedKeys removed, if the system recycling returns DONE directly, if has not been recycling system, Call gctrigger.rungc () manually; Manual trigger system gc, then call again removeWeaklyReachableReferences () method, or null, if the judge as memory leaks
GcTrigger DEFAULT = new GcTrigger() {
@Override public void runGc(a) {
// 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();
}
private void enqueueReferences(a) {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw newAssertionError(); }}};Copy the code
The enqueueReferences method is used to suspend GC for 100ms. System.runfinalization (); This forces a call to the Finalize method of an object that has lost its reference
When a memory leak is determined, call heapDumper.Dumpheap (); Generate the.hprof file and then call back to the heapdumpListener listener, which implements the ServiceHeapDumpListener class and calls the Analyze () method
public final class ServiceHeapDumpListener implements HeapDump.Listener {...@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump"); HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass); }}Copy the code
Heapdumps is a modle class, which is used to store some information analysis of such strong reference need HeapAnalyzerService. RunAnalysis method is to send an intent, launched HeapAnalyzerService service, This is an intentService
public static void runAnalysis(Context context, HeapDump heapDump, Class
listenerServiceClass) {
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
context.startService(intent);
}
Copy the code
After the service is started, the analysis will start in the onHandleIntent method to find the memory leak reference
@Override
protected void onHandleIntent(Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
Copy the code
Finding references
- One is instantiated after the memory leak analysis service is started
HeapAnalyzer
Object, and then callcheckForLeak
Method to analyze the final result, checkForLeak
Here’s another Square library. Haha, ha, ha, ha, ha, ha, ha, ha.Github.com/square/haha…- Call when you get the result
AbstractAnalysisResultService.sendResultToListener()
Method that starts another service
public static void sendResultToListener(Context context, String listenerServiceClassName, HeapDump heapDump, AnalysisResult result) { Class<? > listenerServiceClass;try {
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Intent intent = new Intent(context, listenerServiceClass);
intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
intent.putExtra(RESULT_EXTRA, result);
context.startService(intent);
}
Copy the code
ListenerServiceClassName is the DisplayLeakService passed in to start LeakCanary. Install, which is itself an intentService
@Override
protected final void onHandleIntent(Intent intent) {
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
try {
onHeapAnalyzed(heapDump, result);
} finally {
//noinspection ResultOfMethodCallIgnoredheapDump.heapDumpFile.delete(); }}Copy the code
Then call its own onHeapAnalyzed method
protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = LeakCanary.leakInfo(this, heapDump, result, true);
CanaryLog.d("%s".new Object[]{leakInfo});
boolean resultSaved = false;
booleanshouldSaveResult = result.leakFound || result.failure ! =null;
if(shouldSaveResult) {
heapDump = this.renameHeapdump(heapDump);
resultSaved = this.saveResult(heapDump, result);
}
PendingIntent pendingIntent;
String contentTitle;
String contentText;
/ / set up alerts pendingIntent/contentTitle/contentText.int notificationId1 = (int)(SystemClock.uptimeMillis() / 1000L);
LeakCanaryInternals.showNotification(this, contentTitle, contentText, pendingIntent, notificationId1);
this.afterDefaultHandling(heapDump, result, leakInfo);
}
Copy the code
This method first determines whether you need to save the information to the local, if you need to save to your local, and then set the basic information of the notification, finally through LeakCanaryInternals. ShowNotification method call system notification bar, tell the user has a memory leak
So far, LeakCanary’s detection of memory leak source code has been analyzed
Reference: blog.csdn.net/xiaohanluo/…
Juejin. Im/post / 684490…