内存泄漏优化

目录介绍:

  • 1.什么是内存泄漏
  • 2.内存泄漏形成什么影响
  • 3.内存泄漏检测的工具备哪些
  • 4.关于Leakcanary使用介绍
  • 5.Leakcanary捕捉常见的内存泄漏及解决办法php

    • 5.0.1 错误使用单例形成的内存泄漏
    • 5.0.2 错误使用静态变量,致使引用后没法销毁
    • 5.0.3 [常见]Handler使用不当形成的内存泄漏
    • 5.0.4 线程形成的内存泄漏[比较少见]
    • 5.0.5 非静态内部类建立静态实例形成的内存泄漏
    • 5.0.6 不须要用的监听未移除会发生内存泄露
    • 5.0.7 [常见]资源未关闭形成的内存泄漏
    • 5.0.8 未注销EventBus致使的内存泄漏
    • 5.0.9 [常见]持有activity引用未被释放致使内存泄漏
    • 5.1.0 静态集合使用不当致使的内存泄漏
    • 5.1.1 动画资源未释放致使内存泄漏
    • 5.1.2 系统bug之InputMethodManager致使内存泄漏
  • 6.其余建议android

    • 6.0.1 尽可能避免使用 static 成员变量
  • 7.版本更新git

    • v1.0.0 更新于2016年3月19日
    • v1.1.0 更新于2017年7月8日
    • v1.2.0 更新于2018年5月3日
    • v1.3.0 更新于2018年9月18日

1.什么是内存泄漏?

  • 一些对象有着有限的声明周期,当这些对象所要作的事情完成了,咱们但愿它们会被垃圾回收器回收掉。可是若是有一系列对这个对象的引用存在,那么在咱们期待这个对象生命周期结束时被垃圾回收器回收的时候,它是不会被回收的。它还会占用内存,这就形成了内存泄露。持续累加,内存很快被耗尽。
  • 好比:当Activity的onDestroy()方法被调用后,Activity以及它涉及到的View和相关的Bitmap都应该被回收掉。可是,若是有一个后台线程持有这个Activity的引用,那么该Activity所占用的内存就不能被回收,这最终将会致使内存耗尽引起OOM而让应用crash掉。

2.内存泄漏会形成什么影响?

  • 它是形成应用程序OOM的主要缘由之一。因为android系统为每一个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就不免会致使应用所须要的内存超过这个系统分配的内存限额,这就

3.内存泄漏检测的工具备哪些

  • 最多见的是:Leakcanary

4.关于Leakcanary使用介绍

  • leakCanary是Square开源框架,是一个Android和Java的内存泄露检测库,若是检测到某个 activity 有内存泄露,LeakCanary 就是自动地显示一个通知,因此能够把它理解为傻瓜式的内存泄露检测工具。经过它能够大幅度减小开发中遇到的oom问题,大大提升APP的质量。
  • 关于如何配置,这个就不说呢,网上有步骤

5.Leakcanary捕捉常见的内存泄漏及解决办法

5.0.1 错误使用单例形成的内存泄漏

  • 在平时开发中单例设计模式是咱们常用的一种设计模式,而在开发中单例常常须要持有Context对象,若是持有的Context对象生命周期与单例生命周期更短时,或致使Context没法被释放回收,则有可能形成内存泄漏,错误写法以下:
  • 问题引发内存泄漏代码github

    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() {}
    }
  • 使用场景segmentfault

    • 在一个Activity中调用的,而后关闭该Activity则会出现内存泄漏。
    LoginManager.getInstance(this).dealData();
  • 看看报错截图设计模式

    • image
  • 解决办法:网络

    • 要保证Context和AppLication的生命周期同样,修改后代码以下:
    • this.mContext = context.getApplicationContext();
    • 一、若是此时传入的是 Application 的 Context,由于 Application 的生命周期就是整个应用的生命周期,因此这将没有任何问题。
    • 二、若是此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,因为该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,因此当前 Activity 退出时它的内存并不会被回收,这就形成泄漏了。

5.0.2 错误使用静态变量,致使引用后没法销毁

  • 在平时开发中,有时候咱们建立了一个工具类。好比分享工具类,十分方便多处调用,所以使用静态方法是十分方便的。可是建立的对象,建议不要全局化,全局化的变量必须加上static。这样会引发内存泄漏!
  • 问题代码架构

    • image
  • 使用场景app

    • 在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持有而没法回收形成内存泄露。
    
    静态变量引用不当会致使内存泄漏
    静态变量Activity和View会致使内存泄漏,在下面这段代码中对Activity的Context和TextView设置为静态对象,从而产生内存泄漏。
    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);
        }
    }

5.0.3 Handler使用不当形成的内存泄漏

  • handler是工做线程与UI线程之间通信的桥梁,只是如今大量开源框架对其进行了封装,咱们这里模拟一种常见使用方式来模拟内存泄漏情形。
  • 问题代码

    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 class SampleActivity extends Activity {

    private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;
    public MyHandler(SampleActivity activity) {
        mActivity = new WeakReference<SampleActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        SampleActivity activity = mActivity.get();
        if (activity != null) {
            // ...
        }
    }

    private final MyHandler mHandler = new MyHandler(this);
    private static final Runnable sRunnable = new Runnable() {
        @Override
        public void run() { /* ... */ }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
        finish();
    }
}

即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。
```

5.0.4 线程形成的内存泄漏

  • 早时期的时候处理耗时操做多数都是采用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();
    }

5.0.5 非静态内部类建立静态实例形成的内存泄漏

  • 有的时候咱们可能会在启动频繁的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的内存资源不能正常回收。

5.0.6 不须要用的监听未移除会发生内存泄露

  • 问题代码

    //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监听,放到集合里面,须要考虑内存泄漏

5.0.7 [常见]资源未关闭形成的内存泄漏

  • BroadcastReceiver,ContentObserver,FileObserver,Cursor,Callback等在 Activity onDestroy 或者某类生命周期结束以后必定要 unregister 或者 close 掉,不然这个 Activity 类会被 system 强引用,不会被内存回收。值得注意的是,关闭的语句必须在finally中进行关闭,不然有可能由于异常未关闭资源,导致activity泄漏。
5.0.7.1 广播注册以后没有被销毁致使内存泄漏
- 好比咱们在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);
    }
}
```
5.0.7.2 资源未关闭致使资源被占用而内存泄漏
  • 在使用IO、File流或者Sqlite、Cursor等资源时要及时关闭。这些资源在进行读写操做时一般都使用了缓冲,若是及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以至发生内存泄露。所以咱们在不须要使用它们的时候就及时关闭,以便缓冲能及时获得释放,从而避免内存泄露。

5.0.8 未注销EventBus致使的内存泄漏

  • 直接展现代码

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_common);
        EventBus.getDefault().register(this);
    }
    
    @Subscribe
    public void onEvent(MessageEvent msg) {
    
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //未移除注册的EventBus
        //EventBus.getDefault().unregister(this);
    }

5.0.9 持有activity引用未被释放致使内存泄漏

5.0.9.1 第一种场景
  • 先看看问题代码

    • 这个是在开发中常常会犯的错误,NastyManager.getInstance() 是一个单例,当咱们经过 addListener(this) 将 Activity 做为 Listener 和 NastyManager 绑定起来的时候,因为单例和Activity生命周期不一样,所以销毁时就会致使内存泄漏。
    public class LeakActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            NastyManager.getInstance().addListener(this);
        }
    }
  • 解决办法:

    • 在你的 Acitivity 被销毁的时候,将他和 NastyManager 取消掉绑定就好
    public class LeakActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            NastyManager.getInstance().addListener(this);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            NastyManager.getInstance().removeListener(this);
        }
    }
5.0.9.2 第二种场景
  • 先来看看形成内存泄漏的代码

    • 经过查看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的生命周期

5.1.0 静态集合使用不当致使的内存泄漏

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

5.1.1 动画资源未释放致使内存泄漏

  • 问题代码

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

5.1.2 系统bug之InputMethodManager致使内存泄漏

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

6.其余建议

6.0.1 尽可能避免使用 static 成员变量

  • 尽可能避免使用 static 成员变量

    • 若是成员变量被声明为 static,那咱们都知道其生命周期将与整个app进程生命周期同样。这会致使一系列问题,若是你的app进程设计上是长驻内存的,那即便app切到后台,这部份内存也不会被释放。按照如今手机app内存管理机制,占内存较大的后台进程将优先回收,若是此app作过进程互保保活,那会形成app在后台频繁重启。当手机安装了你参与开发的app之后一晚上时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。
    • 架构设计上要思考是否真的有必要这样作,尽可能避免。若是架构须要这么设计,那么此对象的生命周期你有责任管理起来。

关于其余内容介绍

01.关于博客汇总连接

02.关于个人博客

相关文章
相关标签/搜索