1.Google官方建议开发人员使用Handler实现异步刷新UI,我们在平常的工作中也很好地采纳了这个提议:首先在主线程中建立Handler,然后在子线程中利用handler.sendMessage(message)发送消息至主线程,最终消息在handleMessage(Message msg) {}得到相应的处理。
package com.ysl.myandroidbase.activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
public class HandleActivity extends AppCompatActivity {
public static final int MSG_CODE = 0;
public static final int MSG_CODE2 = 1;
public static final String TAG = "HandleActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
doThread();
new MyThread().start();
}
//启动一个耗时线程
public void doThread() {
new Thread() {
public void run() {
try {
Log.e("HandleActivity子线程Name:", Thread.currentThread().getName());
//在子线程运行
Thread.sleep(2000);
//完成后,发送消息
Message message = Message.obtain();
message.what = MSG_CODE;
Bundle bundle = new Bundle();
bundle.putString("data", "hi");
message.setData(bundle);
handler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
public static Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case MSG_CODE:
Log.d(TAG, msg.getData().getString("data")+" 线程:"+Thread.currentThread().getName());
break;
case MSG_CODE2:
Log.d(TAG, msg.getData().getString("data")+" 线程:"+Thread.currentThread().getName());
break;
}
}
};
}
package com.ysl.myandroidbase.activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import static com.ysl.myandroidbase.activity.HandleActivity.MSG_CODE2;
public class MyThread extends Thread {
public static final String TAG = "MyThread";
@Override
public void run() {
super.run();
try {
Log.e("MyThread子线程Name:", Thread.currentThread().getName());
Message message = Message.obtain();
message.what = MSG_CODE2;
Bundle bundle = new Bundle();
bundle.putString("data", "sdfbsdkjgsdklj");
message.setData(bundle);
HandleActivity.handler.sendMessage(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面时一个简单的发送handler消息过程。可以看到都是拿到同一个handler对象来发送消息的。
那如果在子线程中创建handler会怎样呢:
直接报错:Can’t create handler inside thread that has not called Looper.prepare()。
这是由于子线程中的Looper.prepare和Looper.loop都没有被调用,那么在子线程中应该怎么使用呢:
public class MyThread extends Thread {
public static final String TAG = "MyThread";
@Override
public void run() {
super.run();
try {
Log.e("MyThread子线程Name:", Thread.currentThread().getName());
//在子线程运行
Thread.sleep(2000);
Looper.prepare();
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
Log.d(TAG, "收到消息 线程:"+Thread.currentThread().getName());
break;
}
}
};
//完成后,发送消息
handler.sendEmptyMessage(0);
Looper.loop();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在主线程,系统会自动调用Looper.prepareMainLooper()方法创建主线程的Looper和消息队列MessageQueue,所以我们从来没用过Looper.prepare()。
2.handler机制
一个线程对应一个Looper;一个Looper对应一个消息队列;一个线程对应一个消息队列;线程,Looper,消息队列三者一一对应。
即:一个线程里有唯一的一个Looper对象和唯一的一个MessageQueue。
但是:可以有多个handler对象,它们可以发送消息和处理消息。
参考:https://blog.youkuaiyun.com/lfdfhl/article/details/53332936
3.如果一个Activity有多个handler时候,怎样知道handler1发送的消息不会被handler2接收,同理handler2发送的消息不会被handler1接收。
我们来追踪源码,当发送消息时:
可以看到在enqueueMessage方法中,把message的target属性赋值为this,this指的就是当前的handler对象。
接下来再看Looper.loop方法是怎么取出message的:
可以看到里面是一个死循环;取出message。
然后获取到message的target值,并调用了dispatchMessage方法:
上面我们提到target其实就是handler对象,我们再去看handler的dispatchMessage方法:
可以发现就是调用了handleMessage方法,也就是我们重写的handler的方法,在里面可以更新UI等操作。
也就是,每个message中都携带了发送自己的handler对象,当Looper.loop取出message时,就会把它携带的handler对象拿出来,并调用handleMessage方法。这样就达到了:谁发送的消息,最后由谁来处理。冤有头债有主。
4.handler.post(Runnable runnable)和handler.sendMessage(Message message)
这两个不同的方法在本质上是相同的——Handler发送了一条消息。在该示例中handler是在主线程中创建的,所以它当然会在主线程中处理消息;如此以来该Runnable亦会在主线程中执行;所以,在Runnable的run()方法中执行耗时的操作是不可取的容易导致应用程序无响应。
调用view.post(Runnable runnable)会在哪个线程,看看源码:
可以看到又调用了handler.post(Runnable runnable)方法。
调用Activity.runOnUiThread(Runnable runnable)方法会在哪个线程,看看源码:
也同样调用了handler.post(Runnable runnable)方法。
handler.post(Runnable runnable)、view.post(Runnable runnable)、Activity.runOnUiThread(Runnable runnable)的runnable均会在主线程中执行,所以切勿在其run()方法中执行耗时的操作。
5.Asynctask异步任务
public class HandleActivity extends AppCompatActivity {
public static final String TAG = "HandleActivity";
private TextView tv;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv1);
asyncTask.execute();
tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
asyncTask.cancel(false);
}
});
}
private AsyncTask asyncTask = new AsyncTask() {
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.d(TAG, "onPreExecute调用了");
tv.setText("准备加载");
}
@Override
protected Object doInBackground(Object[] objects) {
Log.d(TAG, "doInBackground调用了");
try {
int count = 0;
int length = 1;
while (count < 99) {
count += length;
// 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
publishProgress(count);
// 模拟耗时任务
Thread.sleep(50);
}
}catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onProgressUpdate(Object[] values) {
super.onProgressUpdate(values);
Log.d(TAG, "onProgressUpdate调用了");
tv.setText("loading..." + values[0] + "%");
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
Log.d(TAG, "onPostExecute调用了");
tv.setText("加载完成");
}
@Override
protected void onCancelled() {
super.onCancelled();
Log.d(TAG, "onCancelled调用了");
tv.setText("取消加载");
}
};
}
异步任务利用的是线程池+handler的原理,来实现子线程请求处理数据,然后通过handler发送消息到主线程更新UI。doInBackground()方法不可以更新UI,其他的方法都可以。线程池的使用,减少的资源的消耗,提高了程序的性能。