Handler与多线程
简述
- 子线程不能更新UI
当我们在子线程中更新UI会导致Android报异常
AndroidRuntimeException :“Only the original thread that created a view hierarchy can touch its views”
因为如果支持多线程修改View的话,由此产生的线程同步和线程安全问题将是非常繁琐的,所以Android直接就定死了,View的操作必须在UI线程,从而简化了系统设计。但是,我们会发现现在Activity.onCreate()直接使用子线程去更新UI并不会报异常,查源码可知,该异常来自ViewRootImpl.checkThread()方法中,而ViewRootImpl在Activity.onResume()中被创建,即在Activity.onCreate()实际上还是可以子线程更新UI的,详情可查看
- 主线程不能网络请求
由于网络请求过长时会阻塞主线程,导致ANR(应用程序无响应),所以在Android4.0 以后不允许在主线程进行网络连接,否则会出现 android.os.NetworkOnMainThreadException。因此,必须另起一个线程进行网络连接方面的操作。
子线程–>主线程
- 使用Handler发消息
protected Handler mHandler = new ActivityHandler(this);
static class ActivityHandler extends Handler {
WeakReference<MainActivity> mActivityReference;
ActivityHandler(MainActivity activity) {
mActivityReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
final MainActivity activity = mActivityReference.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
protected void handleMessage(Message msg) {
switch (msg.what) {
case 0x001:
text.setText("Task Done!!");
break;
}
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button); //启动线程
text = (TextView) findViewById(R.id.text);//耗时任务完成时在该TextView上显示文本
mRunnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);//模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendEmptyMessage(0x001);//耗时任务完成后发送消息,通知去更新UI
}
};
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Thread thread = new Thread(mRunnable);
thread.start();
}
});
}
Handler机制可以看张鸿洋大神的博客– Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
- 使用Handler.post
把mHandler.sendEmptyMessage(0x001);发消息方法更换成
mHandler.post(new Runnable() {
@Override
public void run() {
text.setText("Task Done!!");
}
});
查看Handler.post源码,发现其实还是调用的发送消息机制
//Handler.java
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
最后在Handler.handleCallback()中执行Runnable的run方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
- runOnUiThread
runOnUiThread这是一个在Activity中的方法,在Fragment中没有,使用也很简单,在子线程中任务完成后调用该方法,把更新UI的任务放入Runnable.run中即可
runOnUiThread(new Runnable() {
@Override
public void run() {
text.setText("Task Done!!");
}
});
最后,再来看看源代码,发现这里调用的还是Handler的方法,调用了post方法,而post又是使用了sendMessageDelayed,所以说白了,还是使用的Handler消息机制
//Activity.java
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
- View.post
使用方法跟前面差不多,只是替换成了
text.post(new Runnable() {
@Override
public void run() {
text.setText("Task Done!!");
}
});
同样的,来看看源码,
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
AttachInfo是在View第一次attach到Window时,ViewRoot传给自己的子View的。这个AttachInfo之后,会顺着布局体系一直传递到最底层的View。而mHandler在ViewRootImpl中被创建,也就是主线程的Handler.所以这个同样的也是使用的Handler消息机制.
private class MyAsyncTask extends AsyncTask<Void, Integer, Void> {
@Override
protected void onPreExecute() {
//执行前的准备,这里是主线程
}
@Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(5000);//模拟耗时任务,这里是子线程
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void result) {
// 进行数据加载完成后的UI操作,主线程
text.setText("Task Done!!");
}
}
源码解析参考张鸿洋大神的Android AsyncTask 源码解析,最后的结论就是,还是使用了Handler消息机制
- 总结
以上五种方法使用的都是Handler的消息机制
主线程–>子线程
- Thread、Runnable
这个是最传统的方法了,相信每个学过Java基础的人都知道。无非就是继承Thread类覆写run()然后通过thread.start()或实现Runnable接口复写run()然后new Thread(Runnable).start(),在上面的例子中就是通过这种最普通的方法去开新线程的,不过在实际开发中,这种开新线程的方法是很不被推荐的,理由如下:1)当你有多个耗时任务时就会开多个新线程,开启新线程的以及调度多个线程的开销是非常大的,这往往会带来严重的性能问题,例如,你有100个耗时任务,那就开100个线程。2)如果在线程中执行循环任务,只能通过一个Flag来控制它的停止,如while(!iscancel){//耗时任务}。