Android 多線程編程
在Android中,我們繪制圖形界面的線程即是主線程,也叫UI線程。由于在主線程中進行過于耗時的操作(Activity超過5秒,BroadCast超過10秒)會導致ANR(Application Not Responding,應用程序無響應),而且Android4.0以后也規定,不允許在主線程中進行網絡操作(耗時操作),因此對于耗時操作我們并需在新開啟的線程中執行,并通過線程間的通信機制在主線程中更新UI。以下總結了幾種多線程編程的方法:
1.開啟新線程,使用handler通信
這是界面的xml代碼:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal"
tools:context="com.example.test.MyActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1+1 = "
android:textSize="24sp" />
<TextView
android:id="@+id/ans"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="\?"
android:textSize="24sp" />
</LinearLayout>
布局只有一行文字,非常簡單。
再來是Activity的代碼:
package com.example.test;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
public class MyActivity extends Activity{
// 使用handler實現線程間通信
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
tv.setText(msg.arg1+"");
};
};
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.ans);
// 使用匿名類開啟新線程
new Thread(new Runnable() {
@Override
public void run() {
// 模擬耗時操作,等待3秒
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {};
// 使用handler實現線程間通信
// 發送消息
Message message = Message.obtain();
message.arg1 = 2;
handler.sendMessage(message);
// post Runnable也可以
// handler.post(new Runnable() {
//
// @Override
// public void run() {
// tv.setText("2");
// }
// });
// runOnUiThread也行
// MyActivity.this.runOnUiThread(new Runnable() {
//
// @Override
// public void run() {
// tv.setText("2");
// }
// });
}
}).start();
}
}
在上面我們開啟了一個子線程來處理延時事件(讓線程休眠模擬延時),并使用了handler發送消息來與主線程通信,在主線程中更新UI界面。我們還可以使用handler.post和Activity的runOnUiThread來傳入Runnable參數更新UI界面,但其實兩者都是使用handler發送消息并添加回調函數來實現的。
效果圖如下:
2.Executor
除了上面手動新建線程來進行多線程編程,我們還可以使用JavaSE5為我們提供的Executor(執行器),他將替我們管理Thread對象,簡化了我們的多線程編程。代碼如下:
package com.example.test;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
public class MyActivity extends Activity {
// 使用handler實現線程間通信
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
tv.setText(msg.arg1 + "");
};
};
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.ans);
// 使用線程池來開啟新線程
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new Runnable() {
@Override
public void run() {
// 模擬耗時操作,等待3秒
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
}
;
// 使用handler實現線程間通信
// 發送消息
Message message = Message.obtain();
message.arg1 = 2;
handler.sendMessage(message);
// post Runnable也可以
// handler.post(new Runnable() {
//
// @Override
// public void run() {
// tv.setText("2");
// }
// });
// runOnUiThread也行
// MyActivity.this.runOnUiThread(new Runnable() {
//
// @Override
// public void run() {
// tv.setText("2");
// }
// });
}
});
}
}
上面使用了CachedThreadPool線程池,他會在執行過程中創建與所需數目相同的線程,然后會回收不使用的舊線程而非創建新線程。除此以外Java還提供了別的多種線程池,在此不一一描述。使用Executor會更便于我們管理線程,而且在線程數較多的時候也能使代碼看起來更優雅。
由于效果圖一樣,在此就不再貼圖。
3.AsyncTask
AsyncTask是Android提供的異步任務類,本質是用Java線程池改造的。以下是Java代碼:
package com.example.test;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
public class MyActivity extends Activity {
// 使用AsyncTask實現多線程編程
private AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
@Override
protected void onPreExecute() {
super.onPreExecute();
// UI線程預處理
}
@Override
protected Void doInBackground(Void... params) {
// 線程休眠3秒模擬耗時操作
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {};
return null;
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
// doInBackground調用publishProgress(values)時調用該方法;
// 該方法處于UI線程,可以更新UI!
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
// 處理結果
tv.setText("2");
}
};
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.ans);
// 必須在UI線程使用
task.execute();
}
}
AsyncTask的三個泛型參數分別代表:處理時傳給Task的參數類型,打印過程時所需數據的參數類型,返回結果的類型,如果不需要該參數只要填寫Void即可。AsyncTask需要重寫幾個關鍵的方法,在代碼中已有注釋在此不再贅述。值的注意的是,同一個AsyncTask不可execute多次,否則會發生java.lang.IllegalStateException: Cannot execute task: the task is already running.的錯誤。如果你想開啟多個新線程,應該考慮繼承AsyncTask類并創建多個實例。同時AsyncTask還存在著些許奇奇怪怪的問題,本文在此也不做深入探究。
4.使用Loader類
Android在3.0以后,SDK提供了Loader技術,使用Loader技術可以很容易進行數據的異步加載。Loader技術為我們提供的核心類有:
- LoaderManager:可以通過Activity或者的Fragment的getLoaderManager()方法得到LoaderManager,用來對Loader進行管理,一個Activity或者Fragment只能有一個LoaderManager。
- LoaderManager.LoaderCallbacks:用于同LoaderManager進行交互,可以在其中創建Loader對象。
- AsyncTaskLoader:抽象類,可以進行異步加載數據的Loader,貌似內部也是通過AsynTask實現的,可以通過繼承它構建自己的Loader,也可以使用現有的子類,例如異步查詢數據庫可以使用CursorLoader。
一般而言還是使用官方實現好的Loader,自己重寫Loader比較麻煩。
以下是我自己重寫Loader的例子:
package com.example.test;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class MyActivity extends Activity {
private static String TAG = "MyActivity";
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.ans);
getLoaderManager().initLoader(0, null, new MyLoaderCallbacks());
}
private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks<Integer> {
@Override
public Loader<Integer> onCreateLoader(int id, Bundle args) {
Log.v(TAG, "onCreateLoader");
// UI預處理
return new MyLoader(getApplicationContext());
}
@Override
public void onLoaderReset(Loader<Integer> loader) {
Log.v(TAG, "onLoaderReset");
// 處理Loader被reset的情況,在此需要清除掉對上一個data的引用
}
@Override
public void onLoadFinished(Loader<Integer> loader, Integer data) {
Log.v(TAG, "onLoadFinished");
// 處理UI結果
tv.setText(data+"");
}
}
// 必須是靜態類,否則會有RuntimeException
private static class MyLoader extends AsyncTaskLoader<Integer> {
public MyLoader(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
Log.v(TAG, "onStartLoading");
// 調用forceLoad來開啟新線程執行任務
forceLoad();
}
@Override
public Integer loadInBackground() {
Log.v(TAG, "loadInBackground");
// 模擬耗時操作
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {};
return 2;
}
}
}
值的注意的有幾個地方:
- LoaderCallbacks的泛型代表處理結果的返回類型
- 自己寫的AsyncTaskLoader的子類必須為靜態類
- 必須在自己寫的Loader中調用forceLoad方法,否則Loader不會開啟新線程執行操作
與AsyncTask相比,Loader有了更多處理上的優化,比如加載被中斷后自動保存現場等,本文在此不做深入探討。
綜上所述,在Android中實現多線程編程的方法有:
- 手動新建線程,使用Handler進行通信
- 使用線程池新建線程,使用Handler進行通信
- 使用Android提供的AsyncTask工具類
- 使用Android提供的Loader工具類