read the fucking source code |
---|
preface
This article does not contain the code to analyze the Haha library used by LeakCanary.
LeakCanary website
This article is based on: Leakcanary – Android :2.7
Simple to use is just a sentence, even the initialization code does not need you to write.
dependencies {
debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 2.7'
}
Copy the code
LeakCanary Initialization analysis
The leakCanary project directory is shown above, and the initialization code is located at leakcanary-Android-process. The manifest file is as follows:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.objectwatcher" >
<uses-sdk android:minSdkVersion="14" />
<application>
<! Init with provider -->
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false" />
</application>
</manifest>
Copy the code
One more thing here is that when AMS starts an app, it calls provider.oncreate and then it calls application.oncreate. Let me learn from LeakCanary again. OnCreate, ContentProvider. OnCreate, activity.oncreate
//AppWatcherInstaller.kt
internal sealed class AppWatcherInstaller : ContentProvider() {
// The inner class can be used to construct two different providers instantly, which can be switched, tested, etc
internal class MainProcess : AppWatcherInstaller(a)internal class LeakCanaryProcess : AppWatcherInstaller(a)override fun onCreate(a): Boolean {
valapplication = context!! .applicationContextas Application
AppWatcher.manualInstall(application)
return true
}
Copy the code
//ObjectWatcher.kt
class ObjectWatcher constructor(
private val clock: Clock,
Executor is clearly a JDK class that encapsulates thread scheduling
private val checkRetainedExecutor: Executor,
private val isEnabled: () -> Boolean = { true})//AppWatcher.kt
object AppWatcher {
//ObjectWatcher objects will be specified later. Let's start by looking at a property called checkRetainedExecutor
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
//internal val mainHandler by lazy { Handler(Looper.getMainLooper()) }
// Get the android mainline handler here
// There is a reason why the main thread is used here
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true})fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
/ /... slightly
// Register some listeners
LeakCanaryDelegate.loadLeakCanary(application)
// Let's look at the set
watchersToInstall.forEach {
it.install()
}
}
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
// Each collection object represents a class that can monitor for memory leaks
return listOf(
/ / monitor the Activity
ActivityWatcher(application, reachabilityWatcher),
/ / fragments and the viewmodel
FragmentAndViewModelWatcher(application, reachabilityWatcher),
/ / monitor the view
RootViewWatcher(reachabilityWatcher),
/ / monitor service
ServiceWatcher(reachabilityWatcher)
)
}
}
Copy the code
This is done by calling install to the four collection objects above.
ActivityWatcher
ActivityWatcher monitors Activity related leaks. Before we get to the bottom of this, let’s add a small fact.
ReferenceQueue use
ReferenceQueue is referenced in relation to which objects we know are recycled.
public class JavaMain {
static String smg = new String("nihao");
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<String> queue = new ReferenceQueue<>();
// When a WeakReference object is reclaimed, the reference is put in the queue
Reference<String> reference = new WeakReference<>(smg, queue);
Reference<? extends String> poll = queue.poll();
// Prints null because the object is referenced by SMG
System.out.println(poll);
smg = null;
On Android, use Runtime.getruntime ().gc(); Specific reasons will be explained later
System.gc();
TimeUnit.SECONDS.sleep(1);
/ / output Java. Lang. Ref. 15 db9742 WeakReference @poll = queue.poll(); System.out.println(poll); }}Copy the code
The above is a small example, please be sure to understand. In addition, if WeakReference holds a reference to a constant pool, it will not be reclaimed. As follows:
public class JavaMain {
// New String("nihao")
// SMG points to the constant pool nihao
static String smg = "nihao";
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<String> queue = new ReferenceQueue<>();
Reference<String> reference = new WeakReference<>(smg, queue);
Reference<? extends String> poll = queue.poll();
/ / output is null
System.out.println(poll);
smg = null;
On Android, use Runtime.getruntime ().gc(); Specific reasons will be explained later
System.gc();
TimeUnit.SECONDS.sleep(1);
poll = queue.poll();
// Output null. Constant pool objects are not easily recycledSystem.out.println(poll); }}Copy the code
The core idea of ActivityWatcher is to put the Activity into a WeakReference when the Activity is destroyed, and the WeakReference will pass in a ReferenceQueue and judge the Referenc after waiting for a certain amount of time Is there a new element in eQueue?
ActivityWatcher
//ActivityWatcher.kt
class ActivityWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
// Register the Activity's destory callback with the Application object.
/ / point reachabilityWatcher AppWatcher objectWatcher
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback")}}// The initialization code is called
override fun install(a) {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall(a) {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
Copy the code
class ObjectWatcher constructor(
private val clock: Clock.private val checkRetainedExecutor: Executor/ / points tomainThread ohprivate val isEnabled: () - >Boolean = { true{})The watchedObjects reference needs to be removed if queue.pool returns an object
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
Queue stores casual to detect, and works with watchedObjects to detect leaks
private val queue = ReferenceQueue<Any>()
@Synchronized override fun expectWeaklyReachable( watchedObject: Any, description: String ) {
if(! isEnabled()) {return
}
// Loop queue.pool until null is returned, cleaning up the watchedObjects that can return data
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
// Put the current Activity into the reference object, and notice that queue is passed in
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
// Put it in an array
watchedObjects[key] = reference
//checkRetainedExecutor is the main thread, execute will drop the task to the queue, so the execution will be delayed.
// Why checkRetainedExecutor is the main thread? Because the Activity call destroy is not immediately reclaimed,
// Android does some cleanup after destroy returns
checkRetainedExecutor.execute {
// Go further
moveToRetained(key)
}
}
private fun removeWeaklyReachableObjects(a) {
Queue. Pool returns the object, so watchedObjects does not need to save the monitor object to prove that the object was recycled
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if(ref ! =null) {
watchedObjects.remove(ref.key)
}
} while(ref ! =null)}}Copy the code
We dropped an event onto the main thread and waited for moveToRetained, at which point the base Activity was reclaimed
private fun moveToRetained(key: String) {
// Remove the reclaimed object
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
/ / if not removeWeaklyReachableObjects remove retainedRef so prove to be no recovery
if(retainedRef ! =null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
/ / onObjectRetainedListeners in LeakCanaryDelegate. LoadLeakCanary (application) is set
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
Copy the code
//InternalLeakCanary.kt
// There are two interfaces inherited:
// (Application) -> Unit
// OnObjectRetainedListener
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
// Run to here
override fun onObjectRetained(a) = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck(a) {
// count as true
if (this::heapDumpTrigger.isInitialized) {
//heapDumpTrigger is heapDumpTrigger, which is again forbidden to check whether the object is not released
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
}
Copy the code
//HeapDumpTrigger.kt
fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
//backgroundHandler is a child thread
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
}
private fun checkRetainedObjects(a) {
// Again strictly check whether there is leakage,
// Get the number of lives
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
// Fetch the number of lives once gc is triggered
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
/ /... slightly
}
Copy the code
object Default : GcTrigger {
override fun 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 perform a gc.
System.gc() does not always execute gc, runtime.gc () is more likely to execute gc
Runtime.getRuntime()
.gc()
enqueueReferences()
System.runFinalization()
}
private fun 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 (e: InterruptedException) {
throw AssertionError()
}
}
}
Copy the code
We don’t analyze after confirming leaks, we look at how other leaks are detected, right
FragmentAndViewModelWatcher
class FragmentAndViewModelWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit> ()/ / slightly AndroidXFragmentDestroyWatcher into fragmentDestroyWatchers here
fragmentDestroyWatchers
}
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?). {
// Notice that an observation is placed when onCreate is called
/ / store AndroidXFragmentDestroyWatcher fragmentDestroyWatchers
for (watcher in fragmentDestroyWatchers) {
/ / call AndroidXFragmentDestroyWatcher. Invoke.
//
watcher(activity)
}
}
}
override fun install(a) {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall(a) {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
Copy the code
//AndroidXFragmentDestroyWatcher.kt
internal class AndroidXFragmentDestroyWatcher(
private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
override fun invoke(activity: Activity) {
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
/ / registerFragmentLifecycleCallbacks can listen to each fragment of add and instances
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
//viewModel ()
ViewModelClearedWatcher.install(activity, reachabilityWatcher)
}
}
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentCreated(
fm: FragmentManager,
fragment: Fragment,
savedInstanceState: Bundle?). {
// ViewModel
ViewModelClearedWatcher.install(fragment, reachabilityWatcher)
}
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if(view ! =null) {
// Check whether the view is held
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
) {
// Check whether the fragment is held. I'm not going to repeat myself here
reachabilityWatcher.expectWeaklyReachable(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback")}}}Copy the code
ViewModelClearedWatcher
The previous section we saw in the fragments of the create function call ViewModelClearedWatcher. Install (fragments, reachabilityWatcher) to monitor the viewmodel leak. Make sure you understand the viewModel basics:
ViewModel source code analysis
internal class ViewModelClearedWatcher(
storeOwner: ViewModelStoreOwner,
private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel(a) {
private val viewModelMap: Map<String, ViewModel>?
init {
// The ViewModelStore contains a mMap object, which holds all viewModels
val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
mMapField.isAccessible = true
// Get the Fragment ViewModelStoreOwner instance to get the mMap object
// To get all the viewModels
mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
} catch (ignored: Exception) {
null}}override fun onCleared(a) {
// Start reclaiming viewModel
// Walk through all viewModels and check for leaksviewModelMap? .values? .forEach { viewModel -> reachabilityWatcher.expectWeaklyReachable( viewModel,"${viewModel::class.java.name} received ViewModel#onCleared() callback"
)
}
}
companion object {
fun install( storeOwner: ViewModelStoreOwner, reachabilityWatcher: ReachabilityWatcher ) {
// Notice the factory passed in here, but what is passed in is creating the ViewModelClearedWatcher objectval provider = ViewModelProvider(storeOwner, object : Factory { override fun <T : ViewModel? > create(modelClass: Class<T>): T = ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T })/ / ViewModelClearedWatcher is a spy viewmodel, mainly in order to monitor the oncleared callback
provider.get(ViewModelClearedWatcher::class.java)
}
}
}
Copy the code
ServiceWatcher
Monitoring the Destroy code for a Service can be a bit complicated. Design reflection, and the service startup process. You need to know something about plug-ins. This article may require some basic knowledge of handler, see blogger: Handler
Knowledge reserves
The creation and destruction of our four major components are transferred from AMS to our App’s ActivityThread.MH through Binder(cross-process IPC).
Mh is our Handler, so we can reflect the MH, and then set an mCallback object to the Handler to intercept all AMS messages. MCallback returns false and continues to be processed by the Handler.
public final class ActivityThread {
final H mH = new H();
private class H extends Handler {}}Copy the code
We setmCallback
after
Let’s take a last lookLeakCanary
The relevant code
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread")}//ActivityThread has a static currentActivityThread. Save its own instance
private val activityThreadInstance by lazy {
activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!!!! }private fun swapActivityThreadHandlerCallback(swap: (Handler.Callback?). ->Handler.Callback?). {
/ / activityThreadClass for Class. Class.forname (" android. App. ActivityThread ")
val mHField =
activityThreadClass.getDeclaredField("mH").apply { isAccessible = true }
// Get attributes, where mHField[XXXX] is kotlin syntax is not confused
val mH = mHField[activityThreadInstance] as Handler
/ / find Handler. MCallback
val mCallbackField =
Handler::class.java.getDeclaredField("mCallback").apply { isAccessible = true }
// Get the mCallback instance
val mCallback = mCallbackField[mH] as Handler.Callback?
// Replace it with its own handler. Callback, which is internally forwarded to the original mCallback
mCallbackField[mH] = swap(mCallback)
}
Copy the code
Service and a related function ActivityManagerProxy. ServiceDoneExecuting, When the service has been created (callback after the oncreate), back to call ActivityManagerProxy. ServiceDoneExecuting. When the service is destroyed (AMS issued news, and after the callback onDestroy) callback ActivityManagerProxy. ServiceDoneExecuting
Service startup process source code read
So we have two hook points that can be used to detect being destroyed.
/ / because are simple operation, the reader can category swapActivityThreadHandlerCallback above
private fun swapActivityManager(swap: (Class<*>, Any) -> Any) {
val singletonClass = Class.forName("android.util.Singleton")
val mInstanceField =
singletonClass.getDeclaredField("mInstance").apply { isAccessible = true }
val singletonGetMethod = singletonClass.getDeclaredMethod("get")
val (className, fieldName) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
"android.app.ActivityManager" to "IActivityManagerSingleton"
} else {
"android.app.ActivityManagerNative" to "gDefault"
}
val activityManagerClass = Class.forName(className)
val activityManagerSingletonField =
activityManagerClass.getDeclaredField(fieldName).apply { isAccessible = true }
val activityManagerSingletonInstance = activityManagerSingletonField[activityManagerClass]
// Calling get() instead of reading from the field directly to ensure the singleton is
// created.
val activityManagerInstance = singletonGetMethod.invoke(activityManagerSingletonInstance)
val iActivityManagerInterface = Class.forName("android.app.IActivityManager")
mInstanceField[activityManagerSingletonInstance] =
swap(iActivityManagerInterface, activityManagerInstance!!)
}
Copy the code
Finally, the mServices for ActivityThread holds all app services
//ActivityThread.java
class ActivityThread{
final ArrayMap<IBinder, Service> mServices
= new ArrayMap<IBinder, Service>();
}
Copy the code
We combine the following with a simple look at ServiceWatcher.
//ServiceWatcher.java
// Store the service to be observed
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)!!!!! }// Store all app services
private val activityThreadServices by lazy {
val mServicesField =
activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }
@Suppress("UNCHECKED_CAST")
mServicesField[activityThreadInstance] as Map<IBinder, Service>
}
override fun install(a) {
checkMainThread()
try {
swapActivityThreadHandlerCallback { mCallback ->
uninstallActivityThreadHandlerCallback = {
swapActivityThreadHandlerCallback {
mCallback
}
}
Handler.Callback { msg ->
// Call the stopService function
if (msg.what == STOP_SERVICE) {
val key = msg.obj as IBinder
//activityThreadServices must not be empty
// So onServicePreDestroy will put the Service in servicesToBeDestroyed internally
ServiceDoneExecuting is a callbackactivityThreadServices[key]? .let { onServicePreDestroy(key, it) } } mCallback? .handleMessage(msg) ? :false}}/ / servicesToBeDestroyed related hooks
swapActivityManager { activityManagerInterface, activityManagerInstance ->
uninstallActivityManager = {
swapActivityManager { _, _ ->
activityManagerInstance
}
}
Proxy.newProxyInstance(
activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
) { _, method, args ->
if(METHOD_SERVICE_DONE_EXECUTING == method.name) { val token = args!! [0] as IBinder
//servicesToBeDestroyed will be called back when it is created and also when it is destroyed
// Add elements when servicesToBeDestroyed above.
// So servicesToBeDestroyed contains the service to be detected and calls onServiceDestroyed directly to check if the service is damaged
if (servicesToBeDestroyed.containsKey(token)) {
onServiceDestroyed(token)
}
}
try {
if (args == null) {
method.invoke(activityManagerInstance)
} else {
method.invoke(activityManagerInstance, *args)
}
} catch (invocationException: InvocationTargetException) {
throw invocationException.targetException
}
}
}
} catch (ignored: Throwable) {
SharkLog.d(ignored) { "Could not watch destroyed services"}}}Copy the code