美文网首页
Handler笔记

Handler笔记

作者: Boahui | 来源:发表于2021-03-31 08:32 被阅读0次

    1、数据通信会带来什么开发中的问题?
    (1)线程间如何进行通信
    Handler通信实现的方案实际上是内存共享方案。
    (2)为什么线程间不会干扰
    (3)为什么wait/notify的用武之地不大
    因为Handler已经将需要这部分功能进行了Linux层的封装
    1、Looper的创建,如果想在一个线程中使用Handler则第一步为执行Looper.prepare ,Looper中存在一个静态的变量ThreadLocal,prepare方法中new一个Looper,并将这个Looper保存到ThreadLocal中。
    2、Looper的无线循环启动,在子线程的run方法中的最后,启动Looper.loop, 这个是一个无限循环,一直从MQ中获取消息进行处理。
    3、MessageQuenue的创建,MQ是Looper的成员变量,在Looper的构造函数中创建。
    4、Handler的创建,在任意线程中创建Handler,都会通过Looper的ThreadLocal获取一下当前线程的Looper,如果没有获得Looper则抛出异常,Handler会持有Looper和从Looper中获取的MQ、
    5、使用,Handler.sendMessage,最终都会调用MQ的enqueueMessage将消息入队
    6、在Looper从MQ中拿到消息后,就会调用与消息绑定的Handler的handlerMessage方法,也就是msg.handler.handlerMessage处理消息。

    1、主线程的Handler启动
    一个APP的启动流程是从桌面启动器Lanuncher点击图标-->fork一个zygote进程,分配一个JVM,JVM的main函数在ActivityThread中,在ActivityThread的main方法中,执行
    Looper.prepareMainLooper,为主线程准备一个Looper,执行Loop.loop开始循环MQ。

    Loop.loop中是一个死循环,一直调用MQ的queue.next查询读取消息。
    因为loop是个死循环,也就是所有的代码都会在Loop中执行。
    线程间通信只是Handler的一个附属的功能,真实的作用是所有的代码都在Handler中执行。维持着Android APP运行的框架。所以要重视。
    那Loop如何停掉,
    for(;;){
    Message msg = queue.next();
    if(msg == null){//什么时候返回一个为空的msg的message,1、应用退出,调用quit()
    //没有消息 ,意味着这messageQueue正在退出。
    return;
    }
    }

    handler-->sendMessage,消息起点
    handlerMessage //消息结束。中间发生了什么,需要看源码。


    image.png

    优先级队列
    入队: Handler.sendMessage-> MQ-> enqueueMessage,向MQ中放入Message,在消息队列中插入一个消息。
    出队: Loop.loop 中MQ.next()方法,出队列。那是谁来取吶,是Loop来调用,

    Looper.loop->MQ.next->msg.target.dispatch->handleMessage()。

    问题,从细节上来说,Message在动的过程
    Message怎么来的,new 或者 obtain;无论Message怎么创建,都是一块内存。
    子线程
    主线程
    因为内存不分线程,所以子线程和主线程都可以使用一块内存。
    handle = new Handler(){
    handleMessage()
    {

    }

    }
    new Thead(new Runable(){
    void run(){
    Looper.prepare
    Loop.loop
    handle.sendEmptyMessage(new Message);//子线程创建消息。
    }

    })

    MQ的数据结构,由单链表形成的优先级队列。优先级队列是有顺序的。
    Message.next-->Message-->next-->Message
    先后顺序,时间,先后顺序。

    有sendMessageAtTime方法,有时间顺序。
    那么是怎么排序的吶?看enqueueMessage
    if (p == null || when == 0 || when < p.when) {这个是队列为空。看else不为空
    {
    Message prev;
    for (;;) {
    prev = p;
    p = p.next;
    if (p == null || when < p.when) {//找到一个p,当p为空或者p的执行时间大于当前入队msg的时间
    break;
    }
    if (needWake && p.isAsynchronous()) {
    needWake = false;
    }
    }
    msg.next = p; // invariant: p == prev.next//将msg按照时间顺序插入队列
    prev.next = msg;
    }
    排序算法,插入。
    为什么是一个队列,先进先出。
    先进无法保证,因为有时间排序,那么先出吶?
    看,loop ,一直都是mq.next 一直取最前面的一个,所以是一个队列。

    Looper源码分析。
    核心在与构造函数,一个数loop函数,ThreadLocal
    1、初始化
    私有构造函数,如果构造函数是共有的,那么Loop就满天飞了,不能进行控制。
    ThreadLocal,多线程,线程上下文的存储变量。每一个线程都有一个ThreadLocalMap,里面有个如因信用的Entry键值对(ThreadLocal k,Object v)
    ThreadLocal.set 先获取当前线程对应的map,map.set(this,value);

    一个线程只有一个Looper ,并且Looper是不能改的。为什么?
    因为一个线程中只有一个ThreadLocalMap >> (this,value) this是唯一的threadLocal,所以value是唯一的,如何保证ThreadLocal/只对应一个value
    <key,value> set(key1,value1) set(key1,value2);
    因为在prepare之前判断一下
    if(sThreadLocal.get() != null){
    throw 异常.
    }

    2、MessageQuenue属于哪个线程
    这个说法是错误的,只有执行的函数才能说属于哪个线程的。变量是可以共享的。

    Handler设计的亮点

    面试题:
    1、一个线程有几个Handler?
    无数个,Handler机制只有一个
    2、一个线程有几个Looper?,只有一个,用过threadLocap和prepare 的 threadLocal,get来保证只能为一个线程设置一个Looper
    3、Handler内存泄露的原因?为什么其他的内部类没有说过这个问题?
    static :
    内部类持有外部类的对象。
    recycleView adpter ViewHolder 也是内部类?
    是生命周期的问题。
    enqueueMessage{
    msg.target = thiss;
    }
    Message持有了Handler ,Handler持有了Activity ,message可能会特定的时间后执行,那么message就不能被释放,也就是Handler不能被释放,Handler持有的Activity也不能被释放,形成了内存泄露。

    4、为啥主线程可以直接new Handler,子线程想要new Handler要干什么?
    5、子线程中维护的Looper,消息队列无消息的时候的处理方案是什么,有什么用?
    需要quit
    涉及到,睡眠和唤醒。MessageQueue的等待和唤醒机制。
    Message的enqueueMessage没有限制入队的消息数量,因为这个MQ是大家公用的。
    MQ的轮询取消息 如果没有消息则会阻塞
    这里的阻塞有两个方面阻塞
    1、如果取出的消息还没有到要执行的时刻,那就得阻塞
    if (now < msg.when) {
    // Next message is not ready. Set a timeout to wake up when it is ready.
    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
    }计算

    循环一遍后从头开始, nativePollOnce(ptr, nextPollTimeoutMillis);//睡眠执行等待操作

    2、第二层等待
    如果消息队列为空,
    nextPollTimeoutMillis = -1;//标识永久 等待,直到有人过来唤醒。
    唤醒 if(needWake){
    nativeWake(mPtr)
    }


    native层

    最终是调用到了Linux层的epoll_wait来实现等待。

    enquenueMessage

    最终也是调用到了Linux层的。
    子线程的消息为空时一定要调用quit来退出:
    quit:唤醒线程-->messageQuenue->null->退出loop

    6、既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同的线程,那么如何确保线程安全的?)
    enqueueMessage会加synchronized:内置锁?为啥叫内置锁,是由jvm完成,系统的。
    可以锁代码块,对象。

     boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            synchronized (this) //只要是锁了this,其他的函数和方法都不能调用,都是等待状态
    

    一个线程只有一个可以操作MQ的地方,而这个地方又加了锁,所以可以确保线程安全。
    enqueueMessage next quit方法都要进行加锁。

    到底:Message:从子线程->>发送到主线程
    首先,内存是没有线程的
    子线程:里面执行的函数,这个函数就在子线程里面
    thread: handler.sendMessage(msg) -> MessageQueue.enqueMessage(msg) MessageQueue是一个容器。
    主线程的Loop就会去轮询主线程对应的MessageQueue,loop函数是在主线程调度,所以在MessageQueue的next中会调用msg.target.dispatchMessage方法,调用handleMessage处理Message

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>( );整个APP里面唯一,所有线程公用一个。
    Looper线程唯一。

    7、Message如何创建
    obtain 和 new
    注意:在Looper的loop中取出一个msg
    msg.target.dispatchMessage(msg);后并没有直接return 而是 执行msg.recycleUnchecked();对msg进行回收,放到sPool中,
    synchronized (sPoolSync) {
    if (sPoolSize < MAX_POOL_SIZE) {
    next = sPool;
    sPool = this;
    sPoolSize++;
    }
    }
    在池子中插入一个msg,这个是防止oom,和内存抖动。避免多次new造成,使得内存碎片严重,造成内存抖动,造成OOM,注意new的内存是连续的,如果内存够,但是不连续,所以要不断回收,无法回收就OOM了。
    这种设计模式就用了享元设计模式,内存复用。

    8、Looper的死循环为什么不会导致应用卡死
    ANR 点击事件 5s:也就是一个点击Message的处理事件超过5s,然后用handler发送一个ANR消息,提醒。ANR的优先级高。
    广播10s 。
    这个是无关的问题,因为这些点击事件被封装为Message,
    MSG:为啥block不会导致ANR?
    block 线程没事做了,CPU让出。

    消息机制之同步屏障:架构思维
    我们都知道,Android系统16ms会刷新一次屏幕,如果主线程的消息过多,在16ms之内没有执行完,必然会造成卡顿或者掉帧。那怎么才能不排队,没有延时的处理呢?这个时候就需要异步消息,在处理异步消息的时候,我们就需要同步屏障,让异步消息不用排队等候处理。可以理解为同步屏障是一堵墙,把同步消息队列拦住,先处理异步消息,等异步消息处理完了,这堵墙就会取消,然后继续处理同步消息Silly_Monkey原文链接

    异步消息:立刻执行
    同步消息/普通消息: 放在MQ中执行
    消息时根据执行时间进行先后排序,然后消息时保存在队列中,因而消息只能从队列的头取出来,那么问题来了,那需要紧急处理的消息怎么办?
    msg.target = null 做一个标志(这就是一个同步屏障);msg1 -> msg2 -> msg3-> msg4 ->
    20: 第20个消息非常重要,必须马上执行。 如何去做?如何确保立即执行。
    msg.target = null -> msg1 -> msg2 -> msg3-> msg4 ->

    if (msg != null && msg.target == null) {
    // Stalled by a barrier. Find the next asynchronous message in the queue.
    do {
    prevMsg = msg;
    msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());找到一个异步消息,退出处理同步消息。
    }

    什么时候用到:就是在更新UI。
    ViewRootImpl;
    shceduleTraversalsf方法,调用MessageQueue得postSyncBarrier方法发送同步屏障和removeSyncBarrier方法移除同步屏障。

    
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//发送同步屏障
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//发送异步消息
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    
    
        private void postCallbackDelayedInternal(int callbackType,
                Object action, Object token, long delayMillis) {
            if (DEBUG_FRAMES) {
                Log.d(TAG, "PostCallback: type=" + callbackType
                        + ", action=" + action + ", token=" + token
                        + ", delayMillis=" + delayMillis);
            }
    
            synchronized (mLock) {
                final long now = SystemClock.uptimeMillis();
                final long dueTime = now + delayMillis;
                mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
    
                if (dueTime <= now) {
                    scheduleFrameLocked(now);
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                    msg.arg1 = callbackType;
                    msg.setAsynchronous(true);//UI更新消息是异步得
                    mHandler.sendMessageAtTime(msg, dueTime);
                }
            }
        }
    

    什么时候删除同步屏障

        void unscheduleTraversals() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
                mChoreographer.removeCallbacks(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            }
        }
    

    HandlerThread存在得意义

    1、方便使用,方便初始化
    2、保证了线程得安全,解决有可能得异步问题。
    如果不用,HandlerThread,那么我们要在使用Handler在子线程中处理消息,那么就需要在子线程得run方法创建Handler。

                public void run() {
                    Looper.prepare();
                    Handler handler = new Handler();
                    Looper.loop();
                }
    

    当然,也可以将子线程得Looper保存到子线程外部,用Handler(Looper)来创建Handler,但是把Looper放在外部,外部类持有Looper得引用,无法进行释放,可能导致内存问题。

                public void run() {
                    Looper.prepare();
                    looper  = Looper.myLooper();
                    Looper.loop();
                }
    

    HandlerThread就是一个线程,不过在run方法中完了Looper得工作。

        @Override
        public void run() {
            mTid = Process.myTid();
            Looper.prepare();
            synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();//此时唤醒其他等待得锁,但并不释放锁,一定等到整个synchronized代码块执行完了才释放锁。
            }
            Process.setThreadPriority(mPriority);
            onLooperPrepared();
            Looper.loop();
            mTid = -1;
        }
    

    处理完任务>>service自动停止,内存释放。

        /**
         * This method returns the Looper associated with this thread. If this thread not been started
         * or for any reason isAlive() returns false, this method will return null. If this thread
         * has been started, this method will block until the looper has been initialized.  
         * @return The looper.
         */
        public Looper getLooper() {
            if (!isAlive()) {
                return null;
            }
            
            // If the thread has been started, wait until the looper has been created.
            synchronized (this) {
                while (isAlive() && mLooper == null) {
                    try {
                        wait();//发现Looper没有创建完成有人想要获取looper,等待,释放锁,wait();synchronized什么关系
    //notifyall 释放锁吗?
                    } catch (InterruptedException e) {
                    }
                }
            }
            return mLooper;
        }
    

    IntentService

    抽象类,子类继承并实现onHandleIntent来处理耗时任务。
    Handler得应用
    Service 处理后台任务
    一般new Thread处理任务,而在IntenService中,onCreate方法里面创建了HandlerThread和一个ServiceHandler,在onStart方法中

        @Override
        public void onStart(@Nullable Intent intent, int startId) {
            Message msg = mServiceHandler.obtainMessage();
            msg.arg1 = startId;
            msg.obj = intent;
            mServiceHandler.sendMessage(msg);
        }
    
     private final class ServiceHandler extends Handler {
            public ServiceHandler(Looper looper) {
                super(looper);
            }
    
            @Override
            public void handleMessage(Message msg) {
                onHandleIntent((Intent)msg.obj);执行任务
                stopSelf(msg.arg1);自杀,关闭Service
            }
        }
    

    应用需求:一项任务分为几个子任务,等子任务全部完成后,这项任务才算完成,
    这个需求可以用多线程来处理,一个线程处理完 -->下一个-->下一个。

    IntentService就可以帮我们完成这个工作,而且,能够很好得管理线程,保证只有一个子线程处理工作,而且是一个一个得完成任务,有条不紊。

    到底还有别的地方在用吗?
    fragment生命周期管理
    如何保证attach先执行FragmentPagerAdapter
    mCurTransaction.attach(fragment); mCurTransaction.detach((Fragment)object);

    Glide生命周期管理
    Handler loop休眠为什么不会导致ANR
    Messagequeue队列处理机制,在fragment生命周期管理中的应用

    相关文章

      网友评论

          本文标题:Handler笔记

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