博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android消息机制
阅读量:5068 次
发布时间:2019-06-12

本文共 14919 字,大约阅读时间需要 49 分钟。

概述

  Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃,因为在ViewRootImpl.checkThread对UI操作做了验证,导致必须在主线程中访问UI,但Android在主线程中进行耗时的操作会导致ANR,为了解决子线程无法访问UI的矛盾,提供了消息机制。

void checkThread() {    if (mThread != Thread.currentThread()) {        throw new CalledFromWrongThreadException(                "Only the original thread that created a view hierarchy can touch its views.");    }}

  Android消息机制主要指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MQ即消息队列,存储消息的单元,但并不能处理消息,这时需要Looper,它会无限循环查找是否有新消息,有即处理消息,没有就等待。

Handler的创建方式很简单,只需要new一个实例即可,但是当前线程中没有Looper而创建Handler就会导致报错,下面来看下两个Handler的创建过程,看看有什么不一样。

private Handler handler1;private Handler handler2;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    handler1 = new Handler();    new Thread(new Runnable() {        @Override        public void run() {            handler2 = new Handler();        }    }).start();}

  运行下会发现handler2会报下面的错误“Can't create handler inside thread that has not called Looper.prepare()”

11-14 11:51:56.591 5751-5769/com.fomin.demo E/AndroidRuntime: FATAL EXCEPTION: Thread-642    Process: com.fomin.demo, PID: 5751    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()        at android.os.Handler.
(Handler.java:200)        at android.os.Handler.
(Handler.java:114)        at com.fomin.demo.MainActivity$1.run(MainActivity.java:20)        at java.lang.Thread.run(Thread.java:818)

  为什么handler1没有报错呢?因为Handler的创建时会采用当前线程的Looper来构建内部的消息循环系统,而handler1是在主线程创建的,而主线程已经默认调用Looper.prepareMainLooper()创建Looper,所以handler2创建时需要先调用Looper.prepare()创建Looper。

  接下来看下整个Handler的处理流程并且会具体分析下ThreadLocal、Handler、MessageQueue和Looper,如图:

ThreadLocal工作原理

  ThreadLocal是一个线程内部的的数据存储类,通过它可以在指定的线程中存储数据,存储以后,也只能在指定的线程中获取存储数据,对于其他线程来说则无法获取到数据。在Handler中,需要获取当前的线程的Looper,而Looper作用域就是线程并且不同线程具有不同的Looper,使用ThreadLocal可以轻松实现Looper在线程中的存取。

  先看一个例子,分别在主线程、线程1和线程2设置和访问它的值,如下:

private ThreadLocal
mBooleanThreadLocal = new ThreadLocal<>();Log.d(TAG, "Current Thread: mBooleanThreadLocal is : " + mBooleanThreadLocal.get());new Thread("Thread#1") { @Override public void run() { mBooleanThreadLocal.set(false); Log.d(TAG, "Thread 1: mBooleanThreadLocal is : " + mBooleanThreadLocal.get()); }}.start();new Thread("Thread#2") { @Override public void run() { Log.d(TAG, "Thread 2: mBooleanThreadLocal is : " + mBooleanThreadLocal.get()); }}.start();

  运行程序,日志如下:

11-14 14:18:41.731 7754-7754/com.fomin.demo D/MainActivity: Current Thread: mBooleanThreadLocal is : true11-14 14:18:41.731 7754-7807/com.fomin.demo D/MainActivity: Thread 1: mBooleanThreadLocal is : false11-14 14:18:41.731 7754-7808/com.fomin.demo D/MainActivity: Thread 2: mBooleanThreadLocal is : null

  日志可以看出,不同线程访问同一个ThreadLocal对象,但是他们的值是不一样的。因为ThreadLocal会从各自的线程中取出一个数据,然后数组根据当前ThreadLocal的索引去查找对应的value值。可以先看下ThreadLocal的set方法:

public void set(T value) {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)        map.set(this, value);    else        createMap(t, value);}

  在看下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();}

  ThreadLocal的get和set方法操作的对象都是当前线程ThreadLocalMap,读写操作仅限于各自线程的内部。这也是为什么ThreadLocal在多个线程中互不干扰的操作。

MessageQueue工作原理

  MessageQueue只有两个操作:插入和读取。其内部是一个单链表的数据结构来维护消息列表,链表的节点就是 Message。它提供了 enqueueMessage() 来进行插入新的消息,提供next() 从链表中取出消息,值得注意的是next()会循环地从链表中取出 Message 交给 Handler,但如果链表为空的话会阻塞这个方法,直到有新消息到来。

boolean enqueueMessage(Message msg, long when) {    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) {            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) {            // 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;}

  enqueueMessage主要操作就是单链表的插入操作,在看下next方法

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; // -1 only during first iteration    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);                    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);        }        ...        // 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方法是一个无线信息的方法,如果消息队列没有消息,会一直阻塞在这里。

Looper工作原理

  Looper在Android的消息机制中扮演着消息循环的角色,它不停从MessageQueue查看是否有新消息,有会立即处理,否则会一直阻塞在那里。

Looper会在构造方法中构建一个MessageQueue和当前线程对象。

private Looper(boolean quitAllowed) {    mQueue = new MessageQueue(quitAllowed);    mThread = Thread.currentThread();}

  Looper提供了两个退出方法quit和quitSafely,区别是前个是直接退出,后一个把消息队列中已有的消息处理完毕后安全退出,均是调用MessageQueue中退出quit方法。

public void quit() {    mQueue.quit(false);}public void quitSafely() {    mQueue.quit(true);}
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.        nativeWake(mPtr);    }}

  Looper最重要的方法是loop方法,只有调用了loop后,消息系统才会真正的起作用,具体代码如下

/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */public static void loop() {    final Looper me = myLooper();    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();    // 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 (;;) {        Message msg = queue.next(); // might block        if (msg == null) {            // No message indicates that the message queue is quitting.            return;        }        // This must be in a local variable, in case a UI event sets the logger        final Printer logging = me.mLogging;        if (logging != null) {            logging.println(">>>>> Dispatching to " + msg.target + " " +                    msg.callback + ": " + msg.what);        }        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;        try {            msg.target.dispatchMessage(msg);            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;        } finally {            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();    }}

  loop方法是一个死循环,唯一跳出就是next返回null。如果next返回了新消息,会调用msg.target.dispatchMessage(msg)处理消息(即Handler处理)。

Handler工作原理

  Handler的工作主要包含消息的发送和接收过程。消息发送通过post系列方法和send系列方法来实现,而post最终还是调用sendMessageAtTime方法来实现发送消息。

public boolean sendMessageAtTime(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(MessageQueue queue, Message msg, long uptimeMillis) {    msg.target = this;    if (mAsynchronous) {        msg.setAsynchronous(true);    }    return queue.enqueueMessage(msg, uptimeMillis);}

  可以发现,发送消息最终只是在向消息队列中插入了一条消息,流程MessageQueue——>Looper——>Handler,最终在dispatchMessage处理,由handleMessage消费。

public void dispatchMessage(Message msg) {    if (msg.callback != null) {        handleCallback(msg);    } else {        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        handleMessage(msg);    }}

转载于:https://www.cnblogs.com/fomin/p/9958190.html

你可能感兴趣的文章
4.9 Parser Generators
查看>>
redis集群如何清理前缀相同的key
查看>>
redis7--hash set的操作
查看>>
20.字典
查看>>
Python 集合(Set)、字典(Dictionary)
查看>>
oracle用户锁定
查看>>
(转)盒子概念和DiV布局
查看>>
Android快速实现二维码扫描--Zxing
查看>>
获取元素
查看>>
nginx+lighttpd+memcache+mysql配置与调试
查看>>
proxy写监听方法,实现响应式
查看>>
前端工具----iconfont
查看>>
Azure Site Recovery 通过一键式流程将虚拟机故障转移至 Azure虚拟机
查看>>
Hello China操作系统STM32移植指南(一)
查看>>
cocos2dx CCEditBox
查看>>
VC++2012编程演练数据结构《8》回溯法解决迷宫问题
查看>>
第一阶段冲刺06
查看>>
WIN下修改host文件并立即生效
查看>>
十个免费的 Web 压力测试工具
查看>>
ckeditor 粘贴后去除html标签
查看>>