IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Handler分析 -> 正文阅读

[移动开发]Handler分析

Handler分析

写本篇文章的原因也是为了知识的记录
在Android开发中,通常会在子线程做某些事然后通知主线程,这时候就会用到handler机制。如写Android听过的第一句话就是子线程不能更新UI,这时候就可以使用handler机制来通知主线程来进行ui更新。

Handler相关的类

Handler: 主要负责发送消息、接收消息
Looper: 轮训消息队列,每个线程只能有一个Looper
Message: 消息实体
MessageQueue: 消息队列,用于存储消息、管理消息 单链表结构

Handler 基本使用

首先要 Looper.prepare()然后在调用Looper.loop()之后就是我们熟悉的创建Handler 用来发送、接受消息

	public Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
			//这里接收消息 msg.what 进行处理消息
        }
    };
    //发送消息 通过参数what来区分消息
    mHandler.sendEmptyMessage(0);

但是我们发现在使用的时候并没有进行 Looper.prepare() Looper.loop()操作,是因为他们是在ActivityThread.java中 main() 中进行的初始化

public static void main(String[] args) {
    	........省略部分代码
        Looper.prepareMainLooper();
        ........省略部分代码
        Looper.loop();
    }

Looper

看下Looper.prepareMainLooper()

	public static void prepareMainLooper() {
    	//原来是在这进行了prepare操作  这个参数是表示这个looper是否可退出
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

再看下 prepare()做了什么

private static void prepare(boolean quitAllowed) {
    	//从ThreadLoacal 中获取当前线程的looper 如果有就抛出异常 没有进创建一个Looper存入ThreadLocal中
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

这也就能看出为什么一个线程只能有一个looper了,再看下初始化Looper时干了什么

private Looper(boolean quitAllowed) {
	//重要角色 MessageQueue出来了, 创建MessageQueue以及Looper 与当前线程绑定
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

能让整个Handler机制跑起来的方法Looper.loop()

public static void loop() {
        final Looper me = myLooper();
        //取出当前线程中的looper  如果为空抛出异常
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
        	//死循环 从消息队列中取出消息
            Message msg = queue.next(); // might block
            //由于刚创建MessageQueue就开始轮询,队列里是没有消息的,等到Handler sendMessage enqueueMessage后队列里才有消息
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            //.....省略部分代码
            //每个message都会有target 这个target就是目标handler
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
             //.....省略部分代码
            msg.recycleUnchecked();
        }
    }

创建Handler

最简单调用

public Handler mHandler = new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
		//这里接收消息 msg.what 进行处理消息
    }
};

最总会走到有参构造方法中

 public Handler(Callback callback, boolean async) {
        //.....省略部分代码
        //从当前线程中获取looper对象
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //从looper中获取消息队列
        mQueue = mLooper.mQueue;
        mCallback = callback;
        //设置消息是否为异步消息
        mAsynchronous = async;
    }

看下Looper.myLooper()

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
 //返回当前线程中存贮的looper对象 注释说获取的时候可能为空,所以必须先执行Looper.prepare()
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

Handler可以指定looper构造函数

public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

Handler发送消息

Handler发送消息sendMessage(Message msg) sendMessage通过一系列的方法重载sendMessageDelayed,sendMessageAtTime,enqueueMessage继续调动messageQueue中的enqueueMessage方法将msg放进messageQueue中通过looper取出交给Handler的dispatchMessage进行处理

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
        //callback在message的构造方法中初始化或者使用handler.post(Runnable)时候才不为空
            handleCallback(msg);
        } else {
        	//mCallback是一个Callback对象,通过无参的构造方法创建出来的handler该属性为null,此段不执行
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //最终会执行这handlerMessage方法
            handleMessage(msg);
        }
    }
    
    private static void handleCallback(Message message) {
        message.callback.run();
    }

创建Message

可以直接 new Message,但是有更好的创建方法Message.obtain(),因为这个方法可以检查是否有复用的message用于复用避免过多创建、销毁message达到优化内存,性能的效果。

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与Handler绑定

可以通过Message.obtain(Handler h)绑定

public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;
        return m;
    }

但是我们平时并没有绑定Handler因为是在Handler发送message时候最总会调用enquueMessage方法

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	//绑定当前的handler
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

MessageQueue

MessageQueue实在创建Looper时候创建的,通过前面的分析每个线程只能有一个looper,每个looper里只有一个MessageQueue,我看再看一下MessageQueue,这个类相当于一个仓库,我们如何能管理好这个仓库就是一个关键点了。enqueueMessage(相当于把消息放入库中),next(相当于把消息取出来)所以这两个方法是负责线程安全的主要挡口。

boolean enqueueMessage(Message msg, long when) {
    	//判断msg.target 不能为空
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
		//设置了锁
        synchronized (this) {
            if (mQuitting) {
            	//正在退出时,回收msg
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
            	//p==null时代表这当前messageQueue中没有消息会走到这里 吧消息插在前边
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                //需要唤醒队列
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //这里就是会把消息插在尾部
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        //锁结束的地方
        return true;
    }

next方法

  Message next() {
       	
        //......省略部分代码
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);
			//锁开始的地方
            synchronized (this) {
               //......省略部分代码 取到相关的msg返回
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
             //.....省略部分代码 锁结束的地方
            }
			//.....省略部分代码 锁结束的地方
        }
    }

synchronized锁是一个内置锁,这个锁,说明的是对所有调用同一个MessageQueue对象的线程来说,他们都是互斥的,然而,在我们的Handler里面,一个线程是对应着一个唯一的Looper对象,而Looper中又只有一个唯一的MessageQueue(这个在上文中也有介绍)。所以,我们主线程就只有一个MessageQueue对象,也就是说,所有的子线程向主线程发送消息的时候,主线程一次都只会处理一个消息,其他的都需要等待,那么这个时候消息队列就不会出现混乱。

消息机制之同步屏障

同步屏障的概念,在Android开发中非常容易被忽略,因为在平时太少见了,很容易被忽略。
线程的消息都是放在同一个MessageQueue中的,取消息时候是互斥的。而且只能从头部取消息,而添加的消息也是按照执行的先后顺序进行的排序,那么问题来了,同一时间范围内的消息,如果它需要立即执行,我们该怎么办,所以我们需要给紧急需要处理的消息一个绿色通道,这个绿色通道就是同步屏障概念。

同步屏障是什么?

同步屏障就是阻碍同步消息,只让异步消息通过。如何开启同步屏障呢?如下而MessageQueue#postSyncBarrier()我们看看它的源码

private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            //从消息池中取消息
            final Message msg = Message.obtain();
            msg.markInUse();
            //初始化Message对象的时候,并没有给target赋值,因此 target==null
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
            //如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步消息里有时间小于T,则prev也不为null
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            //根据prev是不是为null,将 msg 按照时间顺序插入到 消息队列(链表)的合适位置
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

可以看到,Message 对象初始化的时候并没有给 target 赋值,因此, target == null 的 来源就找到了。上面消息的插入也做了相应的注释。这样,一条 target == null 的消息就进入了消息队列。开启同步屏障后,有是如何处理的呢,这回就要重点看下MessageQueue#next()

    Message next() {
    //.....省略一些代码
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    // 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
	// 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
	// 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)
	//如果期间有程序唤醒会立即返回
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            //获取系统开机到现在的时间
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            //当前链表的头结点
            Message msg = mMessages;
            //这里就是处理屏障消息  如果msg.target == null就会一直遍历找到第一个异步消息
            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());
            }
            if (msg != null) {
                if (now < msg.when) {
					//计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
                    //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                	// 获取到消息
                    // Got a message.
                    mBlocked = false;
                    //链表操作,获取msg并且删除该节点
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    //返回拿到的消息
                    return msg;
                }
            } else {
                // No more messages.
                //没有消息,nextPollTimeoutMillis复位
                nextPollTimeoutMillis = -1;
            }
            //.....省略部分代码
    }
}

从上面可以看出,当消息队列开启同步屏障的时候(即标识为 msg.target == null ),消息机制在处理消息的时
候,优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。

同步屏障的应用场景

在View更新时,draw、requestLayout、invalidate等很多地方都调用ViewRootImpl#scheduleTraversals()

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //开启同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //发送异步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
    }

postCallback() 最终走到了 ChoreographerpostCallbackDelayedInternal()

 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);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

这里就开启了同步屏障,并发送异步消息,由于 UI 更新相关的消息是优先级最高的,这样系统就会优先处理这些异步消息,最后,当要移除同步屏障的时候需要调用 ViewRootImpl#unscheduleTraversals() 。

 void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        //移除异步消息
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

小结

同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用
Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的 setAsynchronous(true) 时,target 即为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在 loop() 中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-30 12:02:39  更:2021-09-30 12:04:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:23:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码