Android性能优化之常见的内存泄漏

前言

对于内存泄漏,我想你们在开发中确定都遇到过,只不过内存泄漏对咱们来讲并非可见的,由于它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,一般咱们能够借助LeakCanary、MAT等工具来检测应用程序是否存在内存泄漏,MAT是一款强大的内存分析工具,功能繁多而复杂,而LeakCanary则是由Square开源的一款轻量第三方内存泄漏检测工具,当它检测到程序中有内存泄漏的产生时,它将以最直观的方式告诉咱们该内存泄漏是由谁产生的和该内存泄漏致使谁泄漏了而不能回收,供咱们复查。android

最近腾讯bugly也推出了三篇关于Android内存泄漏调优的文章:api

  1. 内存泄露从入门到精通三部曲之基础知识篇
  2. 内存泄露从入门到精通三部曲之排查方法篇
  3. 内存泄露从入门到精通三部曲之常见缘由与用户实践

Realm一样给出了性能优化文章:性能优化

10条提高Android性能的建议网络

 

内存泄漏

为何会产生内存泄漏?

当一个对象已经不须要再使用了,本该被回收时,而有另一个正在使用的对象持有它的引用从而致使它不能被回收,这致使本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。app

内存泄漏对程序的影响?

内存泄漏是形成应用程序OOM的主要缘由之一!咱们知道Android系统为每一个应用程序分配的内存有限,而当一个应用中产生的内存泄漏比较多时,这就不免会致使应用所须要的内存超过这个系统分配的内存限额,这就形成了内存溢出而致使应用Crash。异步

Android中常见的内存泄漏汇总

单例形成的内存泄漏

单例模式很是受开发者的喜好,不过使用的不恰当的话也会形成内存泄漏,因为单例的静态特性使得单例的生命周期和应用的生命周期同样长,这就说明了若是一个对象已经不须要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就致使了内存泄漏。ide

以下这个典例:工具

public class AppManager {
    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;
    }
}

这是一个普通的单例模式,当建立这个单例的时候,因为须要传入一个Context,因此这个Context的生命周期的长短相当重要: oop

  1. 传入的是Application的Context:这将没有任何问题,由于单例的生命周期和Application的同样长
  2. 传入的是Activity的Context:当这个Context所对应的Activity退出时,因为该Context和Activity的生命周期同样长(Activity间接继承于Context),因此当前Activity退出时它的内存并不会被回收,由于单例对象持有该Activity的引用。

因此正确的单例应该修改成下面这种方式:性能

public class AppManager {
    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;
    }
}

这样无论传入什么Context最终将使用Application的Context,而单例的生命周期和应用的同样长,这样就防止了内存泄漏

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

有的时候咱们可能会在启动频繁的Activity中,为了不重复建立相同的数据资源,可能会出现这种写法:

public class MainActivity extends AppCompatActivity {
    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内部建立了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复建立,不过这种写法却会形成内存泄漏,由于非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类建立了一个静态的实例,该实例的生命周期和应用的同样长,这就致使了该静态实例一直会持有该Activity的引用,致使Activity的内存资源不能正常回收。正确的作法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,若是须要使用Context,请使用ApplicationContext

Handler形成的内存泄漏

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

public class MainActivity extends AppCompatActivity {
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。

线程形成的内存泄漏

对于线程形成的内存泄漏,也是平时比较常见的,异步任务和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、View ),能够在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏
  3. 对于再也不须要使用的对象,显示的将其赋值为null,好比使用完Bitmap后先调用recycle(),再赋为null
  4. 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期
  5. 对于生命周期比Activity长的内部类对象,而且内部类中使用了外部类的成员变量,能够这样作避免内存泄漏:

    1. 将内部类改成静态内部类
    2. 静态内部类中使用弱引用来引用外部类的成员变量
  6. 在涉及到Context时先考虑ApplicationContext,固然它并非万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景以下:

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

相关文章
相关标签/搜索