Android消息机制 - Handler

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

这篇博客是讲大名鼎鼎的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;
    }
});

运行终于成功!!!

这里写图片描述

二、进一步分析
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就会显得结构有些复杂。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值