在 Android 開發中,需要把耗時操作放到子線程中,避免阻塞主線程,從而導致程序 ANR。實現這類異步任務的方式有:
- Thread + Handler
- AsyncTask
- HandlerThread
- IntentService
本文來講解分析下 HandlerThread,在真正開始前,我們先了解下 Handler 的使用方式。
Handler 機制
子線程中創建 Handler
public class MainActivity extends AppCompatActivity {
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
handler = new Handler();
}
}).start();
}
}
程序崩潰了,意思是說:在線程中沒有調用 Looper.prepare() 方法,是不能創建 Handler 對象的。
原因分析:
我點進 Handler 構造函數源碼看下,會發現在創建 Handler 對象時,系統會檢驗當前線程中是否存在 Looper 對象,如果沒有,則會拋出異常。
Handler 源碼:
/**
Handler.java
*/
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper(); // 獲取當前線程的 Looper 對象
if (mLooper == null) {
throw new RuntimeException( // 沒有Looper 對象,則需要調用 Looper.prepare()創建 Looper
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
那麼爲什麼在主線程中創建 Handler 對象,沒有調用 Looper.prepare() 方法,程序沒有崩潰呢?這是因爲主線程在創建時,系統幫我們創建好了 Looper 對象。看下程序入口 ActivityThread.main() 源碼:
/**
* ActivityThread.java
*/
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper(); // 獲取主線程中的Looper對象,該方法最終會調用Looper構造函數
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
結論:在線程中創建 Handler 對象,需要存在該線程的 Looper 對象。子線程需要我們手動創建 Looper 對象,即在該線程中調用 Looper.prepare()創建,並用 Looper.loop() 方法啓動輪詢;主線程中系統幫我們創建好了 Looper 對象。
常規用法:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); // 創建 Looper
workerHandler = new Handler();
Looper.loop(); // 開啓輪詢
}
}).start();
Handler 不同線程之間通信
(1)子線程向子線程發送消息
子線程和子線程發消息,就是在一個子線程中創建 Handler ,這樣回調 handleMessage()就自然會在子線程中,然後,在另一個子線程中使用該 handler 進行發送消息。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "xinxing.tao";
private Handler workerHandler;
private WorkerThread workerThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
workerThread = new WorkerThread("WorkerThread");
workerThread.start();
// 在另一個子線程中發送消息給 WorkerThread 子線程
new Thread(new Runnable() {
@Override
public void run() {
// sleep(1000) 確保 WorkerThread 已經初始化好了 workerHandler
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 6; i++) {
Log.d(TAG, "sendMessage :name = " + Thread.currentThread().getName() + ", i = " + i);
Message message = new Message();
message.what = i;
message.obj = "WorkerThread Message";
workerHandler.sendMessage(message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 停止 WorkThread,因爲 WorkThread run()通過 Looper.loop() 死循環輪詢,所以需要拿到他的 Looper 進行停止。
workerHandler.getLooper().quit();
}
}).start();
}
class WorkerThread extends Thread {
public WorkerThread(String name) {
super(name);
}
@Override
public void run() {
// 子線程創建時,沒有創建 Looper 對象,必須手動調用 prepare() 方法創建 Looper
Looper.prepare();
workerHandler = new Handler() {
// 子線程中創建 Handler, 回調自然在子線程中
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d(TAG, "handleMessage: name = " + Thread.currentThread().getName() + ", msg.what= " + msg.what + " ,msg.obj = " + msg.obj);
}
};
Log.d(TAG, "run: end ??????");
Looper.loop(); // 開啓Looper輪詢,直到 Looper 停止退出輪詢,才能執行後面的代碼
Log.d(TAG, "run: end");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
(2)主線程向子線程發消息(Thread方式)
在子線程中創建 Handler(需要創建Looper對象),然後使用該 Handler 在主線程中發送消息。
public class MainThread2WorkerThreadActivity extends AppCompatActivity {
private static final String TAG = "debug";
private Handler workerHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_thread2_worker_thread);
// 子線程
WorkerThread workerThread = new WorkerThread("WorkerThread");
workerThread.start();
}
public void click(View view) {
// send message
Message message = new Message();
message.what = 100;
message.obj = "message from main to worker thread";
workerHandler.sendMessage(message);
Log.d(TAG, "sendMessage : " + Thread.currentThread().getName());
}
class WorkerThread extends Thread {
public WorkerThread(String name) {
super(name);
}
@Override
public void run() {
super.run();
// 子線程中使用 Handler,需要手動創建 Looper 對象
Looper.prepare();
workerHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d(TAG, "handleMessage: name = " + Thread.currentThread().getName() + ", msg.what = " + msg.what + ",msg.obj = " + msg.obj);
}
};
// 開啓輪詢
Looper.loop();
}
}
}
輸出結果:
D/debug: sendMessage : main
D/debug: handleMessage: name = WorkerThread, msg.what = 100,msg.obj = message from main to worker thread
可以看到在子線程中使用 Handler 寫法還是挺麻煩的,好在 Android 已經爲我們提供更方便的使用方式,即 HandlerThread。
HandlerThread 解析
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.
用於方便開啓子線程的類,內部包含 Looper , 可用來創建 Handler 類,注意,必須調用 start() 方法。
HandlerThread 常規用法
創建 HandlerThread 對象
HandlerThread handlerThread = new HandlerThread("handler-thread"); // 名字隨意取
啓動 HandlerThread
handlerThread.start();
創建子線程 Handler,用於處理異步任務,並與 handlerThread 進行關聯。
Handler workerHandler = new Handler(handlerThread.getLooper()){ public void handleMessage() { // 執行耗時操作,運行於子線程中 } }
Demo1 : 以下代碼使用 HandlerThread 方式完成主線程向子線程發送消息:
public class MainThread2WorkerThreadActivity extends AppCompatActivity {
private static final String TAG = "debug";
private HandlerThread handlerThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_worker_thread2_main_thread);
// 創建,開啓子線程 handlerThread
handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();
// 創建 workerHandler對象,傳入的參數是 handlerThread 中的 Looper,即在 handlerThread這個子線程中創建 handler
Handler workerHandler = new Handler(handlerThread.getLooper()) {
// 這個方法運行在 handlerThread 子線程中,可以執行耗時操作
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d(TAG, "handleMessage: name = " + Thread.currentThread().getName() + ", msg.what = " + msg.what); // handleMessage: name = HandlerThread, msg.what = 11
}
};
// 主線程中使用 workerHandler 發送消息
Log.d(TAG, "sendMessage: name = " + Thread.currentThread().getName()); // sendMessage: name = main
Message message = new Message();
message.what = 11;
message.obj = "message";
workerHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 退出 handlerThread looper 循環
handlerThread.quit();
}
}
輸出結果:
D/debug: sendMessage: name = main
D/debug: handleMessage: name = HandlerThread, msg.what = 11
Demo2 : 自定義 HandlerThread , 繼承自 HandlerThread 類,構造 workerHandler 傳入的 looper 是 HandlerThread 中的 looper 對象,所以 workerHandler 中的 handleMessage() 回調運行於子線程中。
// DownloadHandlerThread.java
public class DownloadHandlerThread extends HandlerThread implements Handler.Callback {
public static final int WHAT_START_DOWNLOAD = 1;
public static final int WHAT_FINISH_DOWNLOAD = 2;
private List<String> urlList = new ArrayList<>();
private Handler uiHandler;
private Handler workerHandler;
public DownloadHandlerThread() {
super("DownloadHandlerThread");
}
/**
* 構造 workerHandler 時,傳入的 Looper 是 HandlerThread 中的 looper,
* 所以 workerHandler 中的 handleMessage() 是運行於子線程中。
*/
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
//
workerHandler = new Handler(getLooper(), this); // 使用HandlerThread子線程中的 looper
// 將接收到的任務消息挨個添加到消息隊列中
for (String url : urlList) {
Message msg = workerHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putString("url", url);
msg.setData(bundle);
workerHandler.sendMessage(msg);
}
}
/**
* 子線程中處理任務,完成後發送消息給主線程
*
* @param msg
* @return
*/
@Override
public boolean handleMessage(Message msg) {
if (msg == null || msg.getData() == null) {
return false;
}
String url = msg.getData().getString("url");
// 下載之前通知主線程顯示下載提示
Message startMsg = uiHandler.obtainMessage();
startMsg.what = WHAT_START_DOWNLOAD;
startMsg.obj = url + " = start download ";
// 發送消息給主線程
uiHandler.sendMessage(startMsg);
// 開始下載
SystemClock.sleep(2000); // 模擬下載過程
// 每個 URL 下載完成通知主線程更新ui
Message finishMsg = uiHandler.obtainMessage();
finishMsg.what = WHAT_FINISH_DOWNLOAD;
finishMsg.obj = url + " = finish download";
uiHandler.sendMessage(finishMsg);
return true;
}
public void setUrlList(List<String> list) {
this.urlList = list;
}
/**
* 注入主線程 handler
*
* @param handler
*/
public void setUiHandler(Handler handler) {
this.uiHandler = handler;
}
@Override
public boolean quitSafely() {
uiHandler = null;
return super.quitSafely();
}
}
// MainActivity.java
public class MainActivity extends AppCompatActivity implements Handler.Callback {
private String[] urls = {"http://www.baidu.com", "http://www.sina.com", "http://google.com"};
private DownloadHandlerThread handlerThread;
private TextView startTextView;
private TextView finishTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startTextView = findViewById(R.id.tv_tip);
finishTextView = findViewById(R.id.tv_finish);
Handler uiHandler = new Handler(this);
handlerThread = new DownloadHandlerThread();
handlerThread.setUrlList(Arrays.asList(urls));
handlerThread.setUiHandler(uiHandler);
}
public void click(View view) {
handlerThread.start();
}
/**
* 主線程回調
*
* @param msg
* @return
*/
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case DownloadHandlerThread.WHAT_START_DOWNLOAD:
startTextView.setText(startTextView.getText().toString() + "\n" + msg.obj);
break;
case DownloadHandlerThread.WHAT_FINISH_DOWNLOAD:
finishTextView.setText(finishTextView.getText().toString() + "\n" + msg.obj);
break;
}
return true;
}
}
HandlerThread 源碼分析
/**
* HandlerThread.java
*/
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
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;
}
/**
* 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 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;
}
/**
* @return a shared {@link Handler} associated with this thread
* @hide
*/
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
/**
* 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 繼承自 Thread ,所以本質上 HandlerThread 還是 Thread,其 run() 方法執行於子線程中。當我們使用 HandlerThread 時,通過調用它的 start() 開啓異步任務時,即會調用 HandlerThread 的 run() 方法,在 run() 方法,調用 Looper.prepare() 創建該線程的 Looper 對象,然後調用了 Looper.loop() 開啓消息的輪詢,loop() 方法是一個無限循環,不斷的從消息隊列 MessageQueue 中拿到消息進行處理。由於消息隊列是由鏈表結構構成的,所以異步任務時串行執行的。