为了避免ANR,我们常常需要在线程中做耗时操作,然后把结果抛到主线程进行处理。
在Android中为了提供了很多方式,其中一种就是HandlerThread。
先看看官网对它的介绍:
Handy class for starting a new thread that has a looper. The looper can then be
used to create handler classes.一个用于启动具有looper的新线程的类,这个looper可以用于创建Handler。
我们先看一个基本的使用,模拟下载的过程:
代码如下:
public class DownLoadThread extends HandlerThread implements Handler.Callback {
private final String TAG = this.getClass().getSimpleName();
private final String KEY_URL = "url";
public static final int TYPE_START = 1;
public static final int TYPE_FINISHED = 2;
private Handler mWorkHandler;
private Handler mUIHandler;
private List<String> mDownLoadUrlList;
public DownLoadThread(String name) {
super(name);
}
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
mWorkHandler = new Handler(getLooper(),this);
if (mUIHandler == null) {
throw new IllegalArgumentException("Not set UIHandler!");
}
for(String url:mDownLoadUrlList){
Message message = mWorkHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putString(KEY_URL,url);
message.setData(bundle);
mWorkHandler.sendMessage(message);
}
}
public void setDownloadUrls(String... urls) {
mDownLoadUrlList = Arrays.asList(urls);
}
public Handler getUIHandler() {
return mUIHandler;
}
//注入主线程 Handler
public DownLoadThread setUIHandler(final Handler UIHandler) {
mUIHandler = UIHandler;
return this;
}
@Override
public boolean handleMessage(Message msg) {
if(msg == null || msg.getData() == null){
return false;
}
String url = (String)msg.getData().get(KEY_URL);
//开始下载
Message startMessage = mUIHandler.obtainMessage(TYPE_START,
"\n 开始下载 @"+System.currentTimeMillis()+"\n"+url);
mUIHandler.sendMessage(startMessage);
SystemClock.sleep(2000);
Message finishMessage = mUIHandler.obtainMessage(TYPE_FINISHED,
"\n 下载完成 @"+System.currentTimeMillis()+"\n"+url);
mUIHandler.sendMessage(finishMessage);
return true;
}
@Override
public boolean quitSafely() {
mUIHandler = null;
return super.quitSafely();
}
}
在Activity中:
public class MainActivity extends AppCompatActivity implements Handler.Callback {
private TextView mStart;
private TextView mFinish;
private Button mDownLoad;
private DownLoadThread mDownLoadThread;
private Handler mUIHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mStart = findViewById(R.id.tv_start);
mFinish = findViewById(R.id.tv_finish);
mDownLoad = findViewById(R.id.btn_start);
mDownLoadThread = new DownLoadThread("DownLoad");
mUIHandler = new Handler(this);
mDownLoadThread.setUIHandler(mUIHandler);
mDownLoadThread.setDownloadUrls("http://pan.baidu.com/s/1qYc3EDQ",
"http://bbs.005.tv/thread-589833-1-1.html", "http://list.youku.com/show/id_zc51e1d547a5b11e2a19e.html?");
mDownLoad.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDownLoadThread.start();
mDownLoad.setText("正在下载");
mDownLoad.setEnabled(false);
}
});
}
@Override
public boolean handleMessage(Message msg) {
if(msg == null ){
return false;
}
switch (msg.what){
case DownLoadThread.TYPE_START:
mStart.setText(mStart.getText().toString()+
"\n"+msg.obj);
break;
case DownLoadThread.TYPE_FINISHED:
mFinish.setText(mFinish.getText().toString()+
"\n"+msg.obj);
break;
}
return true;
}
}
源码分析:
首先我们先看看HandlerThread的构造方法:
public class HandlerThread extends Thread {
int mPriority;//线程优先级
int mTid = -1;
Looper mLooper;//当前线程持有的Looper
private @Nullable Handler mHandler;
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;
}
我们可以看到HandlerThread继承自Thread,它有两个构造方法,name表示线程的名字,priority表示线程的优先级。
既然是Thread,那么我一定会调用它的start
方法,start
方法就一定会调用run
方法,那么我们就看看run方法:
@Override
public void run() {
mTid = Process.myTid();
//这个方法会为这个线程新建一个Looper,也是唯一一个Looper
Looper.prepare();//初始化Looper
synchronized (this) {
//得到该线程的Looper
mLooper = Looper.myLooper();
//唤醒线程
notifyAll();
}
//设置优先级
Process.setThreadPriority(mPriority);
//这个方法需要我们自己实现
onLooperPrepared();
//让Looper工作起来
Looper.loop();
mTid = -1;
}
protected void onLooperPrepared() {
}
我们可以看到,其实HandlerThread的源码没有多少,哪里需要唤醒呢?
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;
}
没什么好说的,就是等待Looper有值。
其实HandlerThread还是一个Handler的消息机制的操作。
我们使用的时候,通过HandlerThread的Handler将消息投递进来,处理完后,然后通过UI的Handler将消息投递到UI线程。
所以我们还是回顾一下Handler的消息机制:
https://blog.youkuaiyun.com/qq_36391075/article/details/80720519
https://blog.youkuaiyun.com/qq_36391075/article/details/80637473
HandlerThread中好还有两个值得注意的方法:
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
其实这两个方法都调用了Looper的方法,最后都会调用Looper中:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
当我们调用quit
方法的时候,实际上是执行了MessageQueue中的removeAllMessagesLocked()
,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息,还是非延迟消息。
当我们调用quitSafely
方法时,实际上是执行了MessageQueue中的removeAllFutureMessagesLocked();
,该方法只会清空MessageQueue消息池中所有延迟消息,并将消息池中所有非延迟消息派发出去让Handler去处理。
两个方法都有一个共同点:Looper不再接受新的消息了,消息循坏就此结束了。