Objective:
Recently, the company's C-round financing was successful, the mobile team is ready to expand, the need to recruit Android development engineers, and continued to interview several Android candidates, interview process to talk about how to avoid memory leaks in performance optimization, few comprehensive answer. So decided to take time to learn to summarize this knowledge, and share how we detect memory leaks. Our company uses open source framework leakcanary to detect memory leaks.
What is a memory leak?
Some objects have only a limited life cycle. When their tasks are complete, they are garbage collected. If the object's lifecycle should end, this object is also referenced by a series of references, which can result in a memory leak. As the leak accumulates, the app will run out of memory.
What is the impact of a memory leak?
It is one of the main causes of application Oom. Because of the limited memory allocated by the Android system for each application, when a memory leak in an application is more likely to occur, it will inevitably result in the application needing more memory than the system allocates, which causes the memory overflow and the application crash.
What is Leakcanary?
Leakcanary is the square open source framework, is an Android and Java memory leak detection library, if detected an activity has a memory leak, Leakcanary is automatically display a notification, so you can interpret it as a fool-like memory leak detection tool. It can greatly reduce the oom problems encountered in the development and improve the quality of the app.
Leakcanary captures common memory leaks and solutions
1.) error using a single case resulting in memory leaks
In peacetime development of a single case design pattern is a design pattern we often use, and in the development of a single case often need to hold the context object, if the holding context object life cycle is shorter than the single life cycle, or cause the context can not be released recycling, it may cause memory leaks. The wrong wording is as follows:
public class Loginmanager {
private static Loginmanager minstance;
Private context Mcontext;
Private Loginmanager {
This.mcontext = context;
}
public static Loginmanager getinstance {
if (minstance = = null) {
synchronized ( Loginmanager.class) {
if (minstance = = null) {
minstance = new Loginmanager (context)
;
}} return minstance;
}
public void Dealdata () {
}
}
Weak we call in an activity, and then close the activity, a memory leak occurs.
Loginmanager.getinstance (This). Dealdata ();
Leakcanary test results are as follows:
The solution is to ensure that the context and application life cycle, the modified code is as follows:
public class Loginmanager {
private static Loginmanager minstance;
Private context Mcontext;
Private Loginmanager (Context context) {
this.mcontext = Context.getapplicationcontext ();
}
public static Loginmanager getinstance {
if (minstance = = null) {
synchronized ( Loginmanager.class) {
if (minstance = = null) {
minstance = new Loginmanager (context)
;
}} return minstance;
}
public void Dealdata () {
}
}
2.) handler caused by the memory leak
The use of handler in the early years was quite high, it was a bridge between worker threads and UI threads, but it was now encapsulated by a large open source framework, where we simulated a common use to simulate a memory leak scenario.
public class Mainactivity extends Appcompatactivity {
private Handler Mhandler = new Handler ();
Private TextView Mtextview;
@Override
protected void onCreate (Bundle savedinstancestate) {
super.oncreate (savedinstancestate);
Setcontentview (r.layout.activity_main);
Mtextview = (TextView) Findviewbyid (r.id.text);//Analog memory leak
mhandler.postdelayed (new Runnable () {
@Override Public
void Run () {
mtextview.settext ("LCJ");
}
, 3 * 1000);
Finish ();
}
@Override
protected void OnDestroy () {
Super.ondestroy ();
Lapplication.getrefwatcher (). Watch (this);
}
The above code creates the Mhandler object in the form of an inner class, at which point Mhandler implicitly holds an external class object reference, which is mainactivity, when the Postdelayed method is executed, the method loads your handler into a message and pushes this message into MessageQueue, MessageQueue is constantly polling processing messages in a looper thread, so when the activity exits, there are unhandled messages in the message queue or processing messages. Messages in message queues hold references to Mhandler instances, Mhandler also hold activity references, so the memory resources of the activity cannot be recycled in time, causing memory leaks.
Leakcanary test results are as follows:
To avoid handler causing a memory leak, we need to remove all messages and all runnable in the message queue when the activity closes out. The above code simply calls Mhandler.removecallbacksandmessages (null) in the OnDestroy () function;
public class MainActivity1 extends Appcompatactivity {
private Handler Mhandler = new Handler ();
Private TextView Mtextview;
@Override
protected void onCreate (Bundle savedinstancestate) {
super.oncreate (savedinstancestate);
Setcontentview (r.layout.activity_main);
Mtextview = (TextView) Findviewbyid (r.id.text);
Analog Memory leak
mhandler.postdelayed (new Runnable () {
@Override public
void Run () {
Mtextview.settext ( "LCJ");
}
, 3 * 1000 *);
Finish ();
}
@Override
protected void OnDestroy () {
Super.ondestroy ();
Mhandler.removecallbacksandmessages (null);
Mhandler=null;
Lapplication.getrefwatcher (). Watch (this);
}
3.) thread-caused memory leak
At the earliest time, processing time-consuming operations was mostly done in Thread+handler, and was gradually replaced by Asynctask until now the Rxjava approach was used to handle asynchrony. In Asynctask, for example, most people can handle a time-consuming operation and then notify the UI to update the results:
public class Mainactivity extends Appcompatactivity {private asynctask<void, Void, integer> asynctask;
Private TextView Mtextview;
@Override protected void OnCreate (Bundle savedinstancestate) {super.oncreate (savedinstancestate);
Setcontentview (R.layout.activity_main);
Mtextview = (TextView) Findviewbyid (R.id.text);
Testasynctask ();
Finish (); private void Testasynctask () {asynctask = new asynctask<void, void, integer> () {@Override Pro
tected Integer doinbackground (Void ... params) {int i = 0;
Simulate time-consuming operations while (!iscancelled ()) {i++;
if (i > 1000000000) {break;
} log.e ("Leakcanary", "asynctask---->" + i);
return i;
} @Override protected void OnPostExecute (Integer integer) {super.onpostexecute (integer);
Mtextview.settext (string.valueof (integer));
}
};
Asynctask.execute ();} @Override protected void OnDestroy () {Super.ondestroy ();
Lapplication.getrefwatcher (). Watch (this);
}
}
For the above example, when dealing with a time-consuming operation, an exit operation may have been performed without processing the end mainactivity, However, Asynctask still holds a reference to mainactivity, which causes the mainactivity to fail to release a memory leak caused by the recycle.
Leakcanary Test Results:
How do you solve this memory leak? When using Asynctask, it is also necessary to cancel the corresponding task Asynctask.cancel () method when the activity is destroyed, so as to avoid the waste of resources in the background and avoid the memory leakage.
public class MainActivity3 extends Appcompatactivity {private asynctask<void, Void, integer> asynctask;
Private TextView Mtextview;
@Override protected void OnCreate (Bundle savedinstancestate) {super.oncreate (savedinstancestate);
Setcontentview (R.layout.activity_main);
Mtextview = (TextView) Findviewbyid (R.id.text);
Testasynctask ();
Finish (); private void Testasynctask () {asynctask = new asynctask<void, void, integer> () {@Override Pro
tected Integer doinbackground (Void ... params) {int i = 0;
Simulate time-consuming operations while (!iscancelled ()) {i++;
if (i > 1000000000) {break;
} log.e ("Leakcanary", "asynctask---->" + i);
return i;
} @Override protected void OnPostExecute (Integer integer) {super.onpostexecute (integer);
Mtextview.settext (string.valueof (integer));
}
};
Asynctask.execute (); private void Destroyasynctask () {if (Asynctask!= null &&!asynctask.iscancelled ()) {ASYNCTASK.C
Ancel (TRUE);
} asynctask = null;
} @Override protected void OnDestroy () {Super.ondestroy ();
Destroyasynctask ();
Lapplication.getrefwatcher (). Watch (this);
}
}
4.) Non-static internal class creates a memory leak caused by a static instance
Sometimes we need an activity that can rotate with the screen, such as video playback activity, and we typically choose to create an inner class and a static instance to hold these parameters in order to prevent some of the parameters from being reinitialized by calling the OnCreate method multiple times. For example, the following implementations:
public class Mainactivity extends Appcompatactivity {private static Config mconfig;
@Override protected void OnCreate (Bundle savedinstancestate) {super.oncreate (savedinstancestate);
Setcontentview (R.layout.activity_main);
Analog memory Leak if (mconfig = = null) {mconfig = new Config ();
Mconfig.setsize (18);
Mconfig.settitle ("Old Nine Door");
Finish ();
} @Override protected void OnDestroy () {Super.ondestroy ();
Lapplication.getrefwatcher (). Watch (this);
Class Config {private int size;
Private String title;
public int GetSize () {return size;
public void setSize (int size) {this.size = size;
Public String GetTitle () {return title;
public void Settitle (String title) {this.title = title; }
}
}
The above code looks like there is no problem, in fact, the inner class will hold an external class reference, where this external class is mainactivity, but the internal class instance is static static variable its life cycle and application life cycle, So when the mainactivity is closed, the internal class static instance still holds a reference to the mainactivity, causing the mainactivity to not be reclaimed and released, causing a memory leak. The leakcanary detects memory leaks as follows:
The solution to this leak is to change the internal class into a static internal class, no longer hold the reference to Mainactivity, the modified code is as follows:
public class Mainactivity extends appcompatactivity {
private static Config mconfig;
@Override
protected void onCreate (Bundle savedinstancestate) {
super.oncreate (savedinstancestate);
Setcontentview (r.layout.activity_main);
Analog Memory leak
if (mconfig = = null) {
mconfig = new Config ();
Mconfig.setsize ();
Mconfig.settitle ("Old Nine Gate");
}
Finish ();
}
@Override
protected void OnDestroy () {
Super.ondestroy ();
Lapplication.getrefwatcher (). Watch (this);
Static class Config {
private int size;
Private String title;
public int GetSize () {return
size;
}
public void setSize (int size) {
this.size = size;
}
Public String GetTitle () {return
title;
}
public void Settitle (String title) {
this.title = title;
}
}
5.) memory leaks caused by webview
In the current development more or less will use the hybrid development method, so we will use WebView to host HTML page, as follows this way:
Java code:
public class MainActivity5 extends Appcompatactivity {
private webview Mwebview;
@Override
protected void onCreate (Bundle savedinstancestate) {
super.oncreate (savedinstancestate);
Setcontentview (r.layout.activity_web);
Mwebview = (webview) Findviewbyid (r.id.web);
Mwebview.loadurl ("http://www.cnblogs.com/whoislcj/p/5720202.html");
}
@Override
protected void OnDestroy () {
Super.ondestroy ();
Lapplication.getrefwatcher (). Watch (this);
}
XML layout file:
<?xml version= "1.0" encoding= "Utf-8"?> <linearlayout xmlns:android=
"http://schemas.android.com/" Apk/res/android "
android:id=" @+id/activity_main "
android:layout_width=" Match_parent "
android: layout_height= "Match_parent"
android:orientation= "vertical" >
<webview android:id= "
@+id/web"
android:layout_width= "wrap_content"
android:layout_height= "wrap_content"/>
</linearlayout >
WebView when parsing a Web page, the native heap memory is applied to save page elements, which can have a large memory footprint when the page is more complex. If the page contains pictures, the memory footprint will be more serious. And when you open a new page, the memory occupied by the page will not be released in order to be able to rewind quickly. Sometimes browsing more than 10 pages can consume hundreds of megabytes of memory. Such loading of web pages, will cause the system overwhelmed, and eventually forced to close the application, that is, the application of the flash-back or restart. It does not make any difference to call the following code in OnDestroy when the activity is closed in time.
private void Destroywebview () {
if (Mwebview!= null) {
mlinearlayout.removeview (mwebview);
Mwebview.pausetimers ();
Mwebview.removeallviews ();
Mwebview.destroy ();
Mwebview = null;
}
}
First look at the results of the leakcanary detected as follows:
How to solve it? This looked up a lot of data, one of which is to use Getapplicationgcontext as a parameter to build WebView, and then dynamically add to a ViewGroup, the last exit when the call to WebView destroy the function, Although it also achieves the effect of preventing memory overflow, there is a unable to add window--token null was not a application error when some pages need to remember a password dialog box, so the workaround here is through Put an activity (or service) that uses webview in a separate process. Then call Android.os.Process.killProcess (Android.os.Process.myPid) after detecting that the application is consuming memory and is likely to be killed by the system or after its activity (or service) is over. ), actively kill the process. Since the system's memory allocation is process-based, the system automatically reclaims all memory after the process shuts down.
The modified code is as follows:
public class MainActivity5 extends Appcompatactivity {
private webview Mwebview;
@Override
protected void onCreate (Bundle savedinstancestate) {
super.oncreate (savedinstancestate);
Setcontentview (r.layout.activity_web);
Mwebview = (webview) Findviewbyid (r.id.web);
Mwebview.loadurl ("http://www.cnblogs.com/whoislcj/p/5720202.html");
}
@Override
protected void OnDestroy () {
destroywebview ();
Android.os.Process.killProcess (Android.os.Process.myPid ());
Super.ondestroy ();
Lapplication.getrefwatcher (). Watch (this);
private void Destroywebview () {
if (Mwebview!= null) {
mwebview.pausetimers ();
Mwebview.removeallviews ();
Mwebview.destroy ();
Mwebview = null;}}}
The corresponding activity configuration in manifest is as follows:
<activity
android:name= ". MainActivity5 "
android:process=" Com.whoislcj.webview "/>
6.) Memory leak due to resource not shutdown
The use of resources such as braodcastreceiver,contentobserver,file,cursor,stream,bitmap should be closed or cancelled in time for the activity to be destroyed, otherwise these resources will not be recycled, caused a memory leak. For example get Media Library picture address code at the end of the query must be called
The Cursor shutdown method prevents memory leaks.
String columns[] = new string[]{
MediaStore.Images.Media.DATA, mediastore.images.media._id, MediaStore.Images.Media.TITLE, MediaStore.Images.Media.DISPLAY_NAME
};
Cursor Cursor = This.getcontentresolver (). Query (MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, NULL, NULL, NULL);
if (cursor!= null) {
int photoindex = Cursor.getcolumnindexorthrow (MediaStore.Images.Media.DATA);
Displays the address of each picture, but first to determine whether cursor has a value while
(Cursor.movetonext ()) {
String Photopath = cursor.getstring ( Photoindex); Here is the location of the image stored information
log.e ("Leakcanary", "photopath---->" + photopath);
}
Cursor.close ();
}
Summarize:
These are the memory leaks detected through leakcanary and the solution. Hope to help you learn, but also hope that we support the cloud-dwelling community.