Memory Leak Optimization

Contents Introduction:

  • 01. What is a memory leak
  • 02. What is the impact of memory leaks
  • 03. memory leak detection tools which
  • 04. About Using Your Leakcanary
  • 05. Use a single case of error caused by a memory leak
  • 06.Handler memory leaks caused by improper use
  • 07.Thread not close the leak caused by the content
  • 08. Use static variables cause the error after reference can not be destroyed
  • 09.AsyncTask caused by a memory leak
  • 10. Create a non-static inner class static instance cause a memory leak
  • 11. The need to use the monitor does not remove a memory leak occurs
  • 12. Resource is not caused by a memory leak shut down
  • 13. The broadcast has not been destroyed after registration
  • 14. The error caused by a context using the context memory leak
  • 15. The collection of static memory leak due to improper use of
  • 16. Animated resources are not released causes a memory leak
  • 17. The system of InputMethodManager bug causes a memory leak

good news

  • Large blog notes summary [16 March] to date, including Java basic and in-depth knowledge, Android technology blog, Python study notes, etc., also includes bug summary usually encountered in the development, of course, also collected his spare time a large number of interview questions, long-term maintenance and updating correction, continuous improvement ...... open source file format is markdown! As well as open source live blog, from 12 years and has accumulated a total of N articles [nearly 100 million words, gradually move to the Internet], please indicate the source, thank you!
  • Link Address: https://github.com/yangchong211/YCBlogs
  • If you feel good, you can star, thank you! Of course, also welcome suggestions, everything is suddenly starting to micro, quantitative cause a qualitative change!

01. What is a memory leak

  • Some objects have a limited lifecycle, when these objects do things done, and we hope that they will be recovered out of the garbage collector. But if there is a series of references to the object exists, then we look forward to the end of the life cycle of this object when it is recycled garbage collector when it is not being recycled. It will also take up memory, which resulted in a memory leak. Continue to accumulate, the memory will soon be exhausted.
  • For example: When the Activity of onDestroy () method is called, Activity, and it relates to the View and the related Bitmap should be recovered out. However, if there is a background thread holds the Activity of reference, then the Activity occupied by the memory can not be recycled, which will ultimately lead to memory exhaustion caused OOM and let the application crash out.

02. What is the impact of memory leaks

  • It is one of the main causes of application OOM. Since the android system is limited each application allocated memory when an application memory leak generated relatively long time, it will inevitably lead to application needs more memory than the system memory allocation limits, which

03. memory leak detection tools which

  • The most common are: Leakcanary

04. About Using Your Leakcanary

  • leakCanary Square is an open source framework, is an Android and Java memory leak detection library, an activity if it detects there is a memory leak, LeakCanary a notification is automatically displayed, so you can understand it as fool-memory leak detection tools. Can significantly reduce the problems encountered in the development oom through it, greatly improve the quality of APP.
  • On how to configure, this is not to say, there are steps online

05. Use a single case of error caused by a memory leak

  • In normal development Singleton design pattern is a design pattern we often use, but in the development of a single case often need to hold the Context object, if the holder of the Context object lifecycle with a single case of the life cycle is shorter, or cause Context can not be recovered released, it may cause a memory leak error worded as follows:
  • Problems caused by a memory leak Code
    public class LoginManager {
        private static LoginManager mInstance; private Context mContext; private LoginManager(Context context) { this.mContext = context; //修改代码:this.mContext = context.getApplicationContext(); } public static LoginManager getInstance(Context context) { if (mInstance == null) { synchronized (LoginManager.class) { if (mInstance == null) { mInstance = new LoginManager(context); } } } return mInstance; } public void dealData() {} } 
  • scenes to be used
    • Called in an Activity, and then close the Activity will be a memory leak occurs.
    LoginManager.getInstance(this).dealData();
    
  • Take a look at the screenshot error
    • image
  • Solution:
    • To ensure Context and AppLication life cycle, as revised code is as follows:
    • this.mContext = context.getApplicationContext();
    • 1, if this time is passed in the Application Context, because Application life cycle is the entire application life cycle, so this will not have any problems.
    • 2、如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。

06.Handler使用不当造成内存泄漏

  • handler是工作线程与UI线程之间通讯的桥梁,只是现在大量开源框架对其进行了封装,我们这里模拟一种常见使用方式来模拟内存泄漏情形。
  • 解决Handler内存泄露主要2点
    • 有延时消息,要在Activity销毁的时候移除Messages
    • 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者Activity使用弱引用。
  • 问题代码
    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); //模拟内存泄露 mHandler.postDelayed(new Runnable() { @Override public void run() { mTextView.setText("yangchong"); } }, 2000); } } 
  • 造成内存泄漏原因分析
    • 上述代码通过内部类的方式创建mHandler对象,此时mHandler会隐式地持有一个外部类对象引用这里就是MainActivity,当执行postDelayed方法时,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,MessageQueue是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。
  • 查看报错结果如下:
    • image
  • 解决方案
    • 第一种解决办法
      • 要想避免Handler引起内存泄漏问题,需要我们在Activity关闭退出的时候的移除消息队列中所有消息和所有的Runnable。
      • 上述代码只需在onDestroy()函数中调用mHandler.removeCallbacksAndMessages(null);就行了。
      @Override
      protected void onDestroy() { super.onDestroy(); if(handler!=null){ handler.removeCallbacksAndMessages(null); handler = null; } } 
    • 第二种解决方案
      //自定义handler
      public static class HandlerHolder extends Handler { WeakReference<OnReceiveMessageListener> mListenerWeakReference; /** * @param listener 收到消息回调接口 */ HandlerHolder(OnReceiveMessageListener listener) { mListenerWeakReference = new WeakReference<>(listener); } @Override public void handleMessage(Message msg) { if (mListenerWeakReference!=null && mListenerWeakReference.get()!=null){ mListenerWeakReference.get().handlerMessage(msg); } } } //创建handler对象 private HandlerHolder handler = new HandlerHolder(new OnReceiveMessageListener() { @Override public void handlerMessage(Message msg) { switch (msg.what){ case 1: TextView textView1 = (TextView) msg.obj; showBottomInAnimation(textView1); break; case 2: TextView textView2 = (TextView) msg.obj; showBottomOutAnimation(textView2); break; } } }); //发送消息 Message message = new Message(); message.what = 1; message.obj = textView; handler.sendMessageDelayed(message,time); 即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。 

07.Thread未关闭造成内容泄漏

  • 当在开启一个子线程用于执行一个耗时操作后,此时如果改变配置(例如横竖屏切换)导致了Activity重新创建,一般来说旧Activity就将交给GC进行回收。但如果创建的线程被声明为非静态内部类或者匿名类,那么线程会保持有旧Activity的隐式引用。当线程的run()方法还没有执行结束时,线程是不会被销毁的,因此导致所引用的旧的Activity也不会被销毁,并且与该Activity相关的所有资源文件也不会被回收,因此造成严重的内存泄露。
  • 因此总结来看, 线程产生内存泄露的主要原因有两点:
    • 1.线程生命周期的不可控。Activity中的Thread和AsyncTask并不会因为Activity销毁而销毁,Thread会一直等到run()执行结束才会停止,AsyncTask的doInBackground()方法同理
    • 2.非静态的内部类和匿名类会隐式地持有一个外部类的引用
  • 例如如下代码,在onCreate()方法中启动一个线程,并用一个静态变量threadIndex标记当前创建的是第几个线程
    public class ThreadActivity extends AppCompatActivity { private final String TAG = "ThreadActivity"; private static int threadIndex; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new Thread(new Runnable() { @Override public void run() { int j = threadIndex; while (true) { Log.e(TAG, "Hi--" + j); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } } 
    • 旋转几次屏幕,可以看到输出结果为:
    04-04 08:15:16.373 23731-23911/com.yc.leakdemo E/ThreadActivity: Hi--2
    04-04 08:15:16.374 23731-26132/com.yc.leakdemo E/ThreadActivity: Hi--4
    04-04 08:15:16.374 23731-23970/com.yc.leakdemo E/ThreadActivity: Hi--3
    04-04 08:15:16.374 23731-23820/com.yc.leakdemo E/ThreadActivity: Hi--1
    04-04 08:15:16.852 23731-26202/com.yc.leakdemo E/ThreadActivity: Hi--5
    04-04 08:15:18.374 23731-23911/com.yc.leakdemo E/ThreadActivity: Hi--2
    04-04 08:15:18.374 23731-26132/com.yc.leakdemo E/ThreadActivity: Hi--4
    04-04 08:15:18.376 23731-23970/com.yc.leakdemo E/ThreadActivity: Hi--3
    04-04 08:15:18.376 23731-23820/com.yc.leakdemo E/ThreadActivity: Hi--1
    04-04 08:15:18.852 23731-26202/com.yc.leakdemo E/ThreadActivity: Hi--5
    ...
    
    • 即使创建了新的Activity,旧的Activity中建立的线程依然还在执行,从而导致无法释放Activity占用的内存,从而造成严重的内存泄漏。想要避免因为 Thread 造成内存泄漏,可以在 Activity 退出后主动停止 Thread
  • 例如,可以为 Thread 设置一个布尔变量 threadSwitch 来控制线程的启动与停止
    public class ThreadActivity extends AppCompatActivity { private final String TAG = "ThreadActivity"; private int threadIndex; private boolean threadSwitch = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new Thread(new Runnable() { @Override public void run() { int j = threadIndex; while (threadSwitch) { Log.e(TAG, "Hi--" + j); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } @Override protected void onDestroy() { super.onDestroy(); threadSwitch = false; } } 
  • 如果想保持Thread继续运行,可以按以下步骤来:
    • 1.将线程改为静态内部类,切断Activity 对于Thread的强引用
    • 2.在线程内部采用弱引用保存Context引用,切断Thread对于Activity 的强引用
    public class ThreadActivity extends AppCompatActivity { private static final String TAG = "ThreadActivity"; private static int threadIndex; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new MyThread(this).start(); } private static class MyThread extends Thread { private WeakReference<ThreadActivity> activityWeakReference; MyThread(ThreadActivity threadActivity) { activityWeakReference = new WeakReference<>(threadActivity); } @Override public void run() { if (activityWeakReference == null) { return; } if (activityWeakReference.get() != null) { int i = threadIndex; while (true) { Log.e(TAG, "Hi--" + i); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } 

08.错误使用静态变量导致引用后无法销毁

  • 在平时开发中,有时候我们创建了一个工具类。比如分享工具类,十分方便多处调用,因此使用静态方法是十分方便的。但是创建的对象,建议不要全局化,全局化的变量必须加上static。这样会引起内存泄漏!
  • 问题代码
    • image
  • 使用场景
    • 在Activity中引用后,关闭该Activity会导致内存泄漏
    DoShareUtil.showFullScreenShareView(PNewsContentActivity.this, title, title, shareurl, logo);
    
  • 查看报错
    • image
  • 解决办法
    • 静态方法中,创建对象或变量,不要全局化,全局化后的变量或者对象会导致内存泄漏;popMenuView和popMenu都不要全局化
  • 知识延伸
    非静态内部类,静态实例化
    public class MyActivity extends AppCompatActivity { //静态成员变量 public static InnerClass innerClass = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); innerClass = new InnerClass(); } class InnerClass { public void doSomeThing() {} } } 这里内部类InnerClass隐式的持有外部类MyActivity的引用,而在MyActivity的onCreate方法中调用了。 这样innerClass就会在MyActivity创建的时候是有了他的引用,而innerClass是静态类型的不会被垃圾回收, MyActivity在执行onDestory方法的时候由于被innerClass持有了引用而无法被回收,所以这样MyActivity就总是被innerClass持有而无法回收造成内存泄露。 静态变量引用不当会导致内存泄漏 静态变量ActivityView会导致内存泄漏,在下面这段代码中对ActivityContextTextView设置为静态对象,从而产生内存泄漏。 public class MainActivity extends AppCompatActivity { private static Context context; private static TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); context = this; textView = new TextView(this); } } 

09.AsyncTask造成的内存泄漏

  • 早时期的时候处理耗时操作多数都是采用Thread+Handler的方式,后来逐步被AsyncTask取代,直到现在采用RxJava的方式来处理异步。这里以AsyncTask为例,可能大部分人都会这样处理一个耗时操作然后通知UI更新结果:
  • 问题代码
    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 protected Integer doInBackground(Void... params) { int i = 0; //模拟耗时操作 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(); } } 
  • 造成内存泄漏原因分析
    • 在处理一个比较耗时的操作时,可能还没处理结束MainActivity就执行了退出操作,但是此时AsyncTask依然持有对MainActivity的引用就会导致MainActivity无法释放回收引发内存泄漏
  • 查看报错结果如下:
    • image
  • 解决办法
    • 在使用AsyncTask时,在Activity销毁时候也应该取消相应的任务AsyncTask.cancel()方法,避免任务在后台执行浪费资源,进而避免内存泄漏的发生
    private void destroyAsyncTask() { if (asyncTask != null && !asyncTask.isCancelled()) { asyncTask.cancel(true); } asyncTask = null; } @Override protected void onDestroy() { super.onDestroy(); destroyAsyncTask(); } 

10.非静态内部类创建静态实例造成内存泄漏

  • 有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法
  • 问题代码
    private static TestResource mResource = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mResource == null){ mResource = new TestResource(); } } class TestResource { //里面代码引用上下文,Activity.this会导致内存泄漏 } 
  • 解决办法
    • 将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请按照上面推荐的使用Application 的 Context。
  • 分析问题
    • 这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。

11.不需要用的监听未移除会发生内存泄露

  • 问题代码
    //add监听,放到集合里面
    tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
        @Override
        public void onWindowFocusChanged(boolean b) { //监听view的加载,view加载出来的时候,计算他的宽高等。 } }); 
  • 解决办法
    //计算完后,一定要移除这个监听
    tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
    
  • 注意事项:
    tv.setOnClickListener();//监听执行完回收对象,不用考虑内存泄漏
    tv.getViewTreeObserver().addOnWindowFocusChangeListene,add监听,放到集合里面,需要考虑内存泄漏
    

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

  • BroadcastReceiver,ContentObserver,FileObserver,Cursor,Callback等在 Activity onDestroy 或者某类生命周期结束之后一定要 unregister 或者 close 掉,否则这个 Activity 类会被 system 强引用,不会被内存回收。值得注意的是,关闭的语句必须在finally中进行关闭,否则有可能因为异常未关闭资源,致使activity泄漏。

13.广播注册之后没有被销毁

  • 比如我们在Activity中注册广播,如果在Activity销毁后不取消注册,那么这个广播会一直存在系统中,同上面所说的非静态内部类一样持有Activity引用,导致内存泄露。因此注册广播后在Activity销毁后一定要取消注册。
  • 在注册观察则模式的时候,如果不及时取消也会造成内存泄露。比如使用Retrofit+RxJava注册网络请求的观察者回调,同样作为匿名内部类持有外部引用,所以需要记得在不用或者销毁的时候取消注册。
    public class MeAboutActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.registerReceiver(mReceiver, new IntentFilter()); } private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // 接收到广播需要做的逻辑 } }; @Override protected void onDestroy() { super.onDestroy(); this.unregisterReceiver(mReceiver); } } 

14.错误使用context上下文引起内存泄漏

  • 先来看看造成内存泄漏的代码
    • 通过查看Toast类的源码可以看到,Toast类内部的mContext指向传入的Context。而ToastUtils中的toast变量是静态类型的,其生命周期是与整个应用一样长的,从而导致activity得不到释放。因此,对Context的引用不能超过它本身的生命周期。
    /**
     * 吐司工具类    避免点击多次导致吐司多次,最后导致Toast就长时间关闭不掉了
     * @param context 注意:这里如果传入context会报内存泄漏;传递activity..getApplicationContext() * @param content 吐司内容 */ private static Toast toast; @SuppressLint("ShowToast") public static void showToast(Context context, String content) { if (toast == null) { toast = Toast.makeText(context , content, Toast.LENGTH_SHORT); } else { toast.setText(content); } toast.show(); } 
  • 解决办法
    • 是改为使用 ApplicationContext即可,因为ApplicationContext会随着应用的存在而存在,而不依赖于Activity的生命周期

15.静态集合使用不当导致的内存泄漏

  • 有时候我们需要把一些对象加入到集合容器(例如ArrayList)中,当不再需要当中某些对象时,如果不把该对象的引用从集合中清理掉,也会使得GC无法回收该对象。如果集合是static类型的话,那内存泄漏情况就会更为严重。因此,当不再需要某对象时,需要主动将之从集合中移除。

16.动画资源未释放导致内存泄漏

  • 问题代码
    public class LeakActivity extends AppCompatActivity { private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak); textView = (TextView)findViewById(R.id.text_view); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360); objectAnimator.setRepeatCount(ValueAnimator.INFINITE); objectAnimator.start(); } } 
  • 解决办法
    • 在属性动画中有一类无限循环动画,如果在Activity中播放这类动画并且在onDestroy中去停止动画,那么这个动画将会一直播放下去,这时候Activity会被View所持有,从而导致Activity无法被释放。解决此类问题则是需要早Activity中onDestroy去去调用objectAnimator.cancel()来停止动画。
    @Override
    protected void onDestroy() { super.onDestroy(); mAnimator.cancel(); } 

17.系统bug之InputMethodManager导致内存泄漏

  • 每次从MainActivity退出程序时总会报InputMethodManager内存泄漏,原因系统中的InputMethodManager持有当前MainActivity的引用,导致了MainActivity不能被系统回收,从而导致了MainActivity的内存泄漏。查了很多资料,发现这是 Android SDK中输入法的一个Bug,在15<=API<=23中都存在,目前Google还没有解决这个Bug。

其他介绍

01.关于博客汇总链接

02.关于我的博客

项目开源地址:https://github.com/yangchong211/YCBlogs

Project Open Source Address: https://github.com/yangchong211/YCRefreshView

Guess you like

Origin www.cnblogs.com/yc211/p/11091504.html