欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android消息机制 - Handler

程序员文章站 2022-03-09 21:05:51
...

不知不觉,发现自己已经写了几十篇文章,但都没有发布出来,从今天起会慢慢放出来互相学习,如有写得不对之处,欢迎讨论。

这篇博客是讲大名鼎鼎的Handler,Handler是Android消息机制的上层接口,Handler的作用是将一个任务切换到某个指定的线程中去执行,所以经常用于切换到主线程中更新UI。
Handler的运行需要底层的MessageQueue和Looper的支撑,MessageQueue是采用单链表的数据结构来存储消息列表,以队列的形式对外提供插入和删除工作。由于MessageQueue只是一个消息的的存储单元,它不能直接处理消息。
而Looper会以无限循环的形式去查找新消息,如果有的话就处理消息,否则一直阻塞。
Looper有个很重要的概念,就是ThreadLocal,可以在不同线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。
如何获取到当前线程的Looper呢? 没错答案就是ThreadLoca。
线程默认没有Looper,如果需要使用Handler就必须为线程创建Looper,主线程为ActivityThread,ActivityThread被创建时就会初始化Looper,这也是主线程默认可以使用Handler的原因。

一、问答
上面已经对Handler做了一个大概的总结了,表示看不懂,疑问还是很多,比如:
为什么有Handler ?-> 因为Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常。

系统为什么不允许在子线程中访问UI呢? -> 因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,加上锁机制的话,缺点:首先加上锁机制会让UI访问的逻辑变得复杂,其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行,因此采用单线程模型来处理UI操作是最简单和高效的。

Handler的工作原理 -> 创建Handler的线程必须有Looper来构建内部的消息循环系统,否则就会报错,Handler通过send或post发送一个消息,接着调用MessageQueue的enqueueMessage方法将这个消息中的Runnable或者Handler的handleMessage方法就会被调用。有一点要注意,Looper是运行在创建Handler所在的线程中的,这样一来Hanlder中的业务逻辑就被切换到创建Handler所在的线程中去执行了。

举个栗子吧!
在布局中添加一个按钮,Activity的代码如下:

public class MainActivity extends AppCompatActivity {
    private Button mBtnStart;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBtnStart = (Button)findViewById(R.id.btn_start);
        mBtnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                consumingTime();
            }
        });
    }

    private void consumingTime(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mBtnStart.setText("执行结束,更新UI");
            }
        }).start();
    }
}

运行logcat如下,出现异常了???? 提示只有创建视图(主线程)的线程才能更新UI。

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

把以上Activity的consumingTime方法改一下

private void consumingTime(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mHandler.sendEmptyMessage(0);
        }
    }).start();
}

Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message message) {
        switch (message.what){
            case 0:
                mBtnStart.setText("执行结束,更新UI");
                break;
        }
        return false;
    }
});

运行终于成功!!!

Android消息机制 - Handler

二、进一步分析
2.1 Handler
源码的Handler调用了Handler(Callback callback, boolean async)

public Handler(Callback callback) {
        this(callback, false);
   }

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(); //重点1
        if (mLooper == null) { //重点2
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue; //重点3
   mCallback = callback;
        mAsynchronous = async;
}

获取本线程的Looper,如果没有Looper,会抛出RuntimeException。
那么上面的例子是如何运行的?答案就是:
Android的主线程就是ActivityThread,主线程的入口方法为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。
mLooper.mQueue就是得到MessageQueue(消息队列)。
我们都知道,Handler的工作主要包含消息的发送和接收过程,发送通过post 、send方法。
源码如下

public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

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中插入一条消息
    }

过程就是:Handler发送消息的过程仅仅是向消息队列中插入了一条消息(queue.enqueueMessage(msg, uptimeMillis)),MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后就会开始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息的阶段。
一不小心说出整个过程。

2.2 MessageQueue

MessageQuboolean More ...enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
       hrow 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("MessageQueue", e.getMessage(), e);
          msg.recycle();
          return false;
               msg.markInUse();
               msg.when = when;
               Message p = mMessages;
               boolean needWake;
               if (p == null || when ==   || when < p.when) {
                   // New head, wake up the event queue if blocked.
                   msg.next = p;
                   mMessages = msg;
                   needWake = mBlocked;
               } else {
                   needWake = mBlocked && p.target == null && msg.isAsynchronous();
                   Message prev;
                   for (;;) {    //重点1
                   prev = p;
                   p = p.next;
                   if (p == null || when < p.when) {
                       break;
                    }
                   if (needWake && p.isAsynchronous()) {
                       needWake = false;
                    }
                }
               msg.next = p; // 重点2
                prev.next = msg;
           }
            // We can assume mPtr !=   because mQuitting is false.
          if (needWake) {
                nativeWake(mPtr);
           }
        }
       return true;
    }

MessageQueue主要包含两个操作:插入和读取(伴随着删除操作),对应方法分别为:enqueueMessage和next,尽管MessageQueue叫消息队列,但是它的内部实现并不是用队列,实际上它是通过一个单链表的数据结构来维护消息列表。
enqueueMessage为单链表的插入操作
next方法是一个无限循环(重点2),有消息返回这条消息并从单链表中删除,没有消息,一直阻塞。

2.3 Looper

public final class More ...Looper {
       private static final String TAG = "Looper";

       static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); //重点1
       private static Looper sMainLooper;  // guarded by Looper.class

       final MessageQueue mQueue;
       final Thread mThread;

       private Printer mLogging;
       public static void More ...prepare() {  //重点2
           prepare(true);
       }

       private static void More ...prepare(boolean quitAllowed) {
           if (sThreadLocal.get() != null) {
               throw new RuntimeException("Only one Looper may be created per thread");
           }
           sThreadLocal.set(new Looper(quitAllowed));
       }

       public static void More ...prepareMainLooper() { //重点3 主线程
           prepare(false);
           synchronized (Looper.class) {
               if (sMainLooper != null) {
                   throw new IllegalStateException("The main Looper has already been prepared.");
               }
               sMainLooper = myLooper();
           }
       }
       public static Looper More ...getMainLooper() {
           synchronized (Looper.class) {
               return sMainLooper;
           }
       }

       public static void More ...loop() { //重点4
           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 (;;) {  //重点4
               Message msg = queue.next(); // might block
               if (msg == null) {
                   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);  //重点5

               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  x"
                           + Long.toHexString(ident) + " to  x"
                           + Long.toHexString(newIdent) + " while dispatching to "
                           + msg.target.getClass().getName() + " "
                           + msg.callback + " what=" + msg.what);
               }

               msg.recycleUnchecked();
           }
       }

重点1:ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
重点2:创建Looper对象并存放在ThreadLocal中。
重点3:主线程创建Looper对象。
重点4:消息息循环,Looper会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。
回顾一下:主线程通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环。除了prepare方法,还提供了prepareMainLooper方法为ActivityThread创建Looper使用。
重点5:loop方法是一个死循环,
唯一跳出循环的方式是MessageQueue的next方法返回了null。当MessageQueue的next方法返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),这里的msg.target 是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了,但是这里不同的是,Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。
Looper退出方法:quit和quitSafely。区别 quit会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。

再说两句
Handler使用异步来实现的,涉及到Handler、Looper、MessageQueue、Message等概念,需要更深入了解,才能更好发挥它的用处,封装出性能更好的框架。当使用只有一个后台异步处理时,相较于AsyncTask就会显得结构有些复杂。