“What about memory optimization? Do you know how to locate a memory problem?” The interviewer, sitting amiably at the side of the small conference room, kindly asked xiao Zhang, who was somewhat stiff.
“Is… Well, use LeakCanary to detect the leak, find the leak, change the wrong code, recycle the unrecycled references, and optimize the dependencies of long and short life cycle threads.”
“Do you know how LeakCanary analyzes memory leaks?”
“I’m sorry. I don’t usually see it.”
Xiao Zhang thought: why do you always ask that in an interview? I’m just an ordinary rookie.
preface
App performance optimization is always an essential part of app development, and memory optimization is one of the key points. Memory leak caused by memory overflow crash, memory jitter caused by the card is not smooth. Are actually affecting the user experience. We often use LeakCanary to locate memory leak problems. It’s time to find out how they did it.
The noun understanding
Hprof: The hprof file is a Java memory snapshot file (short for Heap Profile) with a.hprof format and is used in leakCanary for memory save analysis
WeakReference : Weak references. When an object is referred to only by weak references, but not by any other strong references, if GC runs, the object will be reclaimed, regardless of whether the current memory space is sufficient. Used in leakCanary to monitor whether the recycled unwanted object has been released.
Curtains: Another open source framework for Square, Curtains provides a centralized API for working with Android Windows. LeakCanary is used in leakCanary to monitor window rootView memory leak after detached.
directory
This paper mainly from the following points of analysis
- How to use it in a project
LeakCanary
tool - Official rationale
- How to listen for Activity, View,fragment and ViewModel by default
- How does watcher.watch (object) listen for memory leaks
- How to save a memory leak memory file
- How to analyze memory leak files
- Show the memory leak stack into the UI
One, how does it work?
A look at the official documentation shows how simple it is to use, with the basic usage simply requiring the addition of dependencies
/ / (1)
debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 2.7'
Copy the code
DebugImplementation is only valid for compilation in debug mode and the final debug APK packaging
Note (1): The code for the annotation is initialized in one line. How? By looking at the source code, we can see that LeakCanary initializes through the ContentProvider, The real initialization code appWatcher.manualInstall (Application) is called in the onCreate method of the AppWatcherInstaller class. Register the provider in androidManifest.xml. The registered ContentProvider will automatically call back to the onCreate method when the application starts.
internal sealed class AppWatcherInstaller : ContentProvider() {
/**[MainProcess] automatically sets up the LeakCanary code that runs in the main app process. */
/ / (1)
internal class MainProcess : AppWatcherInstaller(a)internal class LeakCanaryProcess : AppWatcherInstaller(a)override fun onCreate(a): Boolean {
valapplication = context!! .applicationContextas Application
/ / / (2)
AppWatcher.manualInstall(application)
return true
}
/ /...
}
Copy the code
Explain the number annotation in the source code
Two inner classes are defined in code (1) that inherit from the AppWatcherInstaller. When users rely on the Leakcanary – Android-process module in addition, automatically register the provider in process=”: Leakcanary “as well.
See The LeakCanary – Android-Process module androidManifest.xml for the code
Code (2), which is the real initialization code registry entry
Second, the official statement
The official instructions
This section is simplified from the official website’s working principle description
Once LeakCanary is installed, it automatically detects and reports memory leaks through four steps:
-
Detect the object being held
LeakCanary hooks into the Android lifecycle to automatically detect when activity and clips are destroyed and should be garbage collected. These destroyed objects are passed to an ObjectWatcher, which holds a weak reference to them. You can actively observe an object that is no longer needed, such as a Dettached view or a presenter that has been destroyed
AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")
Copy the code
If ObjectWatcher does not clear the held weak references after waiting 5 seconds and running garbage collection, the monitored object is considered reserved and may leak. LeakCanary records this to Logcat:
D LeakCanary: Watching instance of com.example.leakcanary.MainActivity
(Activity received Activity#onDestroy() callback)
... 5 seconds later ...
D LeakCanary: Scheduling check for retained objects because found new object
retained
Copy the code
-
Dump the heap information into a file
When the number of retained objects reaches the threshold, LeakCanary dumps Java memory snapshotThe dumpTo the Android file system.hprof
File (Heap memory snapshot). The dump heap freezes the application for a short time and shows the toast below:
-
Analyzing heap memory
LeakCanary uses Shark to parse the.hprof file and locate the retained leak object in the memory snapshot file. For each retained object, LeakCanary finds the reference path for the object, which prevents the garbage collector from collecting it. Leak tracking. LeakCanary creates a signature (sha1Hash for the addition of the held reference properties) for each leak trace and groups together the leaks that have the same signature (that is, leaks caused by the same error). How to create a signature and pass a signature group will be analyzed later.
-
Classified memory leak
LeakCanary divides the Leaks it finds in your app into two categories: Application Leaks and Library Leaks. A Library Leaks is caused by a known third-party Library over which you have no control. This leak is affecting your application, but unfortunately fixing it may not be within your control, so LeakCanary isolated it. The two categories are printed separately in the Logcat results:
====================================
HEAP ANALYSIS RESULT
====================================
0 APPLICATION LEAKS
====================================
1 LIBRARY LEAK
...
┬───
│ GC Root: Local variable in native code
│
...
Copy the code
LeakCanary uses this in its leak list displayLibrary Leak
Label:LeakCanary ships with a database of known leaks, identified by pattern matching of reference names. Such as:
Leak pattern: instance field android.app.Activity$1#this$0 Description: Android Q added a new IRequestFinishCallback$Stub class [...] ┬─── │ GC Root: Global variable in Native code │ ├─ Android.app.activity $1 instance Leaking: UNKNOWN │ Anonymous ttf_subclass of android. App. IRequestFinishCallback $Stub │ left Activity down $1. This $0 │ ~ ~ ~ ~ ~ ~ ╰ - > com.example.MainActivity instanceCopy the code
Library Leaks are usually not aligned for repair. You can see the full list of known Leaks in the AndroidReferenceMatchers class. If you find an unrecognized Android SDK leak, report it. You can also customize the list of known library leaks.
Monitor activity, fragment, rootView and ViewModel
The initialization code mentioned earlier is as follows, so let’s look at the internal details of manualInstall.
// Initialize the code
AppWatcher.manualInstall(application)
///AppWatcher manualInstall code
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
//******* Check whether the main thread ********/
checkMainThread()
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
installCause = RuntimeException("manualInstall() first called here")
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// Requires AppWatcher.objectWatcher to be set
/ / / (2)
LeakCanaryDelegate.loadLeakCanary(application)
/ / / (1)
watchersToInstall.forEach {
it.install()
}
}
Copy the code
AppWatcher serves as the API hub for the Android platform wrapped in ObjectWatcher. Automatic installation configures default listening. Key points in the above code are numbered
(1) Install the default listening observation
The code in (1) performs the install operation of InstallableWatcher. It does not pass the watchersToInstall parameter, so it uses appDefaultWatchers(Application). The code below provides four default listening watchers
fun appDefaultWatchers(
application: Application./ / / (1.1)
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
/ / / (1.2)
ActivityWatcher(application, reachabilityWatcher),
/ / / (1.3)
FragmentAndViewModelWatcher(application, reachabilityWatcher),
/ / / (1.4)
RootViewWatcher(reachabilityWatcher),
/ / / (1.5)
ServiceWatcher(reachabilityWatcher)
)
}
Copy the code
The four that are numbered we’re going to analyze one by one
(1.1) reachabilityWatcher parameter
The code in (1.1) is a ReachabilityWatcher parameter that will be used in the next four instances, In the code you can see that the reachabilityWatcher instance is a member variable of AppWatcher: objectWatcher, and the corresponding instantiation code is as follows.
/** * The [ObjectWatcher] used by AppWatcher to detect retained objects. * Only set when [isInstalled] is true. */
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true})Copy the code
As you can see, objectWatcher is an objectWatcher object, which is responsible for detecting leaks in the holding objects and is analyzed in section 3. Back to the creation of the ActivityWatcher instance, proceed to the annotated code
(1.2) The ActivityWatcher instance completes the listening of the Activity instance
Back in (1.2), the code creates an instance of ActivityWatcher and installs it at install time. Look at the source code of the ActivityWatcher class
class ActivityWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val lifecycleCallbacks =
//(1.2.1) Construct the implementation class of life cycle callback through dynamic proxy
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
/ / (1.2.3)
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback")}}override fun install(a) {
/ / (1.2.3)
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall(a) {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
Copy the code
(1.2.1) lifecycleCallbacks instance
Note (1.2.1) code creates ActivityLifecycleCallbacks instance, the instance realized Application. ActivityLifecycleCallbacks. By ‘ ‘*noOpDelegate*’ ‘() implements other callback methods using dynamic proxies. For those interested, see the noOpDelegate source code
(1.2.2) Install method of the Activity listener
The code in the annotation (1.2.2) is the primary code for initialization. The method is simply to register lifecycleCallbacks in the application and go to the method implemented there when the activity is destroyed
Monitor the onActivityDestroyed callback on the activity
Annotations (1.2.3) code is the main initialization code, the activity is destroyed, the callback, the method in which the check for leaks the instance, call AppWatcher. ObjectWatcher. ExpectWeaklyReachable method, This is where leak monitoring for the activity is completed. At this point, it is back to the ObjectWatcher source mentioned in 1.1. See section 4 for related analysis.
(1.2-end)Activity monitoring related summary
The ActivityInstaller is done looking at the Activity initialization code and the details of adding listeners. Summarized into the following steps:
- Call ActivityInstaller. Install initialization method
- Register ActivityLifecycleCallbacks in Application
- When all the activity onDestroy, call the ObjectWatcher expectlyWeaklyReachable method to check whether the activity object has been reclaimed after five seconds. Flag memory leaks. I’ll do that in the next video.
- Follow-up operations when a memory leak is detected. Analysis after.
(1.3) FragmentAndViewModelWatcher monitoring fragments and Viewodel instance
(1.3) is to create a FragmentAndViewModelWatcher instance. Monitor fragment and ViewModel for memory leaks.
This class implements SupportFragment compatibility with androidxFragment and androidO. As SDK development, this compatibility method can be learned.
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?). {
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
}
override fun install(a) {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
Copy the code
As with ActivityWatcher, install registers life cycle listeners. This is done by the fragmentDestroyWatchers element on each activity create. So fragmentDestroyWatchers is the true fragment and ViewModel listener. Let’s look at the fragmentDestroyWatchers elements to create:
private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit> ()//(1.3.1) The Android framework supports fragment leak detection starting in Android (26).
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(reachabilityWatcher)
)
}
/ / (1.3.2)getWatcherIfAvailable( ANDROIDX_FRAGMENT_CLASS_NAME, ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, reachabilityWatcher )? .let { fragmentDestroyWatchers.add(it) }/ / (1.3.3)getWatcherIfAvailable( ANDROID_SUPPORT_FRAGMENT_CLASS_NAME, ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, reachabilityWatcher )? .let { fragmentDestroyWatchers.add(it) } fragmentDestroyWatchers }Copy the code
You can see internal created AndroidOFragmentDestroyWatcher for intercepting fragments. Principle is to use the registered in FragmentManager FragmentManager. FragmentLifecycleCallbacks to monitor fragment and fragments. The view and viewmodel instance of the leak. The fragment in Android was added in Api 26. Therefore, LeakCanary’s fragment leak detection support for the Android framework also starts in Android (26), see code (1.3.1). Marked 1.3.1, 1.3.2, The three wathcers instantiated by 1.3.3 are AndroidOFragmentDestroyWatcher AndroidXFragmentDestroyWatcher, AndroidSupportFragmentDestroyWatcher. The internal implementation code is much the same, using reflection to instantiate different watchers to achieve compatibility between androidX and support and between android versions.
(1.3.1) AndroidOFragmentDestroyWatcher instance
The code at (1.3.1) adds an observer instance of androidO. See the code for details, because the implementation is much the same, see 1.3.2 for analysis.
(1.3.2) AndroidXFragmentDestroyWatcher instance
(1.3.2) code calls getWatcherIfAvailable created AndroidXFragmentDestroyWatcher instance through reflection, if there is no Androidx library it returns null. Now jump to AndroidXFragmentDestroyWatcher source code analysis
internal class AndroidXFragmentDestroyWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentCreated(
fm: FragmentManager,
fragment: Fragment,
savedInstanceState: Bundle?). {
//(1.3.2.1) Initialize ViewModelClearedWatcher
ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
}
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
// Monitor fragment.view for leaks
val view = fragment.view
if(view ! =null) {
reachabilityWatcher.expectWeaklyReachable(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)")}}override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
// Monitor the fragment for leaks
reachabilityWatcher.expectWeaklyReachable(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback")}}/ / / initialization, fragmentLifecycleCallbacks registration
override fun invoke(activity: Activity) {
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
// Register the viewModel listener callback for the activity
ViewModelClearedWatcher.install(activity, reachabilityWatcher)
}
}
}
Copy the code
As you can see from the source code, the following steps are used to initialize the Watcher.
FragmentManager.registerFragmentLifecycleCallbacks
Register the listen callbackViewModelClearedWatcher.install
Init foractivity.viewModel
Listening in- In the callback
onFragmentCreated
Is used in the callbackViewModelClearedWatcher.install
Registered forfragment.viewModel
Listening in. - in
onFragmentViewDestroyed
Listening to thefragment.view
The leakage - in
onFragmentDestroyed
Listening to thefragment
The leakage.
Monitoring methods and ActivityWatcher same, different is more than a ViewModelClearedWatcher. Install. Now analyze the source code for this section, which is in the annotation (1.3.2.1).
// The watcher inherits from the ViewModel, and the lifecycle is managed by the ViewModelStoreOwner.
internal class ViewModelClearedWatcher(
storeOwner: ViewModelStoreOwner,
private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {
private val viewModelMap: Map<String, ViewModel>?
init {
//(1.3.2.3) Get all viewModelMaps from all stores by reflection
viewModelMap = try {
val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
mMapField.isAccessible = true
@Suppress("UNCHECKED_CAST")
mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
} catch (ignored: Exception) {
null}}override fun onCleared(a) {
///(1.3.2.4) When the viewmodle is cleared to release callback, check all viewmodle leakviewModelMap? .values? .forEach { viewModel -> reachabilityWatcher.expectWeaklyReachable( viewModel,"${viewModel::class.java.name} received ViewModel#onCleared() callback")}}companion object {
fun install(
storeOwner: ViewModelStoreOwner,
reachabilityWatcher: ReachabilityWatcher
) {
val provider = ViewModelProvider(storeOwner, object : Factory {
@Suppress("UNCHECKED_CAST")
override fun
create(modelClass: Class<T>): T =
ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
})
///(1.3.2.2) Get the ViewModelClearedWatcher instance
provider.get(ViewModelClearedWatcher::class.java)
}
}
}
Copy the code
From the code, you can see that leak detection for the viewModel is done by creating a new viewModel instance. Listen for leaks in the remaining viewModels of the storeOwner at the instance’s onCleared. The highlighted code is analyzed one by one:
(1.3.2.2) Code:
Get the ViewModelClearedWatcher instance and pass the storeOwner and reachabilityWatcher in the custom Factory.
(1.3.2.3) Code:
Get the viewModelMap of the storeOwner through reflection
(1.3.2.4) Code:
When the ViewModel completes its mission OnClear, start monitoring all viewModels owned by storeOwner for memory leaks.
(1.3-end)Fragment and ViewModel
The monitoring method is through expectlyreachable method of ObjectWatcher. Fragments using FragmentLifecyclerCallback callback is registered, the ViewModel is created under the corresponding StoreOwner monitoring the ViewModel to implement the life cycle of the response. We can also learn how to use reflection to create corresponding platform-compatible implementation objects. And the idea of listening to the rest of the viewModel lifecycle by creating a viewModel.
(1.4) Source analysis of RootViewWatcher
By default, the four Watchers come to the next RootViewWatcher. The Window RootView listener relies on squre’s own Curtains framework.
implementation "Com. Squareup. Curtains, curtains, 1.0.1." "
Copy the code
The key source code for the class is as follows:
private val listener = OnRootViewAddedListener { rootView ->
// If it is Dialog TOOLTIP, TOAST, UNKNOWN and other types of Windows
/ / trackDetached to true
if (trackDetached) {
rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
val watchDetachedView = Runnable {
reachabilityWatcher.expectWeaklyReachable(
rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback")}override fun onViewAttachedToWindow(v: View) {
mainHandler.removeCallbacks(watchDetachedView)
}
override fun onViewDetachedFromWindow(v: View) {
mainHandler.post(watchDetachedView)
}
})
}
}
override fun install(a) {
Curtains.onRootViewsChangedListeners += listener
}
override fun uninstall(a) {
Curtains.onRootViewsChangedListeners -= listener
}
}
Copy the code
See the key code, it is added in the Curtains onRootViewsChangedListeners listeners. Listen for leaks while onViewDetachedFromWindow when windowsType is **Dialog** ***TOOLTIP**,** *TOAST*****,** or unknown. The listener in SMB will be called globally if the Windows rootView changes. Curtains, another open source library from SquareUp, provides a centralized API for working with Android Windows. Go to his official warehouse.
(1.5) ServiceWatcher listens to Service memory leak
Here’s the last Watcher in AppWatcher. ServiceWatcher. The code is longer, and the key point analysis is intercepted.
Look at the member variables firstactivityThreadServices
:
private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>()
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread")}private val activityThreadInstance by lazy {
activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!!!! }private val activityThreadServices by lazy {
val mServicesField =
activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }
@Suppress("UNCHECKED_CAST")
mServicesField[activityThreadInstance] as Map<IBinder, Service>
}
Copy the code
ActivityThreadServices is a Map with all
pairs installed. In the code, you can see that the mServices variable is taken rudely from the ActivityThread instance directly by reflection. Assign to activityThreadServices. There are multiple swap operations in the source code, which are executed at install time. The main purpose is to add hooks to some of the original service lifecycle callbacks to detect memory leaks and will be swapped back when unInstall occurs.
(1.5.2) swapActivityThreadHandlerCallback:
Take ActivityThread’s Handler and replace its Callback handleMessage with a loaded handler. Callback
Handler.Callback { msg ->
if (msg.what == STOP_SERVICE) {
val key = msg.obj asIBinder activityThreadServices[key]? .let { onServicePreDestroy(key, it) } } mCallback? .handleMessage(msg) ? :false
}
Copy the code
As you can see in the code, the main hook to the STOP_SERVICE operation is onServicePreDestroy. The main effect is to create a weak reference for the service and add it to servicesToBeDestroyed[token].
(1.5.3) and then lookswapActivityManager
Methods.
This method completes the replacement of ActivityManager with a dynamic proxy class for IActivityManager. The code is as follows:
Proxy.newProxyInstance(
activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
) { _, method, args ->
//private const val METHOD_SERVICE_DONE_EXECUTING = "serviceDoneExecuting"
if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
valtoken = args!! [0] as IBinder
if (servicesToBeDestroyed.containsKey(token)) {
/ / / (1.5.3)
onServiceDestroyed(token)
}
}
try {
if (args == null) {
method.invoke(activityManagerInstance)
} else {
method.invoke(activityManagerInstance, *args)
}
} catch (invocationException: InvocationTargetException) {
throw invocationException.targetException
}
}
Copy the code
If the service is in the servicesToBeDestroyed map that you joined before, you will have to create a new Service from the servicesToBeDestroyed Map. In executing a serviceDoneExecuting method, you will have to create a new service from the servicesToBeDestroyed Map. OnServiceDestroyed is called to monitor the memory leak of the service.
(1.5.4) codeonServiceDestroyed
The specific code is as follows
private fun onServiceDestroyed(token: IBinder){ servicesToBeDestroyed.remove(token)? .also { serviceWeakReference -> serviceWeakReference.get()? .let { service -> reachabilityWatcher.expectWeaklyReachable( service,"${service::class.java.name} received Service#onDestroy() callback")}}}Copy the code
The code here is familiar and is the same as the code used to monitor activities and so on. Go back to the swapActivityManager method and see the specific type of agent ActivityManager. You can see that the proxy object looks like the code below, which may be an ActivityManager instance or an ActivityManagerNative instance, depending on the version. Proxy interface is Class. Class.forname (” android. App. IActivityManager “).
val (className, fieldName) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
"android.app.ActivityManager" to "IActivityManagerSingleton"
} else {
"android.app.ActivityManagerNative" to "gDefault"
}
Copy the code
(1.5-end) Service leak monitoring summary
In summary, the leak analysis of the Service listens for some system executions by adding hooks. It is mainly divided into the following steps:
- Get the mService variable in ActivityThread to get a reference to the service instance
- Through swapActivityThreadHandlerCallback Handler in ActivityThread sendMessage add hooks, in the execution
msg.what == STOP_SERVICE
when
Fourth, ObjectWatcher retains object inspection analysis
Let’s switch to ObjectWatcher’s ExpectlyWeaklyreachable method
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
ObjectWatcher held by AppWatcher is enabled by default
if(! isEnabled()) {return
}
/// Remove the listener that has been reclaimed before
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
//(1) Create a weak reference
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
SharkLog.d {
"Watching " +
(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
(if (description.isNotEmpty()) "($description)" else "") +
" with key $key"
}
watchedObjects[key] = reference
checkRetainedExecutor.execute {
/ / (2)
moveToRetained(key)
}
}
Copy the code
Continue to analyze where the annotations are in the source code.
(1) Create a weak reference
The code in the annotation (1.2.4) is the main code for initialization. It creates the WeakReference of the object to be observed and passes into queue as the object information storage queue after gc. In WeakReference, when holding the object to be collected by gc, its wrapper object will be pushed into the queue. The queue can be observed later.
MoveToRetained (key) moveToRetained(key) moveToRetained(key
Executed as Executor Runner, in AppWatcher, the default delay is five seconds before executing this method to view source analysis
@Synchronized private fun moveToRetained(key: String) {
/// Remove an observation object that has been reclaimed
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if(retainedRef ! =null) {
// Record the leak time
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
// Callback leak listener
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
Copy the code
From the code above, ObjectWatcher monitors memory leaks in the following steps
- Clear the listener that has been reclaimed
- Create a weak reference and pass in the ReferenceQueue to hold the queue for GC information
- After the delay of the specified time, check again whether the target object is reclaimed (by checking whether the ReferenceQueue queue has the WeakReference instance)
- Callback after detecting that the object is not reclaimed
onObjectRetainedListeners
‘onObjectRetained
Five, dumpHeap, what is the dumpHeap process
(1.1) objectWatcher add OnObjectRetainedListeners listening in
Go back to the manualInstall method of the original AppWatcher. You can see that the loadLeakCanary method is executed. The code is as follows:
/ / / (2)
LeakCanaryDelegate.loadLeakCanary(application)
// Reflection gets the InternalLeakCanary instance
val loadLeakCanary by lazy {
try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE").get(null) as (Application) -> Unit
} catch (ignored: Throwable) {
NoLeakCanary
}
}
Copy the code
This method gets a static instance of InternalLeakCanary through reflection. And it calls his invoke(Application: Application) method, so let’s look at the InternalLeakCanary method:
override fun invoke(application: Application) {
_application = application
checkRunningInDebuggableBuild()
/ / add addOnObjectRetainedListener (1.2)
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
/ / Gc triggers
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
/ / / (1.3)
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
/// (1.4) Add the application change front and back listener
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
/ / (1.5)
registerResumedActivityListener(application)
/ / (1.6)
addDynamicShortcut(application)
// 6 Determine whether DumpHeap should be used
// We post so that the log happens after Application.onCreate()
mainHandler.post {
// https://github.com/square/leakcanary/issues/1981
// We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
// which blocks until loaded and that creates a StrictMode violation.
backgroundHandler.post {
SharkLog.d {
when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
is Nope -> application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
}
}
}
Copy the code
So we see that when we initialize, we do six steps like this
- (1.2) Add yourself to ObjectWatcher’s object exception holding listener
- (1.3) Create the memory snapshot dump trigger HeapDumpTrigger
- (1.4) Monitor the changes in the front and background of the application and record the time when it comes to the background, which is convenient for LeakCanary to do leak monitoring for some destroy operations just entered into the background
- (1.5) Register the Activity lifecycle callback to get the current resumed activity instance
- (1.6) Add dynamic Desktop Shortcuts
- (1.7) In an asynchronous thread, determine whether the thread is in a dumpHeap state. If it is, trigger a memory leak check
The most important of these is 1.2, where we focus on what he does in the callback as an ObjectRetainedListener.
(1.2) Add an object holding listener abnormally
You can see the code (1.2) that adds itself to the leak detection callback in objectWatcher. When ObjectWatcher detects that the object is still held abnormally, the onObjectretrace method is called back. From the source code, which calls the heapDumpTrigger scheduleRetainedObjectCheck method, the code is as follows.
fun scheduleRetainedObjectCheck(a) {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
Copy the code
HeapDumpTrigger is, as the name suggests, the trigger for a memory snapshot dump. The checkRetainedObjects method of the HeapDumpTrigger is finally called in the callback to check for memory leaks.
(1.3) Checking memory leaks checkRetainedObjects
private fun checkRetainedObjects(a) {
val iCanHasHeap = HeapDumpControl.iCanHasHeap()
val config = configProvider()
// Omit some code, mainly to judge iCanHasHeap.
// If the memory snapshot is not in the dump state, the process is not processed. Send a notification prompt if a new exception - held object is found
//%d retained objects, tap to dump heap
/ * *... * /
var retainedReferenceCount = objectWatcher.retainedObjectCount
// Triggers the GC
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
// Retrieve the exception held object
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// If the number of leaks is less than the threshold and the app is in the foreground or has just been transferred to the background, display the leak notification and return first
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
// If the number of leaks reaches the dumpHeap requirement, keep going
// Dump memory snapshot when WAIT_BETWEEN_HEAP_DUMPS_MILLIS (60 seconds by default) is triggered only once. If it has been triggered previously, it will not generate a memory snapshot and send a notification directly.
// Omit the snapshot dump timing. If the snapshot dump time is not satisfied, the Last heap dump was less than a minute ago is displayed
/ * *... * /
dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
/// Dump the memory snapshot
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility")}Copy the code
This section also shows whether detection requires dumpHeap to be divided into four steps.
- Returns if no exception held object is detected
- If there is an exception object, the GC is actively triggered
- If there are still exception objects, it’s a memory leak.
- Determine if the number of leaks is large enough to require dumping
- Determine whether a dump is performed within one minute
- dumpHeap
This is all judgment code, but the key focus is the dumpHeap method
(1.4) dumpHeap Dumps memory snapshots
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
when (val heapDumpResult = heapDumper.dumpHeap()) {
is NoHeapDump -> {
// Omit dump failure, wait for retry code and send failure notification code
}
is HeapDump -> {
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
/// Delete objects in objectWatcher that were held before heapDumpUptimeMillis, that is, objects that have been dumped
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
// Send the file to HeapAnalyzerService for parsing
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
}
Copy the code
AndroidHeapDumper#dumpHeap is called in the HeapDumpTrigger#dumpHeap method. And the call immediately after the dump HeapAnalyzerService. RunAnalysis memory analysis work, the method in the next section. First look at the source code of AndroidHeapDumper#dumHeap
override fun dumpHeap(a): DumpHeapResult {
// Create a new hprof file
valheapDumpFile = leakDirectoryProvider.newHeapDumpFile() ? :return NoHeapDump
valwaitingForToast = FutureResult<Toast? > ()/// Show dump toast
showToast(waitingForToast)
/// If the toast is displayed for more than five seconds, the dump will not be performed
if(! waitingForToast.wait(5, SECONDS)) {
SharkLog.d { "Did not dump heap, too much time waiting for Toast." }
return NoHeapDump
}
// Omit the dumpHeap notification bar message code
val toast = waitingForToast.get(a)return try {
val durationMillis = measureDurationMillis {
/ / call DumpHprofData
Debug.dumpHprofData(heapDumpFile.absolutePath)
}
if (heapDumpFile.length() == 0L) {
SharkLog.d { "Dumped heap file is 0 byte length" }
NoHeapDump
} else {
HeapDump(file = heapDumpFile, durationMillis = durationMillis)
}
} catch (e: Exception) {
SharkLog.d(e) { "Could not dump heap" }
// Abort heap dump
NoHeapDump
} finally {
cancelToast(toast)
notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
}
}
Copy the code
Within this method, the debug.dumphProfData method is finally called to complete the generation of the Hprof snapshot.
6. Analyze memory HeapAnalyzerService
As you can see from the code analysis above, dumpHeap is followed by a method to start the memory analysis service. Now we jump to the source code for HeapAnalyzerService.
override fun onHandleIntentInForeground(intent: Intent?). {
// Omit the parameter fetching code
val config = LeakCanary.config
val heapAnalysis = if (heapDumpFile.exists()) {
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
// Omit the code to improve the attributes of the analysis result
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
}
Copy the code
As you can see, the focus is analyzeHeap, where HeapAnalyzer# Analyze HeapAnalyzer is called in the shark module.
(1) HeapAnalyzer# analyze
The memory analysis method code is as follows:
fun analyze(
heapDumpFile: File,
leakingObjectFinder: LeakingObjectFinder,
referenceMatchers: List<ReferenceMatcher> = emptyList(),
computeRetainedHeapSize: Boolean = false,
objectInspectors: List<ObjectInspector> = emptyList(),
metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
proguardMapping: ProguardMapping? = null
): HeapAnalysis {
// Omit the processing code for the memory snapshot file that does not exist
return try {
listener.onAnalysisProgress(PARSING_HEAP_DUMP)
/// I/O reads the memory snapshot
val sourceProvider = ConstantMemoryMetricsDualSourceProvider(FileSourceProvider(heapDumpFile))
sourceProvider.openHeapGraph(proguardMapping).use { graph ->
val helpers =
FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
// Key code: find the result of the leak and its corresponding call stack here
val result = helpers.analyzeGraph(
metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
)
val lruCacheStats = (graph as HprofHeapGraph).lruCacheStats()
/// I/O read status
val randomAccessStats =
"RandomAccess[" +
"bytes=${sourceProvider.randomAccessByteReads}," +
"reads=${sourceProvider.randomAccessReadCount}," +
"travel=${sourceProvider.randomAccessByteTravel}," +
"range=${sourceProvider.byteTravelRange}," +
"size=${heapDumpFile.length()}" +
"]"
val stats = "$lruCacheStats $randomAccessStats"
result.copy(metadata = result.metadata + ("Stats" to stats))
}
} catch (exception: Throwable) {
// Omit exception handling}}Copy the code
Analyzing the code shows that the memory snapshot analysis is divided into the following five steps:
- Read the hprof memory snapshot file
- Find the number of leak objects marked by LeakCanary and the weak reference wrapper IDS, class name is
com.squareup.leakcanary.KeyedWeakReference
The code in KeyedWeakReferenceFinder# findLeakingObjectIds
- Find the path where the leaking object’s gcRoot started
The code in PathFinder# findPathsFromGcRoots
- Return the analysis result and walk the result callback
- A notification bar message is displayed in the callback indicating the success or failure of the memory analysis, and the leak list is stored in the database
Code for details see DefaultOnHeapAnalyzedListener# onHeapAnalyzed and LeaksDbHelper
- Click on the notification bar to jump to
LeaksActivity
Display memory leak information.
Seven,
Finally, from start to finish, finally combed a wave of LeakCanary source code
Learned so much in the process — >
- The way Gc is invoked actively
GcTrigger.Default.runGc()
Runtime.getRuntime().gc()
Copy the code
- The Seald class seals classes to express states, such as the following (the key benefit is that using WHEN can directly override all cases instead of using else).
sealed class ICanHazHeap {
object Yup : ICanHazHeap()
abstract class Nope(val reason: () -> String) : ICanHazHeap()
class SilentNope(reason: () -> String) : Nope(reason)
class NotifyingNope(reason: () -> String) : Nope(reason)
}
sealed class Result {
data class Done(
val analysis: HeapAnalysis,
val stripHeapDumpDurationMillis: Long? = null
) : Result()
data class Canceled(val cancelReason: String) : Result()
}
Copy the code
- Learned about the system API for creating memory snapshots
Debug.dumpHprofData(heapDumpFile.absolutePath)
Copy the code
- Known that the ReferenceQueue detects whether the memory object is GC, WeakReference is rarely used before.
- Learned leakCanary’s sub-module idea. As an SDK, many function modules are introduced automatically on. For example, leakcanary-android-process automatically starts the corresponding process.
- Learn how to add hooks by reflecting hook code and replacing the instance. For example, in the Service leak listening code, replace
Handler
andactivityManager
In the operation.
It’s good to read more source code. No wonder I couldn’t get a job before. Too little.