在開發(fā)Android應(yīng)用時(shí)必須遵守單線程模型的原則:
Android UI操作并不是線程安全的并且這些操作必須在UI線程中執(zhí)行。在單線程模型中始終要記住兩條法則:
不要阻塞UI線程
確保只在UI線程中訪問Android UI工具包 當(dāng)一個(gè)程序第一次啟動(dòng)時(shí),Android會(huì)同時(shí)啟動(dòng)一個(gè)對(duì)應(yīng)的主線程(Main Thread)适滓,主線程主要負(fù)責(zé)處理與UI相關(guān)的事件暂刘,如:用戶的按鍵事件遥金,用戶接觸屏幕的事件以及屏幕繪圖事件逼裆,并把相關(guān)的事件分發(fā)到對(duì)應(yīng)的組件進(jìn)行處理蓝仲。
所以主線程通常又被叫做UI線程。
比如說從網(wǎng)上獲取一個(gè)網(wǎng)頁泡态,在一個(gè)TextView中將其源代碼顯示出來搂漠,這種涉及到網(wǎng)絡(luò)操作的程序一般都是需要開一個(gè)線程完成網(wǎng)絡(luò)訪問,但是在獲得頁面源碼后某弦,是不能直接在網(wǎng)絡(luò)操作線程中調(diào)用TextView.setText()的.因?yàn)槠渌€程中是不能直接訪問主UI線程成員
Android提供了幾種在其他線程中訪問UI線程的方法:
Activity.runOnUiThread( Runnable )
View.post( Runnable )
View.postDelayed( Runnable, long )
Hanlder 這些類或方法同樣會(huì)使你的代碼很復(fù)雜很難理解桐汤。然而當(dāng)你需要實(shí)現(xiàn)一些很復(fù)雜的操作并需要頻繁地更新UI時(shí)這會(huì)變得更糟糕。
為了解決這個(gè)問題靶壮,Android 1.5提供了一個(gè)工具類:AsyncTask
它使創(chuàng)建需要與用戶界面交互的長(zhǎng)時(shí)間運(yùn)行的任務(wù)變得更簡(jiǎn)單怔毛,不需要借助線程和Handler即可實(shí)現(xiàn)。
AsyncTask是抽象類腾降,它定義了三種泛型類型: Params拣度,Progress,Result
Params 啟動(dòng)任務(wù)執(zhí)行的輸入?yún)?shù)蜂莉,比如HTTP請(qǐng)求的URL蜡娶。
Progress 后臺(tái)任務(wù)執(zhí)行的百分比混卵。
Result 后臺(tái)執(zhí)行任務(wù)最終返回的結(jié)果映穗,比如String。
AsyncTask的執(zhí)行分為四個(gè)步驟幕随,每一步都對(duì)應(yīng)一個(gè)回調(diào)方法蚁滋,這些方法不應(yīng)該由應(yīng)用程序調(diào)用(即用戶不可直接調(diào)用,而應(yīng)由系統(tǒng)調(diào)用)赘淮,開發(fā)者需要做的就是實(shí)現(xiàn)這些方法辕录。
- 子類化AsyncTask 2) 實(shí)現(xiàn)AsyncTask中定義的下面一個(gè)或幾個(gè)方法
(a)onPreExecute(), 該方法將在執(zhí)行實(shí)際的后臺(tái)操作前被UI thread調(diào)用∩倚叮可以在該方法中做一些準(zhǔn)備工作走诞,如在界面上顯示一個(gè)進(jìn)度條。
(b)doInBackground(Params...), 將在onPreExecute 方法執(zhí)行后馬上執(zhí)行蛤高,該方法運(yùn)行在后臺(tái)線程中蚣旱。這里將主要負(fù)責(zé)執(zhí)行那些很耗時(shí)的后臺(tái)計(jì)算工作〈鞫福可以調(diào)用 publishProgress方法來更新實(shí)時(shí)的任務(wù)進(jìn)度塞绿。該方法是抽象方法,子類必須實(shí)現(xiàn)恤批。
(c)onProgressUpdate(Progress...),在publishProgress方法被調(diào)用后异吻,UI thread將調(diào)用這個(gè)方法從而在界面上展示任務(wù)的進(jìn)展情況,例如通過一個(gè)進(jìn)度條進(jìn)行展示喜庞。
(d)onPostExecute(Result), 在doInBackground 執(zhí)行完成后诀浪,onPostExecute 方法將被UI thread調(diào)用棋返,后臺(tái)的計(jì)算結(jié)果將通過該方法傳遞到UI thread. 為了正確的使用AsyncTask類,
以下是幾條必須遵守的準(zhǔn)則: - Task的實(shí)例必須在UI thread中創(chuàng)建
- execute方法必須在UI thread中調(diào)用
- 不要手動(dòng)的調(diào)用onPreExecute(), onPostExecute(Result)雷猪,doInBackground(Params...), onProgressUpdate(Progress...)這幾個(gè)方法
- 該task只能被執(zhí)行一次懊昨,否則多次調(diào)用時(shí)將會(huì)出現(xiàn)異常
AsyncTask示例:
從網(wǎng)上獲取一個(gè)網(wǎng)頁,在一個(gè)TextView中將其源代碼顯示出來
/**
*
* @author yanggang
* @see http://blog.csdn.net/sunboy_2050
*/
public class MainActivity extends Activity {
private EditText metURL;
private TextView mtvPage;
private Button mbtnConn;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
metURL = (EditText)findViewById(R.id.etURL); // 輸入網(wǎng)址
mbtnConn = (Button)findViewById(R.id.btnConn); // 連接網(wǎng)站
mtvPage = (TextView)findViewById(R.id.tvPage); // 顯示網(wǎng)頁
mbtnConn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
connURL();
}
});
}
private void connURL(){
URLTask urlTask = new URLTask(this); // 實(shí)例化抽象AsyncTask
urlTask.execute(metURL.getText().toString().trim()); // 調(diào)用AsyncTask春宣,傳入url參數(shù)
}
/** 繼承AsyncTask的子類酵颁,下載url網(wǎng)頁內(nèi)容 */
class URLTask extends AsyncTask<String, Integer, String> {
ProgressDialog proDialog;
public URLTask(Context context) {
proDialog = new ProgressDialog(context, 0);
proDialog.setButton("cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
proDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
});
proDialog.setCancelable(true);
proDialog.setMax(100);
proDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
proDialog.show();
}
@Override
protected void onPreExecute(){
mtvPage.setText(R.string.hello_world); // 可以與UI控件交互
}
@Override
protected String doInBackground(String... params) { // 在后臺(tái),下載url網(wǎng)頁內(nèi)容
try {
HttpGet get = new HttpGet(params[0]); // url
HttpResponse response = new DefaultHttpClient().execute(get);
if(response.getStatusLine().getStatusCode() == 200) { // 判斷網(wǎng)絡(luò)連接是否成功
// String result = EntityUtils.toString(response.getEntity(), "gb2312"); // 獲取網(wǎng)頁內(nèi)容
// return result;
HttpEntity entity = response.getEntity();
long len = entity.getContentLength(); // 獲取url網(wǎng)頁內(nèi)容總大小
InputStream is = entity.getContent();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int ch = -1;
int count = 0; // 統(tǒng)計(jì)已下載的url網(wǎng)頁內(nèi)容大小
while(is != null && (ch = is.read(buffer)) != -1 ) {
bos.write(buffer, 0, ch);
count += ch;
if(len > 0) {
float ratio = count/(float)len * 100; // 計(jì)算下載url網(wǎng)頁內(nèi)容百分比
publishProgress((int)ratio); // android.os.AsyncTask.publishProgress(Integer... values)
}
Thread.sleep(100);
}
String result = new String(bos.toString("gb2312"));
return result;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) { // 可以與UI控件交互
mtvPage.setText("" + values[0]); // 獲取 publishProgress((int)ratio)的values
proDialog.setProgress(values[0]);
}
@Override
protected void onPostExecute(String result) { // 可以與UI控件交互
mtvPage.setText(result);
proDialog.dismiss();
}
}
}
注: 最后別忘記在AndroidManefest.xml 添加網(wǎng)絡(luò)訪問權(quán)限
<uses-permission android:name="android.permission.INTERNET" />
運(yùn)行結(jié)果: