Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Example Analysis of OOM and Leakcanary in Android

2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly introduces the example analysis of OOM and Leakcanary in Android, which is very detailed and has certain reference value. Friends who are interested must finish it!

Common scenarios of Android memory leaks and solution resource objects are not closed

When a resource object is no longer in use, you should immediately call its close () function, turn it off, and then set it to null. For example, resources such as Bitmap are not closed, which will cause memory leaks. At this time, we should close them in time when Activity is destroyed.

Registered object is not logged out

For example, for memory leaks caused by the failure to log out of BraodcastReceiver and EventBus, we should log out in time when Activity is destroyed.

The static variable of class holds big data

Objects try to avoid using static variables to store data, especially big data objects, it is recommended to use database storage.

Memory leak caused by a single case

Give priority to the Context of Application. If you need to use the Context of Activity, you can encapsulate the Context with a weak reference, and then get the Context from the weak reference where it is used. If you cannot get it, you can simply return it.

Static instance of a non-static inner class

The life cycle of the instance is as long as that of the application, which causes the static instance to hold the reference to the Activity all the time, and the memory resources of the Activity cannot be reclaimed normally. At this point, we can set the inner class as a static inner class or extract it and encapsulate it into a singleton. If you need to use Context, try to use Application Context. If you need to use Activity Context, remember to leave it empty so that GC can be recycled, otherwise it will still leak memory.

Handler temporary memory leak

After the Message is issued, it is stored in the MessageQueue. There is a target in the Message, which is a reference to the Handler. If the Message exists in the Queue for too long, the Handler cannot be recycled. If the Handler is non-static, it will cause Activity or Service to not be recycled. And the message queue constantly polls messages for processing in a Looper thread. When the Activity exits, there are still unprocessed messages or messages being processed in the message queue, and the Message in the message queue holds the reference of the Handler instance, and the Handler holds the reference of the Activity, so the memory resources of the Activity can not be reclaimed in time, causing a memory leak. The solution is as follows:

1. Use a static Handler inner class, and then use weak references to objects held by Handler (usually Activity), so that objects held by Handler can also be recycled when reclaimed.

two。 In the Destroy or Stop of Activity, you should remove messages from the message queue to avoid processing messages that need to be processed in the message queue of the Looper thread. It should be noted that AsyncTask is also an internal Handler mechanism, and there is also a risk of memory leaks, but it is generally temporary. For memory leaks like AsyncTask or threads, we can also separate the AsyncTask and Runnable classes or use static inner classes.

Memory leak caused by non-cleaning of objects in the container

Before exiting the program, clear the things in the collection, then set it to null, and then exit the program

WebView

WebView has the problem of memory leak, as long as WebView is used once in the application, the memory will not be released. We can start a separate process for WebView and use AIDL to communicate with the main process of the application. The process of WebView can be terminated at an appropriate time according to the needs of the business to achieve the purpose of releasing memory normally.

Memory leaks caused by using ListView

When constructing an Adapter, the cached convertView is used.

Leakcanaryleakcanary import / / leakcanary add support libraries, only how to install debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'leakcanary under debug

Leakcanary doesn't need to be initialized, it uses ContentProvider!

The ContentProvider.onCreate method is executed earlier than Application.onCreate. It is stated in the Manifest.xml of the LeakCanary source code that all Manifest will be merged into app's Manifest in the ContentProvider,apk packaging process, that is, APP has ContentProvider.

/ / package= "com.squareup.leakcanary.leaksentry"

Here is the initialization code

Internal class LeakSentryInstaller: ContentProvider () {override fun onCreate (): Boolean {CanaryLog.logger = DefaultCanaryLog () val application = contextinitial.applicationContext as Application / / initialize the core InternalLeakSentry.install (application) return true}

Monitoring implementation

Fun install (application: Application) {CanaryLog.d ("Installing LeakSentry") / / can only be called in the main thread Otherwise, an exception checkMainThread () if (this::application.isInitialized) {return} InternalLeakSentry.application = application val configProvider = {LeakSentry.config} / / listens to Activity.onDestroy () ActivityDestroyWatcher.install (application, refWatcher, configProvider) / / listens to Fragment.onDestroy () FragmentDestroyWatcher.install (application, refWatcher) ConfigProvider) / / Sentry Sentinel listener.onLeakSentryInstalled (application)} leakcanary how to monitor Activity, Fragment destruction

It is necessary to understand ActivityLifecycleCallbacks and FragmentLifeCycleCallbacks before understanding the monitoring process.

/ / ActivityLifecycleCallbacks interface public interface ActivityLifecycleCallbacks {void onActivityCreated (Activity var1, Bundle var2); void onActivityStarted (Activity var1); void onActivityResumed (Activity var1); void onActivityPaused (Activity var1); void onActivityStopped (Activity var1); void onActivitySaveInstanceState (Activity var1, Bundle var2); void onActivityDestroyed (Activity var1) } / / FragmentLifecycleCallbacks interface public abstract static class FragmentLifecycleCallbacks {public void onFragmentCreated (FragmentManager fm, Fragment f, Bundle savedInstanceState) {} public void onFragmentViewDestroyed (FragmentManager fm, Fragment f) {} public void onFragmentDestroyed (FragmentManager fm, Fragment f) {} / / omit other lifecycles.}

The Application class provides registerActivityLifecycleCallbacks and unregisterActivityLifecycleCallbacks methods for registering and unregistering Activity lifecycle listener classes, so that we can do some uniform handling of all Activity lifecycle callbacks in Application. Similarly, the FragmentManager class provides lifecycle listener classes for registerFragmentLifecycleCallbacks and unregisterFragmentLifecycleCallbacks methods for user registration and unregistration of Fragment, so that we can register each Activity and get all Fragment lifecycle callbacks.

The following is the implementation of ActivityDestroyWatcher. RefWatcher listens to activity's onActivityDestroyed.

Internal class ActivityDestroyWatcher private constructor (privateval refWatcher: RefWatcher, privateval configProvider: ()-> Config) {privateval lifecycleCallbacks = object: ActivityLifecycleCallbacksAdapter () {override fun onActivityDestroyed (activity: Activity) {if (configProvider () .watchActivities) {/ / after listening to onDestroy () Monitor Activity refWatcher.watch (activity)}} companion object {fun install (application: Application, refWatcher: RefWatcher, configProvider: ()-> Config) {val activityDestroyWatcher = ActivityDestroyWatcher (refWatcher, configProvider) / / register Activity life cycle monitoring application.registerActivityLifecycleCallbacks (activityDestroyWatcher.lifecycleCallbacks)}} via refWatcher

In this way, we can all know when Activity and Fragment call onDestroy. It is reasonable to say that if it is normal to be GC when calling onDestroy, if it is not recycled, there is a memory leak, which is what we need to deal with. What happens after refWatcher.watch (activity) monitors it and destroys it?

Core principle of RefWatcher

It is easier to understand a chestnut before reading this code: for example, we go to the science and technology center for an interview.

When you enter, you will register your personal information in the watch list and indicate the stay time of 30 minutes.

Check to see if there is any logout after 30 minutes.

If not logged out, transfer the information from the watch list to the suspect list.

When there are more than 5 suspected lists, find a public security officer to determine whether they are terrorists.

Make sure it's a terrorist, and the police arrest someone.

The implementation principle of RefWatcher is similar to that of Chestnut above:

After Activity calls onDestroy, key is generated with UUID, wrapped by KeyedWeakReference, associated with ReferenceQueue, and stored in watchedReferences (watchedReferences corresponds to observation queue)

Wait for 5 seconds

Call the moveToRetained method to determine whether it has been released, and if not, transfer it from watchedReferences (observation queue) to retainedReferences (suspect queue).

When the length of the retainedReferences queue is greater than 5, first call GC, and use HAHA as an open source library to analyze the heap memory after dump.

Determine the memory leak object

Let's first take a look at the implementation of refWatcher.watch (activity)

@ Synchronized fun watch (watchedReference: Any, referenceName: String) {if (! isEnabled ()) {return} / / remove the reference to be GC in the queue removeWeaklyReachableReferences () val key = UUID.randomUUID (). ToString () val watchUptimeMillis = clock.uptimeMillis () / / build the currently referenced weak reference object And associate the reference queue queueval reference = KeyedWeakReference (watchedReference, key, referenceName, watchUptimeMillis, queue) if (referenceName! = "") {CanaryLog.d ("Watching instance of% s named% s with key% s", reference.className, referenceName, key)} else {CanaryLog.d ("Watching instance of% s with key% s", reference.className) Key)} / / Save the reference in watchedReferences watchedReferences [key] = reference checkRetainedExecutor.execute {/ / if the current reference has not been removed Still in watchedReferences queue, / / description has not been GC, moved to retainedReferences queue, temporarily marked as leaking moveToRetained (key)}}

Analyze what the above code does:

Remove the reference to be GC in the queue, where the queue includes watchedReferences and retainedReferences

Use UUID to generate unique key, build WeakReference wrapper activity and associate with ReferenceQueue

Put reference into the observation queue watchedReferences

The thread pool calls the moveToRetained function. This function goes through gc first, and the objects that are still uncollected will enter the retainedReferences suspect queue. When the queue is greater than 5, call the HAHA library to analyze the reachability to determine whether it is a memory leak.

The following is a detailed analysis-"removeWeaklyReachableReferences () logic"

Private fun removeWeaklyReachableReferences () {/ / 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. / / once a weak reference becomes weak and reachable, it will join the team immediately. This will happen before finalization or GC. Var ref: KeyedWeakReference? The objects in the do {/ / queue queue are all ref = queue.poll () as KeyedWeakReference? / / of the GC. If (ref! = null) {val removedRef = watchedReferences.remove (ref.key) / / gets the key if (removedRef = = null) {retainedReferences.remove (ref.key)} / / of the released reference. Remove ref objects from the watchedReferences queue that will be GC All that's left is the object} while (ref! = null)} that may be leaked.

The removeWeaklyReachableReferences function removes the references in the watchedReferences (observation queue) and retainedReferences (suspected queue) according to the key of the KeyedWeakReference from ReferenceQueue, that is, remove the freed ones, and the rest are memory leaks.

MoveToRetained (key) logic implementation

@ Synchronized private fun moveToRetained (key: String) {/ / call again to prevent omission of removeWeaklyReachableReferences () val retainedRef = watchedReferences.remove (key) / / indicates that there may be a memory leak if (retainedRef! = null) {retainedReferences [key] = retainedRef onReferenceRetained ()}}

What this function does:

Go through the removeWeaklyReachableReferences method to clear the recycled ones.

Move unrecycled references from watchedReferences (observation queue) to retainedReferences (suspect queue)

OnReferenceRetained () detects memory leaks in the worker thread and finally calls the checkRetainedInstances function.

Here is the implementation of checkRetainedInstances

Private fun checkRetainedInstances (reason: String) {CanaryLog.d ("Checking retained instances because% s", reason) val config = configProvider () / / A tick will be rescheduled when this is turned back on. If (! config.dumpHeap) {return} var retainedKeys = refWatcher.retainedKeys / / the current number of leaked instances is less than 5 No heap dump if (checkRetainedCount (retainedKeys, config.retainedVisibleThreshold)) return if (! config.dumpHeapWhenDebugging & & DebuggerControl.isDebuggerAttached) {showRetainedCountWithDebuggerAttached (retainedKeys.size) scheduleRetainedInstanceCheck ("debugger was attached", WAIT_FOR_DEBUG_MILLIS) CanaryLog.d ("Not checking for leaks while the debugger is attached, will retry in% d ms" WAIT_FOR_DEBUG_MILLIS) return} / / it is possible that the observed reference will become weakly reachable But not yet joined the queue to quote the queue. / / GC should be called actively at this time It may be possible to avoid a heap dump gcTrigger.runGc () retainedKeys = refWatcher.retainedKeys if (checkRetainedCount (retainedKeys, config.retainedVisibleThreshold)) return HeapDumpMemoryStore.setRetainedKeysForHeapDump (retainedKeys) CanaryLog.d ("Found% d retained references, dumping the heap", retainedKeys.size) HeapDumpMemoryStore.heapDumpUptimeMillis = SystemClock.uptimeMillis () dismissNotification () val heapDumpFile = heapDumper.dumpHeap () / AndroidHeapDumper if (heapDumpFile = = null) {CanaryLog.d ("Failed to dump heap, will retry in% d ms" WAIT_AFTER_DUMP_FAILED_MILLIS) scheduleRetainedInstanceCheck ("failed to dump heap", WAIT_AFTER_DUMP_FAILED_MILLIS) showRetainedCountWithHeapDumpFailed (retainedKeys.size) return} refWatcher.removeRetainedKeys (retainedKeys) / remove retainedKeys HeapAnalyzerService.runAnalysis (application, heapDumpFile) / / analyze heap dump files that have been heap dump} flowchart

The above is all the content of the article "sample Analysis of OOM and Leakcanary in Android". Thank you for reading! Hope to share the content to help you, more related knowledge, welcome to follow the industry information channel!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report