一些类似定时更新ui的代码,如动画控制。
具体就是新启动(不让ui线程sleep而卡住)一个线程去计时,之后定时来通知ui修改。
本质上都是一个新线程在背后计时。由于使用一个新的非ui线程执行计时,需要在时间到达后去通知ui修改。出于性能考虑,安卓的ui控件不是线程安全的,然后谷歌设计只让ui线程(主线程)能够直接修改ui控件,其它非ui线程不能来达到ui的线程安全。
runOnUiThread从名字上可以看出就是专门供其它线程更改ui使用的。而handler用于不同线程之间的消息传递,可以让线程T1在希望的时刻去通知T2执行某些特定操作。这当然也完全能满足[非ui线程定时通知ui线程更改ui控件状态] 的目的。
如果只是为了完成“定时执行”,那么不开启一个新线程,使用handler.sendEmptyMessageDelayed(1,2000)也可以。
在开始定时任务执行的地方调用:
handler.sendEmptyMessage();
主线程中的handler重写handleMessage:
final int UPDATE_ANIM = 1001;
boolean isAnimRun;
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_ANIM:
if(isAnimRun){
//更新ui的代码,代码中根据情况改变isAnimRun标志。
handler.sendEmptyMessageDelayed(UPDATE_ANIM, 2000);
}
break;
}
}
};
可见,开启新的线程一般是执行一些非ui但耗时的操作,比如网络获取新闻数据。而定时任务这样的事情Handler也可以搞定。
为了让其它线程发消息通知当前线程执行一些任务,当前线程线程可以这样做:
>> MessageQueue用来接收其它线程丢进来的Message,使用先进先出的时间顺序维护所有消息。
>> Looper管理MessageQueue,主要就是不断的(loop)从中取走Message,然后发送给把此Message的target去处理,
target即为发送此Message的handler。
通过以上Looper、MessageQueue、Handler的合作,每一个线程都通过Handler来让其它线程根据需要通知自己执行一些操作。 Handler可以实现不同线程之间的通信,默认主线程已经提供好了Looper和MessageQueue,所以按需要自己写个handler对象,就可以在新开启的其它线程中使用handler来让主线程执行操作,常见的就是更新ui控件。 每个Message对象同时设置了when属性,这样可以做到消息通知的立即执行和延迟执行。
其它线程默认Looper和MessageQueue是没有准备好的,可以在run方法里通过以下几步配置好:
本质上对ui控件的修改,最终都是ui线程执行的。比如我们的线程里需要设置某个TextView的Text属性,那么只能是使用ui线程的handler去发送消息给ui线程去执行。或者使用runOnUiThread这样的简便方法。
一个特殊的例子就是Toast.show,它仅要求当前线程Looper和MessageQueue准备好即可:
void run(){
Looper.prepare();
Toast.makeText(DemoActivity.this,"Wow......",0).show();
Looper.loop();
}
另一个常见“跨线程改变ui”的例子就是网络数据加载,比如加载新闻列表到ListView,启动新的线程是为了避免主线程阻塞而卡ui。相比启动一个线程去达到计时器的目的,使用非ui线程去执行耗时操作等就划算得多了。一般的套路是:
ListAdapter的要点就是:复用屏幕不可见的View对象,并且使用ViewHolder来避免findViewById的开销。
AsyncTask是围绕Thread和Handler构建的一个简单包裹类,可以完成一些后台执行任务后更新UI的操作,api中指出操作不宜过长——a few seconds at most,如果是更为耗时的任务,就需要自己使用java.util.concurrent 包下的诸如Executor, ThreadPoolExecutor 和 FutureTask等类。
AsyncTask拥有一个静态的Handler成员:
private static final InternalHandler sHandler = new InternalHandler();
InternalHandler继承自Handler,它接收处理两个消息:
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
可以知道,更新进度和结束2个回调方法的执行是涉及到ui操作的,因此必须确保sHandler对象是属于ui线程的: The AsyncTask class must be loaded on the UI thread. This is done automatically as of JELLY_BEAN.
AsyncTask的构造方法使用创建好了一个Callable和一个FutureTask来实现线程的创建。
api要求AsyncTask的创建和execute方法的调用必须在ui线程中执行,实际上重点是execute方法,它里面调用了onPreExecute()方法,此方法会涉及ui操作,而且没有使用handler机制,就必须被在ui线程中执行。execute只能执行一次,我们通常会写new MyAsyncTask().execute() 这样的代码,所以为了确保在ui线程中执行execute,我们最好是在ui线程中执行AsyncTask的创建——当然了,在非ui线程中创建AsyncTask实例通常也没多大意义。
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
result.mTask.finish(result.mData[0]);
result.mTask.onProgressUpdate(result.mData);
//在executeOnExecutor中
exec.execute(mFuture);