Handler

作者: 面向星辰大海的程序员 | 来源:发表于2021-03-28 00:02 被阅读0次

Handler,共享内存的方案,与wait/notify相比使用非常简单

内存共享,线程之间的通信就是共享内存

Handler、Message 、MessageQueue、Looper、Thread共同协作共享内存

Log.i(TAG, "主线程=" + Thread.currentThread().getName());//主线程,(与子线程,两者通信)
        Thread thread = new Thread() {
            @Override
            public void run() {
                super.run();
                /***Looper.prepare();
                 if (sThreadLocal.get() != null) {
                 ThreadLocal线程本地,线程隔离,就是每一个线程中调用它的set和get方法都是操作当前线程的一个局                 部变量,就是以当前线程为key保存的一个变量,在这里这个变量就是Looper的实例,在ThreadLocal                  中使用SparseArray保存key和value,0是key,0+1就是vakue,以此类推
                 并且这个ThreadLocal变量是static final ,静态常量的,所以使用Looper时不用担心有多个的问题
                 使用ThreadLocal保证线程中的Looper实例对象是唯一的,能取到值说明已经设置过,所以再次调用直接                 抛出异常    
                    throw new RuntimeException("Only one Looper may be created per thread");
                }
                    sThreadLocal.set(new Looper(quitAllowed));
                    构造函数,Looper对象唯一,所以消息队列mQueue也是唯一,mThread保存一下当前线程对象
                    private Looper(boolean quitAllowed) {
                    mQueue = new MessageQueue(quitAllowed);
                    mThread = Thread.currentThread();
                    }
                    
                Looper.prepare();就是创建当前线程的唯一的Looper的实例对象
                ***/
                Looper.prepare();
                //创建Handler时将looper传入
                threadHandler = new Handler(Looper.myLooper()) {
                    @Override
                    public void handleMessage(@NonNull Message msg) {
                        super.handleMessage(msg);
                        switch (msg.what) {
                            case -1:
                                Log.i(TAG, "释放子线程");
                                Looper.myLooper().quitSafely();
                                break;
                            case 1:
                                Log.i(TAG, "收到主线程发的消息msg.obj=" + msg.obj);
                                break;
                        }
                    }
                };
                Log.i(TAG, "子线程=" + Thread.currentThread().getName());
                Log.i(TAG, "调用loop()阻塞");
              /***per对象调用loop()方法将会阻塞当前线程(什么不会ANR?)
                在loop方法中有个for死循环,阻塞是queue.next();中的
                 for (;;) {
                    Message msg = queue.next(); // might block
                    }
                再看消息队列中的next()方法
                //阻塞是调用本地方法nativePollOnce的,prt应该是线程标记,nextPollTimeoutMillis则是阻塞超                时时间,正值是这个时间后放行(其实是自动唤醒,接着从这里执行,这里阻塞后线程就干其他事,没事                  干就等着),如果是-1就等待唤醒,就是不会自己唤醒等别人叫,延迟消息的时间就是给它用的
                nativePollOnce(ptr, nextPollTimeoutMillis);
              ***/
                Looper.loop();
                Log.i(TAG, "线程执行完毕");
            }
        };
        thread.start();

/***向子线程发送消息,内容为普通消息
调用Handler的sendXXX或postXXX方法的会走到消息队列MessageQueue的
boolean enqueueMessage(Message msg, long when)方法中,其实MessageQueue的mMessages变量才是真正的消息队列,
mMessages的类型是Message,Message类中有个next,next的类型是Message,就是说当前message有下一个message,以此构成一个消息队列,MessageQueue只是管理Message,在此方法中新进来的消息会和mMwssages根据when就是时间比较,时间早的排前面,就是第一个,这是一个优先级队列,并不是先进先出的队列,
方法中的大致逻辑:新消息进来,判断第一个消息是空的,说明新进的消息才是第一个-》
唤醒阻塞,判断延时==0,说明需要马上处理-》唤醒阻塞,如果时间较早-》唤醒阻塞,唤醒阻塞之前做好了优先级排序

总结:loop方法中一个死循环调用next方法,next方法中也用死循环处理消息,消息为null,就永久阻塞,不为null就且不延时就返回消息,外层死循环处理消息,因为是死循环,再次到next方法,此时消息不为null并且消息要延时处理,那就阻塞延时的时间,延时时间到,自动唤醒检索消息返回,没有消息又进入永久阻塞,Handler发送消息都换唤醒阻塞,此时消息队列在消息入队时已经排序好,该延时的延时,不延时就返回消息,并将下一个消息赋值头消息变量,继续死循环。。。,为了线程安全消息入队,出队的关键代码块都会用synchronized(this){}包住,因为时this,锁并不是作用在方法,同一个this,不能多个线程同时访问,访问出队时不能访问入队,访问入队不能访问出队,是一个生产消费模式。

享元模式,消息Mesage在调用msg.target.dispatchMessage(msg);回到 如下会调用我们使用层的handleMessage方法,
这个target就是handler实例对象,在handler发消息都会走的enqueueMessage方法中 msg.target = this;赋值
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
//在此之后消息对象并不是直接弃用了,而是清空属性值保存到sPool = this;sPool中
    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    @UnsupportedAppUsage
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

//obtain方法可以拿到之前回收的消息对象,没有回收的对象则新建一个返回,这样做有利于减少内存申请,减少gc触发,从而减少内存抖动,可以降低oom出现的概率。
    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
***/
Message message = Message.obtain();
                    message.obj = "点击了向子线程发送消息的按钮";
                    message.what = 1;
                    threadHandler.sendMessage(message);
 //向子线程发送消息,内容为特殊消息,释放子线程(子线程不再阻塞,执行完毕)
  threadHandler.sendEmptyMessageAtTime(-1, 0);
  /***  Looper.myLooper().quitSafely();调用Looper的安全退出方法,传进来的时true,removeAllFutureMessagesLocked();会处理完正在发送的消息,含有的未来的消息将不会发送并回收,
在removeAllFutureMessagesLocked 中有这句 //不允许退出主线程
 if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
***/

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {//为了线程安全,这里也是被锁住的,
            if (mQuitting) {
                return;
            }
            mQuitting = true;//退出循环标志

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            /***
            //唤醒阻塞,此时 mQuitting = true;//退死循环的标之置为true
            next()消息出队的方法中如退出循环返回null;
                if (mQuitting) {
                    dispose();
                    return null;
                }
             loop()方法中的死循环,收到null消息也会退出循环,这样就不会退出了消息的阻塞等待了,如果这时再使用handler发送消息就会报异常了
            if (msg == null) {
                return;
            }
            ***/
            nativeWake(mPtr);
        }
    }

总结:
1.线程Thread使用Handler,在某线程创建Handler实例对象,其他线程通过这个handler实例对象向创建了Handler实例对象的线程发送消息,达到线程之间的通信的目的。

2.Handler负责收发消息Message,发出的消息会包含Handler实例对象,消息分发的时候直接调用Handler的dispatchMessage方法,调用消息的Callback回调,或者调用handleMessage方法。

3.Looper则保证了保证了这个线程的looper对象和messageQueue对象唯一且向对应其实就是保证了这个线程和其对应的looper对象是唯一对应的,messageQueue对象是在looper的构造函数中创建的,looper唯一所以消息队列也是唯一的,其他线程使用handler发送消息时都是会进入与这个线程唯一绑定的消息队列,三个重点方法:prepare创建线程唯一的Looper对象,loop方法死循环调用MessageQueue中的next方法直到拿到null消息退出循环,quitSafely调用MessageQueue的quit方法使loop方法拿到null消息至此退出整个消息阻塞等待,细节再MessageQueue中。

4.MessageQueue,虽然名字叫消息队列但它并不是真正的消息队列,它是负责管理消息队列,真正的消息队列是消息Message,对外来说它就是充当消息队列的角色,有三个重要的方法,enqueueMessage方法负责消息入队,把消息按优先级排好,符合唤醒条件则会唤醒阻塞,next方法最开始被looper对象的loop方法死循环调用进入阻塞状态,等待唤醒处理消息,quit方法则是把退出标志位置为true然后唤醒阻塞,next中退出的标志位命中返回null,loop方法中的死循环退出,不再进入next方法,整个阻塞等待处理消息退出。

5.Message就是消息的意思,其实它是真正的消息队列,它有一个变量next就是下一个Message实例对象,下一个又有下一个,message中包含Handler对象用target保存,Handler发消息的收后msg.target=this将Handler实例赋值,消息分发的时候message.target.dispatchMessage这其实就是调用的Handler实例对象的消息分发方法,when是消息分发的时间,再MessageQueue的enqueueMessage方法中会根据when优先级排序处理消息,消息使用享元模式obtain取一个消息对象,没有回收的消息对象,就新建一个,消息使用完调用recycleUnchecked将消息中的变量值清空回收消息对象,这样做可减少内存申请,减少内存的申请就可减少内存抖动,减少内存块的碎片化,oom的概率也会降低。

使用Handler造成内存泄漏的问题

首先先理解内存泄漏的原理,

通俗的说是占着资源又不用,又不能回收叫内存泄漏,举个例子:某个对象在使用完了之后就不会再用或者一定时间内都不会再用它这个对象可以在再次使用的时候可以再创建,期间是可以释放的,它占用的资源应该再垃圾回收机制触发时被回收,但是它内部的某个成员变量连接着gcroot,导致垃圾回收机制认为它是不可回收的因而没有回收它,在资源紧张的时候容易引发oom。或者最终导致oom,

专业的说简单的说就是实例对象的成员变量连接着gcroot,导致gc触发时不能回收,但是这个对象其实是已经不会再用的,这就是内存泄漏。

那什么是gcroot呢:有4种

1.虚拟机栈(栈帧种的本地变量表)种的引用对象(就是方法正在执行时,用到的变量,这些变量引用的对象,方法正在执行呢怎么可能回收,所以从这里开始的引用对象的往外构成的一条引用链都挂在gcroot上,自然不会回收)

2.本地方法栈中JNI(Native方法)引用的对象(就比如再子线程中创建的Handler,阻塞就是调用的native方法)

3.方法区中静态属性引用的变量(静态变量和常量通常不会被回收)

4.方法区中常量引用的对象

在java中内部类会持有外部类的实例,匿名内部类也是内部类,是没有名字的内部类,如直接new 一个接口,或者直接new一个类然后后面加{}大括号复写方法。

先说说我们再子线程使用Handler的情况直接new Thread已经是匿名内部类了,再调用loop阻塞Activity退出后gc不能回收Activity不能被回收,阻塞是native方法阻塞的,在不使用的时候应该调用quitSafely释放使线程执行完毕。

再看看我们常用的直接在Activity创建的一个Handler

//在使用Handler的时候我们经常这么写
private Handler mainHandler = new Handler(getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

如上,这么些其实只能说有可能会内存泄漏,事实单单这么写是不会内存泄漏的,

分析一下gcroot的可达性,看看是否挂在gcroot上,

首先匿名内部类Handler会持有外部类Activity的实例,

getMainLooper按照我们在子线程中创建Handler时的了解 MainLooper会创建与MainThread唯一的looper对象和messageQueue独享,然而这两者并没有持有handler对象,相反的是handler持有looper和messageQueue对象因此引用链并没有到达gcroot,从而单单写一个Handler匿名内部类是不会造成内存泄漏的。

那经常说的有可能造成内存泄漏的Handler是这么写的呢,那就是延时发消息,如延时30秒发送消息,然后退出这个Activity,gc的时候这个Activity应该回收却因为延时30秒不能回收。

分析:消息Message是持有Handler对象的使用target引用Handler,发送延时消息时这个消息对象进入会按照延时时间排入MessageQueue的mMessages变量的next的next。。。某一个next中,如果只有一个消息那这个消息赋值给mMessages,然后进入next阻塞,在阻塞期间,

构成一个gcroot引用链:activity->handler->message->messageQueue-mainLooper-静态的sThreadLocal对象,因此gc无法回收这条链开端的activity对象,消息使用享元模式,使用完在回收的时候target置为null,gcroot引用链断开,activity对象即可在gc的时候回收。

那如何解决Handler使用不当造成的内存泄漏呢

Activity销毁的时候移除handler的说有消息即可,

或者使用静态内部类,静态内部类使用WeakReference<HandlerActivity>软引用的方式接收传进来的Activity实例,

 /** 
     * 声明一个静态的Handler内部类,并持有外部类的弱引用 
     */  
    private static class MyHandler extends Handler{  
        private final WeakReference<HandlerActivity> mActivty;  
        private MyHandler(HandlerActivity mActivty) {  
            this.mActivty = new WeakReference<HandlerActivity>(mActivty);  
        }  
        @Override  
        public void handleMessage(Message msg) {  
            super.handleMessage(msg);  
            HandlerActivity activity = mActivty.get();  
            if (activity != null){  
                // ....  
  
            }  
        }  
    }  

相关文章

网友评论

      本文标题:Handler

      本文链接:https://www.haomeiwen.com/subject/hjpuhltx.html