Handler异步处理机制
2015-08-27 18:16
211 查看
Handler使用场景:在非UI线程中对UI线程中的View进行操作,必须使用Handler,否则会报错。
错误使用示例:
Only the original thread that created a view hierarchy can touch its views.
正确使用示例:
我们从代码上分析下原理,为什么在handler中对主UI view操作可以。既然UI VIEW只能在UI线程中进行操作,那么handler是否将对View的操作传递到了UI线程中,这样,从原理上是说的通的。下面分析下:
如上代码所示,Handler将CHANGE_TEXT_BACKGROUD_COLOR组装成一个Message,放到了一个Looper的队列(MessageQueue mQueue)中,这个时候我们应该想到,那么这个Looper对象应该是每个对象都有一个,并且每个Looper对象拥有一个队列,用与存储Handler发送的对象。
所以,现在我们来看是否每个线程含有独立的一个Looper对象???
-----------------------------------------------------------------------------------------------------
继续根据代码往下分析,我们看下mQueue对象是怎么实例化的。
Handler的对象的示例,最后都会调用到的构造函数如下:
上述代码实在UI线程执行的,示例得到一个Looper对象
由以上代码可知,在Looper调用myLooper()方法之前,肯定在某个地方调用了Looper.prepare()(在哪个地方调用呢,我们把这个问题放到后面)。
我们来看下Looper.prepare()
代码很好理解,就是在sThreadLocal中放入了一个Looper,在需要的时候通过Looper.myLooper()方法取出来。
另外注意sThreadLocal是静态的,说明说有的Looper对象共享这个对象。
在ThreadLocal的set方法中,代码获取当前线程(本例中,当前线程是UI主线程,因为我们的Handler是在UI主线程中示例化的)Values对象,并将创建的Looper存入了Values的一个Map数据结构中。
至此,我们知道,通过当前线程我们可以获取当前线程的Values对象,然后通过Values对象,可以获取每一个线程独有的Looper对象。每个线程拥有独立的Looper的对象,
也就相当于每一个线程拥有独立的一个队里MessageQueue(每一个Looper有一个MessageQueue对象)。
综上所示:
Handler在当前线程中初始化时,会为当前线程创建一个独一无二的Looper,这个Looper中有一个队里MessageQueue,用与存储Handler发送的消息,并在合适的十几,按照先后顺序执行这些消息对应的操作。
------------------------------------------------------------------------------------------------------------------
现在我们来讨论,到底在什么时候才会执行这行消息对应的操作的,因为并没有看到调用这些消息的代码。
我们先来开心在自定义的一个线程中,Handler的操作。
如上所示,在我们初始化Handler之前,必须调用Looper.prepare()方法,创建Looper对象;并在初始化Handler之后调用Looper.loop()方。
loop()的方法如下,代码非常明了,就是便利MessageQueue对象中的消息,然后通过每个消息得到对应的Handler对象,调用dispatchMessage方法,处理消息对应的操作。
在dispatchMessage是调用了你自己实现的handleMessage方法
我们在Activity的主UI线程中,并没有显式的调用Looper.prepare()和Looper.loop()方法,那系统在何时何地调用呢。
有一个类叫ActivityThread.java,它的main方法是这样的
每一个activity都是通过它进行启动的,因此,在启动Activity之前,已经调用了prepareMainLooper方法初始化Looper对象。
-----------------------------------------------------------------------------------------------------------------------------------------------
错误使用示例:
public class MainActivity extends Activity { TextView text; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text = (TextView)findViewById(R.id.text); new Thread(runnable).start(); } Handler handler = new Handler(new Callback() { @Override public boolean handleMessage(Message msg) { return false; } }); boolean isToRun = true; int count = 0; Runnable runnable = new Runnable() { @Override public void run() { while(isToRun){ try { Thread.sleep(1000); } catch (InterruptedException e) { } count++; if(count%2==0){ //在非UI线程中直接访问view text.setBackgroundColor(Color.RED); }else{ text.setBackgroundColor(Color.BLUE); } } } }; protected void onStart() { super.onStart(); isToRun = true; }; @Override protected void onStop() { super.onStop(); isToRun = false; }; }运行如上代码时,报如下异常:
Only the original thread that created a view hierarchy can touch its views.
正确使用示例:
public class MainActivity extends Activity { TextView text; Thread mThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text = (TextView) findViewById(R.id.text); mThread = new Thread(runnable); mThread.start(); } private static final int CHANGE_TEXT_BACKGROUD_COLOR = 1; Handler handler = new Handler(new Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.what) { case CHANGE_TEXT_BACKGROUD_COLOR: if (count % 2 == 0) { // 在非UI线程中直接访问view text.setBackgroundColor(Color.RED); } else { text.setBackgroundColor(Color.BLUE); } break; default: break; } return false; } }); boolean isToRun = true; int count = 0; Runnable runnable = new Runnable() { @Override public void run() { while (isToRun) { Log.e("Handler", "thread is running"); try { Thread.sleep(1000); } catch (InterruptedException e) { } count++; // 每隔一秒改变text的背景颜色 handler.sendEmptyMessage(CHANGE_TEXT_BACKGROUD_COLOR); } } }; protected void onStart() { super.onStart(); isToRun = true; }; }
我们从代码上分析下原理,为什么在handler中对主UI view操作可以。既然UI VIEW只能在UI线程中进行操作,那么handler是否将对View的操作传递到了UI线程中,这样,从原理上是说的通的。下面分析下:
1.handler.sendEmptyMessage(CHANGE_TEXT_BACKGROUD_COLOR);跟踪sendEmpthMessage调用
public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); } public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { .... return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; .... return enqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; .... return queue.enqueueMessage(msg, uptimeMillis); }
如上代码所示,Handler将CHANGE_TEXT_BACKGROUD_COLOR组装成一个Message,放到了一个Looper的队列(MessageQueue mQueue)中,这个时候我们应该想到,那么这个Looper对象应该是每个对象都有一个,并且每个Looper对象拥有一个队列,用与存储Handler发送的对象。
所以,现在我们来看是否每个线程含有独立的一个Looper对象???
-----------------------------------------------------------------------------------------------------
继续根据代码往下分析,我们看下mQueue对象是怎么实例化的。
Handler的对象的示例,最后都会调用到的构造函数如下:
public Handler(Callback callback, boolean async) { 。。。 mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
上述代码实在UI线程执行的,示例得到一个Looper对象
if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); }
由以上代码可知,在Looper调用myLooper()方法之前,肯定在某个地方调用了Looper.prepare()(在哪个地方调用呢,我们把这个问题放到后面)。
我们来看下Looper.prepare()
Looper.java static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
代码很好理解,就是在sThreadLocal中放入了一个Looper,在需要的时候通过Looper.myLooper()方法取出来。
另外注意sThreadLocal是静态的,说明说有的Looper对象共享这个对象。
在ThreadLocal的set方法中,代码获取当前线程(本例中,当前线程是UI主线程,因为我们的Handler是在UI主线程中示例化的)Values对象,并将创建的Looper存入了Values的一个Map数据结构中。
至此,我们知道,通过当前线程我们可以获取当前线程的Values对象,然后通过Values对象,可以获取每一个线程独有的Looper对象。每个线程拥有独立的Looper的对象,
也就相当于每一个线程拥有独立的一个队里MessageQueue(每一个Looper有一个MessageQueue对象)。
ThreadLocal.java public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread);//得到当前线程的Values,每一个线程的Values都是唯一的 if (values == null) { values = initializeValues(currentThread); } values.put(this, value); } public T get() { // Optimized for the fast path. 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 = initializeValues(currentThread); } return (T) values.getAfterMiss(this); }
综上所示:
Handler在当前线程中初始化时,会为当前线程创建一个独一无二的Looper,这个Looper中有一个队里MessageQueue,用与存储Handler发送的消息,并在合适的十几,按照先后顺序执行这些消息对应的操作。
------------------------------------------------------------------------------------------------------------------
现在我们来讨论,到底在什么时候才会执行这行消息对应的操作的,因为并没有看到调用这些消息的代码。
我们先来开心在自定义的一个线程中,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(); } } public Handler(Callback callback, boolean async) { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
如上所示,在我们初始化Handler之前,必须调用Looper.prepare()方法,创建Looper对象;并在初始化Handler之后调用Looper.loop()方。
loop()的方法如下,代码非常明了,就是便利MessageQueue对象中的消息,然后通过每个消息得到对应的Handler对象,调用dispatchMessage方法,处理消息对应的操作。
在dispatchMessage是调用了你自己实现的handleMessage方法
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(); // might block msg.target.dispatchMessage(msg); msg.recycleUnchecked(); } }
我们在Activity的主UI线程中,并没有显式的调用Looper.prepare()和Looper.loop()方法,那系统在何时何地调用呢。
有一个类叫ActivityThread.java,它的main方法是这样的
public static void main(String[] args) { ...
Looper.prepareMainLooper(); ...
Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } }
每一个activity都是通过它进行启动的,因此,在启动Activity之前,已经调用了prepareMainLooper方法初始化Looper对象。
-----------------------------------------------------------------------------------------------------------------------------------------------
相关文章推荐
- 探讨php中error_log函数输出内容的原子性问题
- 华为OJ(四)
- 数据初始化
- 根据指定的commit查找对应的log
- Emacs命令整理
- Android 4.0新增Space及GridLayout初谈
- [LeetCode] Dungeon Game
- ios 摇一摇
- THINKPHP 中密码在编辑中不填写即不修改,ignore和md5冲突的解决办法
- 黑色星期五
- thinkphp模板中判断变量是否为空
- c/c++之调戏QQ,让QQ无法登陆
- Xamarin.Forms 之 样式Styles
- Mybatis的批量更新
- xxx 表 is marked as crashed and last (automatic?) repair 解决办法
- JS 跨域问题常见的五种解决方式
- powerdesigner简单使用
- 开始Spring Cloud Config
- windows找不到文件computer management.lnk解决方法
- POJ 3122 Pie(二分)