由於本人英文能力實在有限,不足之初敬請諒解
本部落格只要沒有註明“轉”,那麼均為原創,轉貼請註明本部落格網站連結接
Processing Bitmaps Off the UI Thread
在UI線程之外處理Bitmap
The BitmapFactory.decode* methods, discussed in the Load Large Bitmaps Efficiently lesson, should not be executed on the main UI thread if the source data is read from disk or a network location (or really any source other than memory).
The time this data takes to load is unpredictable and depends on a variety of factors (speed of reading from disk or network, size of image, power of CPU, etc.).
If one of these tasks blocks the UI thread, the system flags your application as non-responsive and the user has the option of closing it (see Designing for Responsiveness for more information).
以BitmapFactory.decode*開頭的方法已經在“Load Large Bitmaps Efficiently”中討論過了,如果資料來源在磁碟或者網路上,那麼這些方法不應該在主UI線程中執行
This lesson walks you through processing bitmaps in a background thread using AsyncTask and shows you how to handle concurrency issues.
這裡教你如何使用AsyncTask在後台線程中處理bitmap,並且展示如何處理並發問題
Use an AsyncTask
The AsyncTask class provides an easy way to execute some work in a background thread and publish the results back on the UI thread.
To use it, create a subclass and override the provided methods.
Here’s an example of loading a large image into an ImageView using AsyncTask and decodeSampledBitmapFromResource():
使用AsyncTask
AsyncTask類提供一種簡單的方式來在後台線程執行一些工作並且把結果發布到UI線程上
為了使用它,建立一個子類,並且覆蓋其中的方法
這有一個使用AsyncTask和decodeSampledBitmapFromResource()載入大圖片到ImageView的例子
[java]
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
}
// Once complete, see if ImageView is still around and set bitmap.
@Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
}
// Once complete, see if ImageView is still around and set bitmap.
@Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}The WeakReference to the ImageView ensures that the AsyncTask does not prevent the ImageView and anything it references from being garbage collected.
There’s no guarantee the ImageView is still around when the task finishes, so you must also check the reference in onPostExecute().
The ImageView may no longer exist, if for example, the user navigates away from the activity or if a configuration change happens before the task finishes.
關聯到ImageView上的WeakReference保證AsyncTask不會阻止ImageView和任何引用它的地方被記憶體回收
當task結束的時候,不保證ImageView仍然存在,所以你也必須在onPostExecute()中檢查這個引用
假設例如:使用者導航到activity之外或者如果在task結束之前一個配置改變的發生的時候,ImageView也許不複存在
To start loading the bitmap asynchronously, simply create a new task and execute it:
為了非同步載入bitmap,簡單的建立一個新的task並且執行它
[java]
public void loadBitmap(int resId, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
public void loadBitmap(int resId, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
Handle Concurrency
處理並發
Common view components such as ListView and GridView introduce another issue when used in conjunction with the AsyncTask as demonstrated in the previous section.
In order to be efficient with memory, these components recycle child views as the user scrolls.
If each child view triggers an AsyncTask, there is no guarantee that when it completes, the associated view has not already been recycled for use in another child view. Furthermore, there is no guarantee that the order in which asynchronous tasks are started is the order that they complete.
普通的view組件,例如當ListView 和 GridView與AsyncTask(像上一節展示的那樣)一起使用的時候,會引入另外一個問題
為了在記憶體上的效率,當使用者滾動螢幕的時候這些組件回收子view
如果每一個子view都觸發一個AsyncTask,那麼不保證當它完成的時候,關聯的view為了在另一個子view中使用而沒有被回收
而且,不保證非同步任務開始的順序與完成時的順序一致
The blog post Multithreading for Performance further discusses dealing with concurrency, and offers a solution where the ImageView stores a reference to the most recent AsyncTask which can later be checked when the task completes.
Using a similar method, the AsyncTask from the previous section can be extended to follow a similar pattern.
這個blog發布了Multithreading for Performance,更深入的討論並發的處理,並且提供了一個當任務完成後ImageView將一個引用儲存到最近的一個並且之後能被檢查的AsyncTask的解決方案
使用一個相似的方法,上一節提到的AsyncTask可以擴充為遵循一個相似的形式
Create a dedicated Drawable subclass to store a reference back to the worker task.
In this case, a BitmapDrawable is used so that a placeholder image can be displayed in the ImageView while the task completes:
建立一個專用的Drawable子類來儲存一個關聯到工作task的引用
這樣,使用了BitmapDrawable,當task完成的時候一個站位的圖片就能被顯示到ImageView
[java]
static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}Before executing the BitmapWorkerTask, you create an AsyncDrawable and bind it to the target ImageView:
在執行BitmapWorkerTask之前,你可以建立一個AsyncDrawable,並且把它綁定到目標ImageView上面
[java]
public void loadBitmap(int resId, ImageView imageView) {
if (cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
public void loadBitmap(int resId, ImageView imageView) {
if (cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}The cancelPotentialWork method referenced in the code sample above checks if another running task is already associated with the ImageView.
If so, it attempts to cancel the previous task by calling cancel().
In a small number of cases, the new task data matches the existing task and nothing further needs to happen.
Here is the implementation of cancelPotentialWork:
上面樣本中的cancelPotentialWork方法檢查是否另一個運行中的task已經關聯到這個ImageView
如果是,它試圖通過調用cancel()取消前一個任務
少數情況下,新的task資料與已存在的task匹配
下面是cancelPotentialWork的實現
[java]
public static boolean cancelPotentialWork(int data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;
if (bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
public static boolean cancelPotentialWork(int data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;
if (bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}A helper method, getBitmapWorkerTask(), is used above to retrieve the task associated with a particular ImageView:
上面使用的getBitmapWorkerTask()方法,用來獲得與特定的ImageView關聯的task
[java]
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}The last step is updating onPostExecute() in BitmapWorkerTask so that it checks if the task is cancelled and if the current task matches the one associated with the ImageView:
最後一步是在BitmapWorkerTask中更新onPostExecute(),以便檢查這個task是否被取消、當前task是否與關聯ImageView的task匹配
[java]
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask =
getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask && imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask =
getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask && imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}This implementation is now suitable for use in ListView and GridView components as well as any other components that recycle their child views.
Simply call loadBitmap where you normally set an image to your ImageView.
For example, in a GridView implementation this would be in the getView() method of the backing adapter.
這種實現適用於ListView 和 GridView組件中,也適用於任何其他回收他們的子view的組件
在你通常給ImageView設定圖片的地方簡單的調用loadBitmap
例如,在一個GridView實現中,應該在其內部的adapter的getView()方法中調用loadBitmap