深入了解Android引用类型

这里写图片描述

我们直接开门见山,Android中对象存在着4中引用的类型,因为android开发中绝大部分使用面向对象的java编写的,对于java而言世界万物皆是对象,而引用就好比是对象的代表,可以利用引用做对象包含的所有事情,而在android中Garbage Collection(简称GC)也就是垃圾回收器,是对象的克星,一个对象可以包含多个直接引用和间接引用,GC就像一名私人侦探一样随时在内存中查看对象的引用,一旦发现该对象不存在引用了,马上将该对象回收,释放占用的内存。说了这么多到底什么是引用啊,for example:

/**
 *创建一个Couputer对象,执行运算的行为
 *
 */
Computer mComputer = new Computer();
mComputer.computer();

在这里new Computer()即创建了一个对象并将这个对象存储在mComputer这个引用中,在任何地方只要拿到这个引用就能随时操作Computer,执行计算的功能,虽然引用可以随时随地去操作对象,但是如果创建没有指向任何对象的引用,for example:

/**
 *创建一个Computer的引用,执行computer方法
 */
Computer mComputer;
mComputer.computer();

这样的引用一旦执行,就会出现司空见惯的空指针异常(NullPositionException)。

内存泄漏

谈到内存泄漏,大家肯定不会陌生,一个好的app内存必定得到了良好的管理,但是如何很好的管理内存呢?那么我们先看一下Android中是如何对内存进行回收的,在Android中GC采用了标注和清理(Mark and
sweep)回收算法:从” GC Roots“集合开始,将内存中存活的对象整个遍历一遍,凡是能够被GC Roots直接或者间接引用到的对象保存起来,而剩下的孤魂野鬼的对象便被当成垃圾回收掉了。下面我画几个图展示一下GC的回收过程:
这里写图片描述

这里写图片描述

这里写图片描述

上面三张图描述GC遍历内存中对象的过程。
其中的每个圆形节点代表一个对象(内存的资源),箭头表示引用的路径(可达路径),黄色圆形表示遍历后GC Roots与该对象存在可以到达的路径,深蓝色代表无法到达的路径,意味着该对象不存在引用即将被回收,释放占用的资源。因为android是基于Linux内核的,每个应用程序都是一个单独的Linux进程,也对应着一个单独的Dalvik虚拟机的实例,而每个Dalvik虚拟机的大小是固定的(比如:16M,32M,64等),这就意味着我们使用的内存是有限的,所以需要我们在开发中注意内存的使用,GC就能帮我们回收没有引用的对象释放内存,所以在开发中应该随时注意对对象的引用,不要到处new对象,否则,将会内存泄漏。说了这么多其实内存泄漏最直白的解释就是本该死的不死,还占用着位置

文章开始我就说了Android存在着4种引用类型,之所以要将引用类型分为4种,那是因为咱们的GC回收引用的时候对这4种引用疼爱不一,根据不同的类型执行不同的回收策略,下面我们扯一扯这四种引用类型,了解一下她们和GC之间的爱恨情仇:
Strong peference(强引用)
顾名思义,她和GC的关系肯定是真爱。其实我们平时创建的引用都是强引用,for example:

Person person = new Person();

这里的person就是对象Person的强引用,当一个对象含有强引用的时候,GC是绝对不会回收和销毁该对象的,对象的强引用可以在程序中随意传递,因为她有GC的保护不会回收,很多情况下,会有多个强引用指向一个对象。强引用的存在增加了对象的寿命,比如对象A中包含了对象B的强引用,那么对象B的存活期就不是它自己能够决定,除非当A把它对B 的引用显示的置为null,要不就必须等到A被回收之后才能回收B。下面我们举个例子:


public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private ImageView img;
    private static String url = "http://pic3.zhongsou.com/image/38063b6d7defc892894.jpg";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image);
        img = (ImageView) findViewById(R.id.image);
        new MyAsyncTask().execute(url);
    }

    @TargetApi(Build.VERSION_CODES.CUPCAKE)
    private class MyAsyncTask extends AsyncTask<String, Void, Bitmap> {

        @Override
        protected Bitmap doInBackground(String... strings) {
            //执行耗时操作(图片下载)
            String url = strings[0];
            HttpURLConnection connection;
            Bitmap bmp = null;
            try {
                connection = (HttpURLConnection) new URL(url).openConnection();
                InputStream is = connection.getInputStream();
                bmp = BitmapFactory.decodeStream(is);

            } catch (IOException e) {
                e.printStackTrace();
            }
            return bmp;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            //更新UI
            img.setImageBitmap(bitmap);
        }
    }


}

我创建了一个AsyncTask执行下载图片的任务下载完成后更新UI,在java中,非静态内部类在整个生命周期中持有对其外部类的强引用因此即使当该Activity已经destroy后仍然不会让GC回收内存,因为AsyncTask没有结束,所以必须等到AsyncTask耗时操作完成后回收,只有AsyncTask被GC回收后,MainActivity才能被回收,如果MainActivity中持有Bitmap等比较大的对象,反复进出这个界面可能就会出现OOM而Crash了。

  • Weak peference(弱引用)
    弱引用通过类WeakPeference来表示,弱引用并不能阻止
    GC回收该对象,如果使用一个强引用的话,只要该引用存在,那么被引用的对象将一直存在不能被垃圾回收器回收,在垃圾回收器运行的时候,如果该对象的所用引用都是弱引用则垃圾回收器便会马上回收该对象,下面我们来把上面的例子稍微调整一下来避免内存泄漏:
public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private static ImageView img;
    //private Button but_finish;
    private static String url = "http://pic3.zhongsou.com/image/38063b6d7defc892894.jpg";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image);
        img = (ImageView) findViewById(R.id.image);
        but_finish = (Button) findViewById(R.id.but_finish);
     /*   but_finish.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

            }
        });*/
        new MyAsyncTask(this).execute(url);
    }

    @TargetApi(Build.VERSION_CODES.CUPCAKE)
    private static class MyAsyncTask extends AsyncTask<String, Void, Bitmap>
    {
        private WeakReference<MainActivity> mainActivity;//创建一个泛型为MianActivityd的弱引用
        public MyAsyncTask(MainActivity mainActivity){//创建该弱引用对象
            this.mainActivity = new WeakReference<MainActivity>(mainActivity);
        }

        @Override
        protected Bitmap doInBackground(String... strings) {
            //执行耗时操作(图片下载)
            String url = strings[0];
            HttpURLConnection connection;
            Bitmap bmp = null;
            try {
                connection = (HttpURLConnection) new URL(url).openConnection();
                InputStream is = connection.getInputStream();
                bmp = BitmapFactory.decodeStream(is);

            } catch (IOException e) {
                e.printStackTrace();
            }
            return bmp;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            if(mainActivity.get()!=null) {//判断该对象是否已经被GC回收
                Logger.d("MainActivity没有被回收");
                img.setImageBitmap(bitmap);
                return;
            }
            Logger.d("MainActivity被回收");

        }
    }
}

对比以上两段代码,主要是我们把MySyncTask变成了静态内部类,并且把其对外部类的强引用转换成个弱引用,

private WeakPeference<MainActivity> mainActivity;
this.mainActivity = new WeakPeference<MainActivty>(mainActivity);

修改之后当MainActivity destroy时,由于MyAsyncTask是通过弱引用的方式持有对外部类MainActivity的引用,所以并不会阻止MainActivity被垃圾回收器回收,这样就不会产生内存泄漏了。

  • Soft Reference(软引用)
    软引用利用类SoftReference来表示,它是比弱引用稍强的引用类型,对于很多人来说,软引用和弱引用很容易混淆,其实对于软引用来说,被它修饰的对象生命周期在内存满足的时候和强引用一样不会去回收任何对象,但是当内存紧张时,GC首先将回收所用被软引用修饰的对象,此时的地位和弱引用相同,那么既然软引用只有在内存不足的的时候才会销毁回收,那用它做缓存肯定不错吧,因为只有当内存不足时才会回收,但是事实上,用作软应用作为缓存存在很多不足,比如有些Bitmap的对象引用不应该全部回收如果回收就得重新从SD卡或者网络加载,因此在实际开发中不会使用软引用来进行数据缓存,目前基本如果使用缓存都是用LruCache进行数据缓存,LruCache中LRU是Least Recently Used的缩写,即最近最少使用,它的内部维护了一个固定大小的内存,当内存不足时时候,会根据策略把最近最少使用的数据从内存中移除,让出内存给出最新的数据,下面是我一个简单的LruCache的使用:
/**
 * Created by luweicheng on 2016/12/12.
 */

public class CacheUtils {
    private static LruCache<String, Bitmap> mCache;
    private static CacheUtils mCacheUtils;

    int maxMemory = (int) (Runtime.getRuntime().maxMemory() * (1 / 8));//设置内存大小

    private CacheUtils() {

    }

    /**
     * 获取实例
     * @return
     */
    public static CacheUtils getInstance() {
        if (mCacheUtils == null) {

            synchronized (MyApplication.class) {
                mCacheUtils = new CacheUtils();

            }
            return mCacheUtils;
        } else {
            return mCacheUtils;
        }

    }


    /**
     * 加入数据到缓存
     */
    private void addCaheData(String url, Bitmap bitmap) {
        if (mCache.get(url) == null) {//判断是否已经存在于缓存
            return;
        }
        mCache.put(url, bitmap);

    }

    /**
     * 从缓存中取数据
     */
    private Bitmap getCacheData(String url) {
        if (mCache.get(url) != null) {
            return mCache.get(url);
        }
        return null;
    }

    /**
     * 移除数据从缓存
     */
    private void clearCacheData(String url) {
        if (mCache.get(url) != null) {
            mCache.remove(url);
        }
    }

}
  • PhantomRerence(虚引用)
    一个只被虚引用持有的对象可能会在任何时候被GC回收,虚引用对对象的的生命周期完全没有影响,也无法通过虚引用用来获取对象实例,仅仅实在对象回收时获取一个通知(通过判断该对象是否加入到RefreenceQuenue来判断是否被回收)其实虚引用基本没什么用处,主要是用来跟踪对象被垃圾回收的活动,比如一个Bitmap的对象的生命周期,通过虚引用跟踪该Bitmap是否回收,如果已经回收将重新加载,如果未被回收就可以不用重新加载,直接使用该引用。
    好了,以上就是我对引用的一点理解,晚安 : EveryBody
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值