Android的消息机制主要是Handler的运行机制,而讲Handler的机制,又需要和MessageQueue和Looper结合。MessageQueue中文意思是消息队列,虽说叫队列,但是其内部结构并不是队列组成的,而是采用了单链表的形式来存储消息。MessageQueue只是负责存储消息,并不处理消息(这里指消息的轮训),Looper刚好弥补了这个空缺。我在知道,Handler创建的时候,会默认为我们创建一个Looper对象,那么如何获取当前的Looper呢,这里就使用到了一个TheadLocal的概念,TheadLocal可以轻松的获取当前使用的Looper。
Handler的使用:
Handler 主要有两种用法:( 1 )调度消息和 runnable 对象,并在某一时间点执行。( 2 )依次存放分属不同线程的行为。
线程在默认的时候是没有Looper与之相关联。在线程中,我们可以通过调用prepare 方法来启动一个消息loop,调用loop方法来通知Looper来处理消息,直至结束。Looper提供了很多的静态方法来与线程、消息队列进行交互。一个线程最多允许创建一个Looper。
以下是SDK文档中介绍的在线程中使用handler的一种方法:
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); }
注意这里,官方的例子是和Looper关联了,那么如果我们不关联Looper的话会怎么样呢
mRunnable = new Runnable(){ @Override public void run() { // TODO Auto-generated method stub String s = mHandler.getLooper().getThread().getName(); Log.e("handler", s); mHandler.postDelayed(mRunnable, 1000); }}; Thread t = new Thread(mRunnable,"thread"); t.start();
运行结果:
ERROR/handler(1630): main,可以发现我们创建了一个名为“thread”的线程,病通过handler发送消息,我们创建的handler自动和UI线程的Looper关联上了。
那么,可不可以在主线程中定义handler,在代码中动态改变handler使之与其他线程的looper相关联呢?是可以的。
mRunnable = new Runnable(){ @Override public void run() { // TODO Auto-generated method stub Looper.prepare(); mHandler = new Handler(Looper.myLooper()); String s = mHandler.getLooper().getThread().getName(); Log.e("handler", s); mHandler = new Handler(Looper.getMainLooper()); s = mHandler.getLooper().getThread().getName(); Log.e("handler", s); }}; Thread t = new Thread(mRunnable,"subthread"); t.start();
运行结果:
ERROR/handler(4049):subthread
ERROR/handler(4049): main
不过我们Android为我们提供了一个包含Looper的Thead类,HandlerThread
HandlerThread自带了一个Lopper,
handlerThread=new HandlerThread("handlerThead"); handlerThread.start(); handler=new Handler(handlerThread.getLooper()); handler.post(new Runnable() { @Override public void run() { String s=handler.getLooper().getThread().getName(); Log.d("test",s); } });
注:android认为在非线程中操作UI界面是不安全的,因此禁止在其它线程中修改UI界面。解决的方法有两种,一是通过在主线程中定义的handler更新界面,二是直接调用被修改的view的postInvalidate方法刷新单个view。
其实上面的描述是不准确的,实际上在activity第一次执行的时候,是可以在子线程直接更新主线程数据的,条件是不能执行Thread.sleep(),
原因是什么呢?
1.当刚启动Activity即onCreate里面此时onResume方法还没有执行的时候可以,因为在线程中更新UI时会调用ViewParent.invalidateChild()方法检查当前的Thread是否是UIThread,若为UIThread则可以更新(ViewParent是一个接口类,其实现是ViewRootImpl;invalidateChild()方法调用checkThread()方法来检查线程是否为主线程)。ViewRootImp是在onResume方法中初始化的,所以只要在ViewRootImpl创建之前更新UI(在onCreate方法中创建线程并执行,此时还没有初始化ViewRootImp),就可以逃避掉checkThread()的检查进而更新UI。
2.-->刚启动的时候,立即在非UI线程更新->不报错(onResume还没有执行)
--->休眠2s钟以后,更新——————>报错
最后贴上一张图
分析完了Android消息机制的流程,那么我们接下来分别理解一些重要的概念。主要从以下几个方面增强理解,Handler,MessageQueue,Looper和Threadloacal.