Android优化中弱引用WeakReference的知识

转载请注明出处谢谢:http://blog.csdn.net/printfcc/article/details/79131479

目录:

  • 目录
    • 为什么使用Handle会出现内存泄漏
      • 真的是 no picture say ge J8 按照自己的理解做了两张图
    • 弱引用是什么
    • 一些常见的内存泄漏问题及弱引用的应用
        • 单例造成的内存泄漏
        • 就如下面的代码
        • 非静态内部类创建静态实例造成的内存泄漏
        • 线程造成的内存泄漏
    • 关于一些内存泄漏的建议
    • 其他需要知道的引用

在学习Android中的Handle出现OOM经过搜索接触到Weak Reference这词,哇靠,英文赶紧百度一下:这玩意儿叫:弱引用
经过一顿搜索学习,记录一下~(ps:可能会有错误望大神指正:))

  • 为什么使用Handle会出现内存泄漏?
  • 弱引用是什么?
  • 一些常见的内存泄漏问题及弱引用的使用
  • 关于一些内存泄漏的建议
  • 其他需要知道的引用

为什么使用Handle会出现内存泄漏?

首先我们来看一段使用Handler的代码

/**
 * Created by vveng on 2018/1/6.
 */
public class MainActivity extends AppCompatActivity {
   private TextView mTextView;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
           mTextView.setText("");
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
    private void loadData(){    
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }

}

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

真的是 no picture say ge J8 按照自己的理解做了两张图:

第一张:

Android优化中弱引用WeakReference的知识_第1张图片

当一个android主线程被创建的时候,同时会有一个Looper对象被创建,而这个Looper对象会实现一个MessageQueue(消息队列),当我们创建一个handler对象时,而handler的作用就是放入和取出消息从这个消息队列中,每当我们通过handler将一个msg放入消息队列时,这个msg就会持有一个handler对象的引用。因此当Activity被结束后,这个msg在被取出来之前,这msg会继续存活,但是这个msg持有handler的引用,而handler在Activity中创建,会持有Activity的引用

第二张:
Android优化中弱引用WeakReference的知识_第2张图片

弱引用是什么?

看看Android官方文档怎么说:

Android优化中弱引用WeakReference的知识_第3张图片

弱引用对象,它们并不禁止其指示对象变得可终结,并被终结,然后被回收。弱引用最常用于实现规范化的映射。

假定垃圾回收器确定在某一时间点上某个对象是弱可到达对象。这时,它将自动清除针对此对象的所有弱引用,以及通过强引用链和软引用,可以从其到达该对象的针对任何其他弱可到达对象的所有弱引用。同时它将声明所有以前的弱可到达对象为可终结的。在同一时间或晚些时候,它将那些已经向引用队列注册的新清除的弱引用加入队列。

通俗易懂的讲:我们知道Java中有垃圾回收机制GC,WeakReference弱引用是这么一个东西,在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

那么在Handler中我们要如何使用呢?看下面


/**
 * Created by vveng on 2018/1/6.
 */
public class MainActivity extends AppCompatActivity {
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler {

        private WeakReference reference;   //

        public MyHandler(Context context) {
            reference = new WeakReference<>(context);//这里传入activity的上下文
        }
        @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() {
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
     @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

由于Handler持有的对象是使用弱引用,根据WeakReference弱引用的特点在GC回收时能回收弱引用,这样就避免了OOM,另外还有在消息队列中可能会有待处理的消息Message,所以我们可以在onDestroy()或者onStop()中调用mHandler.removeCallbacksAndMessages(null);来移除所有消息和Runnable

一些常见的内存泄漏问题及弱引用的应用

单例造成的内存泄漏

单例设计模式是日常开发中在常见不过的一种,当时你知道若使用不当也很容易造成OOM吗?
由于CustomizeManager 是一个单例模式,那么这个类的生命周期就伴随整个应用的生命周期,而它在被Activity创建的时候引用了Activity,所以当系统GC的时候试图去回收Activity时,发现它却在被另一个任然在内存里的CustomizeManager 所引用,所以GC回收它失败,从而导致了内存泄漏。

就如下面的代码

/**
 * Created by vveng on 2018/1/6.
 */
public class CustomizeManager {

    private static CustomizeManager instance;

    private Context mContext;

    public CustomizeManager (Context context) {

        this.mContext = context;
    }

    public static CustomizeManager getInstance(Context context) {
        if (instance == null) {
            instance = new CustomizeManager (context);
        }
        return instance;
    }

}

解决问题的方法有两种:

/**
 * Created by vveng on 2018/1/6.
 */
public class PendingOrderManager {

    private static CustomizeManager instance;
   //一种是弱引用
    private WeakReference wr;

    // private Context context;
    public CustomizeManager(Context context) {
        wr = new WeakReference<>(context);
        //第二种获取Application的Context
        /**
        这样不管传入什么Context最终将使用Application的Context,
        而单例的生命周期和应用的一样长,这样就防止了内存泄漏
        */
         //  this.context = context.getApplicationContext();
    }

    public static CustomizeManager getInstance(Context context) {
        if (instance == null) {
            instance = new CustomizeManager(context);
        }
        return instance;
    }

}

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

有的时候我们可能会在启动频繁的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(mManager == null){
            mManager = new TestResource();
        }
        //...
    }
    class TestResource {
        //...
    }
}

这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext

线程造成的内存泄漏

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

//——————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 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(),避免任务在后台执行浪费资源。

关于一些内存泄漏的建议

  1. 对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
  2. 对于生命周期比Activity长的对象如果需要应该使用ApplicationContext
  3. 在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:

Android优化中弱引用WeakReference的知识_第4张图片
**其中:**NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建

  1. 对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏
  2. 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
    a.将内部类改为静态内部类 b.静态内部类中使用弱引用来引用外部类的成员变量
  3. 对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null
  4. 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期

其他需要知道的引用

在JDK1.2,Java就把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

  • 强引用
    强引用是最常用的引用,比如我们常常需要的创建对象
String s = new String("帅哥");

特点:GC不会回收它,就算内存吃紧也打死不回收

  • 软引用
    软引用用来描述一些还有用但是并非必须的对象,对于软引用关联着的对象,只有在内存不足的时候GC才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。
    HappyClass happy = new  HappyClass ();
    SoftReference aSoftRef=new SoftReference(happy);
    HappyClass happy =(HappyClass)aSoftRef.get();

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

    ReferenceQueue queue = new  ReferenceQueue();
    SoftReference  ref=new  SoftReference(aMyObject, queue);
  • 虚引用
    “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
    引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

                                        谢谢浏览到最后~
    

参考连接:http://blog.csdn.net/u010687392/article/details/49909477


你可能感兴趣的:(Android)