Android memory leak troubleshooting analysis and common solutions

What is a memory leak:

In the Android development process, when an object is no longer needed and should be recycled, another object that is being used holds its reference and cannot be recycled, which causes the object that should have been recycled to fail. Being recycled and staying in the heap memory, a memory leak occurs.

Hazards of memory leaks?

It is one of the main reasons for application OOM; due to the limited memory allocated by the Android system for each application, when there are many memory leaks in an application, it will inevitably cause the memory required by the application to exceed the memory allocated by the system. Quota, which caused a memory leak and caused the application Crash;

Memory leak troubleshooting:

1. Use the adb command: adb shell dumpsys meminfo package name to view the current number of activities. Keep opening and closing the page to check the page, because garbage collection will not be performed immediately after closing the page, for testing, use the Profiler that comes with Android Studio, click Force garbage collection. If the number of activities is the same as at the beginning, it means normal. If the number of activities increases, it means a memory leak.

insert image description here
insert image description here

2. Use the Profiler in AS to further troubleshoot the problem, and click Dump Java heap to export the heap allocation.

insert image description here

Common memory leak situations:

1. Static Activity (Activity Context) and View

Static variables Activity and View will cause memory leaks. In the following code, the Context and TextView of Activity are set to static objects, resulting in memory leaks; because the
life cycle of the instance of context and textView is the same as the life of the application, and they hold the current Activity (MemoryTestActivity) reference, once MemoryTestActivity is destroyed, and its reference has been held, it will not be recycled, so a memory leak occurs;

public class MemoryTestActivity extends AppCompatActivity {
    
    

    private static Context context;
    private static TextView textView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_test);
        context = this;
        textView = new TextView(this);
    }
}

2. Memory leak caused by singleton

Android's singleton mode is a frequently used mode in development. Improper use may lead to memory leaks; the life cycle of a single case is the same as the life cycle of the application, that is, the singleton must hold the same object as the application life cycle, and cannot Hold objects that are inconsistent with the application life cycle For example: Activity (Context) context:

public class TestManager {
    
    

    private static TestManager manager;
    private Context context;

    private TestManager(Context context) {
    
    
        this.context = context;
    }

    /**
     * 如果传入的context是activity,service的上下文,会导致内存泄漏
     * 原因是我们的manger是一个static的静态对象,这个对象的生命周期和整个app的生命周期一样长
     * 当activity销毁的时候,我们的这个manger仍然持有者这个activity的context,就会导致activity对象无法被释放回收,就导致了内存泄漏
     */
    public static TestManager getInstance(Context context) {
    
    
        if (manager == null) {
    
    
            manager = new TestManager(context);
        }
        return manager;
    }
}

Solution: Modify the context Context used by the TestManager singleton mode. The TestManager singleton mode refers to the ApplicationContext. The TestManager singleton mode is the same as the application life cycle. The ApplicationContext is the same as the application life cycle, so that there will be no memory leaks;

public class TestManager {
    
    

    private static TestManager manager;
    private Context context;

    private TestManager(Context context) {
    
    
        this.context = context;
    }

    //正确写法
    public static TestManager getInstance(Context context) {
    
    
        if (manager == null) {
    
    
            manager = new TestManager(context.getApplicationContext());
        }
        return manager;
    }
}

3. Memory leaks caused by threads

The inner class of the anonymous thread will implicitly refer to the Activity. When performing time-consuming tasks, the Activity will always be implicitly referenced. When the Activity is closed, the inner class of the anonymous thread will implicitly refer to the Activity and cannot be recycled in time;

public class MemoryTestActivity extends AppCompatActivity {
    
    

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_test);
        anonymousInnerClass();
    }

    //匿名内部类持有MemoryTestActivity实例引用,当耗时匿名线程内部类执行完成以后MemoryTestActivity实例才会回收;
    public void anonymousInnerClass() {
    
    
          new AsyncTask<Void, Void, Void>(){
    
    
            @Override
            protected Void doInBackground(Void... voids) {
    
    
                //执行异步处理
                SystemClock.sleep(120000);
                return null;
            }
        }.execute();
    }
}

Solution: Modify the anonymous inner class of AsyncTask to a static class, remove the implicit reference of Activity, and cancel the asynchronous task staticAsyncTask.cancel(true) in time when MemoryTestActivity is destroyed, so as to prevent the asynchronous task execution from updating and destroying the UI of the MemoryTestActivity instance;

public class MemoryTestActivity extends AppCompatActivity {
    
    
    
    private StaticAsyncTask staticAsyncTask;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_test);

        staticAsyncTask = new StaticAsyncTask(this);
        staticAsyncTask.execute();
    }

    private static class StaticAsyncTask extends AsyncTask<Void, Void, Void> {
    
    
        private WeakReference<Context> weakReference;

        public StaticAsyncTask(Context context) {
    
    
            weakReference = new WeakReference<Context>(context);
        }

        @Override
        protected Void doInBackground(Void... voids) {
    
    
            //执行异步处理
            SystemClock.sleep(120000);
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
    
    
            super.onPostExecute(aVoid);

            MemoryTestActivity activity = (MemoryTestActivity) weakReference.get();
            if (activity != null) {
    
    
                //异步任务执行完成,执行UI处理
            }
        }
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        staticAsyncTask.cancel(true);
    }
}

4. Memory leaks caused by creating static instances of non-static inner classes

public class MemoryTestActivity extends AppCompatActivity {
    
    

    private static TestResource testResource;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_test);

        testResource = new TestResource();
    }

    class TestResource{
    
    
        //资源类
    }

}

In this way, a singleton of a non-static inner class is created inside the Activity, and the data of the singleton will be used every time the Activity is started. Although this avoids repeated creation, this way of writing will cause memory leaks, because the non-static inner class defaults to It will hold a reference to the external class, and use the non-static internal class to create a static instance. The life cycle of the instance is as long as the application, which causes the static instance to always hold the reference of the Activity, resulting in the Activity The memory resource cannot be recovered normally;
solution: set the internal class as a static internal class or abstract the internal class to encapsulate a singleton. If you need to use Context, please use ApplicationContext;

5. Memory leak caused by Handler

Memory leaks caused by the use of Handlers are relatively common. APIs such as processing network tasks or encapsulating some request callbacks should be handled with the help of Handlers. Non-standard codes used for Handlers may cause memory leaks, as shown in the following example:

    private Handler mHandler = new Handler(){
    
    
        @Override
        public void handleMessage(Message msg) {
    
    
            //处理UI显示
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_test);
        loadData();
    }

    //loadData()方法在子线程中执行
    private void loadData() {
    
    
        Message message = Message.obtain();
        //模拟线程延迟120秒发送Message
        mHandler.sendMessageDelayed(message, 120000);
    }
}

This way of creating a Handler may cause a memory leak. Since mHandler is an instance of the non-static anonymous inner class of Handler, it holds a reference to the external class Activity. We know that the message queue is continuously polling and processing messages in a Looper thread, then When the Activity exits, the message queue still has unprocessed messages or messages being processed (for example, in the above example, the time-consuming task is processed in the sub-thread, and the activity will exit and be destroyed before the execution is completed), while the Message in the message queue remains There is an mHandler instance reference, and mHander holds a reference to the Activity, so the memory of the Activity cannot be recovered in time, causing a memory leak;

public class MemoryTestActivity extends AppCompatActivity {
    
    
    private Handler handler = new StaticHandler(this);

    private static class StaticHandler extends Handler {
    
    

        WeakReference<Context> weakReference;

        public StaticHandler(Context context) {
    
    
            weakReference = new WeakReference<>(context);
        }

        @Override
        public void handleMessage(Message msg) {
    
    
            //处理UI显示
            MemoryTestActivity activity = (MemoryTestActivity) weakReference.get();
            if (activity != null) {
    
    

            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_test);
        loadData();
    }

    //loadData()方法在子线程中执行
    private void loadData() {
    
    
        Message message = Message.obtain();
        //模拟线程延迟120秒发送Message
        handler.sendMessageDelayed(message, 120000);
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
    }
}

Create a static Handler internal class, and then use weak applications for the objects held by the Handler, so that the objects held by the Handler can also be recycled during recycling, thus avoiding Activity leaks. If the Handler is delayed (delayed execution), it will be in the Destroy of the Activity Or the message in the message queue should be removed when Stop;
handler.removeCallbacksAndMessages(null); remove all messages and threads in the message queue;
solution summary:

  • Maintenance through program logic
    • Stop the background thread when closing the Activity; stopping the thread is equivalent to cutting off the Handler and the external connection line, and the Activity will naturally be recycled at an appropriate time;
    • If the Handler is referenced by the delayed Message, then use the removeCallbacks() method of the corresponding Handler to remove the message object from the message queue;
  • Declare Handler as a static class
    • In Java, non-static inner classes and anonymous inner classes will implicitly hold references to their outer classes, and static inner classes will not hold references to outer classes. The static class does not hold the object of the external class, so your Activity can be recycled at will; because the Handler no longer holds the reference of the object of the external class, the program does not allow you to operate the object in the Activity in the Handler, so you need to Add a weak reference to Activity (WeakReference) in Handler;

6. Animation

There is a kind of infinite loop animation in property animation. If you play this kind of animation in Activity and don’t stop the animation in onDestroy(), then the animation will continue to play. At this time, Activity will be held by View, which will cause Activity to fail. released. To solve such problems, call objectAnimator.cancel() in the onDestroy() method to stop the animation;

public class MemoryTestActivity extends AppCompatActivity {
    
    
    
    private TextView textView;
    private ObjectAnimator objectAnimator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_test);

        textView = (TextView)this.findViewById(R.id.textView2);
        objectAnimator = ObjectAnimator.ofFloat(textView, "rotation", 0, 360);
        objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
        objectAnimator.start();
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();

    }
}

Since objectAnimator.cancel() is not called in the onDestroy() method to stop the animation, the View that executes the animation always refers to the Activity, causing the Activity to fail to be destroyed; solution: call
objectAnimator.cancel() in the onDestroy() method to stop animation;

7. Improper use of third-party libraries

1. For the use of some third-party open source frameworks such as EventBus and RxJava, if the Activity is not unsubscribed before it is destroyed, it will cause memory leaks; 2. It needs to be relatively registered
and canceled in the life cycle (onCreate->onDestory | onResume->onPause ... )

8. Memory leaks caused by unclosed resources

For the use of resources such as BroadcastReceiver, ContentObserver, File, Cursor, Stream, Bitmap, etc., they should be closed or logged out in time when the Activity is destroyed, otherwise these resources will not be recycled, resulting in memory leaks.

Guess you like

Origin blog.csdn.net/u011106915/article/details/126738716