Android的消息机制

news2024/12/29 9:05:36

Android的消息机制

Android的消息机制概述

Android的消息机制主要指的是Handler的运行机制以及Handler所附带的MessageQueueLooper的工作机制

Handler的主要作用是将一个任务切换到某个指定的线程中执行。

它的主要用处就是当要更新UI界面的时候,我们不能在非UI线程进行更改。

为什么不能在非UI线程中更改UI

因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能导致UI控件处于不可预期的状态,

这不很好解决嘛,我们在多线程中学过,直接给它加个锁不就好了

可是这有个问题,那就是会让UI访问的逻辑更加复杂;其次锁机制会降低UI访问的效率

基于这两点,我们选择在UI线程及主线程中修改UI界面

如果我在另一个线程中进行网络请求,获得到的数据要更新在UI界面怎么弄呢?

这时候就要用到Handler了

Handler的流程如下所示:

Handler会调用send/post方法(post方法最终也会调用send方法),在send内部调用MessageQueueenqueueMessageMessage传入MessageQueue,

Looper会以无限循环的方式进行查找是否有新消息,有的话就处理消息,调用handleMessage,没有的话就进行等待

需要注意的是:

线程默认是没有Looper,如果需要使用Handler就必须为线程创建Looper

但是主线程/UI线程被创建的时候就会初始化Looper,这也是在主线程中默认可以使用Handler的原因

Android的消息机制分析

1.ThreadLocal的工作原理

1.1ThreadLocal的作用

ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。

可以在不同的线程中互不干扰地存储并提供数据,通过它可以轻松获得每个线程的Looper

1.1.1ThreadLocal的作用场景

1.一般来说当某些数据是以线程为作用域,并且不同线程具有不同的数据副本的时候我们就可以用到ThreadLocal

比如说Looper,它的作用域就是线程,并且不同的线程有不同的Looper

这个时候用ThreadLocal就方便我们实现Looper在线程中的查找

2.另一个运行场景是复杂逻辑下的对象传递,比如监听器的传递,在线程内部只要通过get方法获得监听器

1.1.2ThreadLocal的使用

先定义一个ThreadLocal对象,我们先选择Boolean类型

private ThreadLocal<Boolean>mBooleanThreadLocal = new ThreadLocal<Boolean>();

然后分别在主线程,子线程1,子线程2设置和访问它的值

mBooleanThreadLocal.set(true);
Log.d("TAG","mBooleanThreadLocal="+ mBooleanThreadLocal.get());

new Thread("Thread#1"){
    @Override
    public void run(){
        mBooleanThreadLocal.set(false);
        Log.d("TAG","[Thread#1]mBooleanThreadLocal="+mBooleanThreadLocal.get());
    };
}.start();

new Thread("Thread#2"){
    @Override
    public void run(){
   
        Log.d("TAG","[Thread#2]mBooleanThreadLocal="+mBooleanThreadLocal.get());
    };
}.start();

在主线程将mBooleanThreadLocal的值为true,子线程1为false,子线程2为null,因为没有设置,主线程为true

虽然是在不同的线程中访问同一个ThreadLocal,但是不同线程中的ThreadLocal所获取到的值是不一样的。

ThreadLocal之所以会有这种效果,是因为不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引查找对应的value值

然后我们分别看看ThreadLocal的set方法和get方法,看它们是怎么实现的。

1.1.3ThreadLocal的set方法

public void set(T value) {
	  Thread t = Thread.currentThread(); //获取当前线程
	  ThreadLocalMap map = getMap(t);		//查找当前线程的本地储存区
	  if (map != null)
	      map.set(this, value);//保存数据value到当前线程
	  else
	  //当线程本地存储区,尚未存储该线程相关信息时,
	  //创建ThreadLocalMap本地存储区对象
	      createMap(t, value);
}

我们刚才说ThreadLocal的内部会从各自线程中取出一个数组,然后再从各自数组中根据ThreadLocal的索引查找对应的value

然后我们看这段代码:

  1. 获取当前线程的引用,即调用 Thread.currentThread() 方法获取当前线程对象 t。
  2. 调用 getMap(t) 方法从当前线程中获取 ThreadLocalMap 对象 map,该对象是线程本地存储区。
  3. 如果 map 不为 null,则说明当前线程已经拥有 ThreadLocalMap 对象,直接将 value 存储到 map 中。
  4. 如果 map 为 null,则说明当前线程还没有 ThreadLocalMap 对象,需要调用 createMap(t, value) 方法创建该对象,并将 value 存储到其中。

然后我们看一下它是怎么存进去的

private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {

        // Android-changed: Use refersTo() (twice).
        // ThreadLocal<?> k = e.get();
        // if (k == key) { ... } if (k == null) { ... }
        if (e.refersTo(key)) {
            e.value = value;
            return;
        }

        if (e.refersTo(null)) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

这段代码是 Java 中 ThreadLocalMap 类的 set 方法的实现。ThreadLocalMap 是 ThreadLocal 类中用于存储线程局部变量的内部类,它通过哈希表实现。set 方法的作用是将给定的 ThreadLocal 对象和值存储到哈希表中。

这时候我们看看get()方法

1.1.4ThreadLocal的get方法

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

这段代码前面的这部分和set方法一样,获得当前的thread,然后调用 getMap(t) 方法从当前线程中获取 ThreadLocalMap 对象 map,该对象是线程本地存储区。

如果ThreadMap里面的不为空,那么获取当前线程的本地储存区中的Entry,如果Entry不为空,则返回数据

我们看看 ThreadLocalMap.Entry里面的源码

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
Entry(ThreadLocal<?> k, Object v) {
    super(k);
    value = v;
}
}

1.1.5总结

1.为什么不同的线程访问同一个ThreadLocal获得的值不同?

这是因为每个ThreadLocal对象都有一个内部的ThreadMap对象用于存储对象,当一个线程去访问ThreadLocal对象,ThreadLocal会在自己的ThreadLocalMap对象中寻找对应的值,如果当前线程还没有在ThreadLocalMap存储相应的值,ThreadLocal就会为当前线程创建一个新值,这样,每个线程都拥有自己独立的值副本,互不干扰。

2.为什么不同线程的ThreadLocalMap不一样

ThreadLocalMap在Java中被设计为每个线程独立维护的数据结构,这是为了解决多线程并发访问时的线程安全性问题。每个线程拥有自己的ThreadLocalMap实例,它是ThreadLocal类的一个成员变量。

2.MessageQueue的工作原理

2.1MessageQueue的概念

MessageQueue内部不是队列,而是插入和删除有优势的单链表。MessageQueue是消息机制的Java层和C++层的连接纽带,大部分核心方法都交给native层来处理,

2.2MessageQueue的作用

MessageQueue主要包含两个操作:插入和读取

读取操作的本身会带有删除操作,

其中插入方法对应enqueueMessage

读取方法对应next

其中enqueueMessage的相关源码如下:

2.2.1enqueueMessage源码
boolean enqueueMessage(Message msg, long when) {
		// 每一个普通Message必须有一个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没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
//将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
//消息队头存在barrier,并且同时Message是队列中最早的异步消息。
            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;
        }

//消息没有退出,我们认为此时mPtr != 0
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
nativeWake(mPtr);
        }
    }
    return true;
}

前面两个判断不用看,直接进入锁

  msg.markInUse();
  msg.when = when;

它先标记消息为使用中,然后设置使用时间

然后在p == null || when == 0 || when < p.when的if判断中判断MessageQueue中有没有消息,或者msg的触发时间为队列中最早则进入该分支,然后进行以下3步操作:

1.将给定消息的next属性设置为当前消息队列的头消息(p),将给定消息作为新的头消息。

2.更新消息队列的头消息为给定消息。

3.唤醒事件队列

(其实这段if操作是判断消息是不是最先进来的,如果是最先进来的则把它放在头部)

然后如果它的MessageQueue中有消息且msg不是最先进入该链表的,则

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;

第一段代码应该是在判断唤不唤醒,在事件被阻塞或者当前头消息(p)是否是一个barrier(目标为null)或者给出的消息是个异步消息的时候则唤醒

然后在在一个无限循环中,遍历消息队列,找到合适的位置将给定消息插入到消息队列中,并更新needWake标志。

它主要进行的操作是先用pre保存p的值,然后将p指向下一个指针,如果进行完上面的操作后p==null或者给定消息的触发时间小于下一个消息的触发时间则退出循环

如果之前判断需要唤醒事件队列,并且当前消息是异步消息,则将needWake标志设置为false,表示不需要唤醒事件队列。

将给定消息的next属性设置为当前消息指针(p),并将前一个消息的next属性指向给定消息


上面的操作是MessageQueue的插入

现在来看看MessageQueue的读取

2.2.2next源码
Message next() {
        // Return here if the message loop has already quit and been disposed. This can happen if the application tries to restart a looper after quit which is not supported.
       final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
int pendingIdleHandlerCount = -1;
    int nextPollTimeoutMillis = 0;
    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;
            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) {
                    // 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);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
//设置消息的使用状态,即flags |= FLAG_IN_USE
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
                // If first time idle, then get the number of idlers to run. Idle handles only run if the queue is empty or if the first message in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            // Run the idle handlers. We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;
        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

可以发现next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法则会一直阻塞在这里,当有新消息的时候,next方法会返回这条消息,并且将其从单链表中移除

2.2.3总结

enqueueMessage中如果消息队列为空(p为null)或给定消息的触发时间为0或给定消息的触发时间小于头消息的触发时间则把该消息设置为头部

否则就进行单链表的插入操作,同时它也会判断如果被唤醒且处于异步操作的话,则不再唤醒,退出插入操作

next则是无限循环的方法,这一点和Looper很像,如果没有消息则会阻塞,有消息则返回消息,且移除它

3.Looper的工作原理

Looper在Android中扮演消息循环的角色,这一点就和MessageQueuenext很像了,都是无限循环,它的主要作用就是不停地从MessageQueue中查看是否有新消息,如果有新消息就立刻处理,否则就阻塞在那儿

3.1 Looper的构造方法

我们看看它的构造方法

private Looper(boolean quitAllowed) {
	  mQueue = new MessageQueue(quitAllowed);//创建一个MessageQueue
	  mThread = Thread.currentThread();//将当前线程对象保存起来
}

在Looper的构造方法中我们可以发现,Looper创建了一个MessageQueue,然后用于将当前线程对象保存起来

然后在UI线程即主线程中我们是不用手动创建Looper,它会自动帮我们创建好,但是在非主线程它不会自动帮我们创建Looper,没有Looper的话就无法使用Handler

所以我们看看Looper是如何创建的

3.2Looper的创建

new Thread("Thread#2") {
    @Override
    public void run() {
        Looper.prepare();
        Handler handler = new Handler();
        Looper.loop();
    };
}.start();

通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环:

3.2.1prepare()源码

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
		//创建Looper对象,并保存到当前线程的ThreadLocalStorage区域
    sThreadLocal.set(new Looper(quitAllowed));
}

我们会发现Looper的创建是调用的ThreadLocalset方法

所以说明Looper的创建与ThreadLocal有关


Looper其实除了prepare方法以外其实还提供了prepareMainLooper方法,这个方法是给主线程创建Looper使用的,本质上也还是通过prepare实现的

我感觉可以继续说:本质上是通过threadLocal实现的

由于主线程的Looper的特殊性,因为它不用手动实现Looper就可以用,所以Looper提供了一个getMainLooper方法,可以在任何地方获得主线程的Looper

同时Looper是可以退出的,它提供了quitquitSafely两个方法用来退出一个Looper,

区别其实和它们的名字一样,quitSafely更安全一点,

quit是直接退出

quitSafely只是设定一个退出标记,然后把消息队列中已有的消息处理完后才会安全退出

我们来看看它们两个的源码

3.3Looper的退出

public void quit() {//直接退出Looper
    mQueue.quit(false);		//消息移除
}

public void quitSafely() {//设定一个标记,然后把消息队列中已有消息处理完后才安全退出
    mQueue.quit(true);		//安全地消息移除
}

我原本以为代码很长,然后就这?

然后我想点击一下quit继续看里面的源码,然后显示Cannot find declaration to go

Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false

在子线程中,如果你完成所有事后要自己手动退出哦,否则子线程就一直处于等待状态

且如果调用了Looper的quit或者quitSafely的任意一个方法,该线程会立刻终止

3.4Looper的查找

Looper的查找其实就是Looper的loop方法

3.4.1loop的源码

public static void loop() {
        final Looper me = myLooper();//获取TLS存储的Looper对象
        
        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();

允许使用系统道具覆盖阈值。例如adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        // Allow overriding a threshold with a system prop. e.g. adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {//进入loop的主循环方法
            Message msg = queue.next(); // might block
            if (msg == null) {
//没有消息表示消息队列正在退出。
                // No message indicates that the message queue is quitting.
                return;
            }

这必须在局部变量中,以防 UI 事件设置记录器
            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;//默认为null,可通过setMessageLogging()方法来指定输出,用于debug功能
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

确保观察者在处理事务时不会改变。
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);//用于分发Message
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {

一旦我们写了一个慢速传递日志,就压制直到队列耗尽。
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

//恢复调用者信息
确保在调度过程中线程的身份没有被破坏。
            // Make sure that during the course of dispatching the identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();//将Message放入消息池
        }
    }

Looper的loop方法的工作过程很好理解,loop方法是一个死循环,唯一跳出循环的方法事MessageQueue的next方法返回了null

还记得MessageQueue的next是干什么的嘛?它是进行查询的,但是如果有新消息显示出来的话,它会一边返回那个新消息,一边移除它

当Looper的quit方法被调用时,Looper就会调用MeaaageQueuequit或者quitSafely方法来通知MeaaageQueue退出,当消息队列被标为退出状态时,它的next方法就会返回null,跳出loop循环。

next是一个阻塞操作,当没有消息时,next方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。但是这里不同的是,Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。

在Looper的构造方法中会去保存当前的线程,Looper在哪个线程创建,Handler的dispatchMessage方法终究会在那个线程执行。

简单总结一下,就是Looper是通过MessageQueue的next方法获得要处理的消息,然后调用handlerdispatchMessage方法进行处理,

当looper的quit或者quitSafely方法被调用,则代表Handler要被终止,这时候next给looper返回null,Looper的loop方法结束

4.Handler的工作原理

Handler的工作主要包含消息的发送和接收过程。消息的发送可以通过post的一系列方法以及sendMessage的一系列方法来实现,post的一系列方法最终是通过sendMessage的一系列方法来实现的

先看看Handler的有参构造

4.1有参构造

public Handler(@Nullable Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
	//必须先执行Looper.prepare(),才能获取Looper对象,否则为null.
    mLooper = Looper.myLooper();//从当前线程的TLS中获取Looper对象
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;//消息队列,来自Looper对象
    mCallback = callback;//回调方法
    mAsynchronous = async;//设置消息是否为异步处理方式
}

主要就是初始化Looper与进行其他的一些操作

然后调用sendMessage进行传递消息

4.2sendMessage源码

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
    msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

调用sendMessage后会调用sendMessageDelayed然后调用sendMessageDelayed然后调用MessageQueueenqueueMessage,这个方法就是MessageQueue的插入的方法

然后之后MessageQueue的next查询方法会被调用,这样Looper的loop也就会开始执行,并执行Handler的dispatchMessage进行处理

然后我们看看dispatchMessage的源码

4.3dispatchMessage源码

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
//检查Message的callback是否为null,不为null(存在回调方法)就通过handleCallback来处理消息。
        handleCallback(msg);
    } else {
        if (mCallback != null) {
         //当Handler存在Callback成员变量时,回调方法handleMessage();
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
         //Handler自身的回调方法handleMessage()
        handleMessage(msg);
    }
}

我们发现dispatchMessage主要调用的一个是handleCallback方法还有一个是Handler自身的回调方法handleMessage()

我们看看handleCallback是怎么执行的

4.4handleCallback源码

Message的callback是一个Runnable对象,实际上就是Handler的post方法所传递的Runnable参数
。handleCallback的逻辑也是很简单,如下所示

private static void handleCallback(Message message) {
    message.callback.run();
}

其次,msg.callback(就是Handler的post方法所传递的Runnable参数)为null,再检查mCallback是否为null,不为null就调用mCallback的handleMessage方法来处理消息。Callback是个接口,它的定义如下:

  public interface Callback {        
  public boolean handleMessage(Message msg);  
    }

在日常开发中,创建Handler最常见的方式就是派生一个Handler的子类并重写其handleMessage方法来处理具体的消息,而Callback给我们提供了另外一种使用Handler的方式,当我们不想派生子类时,就可以通过Callback来实现。
Handler处理消息的过程可以归纳为一个流程图

在这里插入图片描述

5.主线程消息循环

Android主线程是ActivityThread,主线程的入口方法是main,main方法中会通过Looper.preapareMainLooper()创建主线程的Looper以及MesaageQueue

public static void main(String[] args) {
    // 初始化Environment
    Environment.initForCurrentUser();
    // 初始化Event日志
    EventLogger.setReporter(new EventLoggingReporter());
    // 根据用户Id创建指定目录
    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    // 将当前线程作为主线程,初始化Looper
    Looper.prepareMainLooper();
    // 创建ActivityThread
    ActivityThread thread = new ActivityThread();
    // 如果是System则直接创建Application,如果非System则将ApplicationThread注册到AM中
    thread.attach(false);//建立Binder通道 (创建新线程)
    // 初始化Handler
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    //消息循环运行
    Looper.loop();
    // 异常终止,注意这里的异常通常是执行不到的,上方loop有异常也不会执行到这里。
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

ActivityThread还有一个Handler,ActivityThread.H内部定义了一组消息类型,主要包含了四大组件的启动和停止等过程。

private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = 100;
    public static final int PAUSE_ACTIVITY          = 101;
    public static final int PAUSE_ACTIVITY_FINISHING= 102;
    public static final int STOP_ACTIVITY_SHOW      = 103;
    public static final int STOP_ACTIVITY_HIDE      = 104;
    public static final int SHOW_WINDOW             = 105;
    public static final int HIDE_WINDOW             = 106;
    public static final int RESUME_ACTIVITY         = 107;
    public static final int SEND_RESULT             = 108;
    public static final int DESTROY_ACTIVITY        = 109;
    public static final int BIND_APPLICATION        = 110;
    public static final int EXIT_APPLICATION        = 111;
    public static final int NEW_INTENT              = 112;
    public static final int RECEIVER                = 113;
    public static final int CREATE_SERVICE          = 114;
    public static final int SERVICE_ARGS            = 115;
    public static final int STOP_SERVICE            = 116;
.........
}

ActivityThread通过它的内部类ApplicationThread和ActivityManagerService进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。
型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/592312.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React学习笔记九-高阶函数与函数柯里化

此文章是本人在学习React的时候&#xff0c;写下的学习笔记&#xff0c;在此纪录和分享。此为第九篇&#xff0c;主要介绍高阶函数与函数柯里化。 高阶函数&#xff0c;和函数的柯里化&#xff0c;是学习react的拓展&#xff0c;方便以后优化代码&#xff0c;更好的学习react。…

c语言编程练习题:7-115 小于m的最大的10个素数

#include <stdio.h> int is_prime(int a){for (int i2;i<a;i){if (a%i0){return 0;}}return 1; }int main(){int n;int count10;if (scanf("%d",&n)!EOF && n>50 && n<20000){// 计算150&#xff0c;分配给5&#xff0c;2&#x…

UOS桌面系统开机进入Busybox

UOS桌面系统开机进入Busybox 一、问题现象二、解决方案1、livecd工具修复a、制作livecd工具盘b、从优盘启动c、磁盘修复 2、使用fsck修复a、找出有问题的分区b、修复分区c、重启电脑 一、问题现象 开机进入如下图所示界面 问题原因&#xff1a;roota分区损坏 二、解决方案 …

MySQL — InnoDB引擎、MySQL架构、事务原理、MVCC

文章目录 InnoDB引擎一、逻辑存储架构二、架构2.1 内存结构2.1.1 Buffer Pool 缓冲池2.1.2 Change Buffer 更改缓冲区2.1.3 Log Buffer 日志缓冲区域2.1.4 Adaptive Hash Index 自适应hash索引 2.2 磁盘结构2.2.1 System Tablespace 系统表空间2.2.2 File-Per-Table Tablespace…

搭建一个vuepress静态网站及配置

搭建一个vuepress静态网站及配置 一、搭建一个vuepress网站1、创建并进入一个新目录2、初始化3、安装依赖4、创建文档5、配置启动命令及启动6、展示效果 二、配置及丰富vuepress网站1、增加配置文件2、配置侧边栏目录3、使用部分markdown语法完善页面 一、搭建一个vuepress网站…

【Python实战】Python采集热榜数据

前言 大家好,我们今天来爬取热搜榜,把其文章名称,链接和作者获取下来,我们保存到本地,我们通过测试,发现其实很简单,我们只要简单获取数据就可以。没有加密的东西。 效果如下: 环境使用 python 3.9pycharm模块使用 requests模块介绍 requests requests是一个很…

​​​​Linux Shell 实现一键部署Ruby3

ruby Ruby&#xff0c;一种简单快捷的面向对象&#xff08;面向对象程序设计&#xff09;脚本语言&#xff0c;在20世纪90年代由日本人松本行弘(Yukihiro Matsumoto)开发&#xff0c;遵守GPL协议和Ruby License。它的灵感与特性来自于 Perl、Smalltalk、Eiffel、Ada以及 Lisp …

【上篇】我们邀请了4位专家来探讨消费市场的新增量:W型机会、单客经济、日本市场、DTC......

好久不见了&#xff0c;我是增长黑盒的创始人yolo。最近我们总是发布一些严肃型的行业报告&#xff0c;相信大家的动作都是在第一时间点个收藏&#xff0c;然后....就没有然后了。 所以&#xff0c;今天我们的内容没有复杂的图表和数据&#xff0c;想用比较轻松的对话形式来呈现…

专业的知识图谱应用门槛正在被不断降低

前⾔ 知识图谱&#xff08;knowledge graph&#xff09;⼀度被专家称为“AI皇冠上的明珠”&#xff0c;因为知识图谱技术是⼈⼯智能技术⽅向中的重要⼀环。它不仅可以为其他⼈⼯智能应⽤提供⽀持&#xff0c;如⾃然语⾔处理、推荐系统等&#xff0c;更可以帮助⼈⼯智能系统⾃主…

小程序开发中常见问题解决技巧

众所周知&#xff0c;开发小程序是一件复杂而又繁琐的事情&#xff0c;而且小程序开发也需要一定的技术含量&#xff0c;同时还需要投入大量的时间和精力。所以&#xff0c;在小程序开发过程中&#xff0c;难免会遇到各种各样的问题。为了让大家可以顺利地开发出高质量的小程序…

物联网网关在预付费售电管理系统的构建及应用

摘 要&#xff1a;在社会的不断发展与进步下&#xff0c;信息产业也迎来了自己的繁荣时代&#xff0c;物联网正是在这样的背景之下进入了人们的视野。在互联网的不断发展以及计算机技术的各种进步之下&#xff0c;物联网也迎来了一个又一个的突破&#xff0c;物联网&#xff0c…

契约锁参与第四届【鼎捷智造节】,携手推进制造业数智化转型

如今&#xff0c;制造业正处在智能制造转变的关键期&#xff0c;各类数字化需求不断涌现&#xff0c;为了推动行业数字化转型&#xff0c;鼎捷软件于2021年10月首发启动【鼎捷智造节】&#xff0c;集生态合作圈打造、企业转型赋能、业内优秀产品服务于一体&#xff0c;汇聚业内…

JS的isNAN:判断数字是否合法

定义和用法 isNaN() 函数用于检查其参数是否是非数字值。 如果参数值为 NaN 或字符串、对象、undefined等非数字值则返回 true, 否则返回 false。 语法 isNaN(value) 参数描述value必需。要检测的值。 <!DOCTYPE html> <html lang"en"> <head><…

百万数据导出,居然爆炸了OutOfMemoryError?

一、问题的提出 /*** 大数据导出1.0* /demo/exportExcel4* param response*/ GetMapping("/exportExcel4") public void exportExcel4(HttpServletResponse response) throws IOException {Date start new Date();// 模拟数据List<UserExportVO> users new …

「从零入门推荐系统」19:HM推荐系统代码实战案例

作者 | gongyouliu 编辑 | gongyouliu 我们在上一章中利用Netflix prize数据集讲解了最基础、最简单的一些推荐系统召回、排序算法&#xff0c;大家应该对怎么基于Python实现推荐算法有了一些基本的了解了。接着上一章的思路&#xff0c;本章我们会基于一个更复杂、更近代一点的…

JAVA企业级开发 1.5 初探Spring AOP

一、提出游吟诗人唱赞歌任务 骑士执行任务前和执行任务后&#xff0c;游吟诗人唱赞歌 &#xff08;一&#xff09;采用传统方式实现 修改day04子包的勇敢骑士类 修改day04子包里的救美骑士类 执行测试类 - TestKnight &#xff08;二&#xff09;采用传统方式实现的缺…

Hive部署本地模式

本地模式 使用mysql替换derby进行元数据的存储&#xff0c;hive的相关进程都是在同一台机器上&#xff0c;即本地模式。mysql因为是独立的进程&#xff0c;所以mysql可以和hive在同一机器上&#xff0c;也可以在其他机器上。 说明&#xff1a; 通常使用关系型数据库来进行元数据…

微信公众号、支付接口认证:一步步教您如何实现

1、微信公众号接口认证方案 1.1 认证流程 1&#xff09;官方配置Token验证 Token不在网络中传递 2&#xff09;开发一个Token验证接口 Token及其它参数拼接并字典排序再做sha摘要计算微信定期调用此接口来验证身份正确性通过摘要验证判断请求来源微信&#xff08;Token配置…

TensorFlow进行MNIST数据集手写数字识别,保存模型并且进行外部手写图片测试

首先&#xff0c;你已经配置好Anaconda3的环境&#xff0c;下载了TensorFlow模块&#xff0c;并且会使用jupyter了&#xff0c;那么接下来就是MNIST实验步骤。 数据集官网下载&#xff1a;MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burgeshttp…

apple pencil的替代品买啥比较好?平价电容笔推荐

随着技术的发展&#xff0c;出现了许多种类的电容笔。一款好的电容笔&#xff0c;不但可以极大地提升我们的工作效率&#xff0c;也可以极大地改善我们的学习效果。平替电容笔无论是在技术方面&#xff0c;还是在产品质量方面&#xff0c;都有着非常广泛的应用前景。下面就是我…