在Android開發過程中,耗時操作是不允許寫在主線程(UI線程)中的,以免由於等待時間過長而發生ANR。所以耗時操作需要創建子線程來完成,然而往往這些操作都需要與主線程進行通訊交互(例如更新主線程的UI),但android規定除了UI線程外,其他線程都不可以對UI控制項進行訪問或操控,所以我們需要通 ...
在Android開發過程中,耗時操作是不允許寫在主線程(UI線程)中的,以免由於等待時間過長而發生ANR。所以耗時操作需要創建子線程來完成,然而往往這些操作都需要與主線程進行通訊交互(例如更新主線程的UI),但android規定除了UI線程外,其他線程都不可以對UI控制項進行訪問或操控,所以我們需要通過一些方法來實現這些功能。
1. Handler:
handler是android中專門用來線上程之間傳遞信息類的工具。
API參考:https://developer.android.google.cn/reference/android/os/Handler
1、在B線程中調用Looper.prepare和Looper.loop。(主線程不需要)
2、編寫Handler類,重寫其中的handleMessage方法。
3、創建Handler類的實例,並綁定looper
4、調用handler的sentMessage方法發送消息。
- 子線程更新主線程(UI)
因為主線程自帶Looper機制,所有我們不用創建Looper:
Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 1: //do something,refresh UI; break; default: break; } } };
然後開啟一個子線程,在子線程里直接使用Handler發送消息:
new Thread() { public void run() { Message message = new Message();
message.what = 1; message.obj = "子線程發送的消息Hi~Hi"; mHandler .sendMessage(message);
}; }.start();
- 子線程之間交互
public class ThreadActivity extends AppCompatActivity {
private final String TAG = "ThreadActivity";
// 子線程Handler
private Handler mSubHandler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread1);
new MyThread().start();
createThread();
}
/**
* 創建子線程,用於發送消息
*/
private void createThread() {
new Thread() {
@Override
public void run() {
int count = 0;
while (count < 10) {
Message msg = new Message();
msg.obj = "子線程計時器:" + count;
msg.what = 1;
// 使用子線程Handler發送消息
mSubHandler.sendMessage(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
}.start();
}
/**
* 用於接收子線程發送過來的消息
*/
class MyThread extends Thread {
@Override
public void run() {
Looper.prepare();
mSubHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.i(TAG, (String) msg.obj);
break;
}
}
};
Looper.loop();
}
}
}
2. HandlerThread:
HandlerThread是一個包含Looper的Thread,我們可以直接使用這個 Looper 創建 Handler。
API參考:https://developer.android.google.cn/reference/android/os/HandlerThread
HandlerThread適用於單線程+非同步隊列模型場景,相對Handler + Thread簡潔。
// 也可實現run方法
HandlerThread mHandlerThread = new HandlerThread("HandlerThread_Test"); mHandlerThread.start(); Handler mThreadHandler = new Handler(mHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.i(TAG, "threadName--" + Thread.currentThread().getName() + (String) msg.obj); break; } } }; // 發送消息至HanderThread mThreadHandler.sendMessage(msg);
3. runOnUiThread
Activity 裡面的runOnUiThread( Runnable )方法使子線程更新UI更為簡潔。另外還有View.Post(Runnable)和View.PostDelayed(Runnabe,long)方法,用法與runOnUiThread基本相同。
new Thread(new Runnable() { @Override public void run() { try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); } runOnUiThread(new Runnable() { @Override public void run() { // 執行UI操作 Toast.makeText(MainActivity.this, "Test", Toast.LENGTH_SHORT).show(); } }); } }).start();
4. AsyncTask
API參考:https://developer.android.google.cn/reference/android/os/AsyncTask
AsyncTask是一個抽象類,它是由Android封裝的一個輕量級非同步類。它可以線上程池中執行後臺任務,然後把執行的進度和最終結果傳遞給主線程併在主線程中更新UI。AsyncTasks應該用於短操作(最多幾秒)。如果您需要保持線程長時間運行,強烈建議您使用java.util.concurrent包提供的各種API,例如Executor,ThreadPoolExecutor和FutureTask。 非同步任務由3個泛型類型定義:Params,Progress和Result,以及4個步驟:onPreExecute,doInBackground,onProgressUpdate和onPostExecute組成。
使用AsyncTask時需要繼承AsyncTask類並必須實現doInBackground(params...)方法,大多數情況下還需要實現onPostExecute(Result)方法。
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { protected Long doInBackground(URL... urls) { int count = urls.length; long totalSize = 0; for (int i = 0; i < count; i++) { totalSize += Downloader.downloadFile(urls[i]); publishProgress((int) ((i / (float) count) * 100)); // Escape early if cancel() is called if (isCancelled()) break; } return totalSize; } protected void onProgressUpdate(Integer... progress) { setProgressPercent(progress[0]); } protected void onPostExecute(Long result) { showDialog("Downloaded " + result + " bytes"); } }
創建成功後執行任務非常簡單:
new DownloadFilesTask().execute(url1, url2, url3); // Params
AsyncTask的泛型類型:
- Params:在執行時傳遞給任務的參數類型;
- Progress:非同步任務執行過程中,返回執行進度值的類型;
- Result:後臺任務執行結果類型。
並非所有類型都必須在非同步任務中使用,如果不需要使用,則用Void來代替。
rivate class MyTask extends AsyncTask<Void, Void, Void> { ... }
當執行一個非同步任務時,需要經歷4個步驟:
- onPreExecute():在非同步任務執行前,在UI主線和中調用。此步驟通常用於設置任務,例如在用記界面中顯示進度條。
- doInBackground(Params...):onPreExecute()執行完後,立即在後臺線程中調用此方法。此步驟用於執行運行時間可能較長的後臺任務,參數由execute(params...)傳入。通過此步驟得到結果並返回到最後一步。在計算過程中,可通過調用publishProgress(Progress...)方法,並通過onProgressUpdate(Progress...)更新UI顯示任務執行進度。
- onProgressUpdate(Progress...):當publishProgress(Progress...)方法執行後,此步驟在UI主線程中被調用,並更新UI當前任務進度。
- onPostExecute(Result):在後臺線程計算完成後在UI線程上調用。 後臺線程計算的結果作為參數傳遞給此步驟。
AsyncTask還提供了cancelled(boolean)方法,它同樣在主線程中執行,當非同步任務取消時,onCancelled()會被調用,這個時候onPostExecute()則不會被調用,但是要註意的是,AsyncTask中的cancel(boolean)方法並不是真正去取消任務,只是設置這個任務為取消狀態,我們需要在doInBackground()判斷終止任務。就好比想要終止一個線程,調用interrupt()方法,只是進行標記為中斷,需要線上程內部進行標記判斷然後中斷線程。參數true表示立即取消任務(不一定成功),false則表示允許任務執行完成後再取消。
使用AsyncTask還需要註意以下問題:
- 非同步任務的實例必須在UI線程中創建,即AsyncTask對象必須在UI線程中創建。
- execute(Params...) 必須在UI線程上執行。
- 不要手動調用onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...)方法。
- 該任務只能執行一次(如果嘗試執行第二次,則會引發異常)。
- 不能在doInBackground(Params... params)中更改UI組件的信息。
- AsyncTask不與任何組件綁定生命周期,所以在Activity/或者Fragment中創建執行AsyncTask時,最好在Activity/Fragment的onDestory()調用 cancel(boolean);
- 如果AsyncTask被聲明為Activity的非靜態的內部類,那麼AsyncTask會保留一個對創建了AsyncTask的Activity的引用。如果Activity已經被銷毀,AsyncTask的後臺線程還在執行,它將繼續在記憶體里保留這個引用,導致Activity無法被回收,引起記憶體泄露。
- 屏幕旋轉或Activity在後臺被系統殺掉等情況會導致Activity的重新創建,之前運行的AsyncTask(非靜態的內部類)會持有一個之前Activity的引用,這個引用已經無效,這時調用onPostExecute()再去更新界面將不再生效。
AsyncTask裡面有兩種線程池供我們調用,預設使用SERIAL_EXECUTOR。AsyncTask的核心線程是5,隊列容量是128,最大線程數是9。
- THREAD_POOL_EXECUTOR, 非同步線程池。
- SERIAL_EXECUTOR,同步線程池。
一個簡單的AsyncTask例子:
自定義AsyncTask:
class TestAsyncTask extends AsyncTask<Integer, Integer, Integer> { @Override protected Integer doInBackground(Integer... integers) { int count = integers[0]; int len = integers[1]; while (count < len) { int pro = 100 * count / len; Log.i(TAG, "----------" + pro + ":" + count + ":" + len); publishProgress(pro); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } count++; } return count; } @Override protected void onProgressUpdate(Integer... values) { mText.setText("Progress:" + values[0] + "%"); } @Override protected void onPostExecute(Integer integer) { mText.setText("Finished:" + integer + "%"); } }
創建TestAsyncTask實例:
mAsyncTask.execute(0, 100);