异步消息处理机制

四大核心对象

1. Message   消息(数据载体)

2. MessageQueue   消息队列(存储Handler发来的消Message、Runnable)

3. Looper   轮询器,循环不断地从MessageQueue取消息,交给相应的Handler处理

4. Handler   发送消息和处理消息


Looper

有两个核心方法 prepare() 和 loop()

  • prepare()

prepare()内部调用prepare(true),用来给当前线程创建looper

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) { //一个线程只能有一个looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));//若当前线程还没looper,就给它设一个
    }

  • loop()

调用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;

        ...
        for (;;) {
            Message msg = queue.next(); // 取下一个消息,没有消息时阻塞
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ...
            //Handler发消息的方法中,都会将msg.target赋值为this,即target就是发这个msg的handler对象
            msg.target.dispatchMessage(msg); //让相应handler来处理消息
            ...
        }
    }

 

从这两个方法可以看出Looper的主要作用:

1.与当前线程绑定,保证一个线程只有一个Looper实例,而Looper内部也只有一个MessageQueue。

2.不断取消息,交给发消息的的那个handler去处理

Handler

Handler有多个构造方法,取其中的一个来看源码

    public Handler(Callback callback, boolean async) {
        ...
        mLooper = Looper.myLooper();//获取当前线程的looper对象
        if (mLooper == null) { //当前线程没有looper,抛异常
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
如源码所示,要使用Handler,就先得给当前线程创建一个Looper。

问:在Activity中,明明直接new一个Handler就可以正常使用了,哪需要创建looper?

答:我们经常说的UI thread其实就是ActivityThread,也就是主线程。我们都知道主线程可以使用Handler进行异步通信,因为主线程中已经创建了Looper,而这个Looper就是在ActivityThread的main方法中创建的。如果其他线程需要使用Handler通信,就要自己去创建Looper。

(根据Looper.prepare()源码,如果多此一举地调用prepare()给主线程创建Looper实例,会抛出异常!)

ActivityThread.main()源码:

    public static final void main(String[] args) {
        SamplingProfilerIntegration.start();
       ……
        Looper.prepareMainLooper();//在这里创建主线程Looper
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        ……
        Looper.loop();//开始循环
        ……
        thread.detach();
        ……
    }


实例:子线程处理消息

private Hanlder mHandler;
//Semaphore用来控制多个线程之间的通信
private Semaphore mHandleSemaphorer = new Semaphore(0);//0个信号量

private void init(){
  new Thread(){
     public void run(){
       Looper.prepare(); //给子线程设置一个Looper

       mHandler = new Handler(){ //此时,Handler构造方法会自动与当前子线程的looper绑定 
       @override 
       public void handleMessage(Message msg){  //子线程处理消息、任务  } };

       mHanlderSemaphore.release();//释放1个信号量(执行到这里,说明上面的mHandler初始化完毕)

       Looper.loop(); //开始无限循环取消息 } }.start();
}

//当多个并发线程访问同一个Handler对象,需要保证只有1个线程执行该方法
//否则,n个线程被acquire阻塞,而mHanlder初始化完毕后只释放了1个信号量,会导致n-1个线程永远被阻塞
private synchronized void addTask(){
   if(mHanlder == null){ //mHandler还没初始化,需要等待初始化完成
     try {
        //有信号就放行;没有则阻塞线程(acquire相当于wait;release相当于notify)
        mHanlderSemaphore.acquire();//等上面的mHandler初始化完成,释放了信号,这里就会放行
     } catch(Exception e){}
   }
   mHanlder.sendEmptyMessage(11);//上面放行了,mHanlder才能开始发消息
}

 

在Android中实现线程池,可以通过这个实例中的子线程,处理连续的耗时操作(如:加载图片)。

值得注意的是线程安全问题:多个线程同时操作mHandler,有可能会在mHandler还没完成初始化时就被其他线程调用。

用Java提供的并发类Semaphore可以解决多线程通信的问题。

给Handler绑定一个Looper,Handler就可以不断接收从Looper中分发过来的消息,然后在绑定这个Looper的线程中处理消息。

要给Handler绑定一个looper有2个常用方式:

    1.直接在子线程的Looper.prepare()后创建Handler实例(如上面实例代码)

    2.直接调用Handler的带参构造方法: Handler(Looper looper)

问题

    在子线程里创建了Looper,然后在主线程new Handler(looper)进行初始化时,会导致空指针异常。

原因

    多线程共享同一个Looper变量时,子线程的Looper还没初始化完毕,主线程就已经调用new Handler(Looper)。

    可以用HandlerThread代替Thread来解决这个问题。示例代码如下:   

private void test(){     <pre name="code" class="java">     //1.创建HandlerThread(将这个HandlerThread命名为"handler thread")
 HandlerThread thread = new HandlerThread("handler thread");
 thread.start();//这个HandlerThread线程的run方法中会自动创建1个Looper
     //2.把HandlerThread的Looper传给handler(不会再产生空指针异常了)
 Hanlder handler = new Handler(thread.getLooper()){
  @override
 public void handleMessage(Message msg){
System.out.println("current thread is -- " + Thread.currentThread());
//打印结果为"current thread is -- Thread[handler thread,5,main]"
 }
 };
}
     HandlerThread的run方法会自动帮我们创建Looper,不需要我们动手; 

    getLooper()内部会先判断Looper是否为null,如果为null就调用wait(),等它初始化完成后调用notifyAll()来唤醒。

    前面我们用Looper.prepare()和loop()来为子线程创建Looper,而HandlerThread类已经帮我们实现了带Looper的子线程,使用它会更加简便。

    HandlerThread常常用来处理耗时操作。

根据Looper.loop()源码,Looper取出消息后,会调用msg.target.dispatchMessage(msg),即调用目标handler自身的dispatchMessage(msg)。

我们来看看dispatchMessage源码

    public void dispatchMessage(Message msg) {  
        if (msg.callback != null) { //callback是传给Handler的Runnable任务 
            handleCallback(msg); //执行Runnable的run方法 
        } else {  
            if (mCallback != null) { //mCallback是Hanlder(Callback)中传来的Callback对象 
                //执行handleMessage(),返回true就return,拦截掉后面的handler.handleMessage()
                if (mCallback.handleMessage(msg)) { 
                    return;//这里return,那么下面的handleMessage方法就不会执行  
                }  
            }  
            handleMessage(msg);//调用hanlder.handleMessage方法  
        }  
    } 
 

看来,在Hanlder中,Runnable任务的会更优先地被处理,其次是Callback.handleMessage();

Handler.hanldeMessage方法不是必须被执行的,所以没被写成抽象方法来让我们强制实现它;而且可以通过Callback.handleMessage方法返回true把它拦截掉。

Handler的三种用法

1. sendMessage();

     sendMessageDelayed(); //延时发消息

2. post(Runnable); //执行任务(Handler绑定哪个线程的Looper,就在那个线程中执行任务)

     postDelayed(Runnable, long); //延时执行任务

3. removeCallbacks(Runnable); //移除指定的任务

     removeMessage(int); //移除指定的消息

---- 补充 ----

Android中更新UI的几种方式

1. 用主线程Handler的方法: sendMessage、post

2. Activity的runOnUiThread(runnable) 

    内部会判断当前线程是否为UI线程,是就直接执行;不是就用Activity自带的主线程handler.post(runnable)来执行

3. view.post(runnable)    同样是在主线程中执行

    view.postDelay(runnable)

:以上几种方式都是在UI线程中更新UI,那么在非UI线程中可不可以更新UI呢?

:在Activity.onCreate()中创建的子线程可以更新UI(更新UI前不能进行太多繁琐的操作,也不能sleep一段时间)

原因如下

  • 首先,非UI线程更新UI为什么会报错?

    所有更新UI的操作最终都会调用 View.invalidate(),该方法调用了 ViewParent.invalidateChild(),而这个ViewParent实际上是1个 ViewRootImpl 类的引用,

所以,这个invalidateChild()其实是调用 ViewRootImpl.invalidateChild(),它会调用 checkThread()来检查更新UI的线程是否为UI线程。

这个方法链可以理解为:

View.invalidate() → invalidate(true) → ViewRootImpl.invalidateChild() → ViewRootImpl.invalidateChildInParent() → ViewRootImpl.checkThread()

通常我们在非UI线程中更新UI时,就会报错(如下图所示),可以看出报错的正是 ViewRootImpl.checkThread()。

   

ViewRootImpl 是一个隐藏类,只能翻Framework层源码才能找到。其中 checkThread()源码如下:

   void checkThread() {
       if (mThread != Thread.currentThread()) { //判断UI线程和当前线程是否为同一个线程
           throw new CalledFromWrongThreadException(
                   "Only the original thread that created a view hierarchy can touch its views.");
       }
   }
这方法源码很简单,就是判断该线程是不是UI线程,不是就抛出异常。

  • 为什么可以在onCreate()中用非UI线程更新UI?

一句话解释:

系统执行 onCreate() 时,上面所说的 ViewRootImpl 还没被创建呢,所以它的 checkThread() 就不会被调用;

那么,ViewRootImpl 什么时候创建完成呢?

onResume() 被执行后 ViewRootImpl 才会被生成,也就是说,不仅能在 onCreate() 中用子线程更新UI,onStart()和onResume()也都可以(不过之后跳到其他Activity再跳回来时同样可能会报错,因为此时的ViewRootImpl已经创建好了)


最后提醒一句,虽然有时可以用非UI线程更新UI,但并不推荐这么做!!!


参考文章: 为什么我们可以在非UI线程中更新UI


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值