美文网首页
Android的Handler消息机制

Android的Handler消息机制

作者: 唐牛才是车神 | 来源:发表于2019-07-07 18:58 被阅读0次

Handler

一套Android的消息传递机制/异步通信机制。在多线程的应用场景中,将工作线程中需要更新UI的操作信息传递到UI主线程,从而实现工作线程对UI的更新处理,最终实现异步消息的处理。

  • 消息

Message,是线程间通讯的数据单元(即Hnadler接受&处理的消息对象)。存储需要操作的通信信息。

  • 消息队列

MessageQueue,一种数据结构(存储特点:先进先出)。存储Handler发送过来的消息(Message)。

  • 处理者

Handler,是主线程与子线程的通信媒介,也是线程消息的主要处理者。添加消息(Message)到消息队列、处理循环器(Looper)分派过来的消息(Message)。

  • 循环器

Looper,是消息队列(MessageQueue)与处理者(Handler)的通信媒介。消息循环即(循环取出消息队列的消息)、消息分发(将取出的消息发送给对应的处理者)

  • 系统为什么不允许在子线程中去访问UI

这是因为Android的UI线程是不安全的,多线程并发访问可能导致UI控件处于不可预期的状态。

  • 为什么不加锁呢

缺点有两个:首先加上锁机制会让UI控件的访问逻辑变得复杂,其次锁机制会降低UI的访问效率,锁会堵塞默写线程的执行。

Handler创建时会采用当前线程的Looper来构建内部消息循环系统,如果当前线程没有Looper就会报错。在当前线程创建一个Looper,或者在有Looper的线程中创建Handler。

Handler通过post方法将一个Runnable投递到Handler内部中的Looper中去处理,也可以通过Handler的send方法发送一个消息,这个消息同样会在Looper中处理。(其实post方法最终也是由send方法完成的)。
当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法将消息放入消息队列中,然后Looper发现有新消息到来时就会处理这个消息,最终消息中的Runnable或者Handler的handMessage方法就会被调用。
注意Looper是运行在创建Handler的线程中的,这样一来Handler中的业务逻辑就被切换到创建Handler的线程中去了。

handler.png

ThreadLocal的工作原理

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储之后,值可以在指定的线程中获取到存储数据,其他线程中获取不到。

Looper、ActivityThread以及AMS中都用到了ThreadLocal。
Looper另一个使用场景是复杂逻辑下的对象传递,比如监听器的传递。它可以让监听器在线程内作为一个全局对象的存。

  • 不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找对应的value值。
  • ThreadLocal的set方法
public void set(T value){
  Thread currentThread = Thread.currentThread();
  Values value = values(currentThread);
  if(values == null){
    values = initalizeValues(currentThread);
  }
  values.put(this,value);
}

首先通过values方法去获取当前线程中ThreadLocal中的数据。在Thread类的内部有一个成员专门用于存储ThreadLocal中的数据:ThreadValues.localValues。在localValues内部有一个table数组:private Object[] table,ThreadLocal的值就是存在在这个table数组中的。

ThreadLocal的值在table数组的存储位置总是ThreadLocal的reference字段所标识的对象的下一个位置,比如ThreadLocal的reference对象在table数据中的索引是index,那么ThreadLocal的值在table数组中的索引就是index+1,table[index+1]=value。

  • ThreadLocal的set方法
public T get(){
  Thread currentThread = Thread.currentThread();
  Values values = values(currentThread);
  if(values !=null){
    Object[] table = values.table;
    int index = hash&values.mask;
    if(this.reference == table[index]){
      return (T)table[index+1];
    }
  }else{
    values = initalizeValues(currentThread);
  }
return (T)values.getAfterMiss(this);
}

从ThreadLocal的set/get方法中可以看出,他们操作的对象都是当前线程中localValues的table数组,因此不同线程访问同一个ThreadLocal的set和get方法,他们对ThreadLocal所做的读写/写入操作仅限于各自线程内部。这就是为什么ThreadLocal可以在多个线程中互补干扰的存储和获取数据。

MessageQueue的工作原理

MessageQueuq虽然叫做消息队列,但实际是通过一个单链表结构来维护的消息队列,单链表在插入和删除上有优势

  • enqueueMessage(插入操作)

就是单链表的插入操作

  • next(读取消息,并从消息队列中删除)

next方法是一个无线循环的方法,如果消息队列中没有消息,next方法就会一直堵塞在这里,当有新消息是,next方法会返回这条消息并将从单链表中移除。

Looper的工作原理

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

  • 构造方法
private Looper(boolean quitAllowed){
  //创建消息队列
  mQueue = new MessageQueue(quitAllowed);
  //保存当前线程对象
  mThread = Thread.currentThread();
}
  • Looper的创建
new Thread("Thread#2"){
  @Override
  public void run(){
      Looper.prepare();
      Handler handler = new Handler();
      Looper.loop();//开启消息循环
  };
}.start();

Looper除了prepare方法外,还提供prepareMainLooper方法,这个方法主要是给主线程也就数ActivityThread创建Looper使用,其本质也是通过prepare实现的。
由于主线程Looper比较特殊,所以Looper提供一个getMainLooper方法,通过它可以在任何地方获取主线程的Looper

  • Looper的退出

quit直接退出Looper
quitSafely设定一个退出标记,然后把消息队列中已有消息处理完后安全退出。

Looper退出后通过Handler发送消息就是失败,这个时候Handler的send方法返回就是false。

在子线程中,如果手动创建了Looper,那么在所有的事情处理完成后应立即调用quit方法来终止消息循环,否则在这个子线程就会一直处于等待的状态。

  • 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;
Binder.clearCallingIdentity();
final long ident=Binder.clearCallingIdentity();

for(;;){
    Message msg=queue.next();
    if(msg==null){
        //没有消息,退出死循环
        return;
    }

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

    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.getCalss().getName()+" "
        +msg.callback+" what="+msg.what);
    }
    msg.recycleUnchecked();
}
}

Hnadler的工作原理

Hnadler的工作主要包含消息的发送和接收过程。通过post或send来实现。post最终也是send实现的。

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

可以发现Handler发送消息过程仅仅是向消息队列中插入一条消息,MessageQueue的next方法就会返回一条消息给Looper,Looper收到消息后开始处理,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法被调用,这是Handler进入消息处理阶段。

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

主线程的消息循环

Android的主线程就是ActivityThread,主线程的入口方法是main,在main方法中系统会通过Looper.prepareMainLooper()方法来创建主线程的Looper和MessageQueue,并通过Looper.loop()来开启主线程的消息循环。

  • main方法
public static void main(String[] args){
  ...
  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();

  throws new RuntimeException("Main thread loop unexpectedly exited");
}

主线程消息循环开启后,ActivityThread还需要一个Handler来和消息队列进行交互,这个Handler就是ActivityThread.H,它内部定义了一组消息模型,主要包含四大组件的启动和停止过程。
ActivityThread通过applicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中执行,即切换到主线程中去执行,这个过程就是主线程中的消息模型。

相关文章

网友评论

      本文标题:Android的Handler消息机制

      本文链接:https://www.haomeiwen.com/subject/jnobhctx.html