Android performance optimization must see that memory leaks are nowhere to hide

Hi, I will share Android knowledge points and analysis regularly, and update BATJ interview topics constantly. Welcome to discuss and exchange. If you have good articles, you are welcome to submit contributions. If you like friends, you are also welcome to pay attention.

Preface

For memory leaks, I think we have all encountered them in development, but memory leaks are not visible to us, because they are active in the heap. To detect whether there is a memory leak in the program, we can usually use LeakCanary, MAT and other tools to detect whether there is memory in the application. Leak Canary is a lightweight third-party memory leak detection tool, which is open source by Square. When it detects a memory leak in a program, it will tell us in the most intuitive way who caused the memory leak and who the memory leak is. Who leaked and could not be recovered for our review?

Memory leak

Why does memory leak occur?

When an object is no longer needed and should be recycled, another object in use holds its reference so that it can not be recycled, which results in the object that should be recycled can not be recycled and stays in heap memory, resulting in memory leaks.

The impact of memory leaks on programs?

Memory leak is one of the main causes of application OOM! We know that Android system allocates limited memory for each application, and when there are more memory leaks in an application, it will inevitably lead to the application required more memory than the system allocated memory limit, which leads to memory overflow and application Crash.

Summary of common memory leaks in Android

Memory leaks caused by singletons

Singleton mode is very popular with developers, but improper use can also cause memory leaks. Because the static characteristics of singletons make the lifecycle of singletons as long as the lifecycle of applications. This shows that if an object is no longer needed and a singleton object still holds a reference to that object, then Then the object will not be recovered properly, which leads to memory leaks.
The following is an example:

    private static AppManager instance;    private Context context;    private AppManager(Context context) {        this.context = context;
    }    public static AppManager getInstance(Context context) {        if (instance != null) {
            instance = new AppManager(context);
        }        return instance;
    }
}

This is a common singleton pattern. When creating this singleton, the length of the life cycle of the Context is critical because a Context needs to be passed in.

1. Application Context is passed in: There will be no problem because the lifecycle of the singleton is as long as that of Application.
2. Input is Activity's Content: When this Context's corresponding Activity exits, its memory will not be reclaimed when the current Activity exits because the Context is as long as the Activity's life cycle (Activity indirectly inherits Context), because the singleton object holds the reference to the Activity.
So the correct singleton should be modified as follows:

    private static AppManager instance;    private Context context;    private AppManager(Context context) {        this.context = context.getApplicationContext();
    }    public static AppManager getInstance(Context context) {        if (instance != null) {
            instance = new AppManager(context);
        }        return instance;
    }
}

No matter what context is passed in, the Application Context will eventually be used, and the lifecycle of the singleton is as long as that of the application, thus preventing memory leaks.

Memory leaks caused by creating static instances of non-static internal classes

Sometimes we may start frequent Activities to avoid duplicating the creation of the same data resources.

    private static TestResource mResource = null;    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);        if(mManager == null){
            mManager = new TestResource();
        }        //...
    }    class TestResource {        //...
    }
}

In this way, a singleton of non-static inner class is created inside Activity, and the data of the singleton is used every time the Activity is started, which avoids the repeated creation of resources, but this writing will cause memory leak.

Because the non-static inner class will hold the reference of the external class by default, and use the non-static inner class to create a static instance, the life cycle of the instance is as long as the application, which leads to the static instance holding the reference of the Activity all the time, leading to the memory resources of the Activity can not be recovered properly. The correct approach is:
Set the inner class as a static inner class or extract the inner class to encapsulate it as a singleton. If you need to use Context, use ApplicationContext.

Memory leak caused by Handler

The memory leak problem caused by the use of Handler is the most common one. In normal times, APIs such as dealing with network tasks or encapsulating some request callbacks should be handled with the help of Handler. Writing an irregular code for the use of Handler may cause memory leak. The following examples are given:

    private Handler mHandler = new Handler() {        @Override
        public void handleMessage(Message msg) {            //...
        }
    };    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }    private void loadData(){        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

This way of creating Handler can cause memory leaks. Because mHandler is an instance of Handler's non-static anonymous inner class, it holds a reference to the external class Activity. We know that Message queue is polling continuously in a Looper thread to process messages, so there is still room in the Message queue when the activity exits. The Message in the Message queue holds the reference of the mHandler instance and the mHandler holds the reference of the activity, so that the memory resources of the activity can not be recovered in time and cause memory leak. So another method is:

public class MainActivity extends AppCompatActivity {    private MyHandler mHandler = new MyHandler(this);    private TextView mTextView ;    private static class MyHandler extends Handler {        private WeakReference<Context> reference;        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) reference.get();            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }    private void loadData() {        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

Create a static Handler inner class, and then use weak references to the objects held by Handler, so that the objects held by Handler can also be recycled when they are recycled. This avoids activity leak, but the message queue of Looper thread may still be messages to be processed, so when we are in Activity Dextroy or St. When op, the message in the message queue should be removed, more accurately as follows:

public class MainActivity extends AppCompatActivity {    private MyHandler mHandler = new MyHandler(this);    private TextView mTextView ;    private static class MyHandler extends Handler {        private WeakReference<Context> reference;        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) reference.get();            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }    private void loadData() {        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }    @Override
    protected void onDestroy() {        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

Use mHandler. removeCallbacks AndMessages (null); remove all messages in the Message queue and all Runnable. Of course, you can also use mHandler.removeCallbacks(); or mHandler.removeMessages(); to remove the specified Runnable and Mesage.

Memory leaks caused by threads

Memory leaks caused by threads are also common. The following two examples may have been written by everyone as follows:

//---test1
        new AsyncTask<Void, Void, Void>() {            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(10000);                return null;
            }
        }.execute();//---test2
        new Thread(new Runnable() {            @Override
            public void run() {
                SystemClock.sleep(10000);
            }
        }).start();

The above asynchronous tasks and Runnable are both anonymous inner classes, so they all have an implicit reference to the current Activity. If Activity is not finished before it is destroyed;
Then it will cause the memory resources of Activity to be unrecoverable and cause memory leak. The correct way to do this is to use static internal classes as follows:

    static class MyAsyncTask extends AsyncTask<Void, Void, Void> {        private WeakReference<Context> weakReference;        public MyAsyncTask(Context context) {
            weakReference = new WeakReference<>(context);
        }        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(10000);            return null;
        }        @Override
        protected void onPostExecute(Void aVoid) {            super.onPostExecute(aVoid);
            MainActivity activity = (MainActivity) weakReference.get();            if (activity != null) {                //...
            }
        }
    }    static class MyRunnable implements Runnable{        @Override
        public void run() {
            SystemClock.sleep(10000);
        }
    }//---
    new Thread(new MyRunnable()).start();    new MyAsyncTask(this).execute();

This avoids the leakage of Activity's memory resources. Of course, when the Activity is destroyed, the corresponding task AsyncTask::cancel() should also be cancelled to avoid wasting resources when the task is executed in the background.

Memory leak caused by resource not closing

For the use of BraodcastReceiver, ContentObserver, File, Cursor, Stream, Bitmap and other resources, it should be closed or cancelled in time when the Activity is destroyed, otherwise these resources will not be recycled, resulting in memory leaks.

Some suggestions

  1. ApplicationContext should be used for objects whose lifecycle is longer than Activity if necessary

  2. When it comes to Context, consider ApplicationContext first. Of course, it is not omnipotent. For some places, you must use Activity's Context. For Application, Service and Activity, the application scenarios of Context are as follows:


Among them: NO1 means that Application and Service can start an Activity, but a new task queue needs to be created. For Dialog, it can only be created in Activity.

  1. For non-static external member variables (such as Context, View) that need to be used in static internal classes, weak references can be used in static internal classes to reference variables of external classes to avoid memory leaks.

  2. For internal class objects with longer life cycles than Activity, and internal classes using member variables of external classes, memory leaks can be avoided by:

  • Change internal classes to static internal classes

  • Use weak references in static inner classes to refer to member variables of external classes

  1. For objects that no longer need to be used, the display assigns them null, such as calling recycle() after using Bitmap, and then null.

  2. Keep sensitive to the object life cycle, paying special attention to the life cycle of singletons, static objects, global collections, etc.

Friends who think the article is good help a little Zangjia attention Oh, if there are any problems, you are welcome to come to discuss and exchange.


Tags: Android network

Posted on Thu, 11 Jul 2019 17:54:49 -0400 by billli