Android多線程編程
有一些耗時操作如果在主線程中進行會阻塞主線程,所以需要將這類操作放到子線程中操作。
Android的UI操作必須在主線程中進行,否則會出現異常。但是有時,我們必須在子線程中執行一些耗時任務,然後根據任務的執行結果來更新UI,這裏就用到了一套異步消息處理機制。
異步消息處理機制
異步處理四個部分
Android中的異步處理消息機制主要由四部分組成。
Message
Message是在線程之間傳遞的消息,他可以在內部攜帶少量的信息,用於在不同線程之間交換數據。Handler
處理者,用於發送和處理消息。發送消息通常調用Handler的sendMessage()方法,發出的消息最終會傳遞到handlerMessage()方法中- MessageQueue
MessageQueue是消息隊列的意思,主要用於存放所有通過Handler發送的消息,這部分消息會一直存在於消息隊列中,等待被處理。每個線程中只有一個MessageQueue對象。 - Looper
Looper是每個線程中的MessageQueue的管家,調用loop()方法後,就會進入到一個無限循環中,每當發現MessageQueue的一條消息,就會將它取出,並傳遞到Handler的handlerMessage()方法中。每個線程中也只有一個Looper對象。
異步處理整體流程
首先在主線程中創建一個Handler對象,並重寫handlerMessage方法。然後當子線程中需要進行UI操作時,就創建一個Message對象,並通過handler將這條消息發出去。之後這條消息會被添加到MessageQueue中等待被處理,Looper一直嘗試從MessageQueue中取出待處理消息,最後分發會Handler的handleMessage方法中。由於Handler是在主線程中進行的,所以可以進行UI操作。
異步處理Demo
線程中:
new Thread(new Runnable() {
@Override
public void run() {
count=60;
while (count>0){
count--;
// 沒有Message對象的話再創建一個,有的話用之前的對象
Message msg = handler.obtainMessage()
// Message msg=new Message();
// 給這條信息加一個標誌
msg.what=TIME_DESK;
// 將int型的count轉換成string類型的值
msg.obj=count+"秒";
handler.sendMessage(msg);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
活動中(主線程中)
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case TIME_DESK:
String time= (String) msg.obj;
mBtnTimeDesk.setText(time);
break;
}
}
};
通過sendEmptyMessage簡化代碼,效果和上面的一樣
按鍵監聽器中
count = 60;
// 發送空消息,僅僅用來傳遞,和延時
handler.sendEmptyMessageDelayed(TIME_DESK,100);
主線程中
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case TIME_DESK:
// String time= (String) msg.obj;
count--;
if (count >= 0) {
mBtnTimeDesk.setText(count + "秒");
// 形成循環,延時0.1秒
handler.sendEmptyMessageDelayed(TIME_DESK,100);
}
break;
}
}
};
從子線程往主線程發送消息Demo
主線程給子線程發消息,在子線程中寫handler並且其中必須有一個
Looper隊列。子線程給主線程發消息時主線程自帶looper,不需要再加
兩個按鍵,按鍵一啓動子線程,按鍵二將消息傳入到子線程的handler中。
public class MainActivity extends Activity implements View.OnClickListener {
private Button mBtnTimeDesk;
private int count;
private static final int TIME_DESK = 0x23;
private Button mBtnSendMessage;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnTimeDesk = (Button) findViewById(R.id.btn_timedesk);
mBtnSendMessage = (Button) findViewById(R.id.btn_sendmessage);
mBtnTimeDesk.setOnClickListener(this);
mBtnSendMessage.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_timedesk:
MyThread thread=new MyThread();
thread.start();
break;
case R.id.btn_sendmessage:
handler.sendEmptyMessage(0);
break;
default:
break;
}
}
class MyThread extends Thread{
@Override
public void run() {
Looper.prepare();
handler=new Handler(){
@Override
public void handleMessage(Message msg) {
Log.d("子線程收到信息", "(~ ̄▽ ̄)~");
}
};
Looper.loop();
}
}
}
先點擊btn_timedesk(啓動線程)再點擊sendMessage發送信息
不能夠放在一個按鍵中:如果放到一個鍵中
MyThread thread=new MyThread();
thread.start();
handler.sendEmptyMessage(0);
handler可能會報空指針,因爲handler對象的初始化實在子線程進行的,如果子線程沒有new出來handler就調用handler.sendEmptyMessage(0)就會報空指針。
子線程中public void handleMessage(Message msg)一直等待接收消息。子線程中接受消息要放在Looper.prepare()和Looper.loop()裏面實現循環。第一個例子中從子線程往主線程發送消息,因爲主線程會自動將handler添加到looper中,所以不用像子線程一樣手動添加。
AsyncTask
AsyncTask的實現原理基於異步消息處理機制,只是Android幫我們做了很好的封裝而已。
創建類繼承AsyncTask
AsyncTask是一個抽象類,所以如果想使用它,就必須創建一個子類去繼承他,繼承時要爲AsyncTask類指定三個泛型參數,這三個參數用途如下:
- Params
在執行AsyncTask時需要傳入的參數,可以用於後臺服務中使用。 - Progress
後臺任務執行時,如果需要在界面上顯示當前的進度,則使用這裏指定的泛型作爲進度單位。 - Result
當任務執行完畢後,如果需要對結果進行返回,則使用這裏指定的泛型作爲返回值類型。
最簡單的自定義AsyncTask就可以這樣寫。
class MyAsyncTask extends AsyncTask<String, String, String> {...}
第一個String表示執行AsyncTask的時候不需要傳入參數給後臺任務。第二個String表示使用String類型數據作爲進度顯示單位。第三個String表示使用String類型數據來反饋執行結果。
複寫AsyncTask的方法
經常需要重寫的方法有以下四個:
- onPreExecute()
這個方法會在後臺任務開始執行之前調用,用於進行一些界面上的初始化操作,比如顯示一個進度條對話框等。 - doInBackground(Params…)
這個方法中的所有代碼都會在子線程中運行,處理所有耗時任務。任務完成後通過return返回結果,如果第三個泛型參數是void,就不必返回執行結果。這個方法不可以進行UI操作。反饋當前任務的執行進度,可以調用publishProgress(Progress…)方法來完成。 - onProgressUpdate(Progress…)
後臺調用了publishProgress(Progress…)方法後,本方法會被調用,方法中的參數就是後臺任務傳遞過來的。本方法中可以對UI進行操作,利用參數中的數值就可以對界面元素進行相應的更新。 - onPostExecute(Result)
後臺執行完畢並且return語句返回時,本方法會被調用。返回的詩句會作爲參數傳遞到此方法中,可以利用返回值進行一些UI操作。
啓動AsyncTask
啓動發法非常簡單。
new MyAsyncTask().execute();
AsyncTask的Demo
本例實現點擊按鈕後進度條填滿的動作
佈局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity">
<ProgressBar
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:progress="100"
/>
<Button
android:id="@+id/btn_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="開始執行"/>
</LinearLayout>
活動
public class MainActivity extends Activity implements View.OnClickListener {
private Button mBtnStart;
private ProgressBar mProgerssBar;
private int count;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnStart = (Button) findViewById(R.id.btn_start);
mProgerssBar = (ProgressBar) findViewById(R.id.progress);
mBtnStart.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_start:
MyAsyncTask myAsyncTask=new MyAsyncTask();
myAsyncTask.execute("");
break;
default:
break;
}
}
// 不必建立線程即可進行耗時操作
class MyAsyncTask extends AsyncTask<String, String, String> {
// 後臺操作
@Override
protected String doInBackground(String... params) {
count = 0;
while (count < 101) {
count++;
publishProgress(count+"");//將數值傳入中間操作
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String s=count+"";
return s;
}
// 中間操作
@Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
// values中存儲變量。變量爲publishProgress傳入的的參數
int value=Integer.parseInt(values[0]);
mProgerssBar.setProgress(value);
}
// 得到後臺操作返回的值
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
mBtnStart.setText("完成操作");
}
}
}
效果
AsyncTask小總結
在doInBackground()方法中去執行具體的耗時任務,在onProgressUpdate()方法中進行UI操作,在onPostExcute()方法中去執行一些任務的收尾工作。
參考文獻:《第一行代碼》,作者郭霖