zoukankan      html  css  js  c++  java
  • 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要视情况而定,考虑方法生命周期,变量生命周期与使用时间。

  • 相关阅读:
    hyper虚拟机下对centos进行动态扩容
    《C#高级编程第七版》多线程之Events
    借鉴StanZhai核心代码,写了个博客园采集器
    文档转换之PDF转换为HTML
    书香电子书下载地址分析器
    c#常用类库及资源
    iis7.5 配置伪静态
    根据枚举类型获取描述
    从客户端检测到有潜在危险的Request.Form值
    Sql行列转换
  • 原文地址:https://www.cnblogs.com/vitabebeauty/p/7731664.html
Copyright © 2011-2022 走看看