在前面的關(guān)于Java I/O的學(xué)習(xí)中,有一個(gè)我們需要牢記的是:對(duì)數(shù)據(jù)流的操作都是阻塞的,在一般情況下,我們是不需要考慮這個(gè)問(wèn)題的,但是在Android 實(shí)現(xiàn)聯(lián)網(wǎng)的時(shí)候,我們必須考慮到這個(gè)問(wèn)題。比如:從網(wǎng)絡(luò)上下載一張圖片:
public Bitmap returnBitmap(String url)
{
URL myFileUrl = null;
Bitmap bitmap = null;
try{
myFileUrl = new URL(url);
}catch(MalformedURLException e){
e.printStackTrace();
return null;
};
try{
HttpURLConnection conn = (HttpURLConnection)myFileUrl.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
bitmap = BitmapFactroy.decodeStream(is);
is.close();
}catch(IOException e){
e.printStackTrace();
}
return bitmap;
}
由于網(wǎng)絡(luò)連接需要很長(zhǎng)的時(shí)間,需要3-5秒,甚至更長(zhǎng)的時(shí)間才能返回頁(yè)面的內(nèi)容。如果此連接動(dòng)作直接在主線程,也就是UI線程中處理,會(huì)發(fā)生什么情況呢?整個(gè)程序處于等待狀態(tài),界面似乎是“死”掉了。為了解決這個(gè)問(wèn)題,必須把這個(gè)任務(wù)放置到單獨(dú)線程中運(yùn)行,避免阻塞UI線程,這樣就不會(huì)對(duì)主線程有任何影響。舉個(gè)例子如下:
private void connect(String strURL){
new Thread() {
public void run() {
try {
HttpClient client = new DefaultHttpClient();
// params[0]代表連接的url
HttpGet get = new HttpGet(url.getText().toString());
HttpResponse response = client.execute(get);
HttpEntity entity = response.getEntity();
long length = entity.getContentLength();
InputStream is = entity.getContent();
String s = null;
if (is != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[128];
int ch = -1;
int count = 0;
while ((ch = is.read(buf)) != -1) {
baos.write(buf, 0, ch);
count += ch;
}
s = new String(baos.toByteArray());
Log.V(“moandroid sample”,s);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
使用Handler更新界面
如何將下載的信息顯示在界面上了,比如說(shuō)下載的進(jìn)度。Android SDK平臺(tái)只允許在主線程中調(diào)用相關(guān)View的方法來(lái)更新界面。如果返回結(jié)果在新線程中獲得,那么必須借助Handler來(lái)更新界面。為此,在界面Activity中創(chuàng)建一個(gè)Handler對(duì)象,并在handleMessage()中更新UI。
//Task在另外的線程執(zhí)行,不能直接在Task中更新UI,因此創(chuàng)建了Handler
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
String m = (String) msg.obj;
message.setText(m);
}
};
只需要將上面的
Log.V(“moandroid sample”,s);
替換為:
s = new String(baos.toByteArray());
Message mg = Message.obtain();
mg.obj = s;
handler.sendMessage(mg);
AsyncTask
看上去修改后的connect()方法已經(jīng)可用了,但是這種匿名程的方式是存在缺陷的:
- 線程的開(kāi)銷較大,如果每個(gè)任務(wù)都要?jiǎng)?chuàng)建一個(gè)線程,那么應(yīng)用程 序的效率要低很多;
- 線程無(wú)法管理,匿名線程創(chuàng)建并啟動(dòng)后就不受程序的控制了,如果有很多個(gè)請(qǐng)求發(fā)送,那么就會(huì)啟動(dòng)非常多的線程,系統(tǒng)將不堪重負(fù)。
- 另外,前面已經(jīng)看到,在新線程中更新UI還必須要引入handler,這讓代碼看上去非常臃腫。
為了解決這一問(wèn)題,Android在1.5版本引入了AsyncTask。AsyncTask的特點(diǎn)是任務(wù)在主線程之外運(yùn)行,而回調(diào)方法是在主線程中執(zhí)行,這就有效地避免了使用Handler帶來(lái)的麻煩。閱讀AsyncTask的源碼可知,AsyncTask是使用java.util.concurrent 框架來(lái)管理線程以及任務(wù)的執(zhí)行的,concurrent框架是一個(gè)非常成熟,高效的框架,經(jīng)過(guò)了嚴(yán)格的測(cè)試。這說(shuō)明AsyncTask的設(shè)計(jì)很好的解決了匿名線程存在的問(wèn)題。
AsyncTask是抽象類,其結(jié)構(gòu)圖如下圖所示:
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。
子類必須實(shí)現(xiàn)抽象方法doInBackground(Params… p) ,在此方法中實(shí)現(xiàn)任務(wù)的執(zhí)行工作,比如連接網(wǎng)絡(luò)獲取數(shù)據(jù)等。通常還應(yīng)該實(shí)現(xiàn)onPostExecute(Result r)方法,因?yàn)閼?yīng)用程序關(guān)心的結(jié)果在此方法中返回。需要注意的是AsyncTask一定要在主線程中創(chuàng)建實(shí)例。
AsyncTask的執(zhí)行分為四個(gè)步驟,每一步都對(duì)應(yīng)一個(gè)回調(diào)方法,需要注意的是這些方法不應(yīng)該由應(yīng)用程序調(diào)用,開(kāi)發(fā)者需要做的就是實(shí)現(xiàn)這些方法。在任務(wù)的執(zhí)行過(guò)程中,這些方法被自動(dòng)調(diào)用,運(yùn)行過(guò)程,如下圖所示:
- onPreExecute() 當(dāng)任務(wù)執(zhí)行之前開(kāi)始調(diào)用此方法,可以在這里顯示進(jìn)度對(duì)話框。
- doInBackground(Params…) 此方法在后臺(tái)線程執(zhí)行,完成任務(wù)的主要工作,通常需要較長(zhǎng)的時(shí)間。在執(zhí)行過(guò)程中可以調(diào)用publicProgress(Progress…)來(lái)更新任務(wù)的進(jìn)度。
- onProgressUpdate(Progress…) 此方法在主線程執(zhí)行,用于顯示任務(wù)執(zhí)行的進(jìn)度。
- onPostExecute(Result) 此方法在主線程執(zhí)行,任務(wù)執(zhí)行的結(jié)果作為此方法的參數(shù)返回。
舉個(gè)簡(jiǎn)單的例子如下:
// 設(shè)置三種類型參數(shù)分別為String,Integer,String
class PageTask extends AsyncTask {
// 可變長(zhǎng)的輸入?yún)?shù),與AsyncTask.exucute()對(duì)應(yīng)
@Override
protected String doInBackground(String… params) {
try {
HttpClient client = new DefaultHttpClient();
// params[0]代表連接的url
HttpGet get = new HttpGet(params[0]);
HttpResponse response = client.execute(get);
HttpEntity entity = response.getEntity();
long length = entity.getContentLength();
InputStream is = entity.getContent();
String s = null;
if (is != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[128];
int ch = -1;
int count = 0;
while ((ch = is.read(buf)) != -1) {
baos.write(buf, 0, ch);
count += ch;
if (length > 0) {
// 如果知道響應(yīng)的長(zhǎng)度,調(diào)用publishProgress()更新進(jìn)度
publishProgress((int) ((count / (float) length) * 100));
}
// 為了在模擬器中清楚地看到進(jìn)度,讓線程休眠100ms
Thread.sleep(100);
}
s = new String(baos.toByteArray()); }
// 返回結(jié)果
return s;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onCancelled() {
super.onCancelled();
}
@Override
protected void onPostExecute(String result) {
// 返回HTML頁(yè)面的內(nèi)容
message.setText(result);
}
@Override
protected void onPreExecute() {
// 任務(wù)啟動(dòng),可以在這里顯示一個(gè)對(duì)話框,這里簡(jiǎn)單處理
message.setText(R.string.task_started);
}
@Override
protected void onProgressUpdate(Integer… values) {
// 更新進(jìn)度
message.setText(values[0]);
}
}
執(zhí)行PageTask非常簡(jiǎn)單,只需要調(diào)用如下代碼。
PageTask task = new PageTask();
task.execute(url.getText().toString());
總結(jié)說(shuō)明
Handler在前面我們已經(jīng)學(xué)習(xí)過(guò),今天突然看到AsyncTask,以及學(xué)習(xí)其他人的博客基礎(chǔ)上,做出了上面的總結(jié),感覺(jué)自己收獲很多,在這里與大家分享。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

