Android多線程任務優化:實現後台預讀線程
轉載請注明出處。博客地址:https://blog.csdn.net/mylzc
導語:從上一篇《多線程任務的優化1:探討AsyncTask的缺陷》我們了解到,使用AsyncTask有導致應用FC的風險,而且AsyncTask並不能滿足我們一些特定的需求。下麵我們介紹一種通過模仿AsyncTask的封裝方式,實現一個後台預讀數據的線程。
概述:在空閑時對獲取成本較高的數據(如要讀取本地或網絡資源)進行預讀是提高性能的有效手段。為了給用戶帶來更好的交互體驗,提高響應性,很多網絡應用(如新聞閱讀類應用)都在啟動的時候進行預讀,把網絡數據緩存到sdcard或者內存中。
例子:下麵介紹一個實現預讀的例子,打開應用之後會有一個歡迎界麵,在打開歡迎界麵的同時我們在後台啟動預讀線程,預讀下一個Activity需要顯示的數據,預讀數據保存到一個靜態的Hashmap中。
首先要編寫我們的後台預讀線程,遵循不重複造輪子的原則,我們對AsyncTask稍作修改就可以滿足需求。下麵是我們自定義的PreReadTask類代碼:
PreReadTask.java 實現預讀線程
- package com.zhuozhuo;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.PriorityBlockingQueue;
- import java.util.concurrent.RejectedExecutionHandler;
- import java.util.concurrent.SynchronousQueue;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.BlockingQueue;
- import java.util.concurrent.LinkedBlockingQueue;
- import java.util.concurrent.ThreadFactory;
- import java.util.concurrent.Callable;
- import java.util.concurrent.FutureTask;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.TimeoutException;
- import java.util.concurrent.CancellationException;
- import java.util.concurrent.atomic.AtomicInteger;
- import android.content.SyncResult;
- import android.os.Process;
- import android.os.Handler;
- import android.os.Message;
- public abstract class PreReadTask<Params, Progress, Result> {
- private static final String LOG_TAG = "FifoAsyncTask";
- // private static final int CORE_POOL_SIZE = 5;
- // private static final int MAXIMUM_POOL_SIZE = 5;
- // private static final int KEEP_ALIVE = 1;
- // private static final BlockingQueue<Runnable> sWorkQueue =
- // new LinkedBlockingQueue<Runnable>();
- //
- private static final ThreadFactory sThreadFactory = new ThreadFactory() {
- private final AtomicInteger mCount = new AtomicInteger(1);
- public Thread newThread(Runnable r) {
- return new Thread(r, "PreReadTask #" + mCount.getAndIncrement());
- }
- };
- private static final ExecutorService sExecutor = Executors.newSingleThreadExecutor(sThreadFactory);//隻有一個工作線程的線程池
- private static final int MESSAGE_POST_RESULT = 0x1;
- private static final int MESSAGE_POST_PROGRESS = 0x2;
- private static final int MESSAGE_POST_CANCEL = 0x3;
- private static final InternalHandler sHandler = new InternalHandler();
- private final WorkerRunnable<Params, Result> mWorker;
- private final FutureTask<Result> mFuture;
- private volatile Status mStatus = Status.PENDING;
- /**
- * Indicates the current status of the task. Each status will be set only once
- * during the lifetime of a task.
- */
- public enum Status {
- /**
- * Indicates that the task has not been executed yet.
- */
- PENDING,
- /**
- * Indicates that the task is running.
- */
- RUNNING,
- /**
- * Indicates that {@link FifoAsyncTask#onPostExecute} has finished.
- */
- FINISHED,
- }
- /**
- * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
- */
- public PreReadTask() {
- mWorker = new WorkerRunnable<Params, Result>() {
- public Result call() throws Exception {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- return doInBackground(mParams);
- }
- };
- mFuture = new FutureTask<Result>(mWorker) {
- @Override
- protected void done() {
- Message message;
- Result result = null;
- try {
- result = get();
- } catch (InterruptedException e) {
- android.util.Log.w(LOG_TAG, e);
- } catch (ExecutionException e) {
- throw new RuntimeException("An error occured while executing doInBackground()",
- e.getCause());
- } catch (CancellationException e) {
- message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
- new PreReadTaskResult<Result>(PreReadTask.this, (Result[]) null));
- message.sendToTarget();
- return;
- } catch (Throwable t) {
- throw new RuntimeException("An error occured while executing "
- + "doInBackground()", t);
- }
- message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
- new PreReadTaskResult<Result>(PreReadTask.this, result));
- message.sendToTarget();
- }
- };
- }
- /**
- * Returns the current status of this task.
- *
- * @return The current status.
- */
- public final Status getStatus() {
- return mStatus;
- }
- /**
- * Override this method to perform a computation on a background thread. The
- * specified parameters are the parameters passed to {@link #execute}
- * by the caller of this task.
- *
- * This method can call {@link #publishProgress} to publish updates
- * on the UI thread.
- *
- * @param params The parameters of the task.
- *
- * @return A result, defined by the subclass of this task.
- *
- * @see #onPreExecute()
- * @see #onPostExecute
- * @see #publishProgress
- */
- protected abstract Result doInBackground(Params... params);
- /**
- * Runs on the UI thread before {@link #doInBackground}.
- *
- * @see #onPostExecute
- * @see #doInBackground
- */
- protected void onPreExecute() {
- }
- /**
- * Runs on the UI thread after {@link #doInBackground}. The
- * specified result is the value returned by {@link #doInBackground}
- * or null if the task was cancelled or an exception occured.
- *
- * @param result The result of the operation computed by {@link #doInBackground}.
- *
- * @see #onPreExecute
- * @see #doInBackground
- */
- @SuppressWarnings({"UnusedDeclaration"})
- protected void onPostExecute(Result result) {
- }
- /**
- * Runs on the UI thread after {@link #publishProgress} is invoked.
- * The specified values are the values passed to {@link #publishProgress}.
- *
- * @param values The values indicating progress.
- *
- * @see #publishProgress
- * @see #doInBackground
- */
- @SuppressWarnings({"UnusedDeclaration"})
- protected void onProgressUpdate(Progress... values) {
- }
- /**
- * Runs on the UI thread after {@link #cancel(boolean)} is invoked.
- *
- * @see #cancel(boolean)
- * @see #isCancelled()
- */
- protected void onCancelled() {
- }
- /**
- * Returns <tt>true</tt> if this task was cancelled before it completed
- * normally.
- *
- * @return <tt>true</tt> if task was cancelled before it completed
- *
- * @see #cancel(boolean)
- */
- public final boolean isCancelled() {
- return mFuture.isCancelled();
- }
- /**
- * Attempts to cancel execution of this task. This attempt will
- * fail if the task has already completed, already been cancelled,
- * or could not be cancelled for some other reason. If successful,
- * and this task has not started when <tt>cancel</tt> is called,
- * this task should never run. If the task has already started,
- * then the <tt>mayInterruptIfRunning</tt> parameter determines
- * whether the thread executing this task should be interrupted in
- * an attempt to stop the task.
- *
- * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
- * task should be interrupted; otherwise, in-progress tasks are allowed
- * to complete.
- *
- * @return <tt>false</tt> if the task could not be cancelled,
- * typically because it has already completed normally;
- * <tt>true</tt> otherwise
- *
- * @see #isCancelled()
- * @see #onCancelled()
- */
- public final boolean cancel(boolean mayInterruptIfRunning) {
- return mFuture.cancel(mayInterruptIfRunning);
- }
- /**
- * Waits if necessary for the computation to complete, and then
- * retrieves its result.
- *
- * @return The computed result.
- *
- * @throws CancellationException If the computation was cancelled.
- * @throws ExecutionException If the computation threw an exception.
- * @throws InterruptedException If the current thread was interrupted
- * while waiting.
- */
- public final Result get() throws InterruptedException, ExecutionException {
- return mFuture.get();
- }
- /**
- * Waits if necessary for at most the given time for the computation
- * to complete, and then retrieves its result.
- *
- * @param timeout Time to wait before cancelling the operation.
- * @param unit The time unit for the timeout.
- *
- * @return The computed result.
- *
- * @throws CancellationException If the computation was cancelled.
- * @throws ExecutionException If the computation threw an exception.
- * @throws InterruptedException If the current thread was interrupted
- * while waiting.
- * @throws TimeoutException If the wait timed out.
- */
- public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
- ExecutionException, TimeoutException {
- return mFuture.get(timeout, unit);
- }
- /**
- * Executes the task with the specified parameters. The task returns
- * itself (this) so that the caller can keep a reference to it.
- *
- * This method must be invoked on the UI thread.
- *
- * @param params The parameters of the task.
- *
- * @return This instance of AsyncTask.
- *
- * @throws IllegalStateException If {@link #getStatus()} returns either
- * {@link FifoAsyncTask.Status#RUNNING} or {@link FifoAsyncTask.Status#FINISHED}.
- */
- public final PreReadTask<Params, Progress, Result> execute(Params... params) {
- if (mStatus != Status.PENDING) {
- switch (mStatus) {
- case RUNNING:
- throw new IllegalStateException("Cannot execute task:"
- + " the task is already running.");
- case FINISHED:
- throw new IllegalStateException("Cannot execute task:"
- + " the task has already been executed "
- + "(a task can be executed only once)");
- }
- }
- mStatus = Status.RUNNING;
- onPreExecute();
- mWorker.mParams = params;
- sExecutor.execute(mFuture);
- return this;
- }
- /**
- * This method can be invoked from {@link #doInBackground} to
- * publish updates on the UI thread while the background computation is
- * still running. Each call to this method will trigger the execution of
- * {@link #onProgressUpdate} on the UI thread.
- *
- * @param values The progress values to update the UI with.
- *
- * @see #onProgressUpdate
- * @see #doInBackground
- */
- protected final void publishProgress(Progress... values) {
- sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
- new PreReadTaskResult<Progress>(this, values)).sendToTarget();
- }
- private void finish(Result result) {
- if (isCancelled()) result = null;
- onPostExecute(result);
- mStatus = Status.FINISHED;
- }
- private static class InternalHandler extends Handler {
- @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
- @Override
- public void handleMessage(Message msg) {
- PreReadTaskResult result = (PreReadTaskResult) msg.obj;
- switch (msg.what) {
- case MESSAGE_POST_RESULT:
- // There is only one result
- result.mTask.finish(result.mData[0]);
- break;
- case MESSAGE_POST_PROGRESS:
- result.mTask.onProgressUpdate(result.mData);
- break;
- case MESSAGE_POST_CANCEL:
- result.mTask.onCancelled();
- break;
- }
- }
- }
- private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
- Params[] mParams;
- }
- @SuppressWarnings({"RawUseOfParameterizedType"})
- private static class PreReadTaskResult<Data> {
- final PreReadTask mTask;
- final Data[] mData;
- PreReadTaskResult(PreReadTask task, Data... data) {
- mTask = task;
- mData = data;
- }
- }
- }
對比AsyncTask我們實際隻修改了一個地方
- private static final ExecutorService sExecutor = Executors.newSingleThreadExecutor(sThreadFactory);//隻有一個工作線程的線程池
通過Executors.newSingleThreadExecutor,我們把PreReadTask的的線程池設置成隻有一個工作線程,並且帶有一個無邊界的緩衝隊列,這一個工作線程以先進先出的順序不斷從緩衝隊列中取出並執行任務。
創建完後台預讀的線程。我們通過一個例子介紹如何使用這個後台預讀線程。
這個例子由兩個Activity組成,WelcomeActivity是歡迎界麵,在歡迎界麵中會停留三秒,在此時我們對數據進行預讀,預讀成功後保存到一個全局的靜態hashmap中。MainActivity是主界麵,在主界麵中顯示一個listview,listview中的圖片是模擬從網絡獲取的,當靜態hashmap中存在數據(也就是已經成功預讀的數據)的時,從hashmap中取,如果不存在,才從網絡獲取。
WelcomeActivity.java 歡迎界麵,停留三秒,預讀數據
- package com.zhuozhuo;
- import android.app.Activity;
- import android.content.Intent;
- import android.graphics.BitmapFactory;
- import android.os.AsyncTask;
- import android.os.Bundle;
- import android.os.Handler;
- import android.widget.Toast;
- public class WelcomeActivity extends Activity {
- private Handler handler = new Handler();
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.welcome);
- for(int i = 0; i < 15; i++) {//預讀15張圖片
- ReadImgTask task = new ReadImgTask();
- task.execute(String.valueOf(i));
- }
- Toast.makeText(getApplicationContext(), "PreReading...", Toast.LENGTH_LONG).show();
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
- startActivity(new Intent(WelcomeActivity.this, MainActivity.class));//啟動MainActivity
- finish();
- }
- }, 3000);//顯示三秒鍾的歡迎界麵
- }
- class ReadImgTask extends PreReadTask<String, Void, Void> {
- @Override
- protected Void doInBackground(String... arg0) {
- try {
- Thread.sleep(200);//模擬網絡延時
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- Data.putData(arg0[0], BitmapFactory.decodeResource(getResources(), R.drawable.icon));//把預讀的數據放到hashmap中
- return null;
- }
- }
- }
MainActivity.java 主界麵,有一個listview,listview中顯示圖片和文字,模擬圖片從網絡異步獲取
- package com.zhuozhuo;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.List;
- import java.util.ListIterator;
- import java.util.Map;
- import android.app.Activity;
- import android.app.AlertDialog;
- import android.app.Dialog;
- import android.app.ListActivity;
- import android.app.ProgressDialog;
- import android.content.Context;
- import android.content.DialogInterface;
- import android.content.Intent;
- import android.database.Cursor;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.AsyncTask;
- import android.os.Bundle;
- import android.provider.ContactsContract;
- import android.util.Log;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.AbsListView;
- import android.widget.AbsListView.OnScrollListener;
- import android.widget.Adapter;
- import android.widget.AdapterView;
- import android.widget.AdapterView.OnItemClickListener;
- import android.widget.BaseAdapter;
- import android.widget.GridView;
- import android.widget.ImageView;
- import android.widget.ListAdapter;
- import android.widget.ListView;
- import android.widget.SimpleAdapter;
- import android.widget.TextView;
- import android.widget.Toast;
- public class MainActivity extends Activity {
- private ListView mListView;
- private List<HashMap<String, Object>> mData;
- private BaseAdapter mAdapter;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mListView = (ListView) findViewById(R.id.listview);
- mData = new ArrayList<HashMap<String,Object>>();
- mAdapter = new CustomAdapter();
- mListView.setAdapter(mAdapter);
- for(int i = 0; i < 100; i++) {//初始化100項數據
- HashMap data = new HashMap<String, Object>();
- data.put("title", "title" + i);
- mData.add(data);
- }
- }
- class CustomAdapter extends BaseAdapter {
- CustomAdapter() {
- }
- @Override
- public int getCount() {
- return mData.size();
- }
- @Override
- public Object getItem(int position) {
- return mData.get(position);
- }
- @Override
- public long getItemId(int position) {
- return 0;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View view = convertView;
- ViewHolder vh;
- if(view == null) {
- view = LayoutInflater.from(MainActivity.this).inflate(R.layout.list_item, null);
- vh = new ViewHolder();
- vh.tv = (TextView) view.findViewById(R.id.textView);
- vh.iv = (ImageView) view.findViewById(R.id.imageView);
- view.setTag(vh);
- }
- vh = (ViewHolder) view.getTag();
- vh.tv.setText((String) mData.get(position).get("title"));
- Bitmap bitmap = (Bitmap) mData.get(position).get("pic");
- if(bitmap != null) {
- vh.iv.setImageBitmap(bitmap);
- }
- else {
- vh.iv.setImageBitmap(null);
- }
- AsyncTask task = (AsyncTask) mData.get(position).get("task");
- if(task == null || task.isCancelled()) {
- mData.get(position).put("task", new GetItemImageTask(position).execute(null));//啟動線程異步獲取圖片
- }
- return view;
- }
- }
- static class ViewHolder {
- TextView tv;
- ImageView iv;
- }
- class GetItemImageTask extends AsyncTask<Void, Void, Void> {//獲取圖片仍采用AsyncTask,這裏的優化放到下篇再討論
- int id;
- GetItemImageTask(int id) {
- this.id = id;
- }
- @Override
- protected Void doInBackground(Void... params) {
- Bitmap bm = (Bitmap) Data.getData(String.valueOf(id));
- if(bm != null) {//如果hashmap中已經有數據,
- mData.get(id).put("pic", bm);
- }
- else {//模擬從網絡獲取
- try {
- Thread.sleep(200);//模擬網絡延時
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- mData.get(id).put("pic", BitmapFactory.decodeResource(getResources(), R.drawable.icon));
- }
- return null;
- }
- protected void onPostExecute (Void result) {
- mAdapter.notifyDataSetChanged();
- }
- }
Data.java 靜態的Hashmap保存預讀數據
- package com.zhuozhuo;
- import java.util.AbstractMap;
- import java.util.HashMap;
- import java.util.concurrent.ConcurrentHashMap;
- public class Data {
- private static AbstractMap<String, Object> mData = new ConcurrentHashMap<String, Object>();
- private Data() {
- }
- public static void putData(String key,Object obj) {
- mData.put(key, obj);
- }
- public static Object getData(String key) {
- return mData.get(key);
- }
- }
運行結果:
從執行結果可以看到,當進入MainActivity時,listview中的第一屏的圖片已經加載好了。
這個簡單例子中還不能很好地體現預讀帶來的用戶體驗的優勢,不過一些應用(如前麵提到過的新聞閱讀類應用),實現了預讀機製,使響應性大大提高,增強了用戶體驗。
總結:
1、通過實現自定義的AsyncTask來避免AsyncTask引起的FC風險和滿足特定的後台異步任務需求
2、實現後台預讀可以提高應用的響應性。
3、使用Executors.newSingleThreadExecutor()創建隻有一個工作隊列的線程池來實現預讀需求。
引申:
1、預讀隊列的工作線程可以不止一個,請根據需求配置自己的線程池。
2、adapter的getview()方法中,我們仍然采用了AsyncTask實現異步獲取圖片,下篇我們將探討更好的解決辦法,在提高響應性的同時,避免了AyncTask帶來的FC風險
3、預讀也要控製成本,存儲空間、耗電和流量都是要考慮的因素。
最後更新:2017-04-03 12:55:33