4.下載圖片檔案
4.1 問題
應用程式需要從Web或其他遠端伺服器下載一張圖片並顯示。
4.2 解決方案
(API Level 4)
使用AsyncTask在後臺執行緒中下載資料。AsyncTask是封裝類,它可以很方便地讓需要長時間允許操作的執行緒在後臺允許;同樣,它通過一個內部執行緒池管理執行緒的併發。除了管理後臺執行緒外,在操作執行前、中、後都會提供回撥方法,讓你可以做任何需要在主UI執行緒中進行的更新。
4.3 實現機制
在下載圖片的環境中,我們會建立ImageView的一個子類,叫做WebImageView,它會從遠端來源中延遲載入一張圖片並且在該圖片可用時就顯示它。下載過程會在一個AsyncTask操作中執行(參見以下程式碼清單)。
WebImageView
public class WebImageView extends ImageView { private Drawable mPlaceholder,mImage; public WebImageView(Context context) { super(context,null); } public WebImageView(Context context, AttributeSet attrs) { super(context, attrs,0); } public WebImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setPlaceholder(Drawable drawable) { this.mPlaceholder = drawable; if (mImage == null){ setImageDrawable(mPlaceholder); } } public void setPlaceholder(int resid) { mPlaceholder = getResources().getDrawable(resid); if (mImage == null){ setImageDrawable(mPlaceholder); } } public void setImageURl(String url) { DownloadTask task = new DownloadTask(); task.execute(url); } private class DownloadTask extends AsyncTask<String,Void,Bitmap> { @Override protected Bitmap doInBackground(String... params) { String url = params[0]; try { URLConnection connection = (new URL(url)).openConnection(); InputStream is = connection.getInputStream(); BufferedInputStream bis = new BufferedInputStream(is); ByteArrayBuffer baf = new ByteArrayBuffer(50); int current = 0; while ((current = bis.read()) != -1) { baf.append((byte) current); } byte[] imageData = baf.toByteArray(); return BitmapFactory.decodeByteArray(imageData, 0, imageData.length); } catch (Exception exc) { return null; } } @Override protected void onPostExecute(Bitmap result) { mImage = new BitmapDrawable(getContext().getResources(),result); if (mImage != null){ setImageDrawable(mImage); } } } }
正如你所看到的那樣,WebImageView是Android的mageView小部件的一個簡單擴充套件。在遠端內容下載完成之前,setPlaceholderImage()會為要顯示的圖片設定一個本地Drawable。大多數有趣的工作都是從使用setImageUrl()向檢視傳入一個給定遠端URL開始的,該方法表示自定義的AsyncTask開始工作了。
請注意,AsyncTask是強型別化的,它需要三個值:輸入引數、進度值、結果值。在這種情況下,會傳遞給任務的execute()方法一個字串,而後臺操作應該返回一個Bitmap。對於中間的進度值,我們在本例中並不會使用,因此它被設定為Void。繼承AsyncTask後,唯一需要實現的方法就是doInBackground(),它定義了後臺執行緒中需要大量執行的操作。在前面的示例中,這裡是與提供的遠端URL進行連線以及下載圖片的地方。完成後,我們會試圖用下載的資料建立一個Bitmap。發生任何錯誤時,操作將中止並返回null。
要點:
Android UI類是執行緒不安全的。確保在更新UI時使用執行在主執行緒上的回撥方法。不要在doInBackground()中更新檢視。
以下兩段程式碼清單展示了在一個Activity中使用這個類的示例。因為這個類不是android.widget或android.view包的一部分,所以在XML中使用它時必須先指定它的完全限定包名。
res/layout/main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.jennyni.webimageviewdemo.MainActivity"> <com.jennyni.webimageviewdemo.WebImageView android:id="@+id/webImage" android:layout_width="wrap_content" android:layout_height="wrap_content"> </com.jennyni.webimageviewdemo.WebImageView> </LinearLayout>
示例Activity
public class WebImageActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); WebImageViewimageView = (WebImageView)findViewById(R.id.webImage); imageView.setPlaceholderImage(R.drawable.ic_launcher); imageView.setImageUrl("http://lorempixel.com/400/200"); } }
本例中,首先會設定一張本地圖片(應用程式圖示)作為WebImageView的佔位圖片,這張圖片會立刻顯示給使用者。然後我們會告訴檢視從Web上獲取Apress的徽標圖片。如前所述,這裡會在後臺下載圖片,下載完成後會替換掉檢視中的佔位圖片。正是因為建立後臺操作的簡單性,Android團隊才會把AsyncTask叫作“無痛苦使用執行緒”。