Android消息机制Handler的原理详解

本文详细解析了Android中为何不能直接在子线程更新UI的原因,并深入探讨了Handler的工作原理,包括Looper、MessageQueue的角色及其如何实现跨线程通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Handler的作用

   大家都知道,Handler是用来做子线程和UI线程之间的通信的,在子线程进行耗时的操作,然后通过Handler将消息传递到主线程中进行UI操作。大家有没有一个疑问,为什么不能在子线程更新UI?这是因为ViewRootImpl对UI操作做了验证

  

void checkThread(){
              if(mThread!=Thread.currentThread()){
                 throw new CalledFromWrongThreadException(
                     "Only the original thread that created a view hierarchy can
                     touch its views."
                 )
              }
          }
另外,题外话:大家有没有遇到过在onCreate方法中new一个Thread去进行UI操作是不会报错的,那是因为什么原因呢?那是因为在onCreate()方法中进行UI的修改,ViewRootImpl的checkThread()还没有来得及工作的原因

再另外,系统为什么限制不能在子线程中更新UI呢?因为Android的UI 控件不是线程安全的,多线程并发访问会导致线程安全问题,

那么为什么不加锁呢?加锁会让UI访问的逻辑变得复杂,并且阻塞某些线程的执行降低对UI访问的效率



Handler的原理

Android的消息机制主要指Handler的消息机制,Handler的运行主要依靠底层的Looper和Handler支撑

    MessageQueue:消息队列,并不是真正的队列,而是以单链表的数据结构来存储Handler发送过来的Message;

    Looper:Looper以无限循环的方式去查看消息队列中是否有新消息,线程默认是没有Looper的,如果需要使用就必须为线程创建Looper,主线程创建时会初始化Looper,这就是主线程可以使用Handler的的原因

    Handler:通过Handler从子线程发送消息到主线程的MessageQueue中,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,另外Handler通过ThreadLocal可以轻松的获取每一个线程的Looper,从而获取消息队列中的message

 

源码 

 new Thread(new Runnable() {  
            @Override  
            public void run() {  
                handler2 = new Handler();  
            }  
        }).start();  

如果现在运行一下程序,你会发现,在子线程中创建的Handler是会导致程序崩溃的,提示的错误信息为 Can't create handler inside thread that has not called Looper.prepare() 。说是不能在没有调用Looper.prepare() 的线程中创建Handler,那我们尝试在子线程中先调用一下Looper.prepare()呢,代码如下所示:

<span style="font-size:14px;">  new Thread(new Runnable() {  
        @Override  
        public void run() {  
            Looper.prepare();  
            handler2 = new Handler();  
        }  
    }).start(); </span>

这样的方法不会奔溃,我们来看一下Handler的源码,为什么创建Handler需要Looper.prepare()?

 public Handler(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());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

我们看到通过Looper,myLooper()获取到一个Looper对象,如果Looper对象为null就会抛出Can't create handler inside thread that has not called Looper.prepare()异常,我们来看一下Looper.myLooper()中的源码

   public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

就是从ThreadLocal对象中get一个Looper,有get就有set,我们看一下在哪里set了?

 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");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

正是这个Looper.prepare()方法,这个方法会判断ThreadLocal中是否存在Looper,存在则报错,一个线程只能存在一个Looper;

这样就找到了为什么创建Handler对象需要Looper.prepare();我们可能会有疑问主线程创建Handler也没有也调用Looper.prep[are()?

这是因为在ActivityThread的main方法中,系统以及帮我们自动调用了Looper.prepare()

    public static void main(String[] args) {  
        SamplingProfilerIntegration.start();  
        CloseGuard.setEnabled(false);  
        Environment.initForCurrentUser();  
        EventLogger.setReporter(new EventLoggingReporter());  
        Process.setArgV0("<pre-initialized>");  
        Looper.prepareMainLooper();  
        ActivityThread thread = new ActivityThread();  
        thread.attach(false);  
        if (sMainThreadHandler == null) {  
            sMainThreadHandler = thread.getHandler();  
        }  
        AsyncTask.init();  
        if (false) {  
            Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));  
        }  
        Looper.loop();  
        throw new RuntimeException("Main thread loop unexpectedly exited");  
    }  

    public static final void prepareMainLooper() {  
        prepare();  
        setMainLooper(myLooper());  
        if (Process.supportsProcesses()) {  
            myLooper().mQueue.mQuitAllowed = false;  
        }  
    }  

这样基本就将Handler的创建过程完全搞明白了,总结一下就是在主线程中可以直接创建Handler对象,而在子线程中需要先调用Looper.prepare()才能创建Handler对象。


创建完Looper后,我们来看一下如何发生消息,就是通过handler的sendMessage方法发送一个Message

    new Thread(new Runnable() {  
        @Override  
        public void run() {  
            Message message = new Message();  
            message.arg1 = 1;  
            Bundle bundle = new Bundle();  
            bundle.putString("data", "data");  
            message.setData(bundle);  
            handler.sendMessage(message);  
        }  
    }).start();  

我们来看一下sendMessage(message)的源码,基本上大多数的发送信息的方法最终都会走到下面的方法

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);
    }

sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时

间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为

0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。MessageQueue中所有的Message都会以队列的形式

存放,提供入队和出对的方法,这个类是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。


那么enqueueMessage()方法毫无疑问就是入队的方法了,我们来看下这个方法的源码:

   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;
    }

所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数,入队操作我们就已经看

明白了,那出队操作是在哪里进行的呢?这个就需要看一看Looper.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();

        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
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(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();
        }
    }

我们可以看到,这个方法中存在一个死循环,会不同的调用MessageQueue.next()方法,其实这个next()方法就是消息队列的出队方法,它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。

从消息队列中获取消息后会调用msg.target.dispatchMessage(msg),msg的target其实就是Handler,我们来看下源码

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

如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作

为参数传递过去。这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧!



那么我们还是要来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。整个异步消息处理流程的示意图如下图所示:




另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:

1. Handler的post()方法

2. View的post()方法

3. Activity的runOnUiThread()方法

我们先来看下Handler中的post()方法,代码如下所示:

    public final boolean post(Runnable r)  
    {  
       return  sendMessageDelayed(getPostMessage(r), 0);  
    }  

原来这里还是调用了sendMessageDelayed()方法去发送一条消息啊,并且还使用了getPostMessage()方法将Runnable对象转换成了一条消息,我们来看下这个方法的源码:

    private final Message getPostMessage(Runnable r) {  
        Message m = Message.obtain();  
        m.callback = r;  
        return m;  
    }  

这个callback字段看起来有些眼熟啊,在Handler的dispatchMessage()方法中原来有做一个检查,如果Message的callback等于null才会去调用handleMessage()方法,否则就调用handleCallback()方法。那我们快来看下handleCallback()方法中的代码吧:

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

竟然就是直接调用了一开始传入的Runnable对象的run()方法。因此在子线程中通过Handler的post()方法进行UI操作就可以这么写:

    public class MainActivity extends Activity {  
      
        private Handler handler;  
      
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            handler = new Handler();  
            new Thread(new Runnable() {  
                @Override  
                public void run() {  
                    handler.post(new Runnable() {  
                        @Override  
                        public void run() {  
                            // 在这里进行UI操作  
                        }  
                    });  
                }  
            }).start();  
        }  
    }  

虽然写法上相差很多,但是原理是完全一样的,我们在Runnable对象的run()方法里更新UI,效果完全等同于在handleMessage()方法中更新UI。

然后再来看一下View中的post()方法,代码如下所示:

    public boolean post(Runnable action) {  
        Handler handler;  
        if (mAttachInfo != null) {  
            handler = mAttachInfo.mHandler;  
        } else {  
            ViewRoot.getRunQueue().post(action);  
            return true;  
        }  
        return handler.post(action);  
    }  

原来就是调用了Handler中的post()方法。

最后再来看一下Activity中的runOnUiThread()方法,代码如下所示:

    public final void runOnUiThread(Runnable action) {  
        if (Thread.currentThread() != mUiThread) {  
            mHandler.post(action);  
        } else {  
            action.run();  
        }  
    }   

如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法。





 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值