还记得上篇文章的傻傻子线程吗?
回顾一下。当时,这条子线程经过一段时间的网络请求,终于得到了数据,想直接在 UI 上显示数据,却被告知只有在主线程才能修改 UI 数据。为了将数据传递给主线程,子线程向主线程设置的一个叫 Handler 的消息处理者,发送了自己的请求 Message,并携带上了自己的数据。接着,Message 在 Handler 内部先排列在 MessageQueue 里,再经过主线程为 Handler 准备好的 Looper,通过人家的 loop() 方法,才一个个被送给 Handler 进行处理,在处理方法中,结果才得以显示在 UI 上。
被这套机制吸引的子线程异想天开,想效仿主线程,给自己也设置个 Handler,结果吃了苦头,发现没准备 Looper 的话,Handler 压根设置不了,接着又发现,Looper、Handler 设置好了还不行,这 Looper 还得开启 loop(),消息处理才能真正流转起来。然而人家主线程,应用启动时就在 ActivityThread 的 main() 方法里为它全权安排好了 Looper 事宜,自己就放心 new Handler() 即可,真是线程比线程,气死个线程。
然而很多人觉得,给子线程这么多磕磕绊绊是有道理的。人家主线程设置 Handler 顺顺利利,因为有其他线程要托它处理消息,而且主线程 Handler 处理完的结果可以直接更新在 UI 上,你子线程就算设置了属于自己的 Handler,别的线程找你又能干些什么呢?难不成你也能把处理结果往 UI 上放?
子线程里设置的 handler,能不能在 handleMessage(msg) 方法里访问UI?
当然不行!想都不用想哦,常言道,只有在主线程才可以更改 UI,子线程不可以。人家主线程 handler 就是在主线程创建的,而子线程 handler 则是在子线程里创建的,要是在子线程 handler 的 handleMessage(msg) 方法里访问 UI,一准报错,异常信息都可以提前告诉你:
“Only the original thread that created a view hierarchy can touch its views”
不信我们验证一下:
public class MainActivity extends AppCompatActivity{
private Handler mUiHandler;
private Handler mZiHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_thread);
mUiHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 200:
mTextView.setText((String) msg.obj);
break;
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
// 1
Looper.prepare();
// 2
mZiHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 100:
mTextView.setText((String) msg.obj);
break;
}
}
};
// 3
Looper.loop();
Message message = Message.obtain();
message.what = 100;
message.obj = "请子线程处理者帮我在界面显示这句话";
mZiHandler.sendMessage(message);
}
}).start();
}
代码很明朗,首先,声明了两个 Handler,一个主线程的,一个子线程的,然后,在主线程里初始化 mUiHandler,方式和以前一样,直接 new;接着开启一条子线程,在其内部 run() 方法里,初始化 mZiHandler。嗯,注意看看子线程里建 handler 的操作,代码里的写法确实是标准的三步走,一步没落哦。只不过最后,我们让子线程的 Message 作个死,弃主线程处理者 mUiHandler 于不顾,转投自己的子线程处理者 mZiHandler ,向它表达自己想要更新 UI 的意愿。能成功吗?我们拭目以待:
“Only the original thread that created a view hierarchy can touch its views”
意料之中的胜利~ 毕竟,常识怎么可能出错呢?
不过接下来的操作,列位可瞧好了,我们把子线程内部改成如下几句
new Thread(new Runnable() {
@Override
public void run() {
// 1
Looper looper = Looper.getMainLooper();
// 2
mZiHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 100:
mTextView.setText((String) msg.obj);
break;
}
}
};
Message message = Message.obtain();
message.what = 100;
message.obj = "请子线程处理者帮我在界面显示这句话";
mZiHandler.sendMessage(message);
}
}).start();
和之前的三步走相比,每步都不一样不说,居然还少了一步,肯定没戏,运行一下看看。
……场面一度十分尴尬,子线程的数据,居然在 UI 上显示了!
赶紧看看哪里出了问题,没多少,全在这三步
1. 这次没为子线程新准备 looper,而是直接拿了主线程的 MainLooper
2. 新建 handler 时将获取的 MainLooper 作为参数传进了 Handler 构造函数
3. 并没有 loop() 起来
这时,mUiHandler 最觉无颜以对,原本以为它生于主线程,作用于主线程,所以 UI 操作才不在话下信手拈来,没想到,却被生于子线程的 mZiHandler 砸了场子。
目前可以看出,一个线程的 Handler,处理方法能否作用于 UI,并不取决于 handler 归属在哪,而是要看构建它消息处理机制的 looper 是属于哪个线程的。
之前子线程 handler 的失败,在于新建时,new Handler() 操作会默认取得当前线程的 looper,进而构建消息处理机制,looper属于子线程,MessageQueue 也属于子线程,handler 接收的 message 就在子线程中排列、遍历,handleMessage(msg) 所属的 dispatchMessage(msg) 方法,也还嵌套在 loop() 方法里,整个处理过程自然也就跳不出子线程的范围,当然不能去碰更新 UI 这雷区。
而后来的成功也是一样的道理,looper 采用的是 MainLooper,消息等于说都跑主线程里去了,处理时不仅可以放心大胆地更新 UI,MainLooper 的 loop() 方法也不用操心,毕竟应用启动时就安排好了。
嗯,真是有意义的一次探索啊……
绕了一大圈,说回异想天开的子线程,关于更新 UI 还是不要想太多,不是说你不行,而是与其给你 MainLooper 去构建消息处理机制,不如直接给主线程的 handler 处理好了,反正最终都是在主线程流转处理,没必要你还建个 handler 走形式。
沉默了半天的子线程终于开口了。
子线程:其实……我要建 handler,根本就没打算作更新UI用……
我:?!
子线程:前面你根本没给我说话机会,絮絮叨叨就把我不适合去更新 UI 的事剖析完了,只不过谁说建 handler 就是要去更新UI了?异步消息处理机制在你眼里就这么狭隘吗?
我:不更新 UI,那别的子线程找你的 handler 干嘛?有什么诉求是你能干的而人家主线程干不了的吗?
子线程:没有。
我:……
子线程:但是,有主线程不方便做的,或者说,它需要帮忙的。
我:展开讲讲?
子线程:之前的例子都太简单了,一个子线程,发一个简单的消息,通知主线程简单地显示,根本没优化空间……你想一下这种的情况,假如,咱写的是省高考成绩处理程序……
我:嚯,不得了
子线程:嗯,这程序里,开了100条子线程,它们都忙于查询考生的分数信息,全省20万考生的分数信息,包含语数外、数理化、政史地以及学科权重,加分情况什么的。因为查询检索工作繁重麻烦,所以这些子线程就只专心做查询这一件事,查完一个考生,就把其分数结果以 JSON 的形式发给主线程。
我:它们要主线程干嘛?不还是得 UI 显示~
子线程:是要显示,不过要求显示的,是一个综合分排行榜。
我:综合分排行榜?你的意思是,主线程 handler 收到一个线程的 message,从里面拿出个 JSON,自己解析、计算出一个考生的综合分,满20万个了再去排序,这么绕一大圈最后再显示?
子线程:嗯,其实可以边来边排…先不管这些,对,反正就是这样,你说这种情况是不是很不合理!
我:不合理!主线程本来就要避免耗时的操作,20万个数的排序我看都费劲,还解析计算,这步骤各子线程自己就不能担着点?
子线程:前面说了啊,子线程们查询检索就够忙了,没功夫给你解析计算综合分,只能把所有未处理的原始数据,包装成一个个消息,统统找主线程的 handler 抛给 MainLooper 处理。
我:嗯,是有点糟糕,主线程里是串行处理,这样万一碰个难算的,非卡死不可。要是能有个子线程帮助主线程,先把这些原始数据接收,将解析、计算以及排序等耗时工作都做完,直接给主线程一串排好的分数拿去显示就好了……
子线程:是啊,要是有这么个线程就好了(手指来回指自己状
我:?莫非你……
子线程:没错,我,就是你们要找的“协同处理线程”!
我:真敢给自己加戏…想清楚了吗,这工作你要干的话,首先100条子线程都得找你通讯,你处理完还得把结果反馈给主线程,你就一路人子线程有个啥?
子线程:…你还记得我从头到尾一直嚷嚷着要什么吗?
我:建 handler 啊…哦,对对对,哎呀,妹想到妹想到,handler。嘿,其实我早就看好你,就知道你要 handler 肯定有你的理由!
子线程:……
我:那别愣着啊,写个 Demo 看看效果。
子线程:且慢,我有个条件。
我:?
子线程:实不相瞒,拥有如此有意义的工作的我,已经厌倦了作为一条普通路人子线程,乏味地在 run() 方法里三步走才能设好一个属于自己的 handler 的这种感觉了。
我:大家不都这样吗……
子线程:并不,你看主线程,它就没有这么多规矩,Looper 一早就备好了,还自己 loop() 了起来,handler 直接 new 就行。我是来帮它的,我也要这种待遇!
我:嗯,要得要得……有想法了,你之前给自己起名叫什么来着?
子线程:“协同处理线程”。
我:行,这样,你现在就不再是一条普通的Thread了,你的新类名,就叫“HandlerThread”吧!
子线程:还可以,你源码怎么给的。
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
/**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*
* @see #quitSafely
*/
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
/**
* Quits the handler thread's looper safely.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}
HandlerThread,源码131行,实际有效60行,简明扼要。子线程的要求是被如何满足的呢?往 run() 方法里看,prepare 和 loop 一应俱全,子线程很是满意。
不过要提醒的是,这俩重要操作可都仅存在于 run() 方法里,真想建 handler,当然千万别忘了把线程先 start() 起来!还有关于线程的关闭,也很有作用,读者可以自行阅读下源码注释。
行了,了解完 HandlerThread 的由来,最后就附上一个 HandlerThread 的 Demo 吧。先明确功能,该 HandlerThread,要广泛接受其它子线程的消息,自己处理完毕,再反馈给主线程,实现效果嘛,就还是狭隘的 UI 更新吧。
public class HandlerThreadActivity extends AppCompatActivity implements View.OnClickListener {
private Button mButton;
private TextView mTextView;
private Thread mUiThread;
private Handler mUiHandler;
private HandlerThread mHandlerThread;
private Handler mZiHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_thread);
mButton = (Button) findViewById(R.id.button2);
mButton.setOnClickListener(this);
mTextView = (TextView) findViewById(R.id.textView2);
initUiThreadAndHandler();
initHandlerThreadAndHandler();
}
private void initUiThreadAndHandler() { // 初始化主线程及其 handler
mUiThread = Thread.currentThread();
Looper mainLooper = Looper.getMainLooper();
mUiHandler = new Handler(mainLooper) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 200:
mTextView.setText((String) msg.obj);
break;
}
}
};
}
private void initHandlerThreadAndHandler() { // 初始化 HandlerThread 线程及其 handler
mHandlerThread = new HandlerThread("HandlerThread");
mHandlerThread.start(); // 不能忘记哦!
Looper looper = mHandlerThread.getLooper(); // 不用自己准备了,直接从当前线程拿现成的
mZiHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 100:
String result_past = (String) msg.obj;
String result_now = result_past + " --> HandlerThread成功接收,经过3秒的处理,向主线程反馈结果";
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = Message.obtain();
message.what = 200;
message.obj = result_now;
mUiHandler.sendMessage(message);
break;
}
}
};
}
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Message message = Message.obtain();
message.what = 100;
message.obj = “虽然想找主线程更新UI,但中途需要请HandlerThread先帮我处理一下数据";
mZiHandler.sendMessage(message);
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandlerThread.quit(); // 必要的停止操作
}
}
网友评论