Android allows must-see performance optimization memory leak nowhere to hide

Hi, I will share the knowledge and Android regularly resolution, the topic will be constantly updated BATJ interview, welcome to come to discuss the exchange, if any good articles are also welcome to contribute, if you like friends welcome attention .

Foreword

For memory leaks, I think we certainly have encountered in development, but a memory leak is not visible to us, because it is in the heap activity, and whether there are memory leaks in order to generate detection program, usually we can help LeakCanary, MATand other tools to detect whether the application memory leak, MAT is a powerful memory analysis tools, many functions and complex, and LeakCanaryis powered by a lightweight Square open source third-party memory leak detection tool, when when it detects that the program has generated a memory leak, it will be the most intuitive way of telling us that the memory leak is produced and by whom the memory leak causes a leak but who can not be recycled, for our review.

Memory Leak

Why would a memory leak?

When an object has no need to use, and ought to be recovered, but there is another object that is being used to hold its reference leading to it can not be recovered, which led to this being the object can not be recovered and recycled to stay in heap memory, which creates a memory leak.

Memory Leak impact on the program?

Memory leaks are one of the main causes of application OOM! We know that the Android system is limited each application allocated memory, and when an application memory leak generated relatively long time, which will inevitably lead to application needs more memory than the memory allocated quota system, which resulted in a memory leak which led to the application Crash.

Android common memory leak summary

A single case caused by a memory leak

Singleton very loved by developers, but if used improperly can also cause memory leaks, because the static characteristics of a single case of making a single example of the application life cycle and life cycle of the same length, which indicates that if an object has been do not need to use, but also holds the singleton object reference to the object, then the object will not be properly recycled, which causes a memory leak.
The typical example is as follows:

    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 normal singleton, when creating this single example, because of the need to pass a Context, it is essential to the length of the life cycle of this Context:

1, was passed in the Application Context:  This will not have any problem, because as long as the life cycle of a single case and Application of
2, is passed Activity in Context:  When this Context corresponding Activity exit, since the Context and Activity life cycle as long as (Activity indirectly inherited Context), so the current time Activity withdraw its memory and will not be recovered because the singleton object holds the Activity of reference.
Therefore, the correct single embodiment should be modified in this manner is the following:

    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;
    }
}

So that regardless of what passed in the Context of end-use Application Context, and as long as the life cycle of a single case and applications, thus preventing memory leaks.

Non-static inner classes to create a memory leak caused by static instance

Sometimes we might start frequent Activity in order to avoid recreating the same data resource, such an approach may appear:

    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 {        //...
    }
}

这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏;

因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext

Handler造成的内存泄漏

Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例:

    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);
    }
}

这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例 ,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:

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);
    }
}

创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,更准确的做法如下:

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);
    }
}

使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();mHandler.removeMessages();来移除指定的Runnable和Message。

线程造成的内存泄漏

对于线程造成的内存泄漏,也是平时比较常见的,如下这两个示例可能每个人都这样写过:

//——————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();

上面的异步任务Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成;
那么将导致Activity的内存资源无法回收,造成内存泄漏。正确的做法还是使用静态内部类的方式,如下:

    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();

这样就避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源。

资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

一些建议

  1. 对于生命周期比Activity长的对象如果需要应该使用ApplicationContext

  2. 在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:


    webp

其中:NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建。

  1. 对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏

  2. 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:

  • 将内部类改为静态内部类

  • 静态内部类中使用弱引用来引用外部类的成员变量

  1. 对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。

  2. 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。

觉得文章不错的朋友帮忙点点赞加关注哦,有什么问题的话也欢迎大家前来探讨交流。


Guess you like

Origin blog.51cto.com/13673213/2419528