1. 程式人生 > >使用AsyncTask非同步更新UI介面(載入網路圖片)

使用AsyncTask非同步更新UI介面(載入網路圖片)

寫這個部落格時抽抽了,順便又用了一些WebView的東西,更多webview參見這裡

1.簡單介紹下AsyncTask

AsyncTask,是android提供的輕量級的非同步類,可以直接繼承AsyncTask,在類中實現非同步操作,提供介面反饋當前非同步執行的程度(可以通過介面實現UI進度更新),最後反饋執行的結果給UI主執行緒.比Handler更輕量級,但是在使用多個非同步操作和並需要進行Ui變更時,就變得複雜起來.程式碼量上,如果只開一個後臺程序AsyncTask是絕對首選,因為handler處理結構相對複雜。

2.使用

要使用AsyncTask工作我們要提供三個泛型引數,並重載幾個方法(至少過載一個doInBackground

)。

AsyncTask定義了三種泛型型別 Params,Progress和Result。

  • Params 啟動任務執行的輸入引數,比如HTTP請求的URL。
  • Progress 後臺任務執行的百分比, 比如Integer。
  • Result 後臺執行任務最終返回的結果,比如String。

使用過AsyncTask 的同學都知道一個非同步載入資料最少要重寫以下這兩個方法:

  • doInBackground(Params…) 後臺執行,比較耗時的操作都可以放在這裡。注意這裡不能直接操作UI。此方法在後臺執行緒執行,完成任務的主要工作,通常需要較長的時間。在執行過程中可以呼叫publicProgress(Progress…)來更新任務的進度。
  • onPostExecute(Result)  相當於Handler 處理UI的方式,在這裡面可以使用在doInBackground 得到的結果處理操作UI。 此方法在主執行緒執行,任務執行的結果作為此方法的引數返回

有必要的話你還得重寫以下這三個方法,但不是必須的:

  • onProgressUpdate(Progress…)    可以使用進度條增加使用者體驗度。 此方法在主執行緒執行,用於顯示任務執行的進度。
  • onPreExecute()        這裡是終端使用者呼叫Excute時的介面,當任務執行之前開始呼叫此方法,可以在這裡顯示進度對話方塊。
  • onCancelled()           使用者呼叫取消時,要做的操作
總結一句話: 在主執行緒中呼叫YouAsyncTask.execute("你的引數"),doInBackground(...)裡面的引數就是你傳的引數集,你可以在這裡處理耗時操作,它會返回一個結果result給onPostExecute(...)在這裡處理結果,如果你在doInBackground(...)中呼叫了publishProgress(progressNumber)方法,那麼進度的顯示在onProgressUpdate(...)中,裡面的引數就是progressNumber。 使用AsyncTask類,以下是幾條必須遵守的準則:
  • Task的例項必須在UI thread中建立;
  • execute方法必須在UI thread中呼叫;
  • 不要手動的呼叫onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)這幾個方法;
  • 該task只能被執行一次,否則多次呼叫時將會出現異常;
3.看個栗子

建立在上一個部落格專案基礎上,建立了新的佈局處理。

XMl:activity_async_task.xml

<RelativeLayout 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" >

    <Button
        android:id="@+id/download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:padding="10dp"
        android:text="download" />
    <ImageView 
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_below="@id/download"
        android:layout_marginTop="10dp"
        android:scaleType="centerCrop"/>
    
    <SeekBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_below="@id/image"
        android:layout_margin="5dp"
        android:max="100"
        android:progress="0"
        android:id="@+id/seekbar"/>
    
    <WebView 
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/seekbar"/>
</RelativeLayout>

AsyncTaskActivity.java(該檔案棄用,因為後來優化遇到了些問題,作為對比故保留,詳見第二個檔案)

package com.lzy.exploremessagedemo;

import java.io.IOException;
import java.io.InputStream;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.Toast;

public class AsyncTaskActivity extends Activity {
	private Button downloadButton;
	private ImageView mImageView;
	private ProgressDialog progressDialog;
	private SeekBar seekBar;
	private WebView webView;
	
	private final String url = "http://b.hiphotos.baidu.com/image/h%3D300/sign=1b921b860d24ab18ff16e" +
			"73705fbe69a/86d6277f9e2f070861ccd4a0ed24b899a801f241.jpg";
	@SuppressLint("SetJavaScriptEnabled")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_async_task);
		progressDialog = new ProgressDialog(this);
		
		mImageView = (ImageView) findViewById(R.id.image);
		seekBar = (SeekBar) findViewById(R.id.seekbar);
		webView = (WebView) findViewById(R.id.webview);
		
		WebSettings settings = webView.getSettings();
		settings.setJavaScriptEnabled(true);
		settings.setBuiltInZoomControls(true);
		settings.setDisplayZoomControls(true);
		settings.setSupportZoom(true);
		downloadButton = (Button) findViewById(R.id.download);
		downloadButton.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				new MyAsyncTask().execute(url);//非同步載入網路圖片
				webView.loadUrl(url);//使用webview載入網路圖片
			}
		});
	}
	
	class MyAsyncTask extends AsyncTask<String, Integer, Bitmap>{//引數,進度,結果

		@Override
		protected void onPreExecute() {
			progressDialog.setMessage("請稍後...");
			progressDialog.show();
		}

		@Override
		protected Bitmap doInBackground(String... prarm) {
			String url = prarm[0];
			Bitmap bitmap = null;
			HttpClient httpClient = new DefaultHttpClient();
			HttpGet httpGet = new HttpGet(url);
			try {
				HttpResponse httpResponse = httpClient.execute(httpGet);
				if (HttpStatus.SC_OK == httpResponse.getStatusLine().getStatusCode()) {
					HttpEntity entity = httpResponse.getEntity();
					InputStream in = entity.getContent();
					bitmap = BitmapFactory.decodeStream(in);
					publishProgress(100);//進度顯示,這裡作為一個知識點,結果顯示回撥在onProgressUpdate(Integer... values)方法中
				}
			} catch (ClientProtocolException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				httpClient.getConnectionManager().shutdown();
			}
			return bitmap;
		}
		
		@Override
		protected void onProgressUpdate(Integer... values) {
			seekBar.setProgress(values[0]);
		}
		
		@Override
		protected void onPostExecute(Bitmap result) {
			progressDialog.dismiss();
			mImageView.setImageBitmap(result);
			Toast.makeText(getApplicationContext(), "下載成功!", Toast.LENGTH_SHORT).show();
		}
		
	}
}

vvvvvvvv22222222222222222222222222↓

package com.lzy.exploremessagedemo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.Toast;

public class AsyncTaskActivity extends Activity {
	private Button downloadButton;
	private ImageView mImageView;
	private ProgressDialog progressDialog;
	private SeekBar seekBar;
	private WebView webView;
	
	private final String url = "http://b.hiphotos.baidu.com/image/h%3D300/sign=1b921b860d24ab18ff16e" +
			"73705fbe69a/86d6277f9e2f070861ccd4a0ed24b899a801f241.jpg";
	@SuppressLint("SetJavaScriptEnabled")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_async_task);
		progressDialog = new ProgressDialog(this);
		
		mImageView = (ImageView) findViewById(R.id.image);
		seekBar = (SeekBar) findViewById(R.id.seekbar);
		webView = (WebView) findViewById(R.id.webview);
		
		WebSettings settings = webView.getSettings();
		settings.setJavaScriptEnabled(true);
		settings.setBuiltInZoomControls(true);
		settings.setDisplayZoomControls(true);
		settings.setSupportZoom(true);
		downloadButton = (Button) findViewById(R.id.download);
		downloadButton.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				new MyAsyncTask().execute(url);
				webView.loadUrl(url);
			}
		});
	}
	
	class MyAsyncTask extends AsyncTask<String, Integer, Bitmap>{//引數,進度,結果

		@Override
		protected void onPreExecute() {
			progressDialog.setMessage("請稍後");
			progressDialog.show();
		}

		@SuppressWarnings("unused")
		@Override
		protected Bitmap doInBackground(String... prarm) {
			String url = prarm[0];
			Bitmap bitmap = null;
			InputStream in = null;
			HttpClient httpClient = new DefaultHttpClient();
			HttpGet httpGet = new HttpGet(url);
			try {
				HttpResponse httpResponse = httpClient.execute(httpGet);
				if (HttpStatus.SC_OK == httpResponse.getStatusLine().getStatusCode()) {
					HttpEntity entity = httpResponse.getEntity();
					in = entity.getContent();
					long total = entity.getContentLength();  
					ByteArrayOutputStream baos = new ByteArrayOutputStream();  
                    byte[] buf = new byte[1024];  
                    int count = 0;  
                    int length = -1; 
                    while ((length = in.read(buf)) != -1) { 
                        baos.write(buf, 0, length);  
                        count += length;  
                        //呼叫publishProgress公佈進度,最後onProgressUpdate方法將被執行  
                        publishProgress((int) ((count / (float) total) * 100));  
                        //為了演示進度,休眠100毫秒  
						Thread.sleep(100);
                    }
                    /**
                     * @time 2015-9-22 - 下午1:48:59
                     * @bug 這是android 1.6的一個bug,解決方案如下
                     * */
                    //bitmap = BitmapFactory.decodeStream(in);//不清楚為什麼這樣寫獲取不到圖片,改用如下方式就能了,如果不為了演示進度條的功能,將while注掉,然後使用此方法,卻能顯示圖片??????
                    //bitmap = BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baos.toByteArray().length);
                    
                    /**
                     * 最終解決辦法
                     * */
                    if (in == null) {
                    	bitmap = BitmapFactory.decodeStream(in);
					}else {
						//為了防止載入大圖片OOM 採用BitmapFactory.Options進行縮圖處理
						BitmapFactory.Options options = new BitmapFactory.Options();
						options.inJustDecodeBounds = true;//不返回實際的Bitmap
						options.inSampleSize = Util.computeSampleSize(options, -1, 128 * 128);
						
						//為什麼加上options引數獲取的Bitmap就為null呢????????????   心累。。。。。
						/**
						 * @time 2015-9-22 - 下午3:03:32
						 * @bug 這是因為options.inJustDecodeBounds = true後不會建立Bitmap空間只作為暫存,最後
						 * 還要設定回來顯示Bitmap
						 * */
						options.inJustDecodeBounds = false;
						bitmap = BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baos.toByteArray().length, options);
					}
                    return bitmap;
				}
			} catch (ClientProtocolException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (Exception e) {
				e.printStackTrace();
			}finally {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				httpClient.getConnectionManager().shutdown();
			}
			return null;
		}
		
		@Override
		protected void onProgressUpdate(Integer... values) {
			seekBar.setProgress(values[0]);
		}
		
		@Override
		protected void onPostExecute(Bitmap result) {
			progressDialog.dismiss();
			/**
			 * 儘量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource來設定一張大圖,
			 * 因為這些函式在完成decode後,最終都是通過java層的createBitmap來完成的,需要消耗更多記憶體。 ☆
			 * */
			mImageView.setImageBitmap(result);
			Toast.makeText(getApplicationContext(), "下載成功!", Toast.LENGTH_SHORT).show();
		}
		
	}
}

裡面有用到一個縮放比的工具類:在這裡 最後別忘了新增網路訪問許可權
<uses-permission android:name="android.permission.INTERNET"/>
我把manifest也貼出來吧:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.lzy.exploremessagedemo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="21" />
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.Holo.Light" >
        <activity
            android:name=".AsyncTaskActivity" <!-- 換成上一篇博文的主檔案".MainActivity"就可以啟動上一個專案了 -->
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

至此關於AsyncTask的使用就是這麼簡單。

栗子原始碼下載:點我下載

—— lovey hy.

【歡迎上碼】

【微信公眾號搜尋 h2o2s2】