OnDestroy()里的一些思考。----以及对“”不使用的对象应手动赋值为null” 的正确理解姿势

@1@导火索来自于app启动页的 onDestroy里的 mImage=null;这一句。

起初我在思考为什么要把成员变量设为null呢?难道destroy不会清除掉mImage吗?

//崇拜一下健哥。持有imageview对象的理解焕然一新。

拿Activity举例,fragment,view啊都适用。

在一个Activity中,通过布局,Activity会持有ImageView的对象。我们通过findviewbyid去获取ImageView对象的引用,如上面提到的mImage。而ImageView对象通过Glide或者别的图片加载框架与底层中图片(bitmap)形成持有关系。ImageView这个对象在堆中占有内存非常小,而bitmap往往非常大,十几m或者几十m,与屏幕大小以及清晰度有关。所以app需要及时清除掉不用的bitmap,我们可以断开bitmap与ImageView对象的持有关系。有两种思路,1:断开bitmap与ImageView对象的持有关系。项目里常常用到的releaseBitmap(),核心方法

Glide.clear(view);

。这个方法有一个好处就是,ImageView对象仍在,当需要重新加载bitmap的时候会非常的快,省去了创建ImageVierw的过程。

2,把ImageView对象与Bitmap一起remove掉。有的时候ImageView的对象的生命周期非常长,ImageView不被释放掉,可我们也不需要它,我们就remove这个对象。这个时候你有没有想到上文提到的mImage=null呢?如果你想到了,恭喜你,想错了。mImage只是ImageView对象的引用,mImage=null了并不影响Activity对ImageView对象的持有,要断开Activity对ImageView对象的持有就不像别的地方(如mData=null,可以断开对mData指向的对象持有关系,如果mData没有别的引用关系,mData就会被GC回收掉)处理方式。正确姿势是:

((ViewGroup) mRoot.getParent()).removeView(mRoot);

mRoot可以换成mImage。将ImageView与Bitmap都remove掉。所以mImage=null,好像真的有点多余,不知道之前程序员小哥哥为什么写这一句,也不太敢删。

 

@2@在OnDestroy里。handler可能导致内存泄漏。

public class MainActivity extends Activity {
 
    private  Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //TODO handle message...
        }
    };
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.sendMessageDelayed(Message.obtain(), 60000);
 
        //just finish this activity
        finish();
    }
}

Handler是MainActivity的匿名内部类。

1:MainActvity运行在UI主线程上,在开启该应用的时候,系统自动帮先创建一个应用主线程的Looper对象,Looper实现了一个简单的消息队列MessageQuene,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。只要我们的mHandler发送的message在MessageQuene上,我们的message就存活着,拥有整个应用生命周期。

2:message是持有外部类MainActivity引用。所以message不被消耗或者清除,MainActivity这个对象就不能被释放掉。

3:如果在MainActivity的OnDestroy()里不remove掉message,就会导致MainActivity即使调用了OnDestroy(),由于message存活,仍然不能被释放掉,最终导致内存泄漏。

所以:记得removeCallBacks()。

( 

  关于`Handler.remove*`方法
                        - `removeCallbacks(Runnable r)` ——清除r匹配上的Message。    
                        - `removeC4allbacks(Runnable r, Object token)` ——清除r匹配且匹配token(Message.obj)的Message,token为空时,只匹配r。
                        - `removeCallbacksAndMessages(Object token)` ——清除token匹配上的Message。
                        - `removeMessages(int what)` ——按what来匹配     
                        - `removeMessages(int what, Object object)` ——按what来匹配      

这个问题抽象化来看,我们的收获就是要注意匿名内部类的生命周期的控制。

在OnDestroy()里该remove的remove,该cancel的cancel。

 

 

@3@在OnDestroy里。网络请求是否取消问题,衍生异步回调引发的空指针问题。

这个是否取消根据使用情形来确定。

当用异步的网络请求,有可能当数据来了,可是activity已经不在了,要对ui进行操作的时候,报错:空指针,所以OnDestroy()要cancel request。但是若获取数据保存到数据库,OnDestroy()就不用cancel request。

 

@4@单例模式可能造成泄漏。

 

public class SingleTon { 

    private static SingleTon singleTon; 

    private Context context; 

    private SingleTon(Context context) { 
        this.context = context; 
    } 

    public static SingleTon getInstance(Context context) { 
        if (singleTon == null) { 
            synchronized (SingleTon.class) { 
                if (singleTon == null) { 
                    singleTon = new SingleTon(context); 
                } 
            } 
        } 
        return singleTon; 
    } 

} 

 

 

这是单例模式饿汉式的双重校验锁的写法,这里的 singleTon 持有 Context 对象,如果 Activity 中调用 getInstance 方法并传入 this 时,singleTon 就持有了此 Activity 的引用,当退出 Activity 时,Activity 就无法回收,造成内存泄漏,所以应该修改它的构造方法:

private SingleTon(Context context) { 
    this.context = context.getApplicationContext(); 
}

通过 getApplicationContext 来获取 Application 的 Context,让它被单例持有,这样退出 Activity 时,Activity 对象就能正常被回收了,而 Application 的 Context 的生命周期和单例的生命周期是一致的,所有再整个 App 运行过程中都不会造成内存泄漏。

 

 

@@补充一下,之前为了了解mImage=null,我特地查了一下 ,相关有一篇这个文章:

Java: 对象不再使用时赋值为null的作用和原理

讲的没错,举例有点过犹不及。在一个调用一个方法的时候,一个线程对应一个Java栈,栈中会存储局部变量。

public static void main(String[] args) {
    if (true) {
        byte[] placeHolder = new byte[64 * 1024 * 1024];
        System.out.println(placeHolder.length / 1024);
    }
    System.gc();
}

当方法没执行完,在局部变量的作用域之外,placeHolder这个引用变成了空指针,但是new byte[64 * 1024 * 1024]这个对象仍可能存在,它的实际地址值(if里placeHolder被赋的值)也就是这个对象在内存中的地址还是保存在栈里面的,gc就无法把它给回收掉。

public static void main(String[] args) {
    if (true) {
        byte[] placeHolder = new byte[64 * 1024 * 1024];
        System.out.println(placeHolder.length / 1024);
        placeHolder = null;
    }
    System.gc();
}

public static void main(String[] args) {
    if (true) {
        byte[] placeHolder = new byte[64 * 1024 * 1024];
        System.out.println(placeHolder.length / 1024);
    }
    int replacer = 1;
    System.gc();
}

gc可以马上回收掉new byte[64 * 1024 * 1024]对象。当在placeHolder 作用域范围内手动赋值null,就断开了引用和对象间的联系,对象如果没有被别的地方引用,就会被孤立于GC算法的引用表之外,GC就会回收它。如果在作用域外部,栈内添加局部变量,由于栈会自己检测超出作用域的引用的对象,然后remove该对象的栈中的地址,腾出空间保存新对象的地址,所以原来对象也会被GC。

这样做的意义?这篇文章为了阐明这个观点引用了不太合适的例子。倘若System.gc()在方法以外,那么结果会相同,new byte[64 * 1024 * 1024]对象都会被GC掉。因为Java栈都被清空了;手动赋值null使之GC,比方法执行完系统GC的结果可能就是提前几十毫秒。所以赋值为null要视情况而定,考虑方法生命周期,变量生命周期与使用时间。

 

posted @ 2017-10-25 19:03  callMeVita  阅读(675)  评论(0编辑  收藏  举报