Android多线程任务优化2:实现后台预读线程
导语:从上一篇《多线程任务的优化1:探讨AsyncTask的缺陷》我们了解到,使用AsyncTask有导致应用FC的风险,而且AsyncTask并不能满足我们一些特定的需求。下面我们介绍一种通过模仿AsyncTask的封装方式,实现一个后台预读数据的线程。
概述:在空闲时对获取成本较高的数据(如要读取本地或网络资源)进行预读是提高性能的有效手段。为了给用户带来更好的交互体验,提高响应性,很多网络应用(如新闻阅读类应用)都在启动的时候进行预读,把网络数据缓存到sdcard或者内存中。
例子:下面介绍一个实现预读的例子,打开应用之后会有一个欢迎界面,在打开欢迎界面的同时我们在后台启动预读线程,预读下一个Activity需要显示的数据,预读数据保存到一个静态的Hashmap中。
首先要编写我们的后台预读线程,遵循不重复造轮子的原则,我们对AsyncTask稍作修改就可以满足需求。下面是我们自定义的PreReadTask类代码:
PreReadTask.java 实现预读线程
- packagecom.zhuozhuo;
- importjava.util.concurrent.ExecutorService;
- importjava.util.concurrent.Executors;
- importjava.util.concurrent.PriorityBlockingQueue;
- importjava.util.concurrent.RejectedExecutionHandler;
- importjava.util.concurrent.SynchronousQueue;
- importjava.util.concurrent.ThreadPoolExecutor;
- importjava.util.concurrent.TimeUnit;
- importjava.util.concurrent.BlockingQueue;
- importjava.util.concurrent.LinkedBlockingQueue;
- importjava.util.concurrent.ThreadFactory;
- importjava.util.concurrent.Callable;
- importjava.util.concurrent.FutureTask;
- importjava.util.concurrent.ExecutionException;
- importjava.util.concurrent.TimeoutException;
- importjava.util.concurrent.CancellationException;
- importjava.util.concurrent.atomic.AtomicInteger;
- importandroid.content.SyncResult;
- importandroid.os.Process;
- importandroid.os.Handler;
- importandroid.os.Message;
- publicabstractclassPreReadTask<Params,Progress,Result>{
- privatestaticfinalStringLOG_TAG="FifoAsyncTask";
- //privatestaticfinalintCORE_POOL_SIZE=5;
- //privatestaticfinalintMAXIMUM_POOL_SIZE=5;
- //privatestaticfinalintKEEP_ALIVE=1;
- //privatestaticfinalBlockingQueue<Runnable>sWorkQueue=
- //newLinkedBlockingQueue<Runnable>();
- //
- privatestaticfinalThreadFactorysThreadFactory=newThreadFactory(){
- privatefinalAtomicIntegermCount=newAtomicInteger(1);
- publicThreadnewThread(Runnabler){
- returnnewThread(r,"PreReadTask#"+mCount.getAndIncrement());
- }
- };
- privatestaticfinalExecutorServicesExecutor=Executors.newSingleThreadExecutor(sThreadFactory);//只有一个工作线程的线程池
- privatestaticfinalintMESSAGE_POST_RESULT=0x1;
- privatestaticfinalintMESSAGE_POST_PROGRESS=0x2;
- privatestaticfinalintMESSAGE_POST_CANCEL=0x3;
- privatestaticfinalInternalHandlersHandler=newInternalHandler();
- privatefinalWorkerRunnable<Params,Result>mWorker;
- privatefinalFutureTask<Result>mFuture;
- privatevolatileStatusmStatus=Status.PENDING;
- /**
- *Indicatesthecurrentstatusofthetask.Eachstatuswillbesetonlyonce
- *duringthelifetimeofatask.
- */
- publicenumStatus{
- /**
- *Indicatesthatthetaskhasnotbeenexecutedyet.
- */
- PENDING,
- /**
- *Indicatesthatthetaskisrunning.
- */
- RUNNING,
- /**
- *Indicatesthat{@linkFifoAsyncTask#onPostExecute}hasfinished.
- */
- FINISHED,
- }
- /**
- *Createsanewasynchronoustask.ThisconstructormustbeinvokedontheUIthread.
- */
- publicPreReadTask(){
- mWorker=newWorkerRunnable<Params,Result>(){
- publicResultcall()throwsException{
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- returndoInBackground(mParams);
- }
- };
- mFuture=newFutureTask<Result>(mWorker){
- @Override
- protectedvoiddone(){
- Messagemessage;
- Resultresult=null;
- try{
- result=get();
- }catch(InterruptedExceptione){
- android.util.Log.w(LOG_TAG,e);
- }catch(ExecutionExceptione){
- thrownewRuntimeException("AnerroroccuredwhileexecutingdoInBackground()",
- e.getCause());
- }catch(CancellationExceptione){
- message=sHandler.obtainMessage(MESSAGE_POST_CANCEL,
- newPreReadTaskResult<Result>(PreReadTask.this,(Result[])null));
- message.sendToTarget();
- return;
- }catch(Throwablet){
- thrownewRuntimeException("Anerroroccuredwhileexecuting"
- +"doInBackground()",t);
- }
- message=sHandler.obtainMessage(MESSAGE_POST_RESULT,
- newPreReadTaskResult<Result>(PreReadTask.this,result));
- message.sendToTarget();
- }
- };
- }
- /**
- *Returnsthecurrentstatusofthistask.
- *
- *@returnThecurrentstatus.
- */
- publicfinalStatusgetStatus(){
- returnmStatus;
- }
- /**
- *Overridethismethodtoperformacomputationonabackgroundthread.The
- *specifiedparametersaretheparameterspassedto{@link#execute}
- *bythecallerofthistask.
- *
- *Thismethodcancall{@link#publishProgress}topublishupdates
- *ontheUIthread.
- *
- *@paramparamsTheparametersofthetask.
- *
- *@returnAresult,definedbythesubclassofthistask.
- *
- *@see#onPreExecute()
- *@see#onPostExecute
- *@see#publishProgress
- */
- protectedabstractResultdoInBackground(Params...params);
- /**
- *RunsontheUIthreadbefore{@link#doInBackground}.
- *
- *@see#onPostExecute
- *@see#doInBackground
- */
- protectedvoidonPreExecute(){
- }
- /**
- *RunsontheUIthreadafter{@link#doInBackground}.The
- *specifiedresultisthevaluereturnedby{@link#doInBackground}
- *ornullifthetaskwascancelledoranexceptionoccured.
- *
- *@paramresultTheresultoftheoperationcomputedby{@link#doInBackground}.
- *
- *@see#onPreExecute
- *@see#doInBackground
- */
- @SuppressWarnings({"UnusedDeclaration"})
- protectedvoidonPostExecute(Resultresult){
- }
- /**
- *RunsontheUIthreadafter{@link#publishProgress}isinvoked.
- *Thespecifiedvaluesarethevaluespassedto{@link#publishProgress}.
- *
- *@paramvaluesThevaluesindicatingprogress.
- *
- *@see#publishProgress
- *@see#doInBackground
- */
- @SuppressWarnings({"UnusedDeclaration"})
- protectedvoidonProgressUpdate(Progress...values){
- }
- /**
- *RunsontheUIthreadafter{@link#cancel(boolean)}isinvoked.
- *
- *@see#cancel(boolean)
- *@see#isCancelled()
- */
- protectedvoidonCancelled(){
- }
- /**
- *Returns<tt>true</tt>ifthistaskwascancelledbeforeitcompleted
- *normally.
- *
- *@return<tt>true</tt>iftaskwascancelledbeforeitcompleted
- *
- *@see#cancel(boolean)
- */
- publicfinalbooleanisCancelled(){
- returnmFuture.isCancelled();
- }
- /**
- *Attemptstocancelexecutionofthistask.Thisattemptwill
- *failifthetaskhasalreadycompleted,alreadybeencancelled,
- *orcouldnotbecancelledforsomeotherreason.Ifsuccessful,
- *andthistaskhasnotstartedwhen<tt>cancel</tt>iscalled,
- *thistaskshouldneverrun.Ifthetaskhasalreadystarted,
- *thenthe<tt>mayInterruptIfRunning</tt>parameterdetermines
- *whetherthethreadexecutingthistaskshouldbeinterruptedin
- *anattempttostopthetask.
- *
- *@parammayInterruptIfRunning<tt>true</tt>ifthethreadexecutingthis
- *taskshouldbeinterrupted;otherwise,in-progresstasksareallowed
- *tocomplete.
- *
- *@return<tt>false</tt>ifthetaskcouldnotbecancelled,
- *typicallybecauseithasalreadycompletednormally;
- *<tt>true</tt>otherwise
- *
- *@see#isCancelled()
- *@see#onCancelled()
- */
- publicfinalbooleancancel(booleanmayInterruptIfRunning){
- returnmFuture.cancel(mayInterruptIfRunning);
- }
- /**
- *Waitsifnecessaryforthecomputationtocomplete,andthen
- *retrievesitsresult.
- *
- *@returnThecomputedresult.
- *
- *@throwsCancellationExceptionIfthecomputationwascancelled.
- *@throwsExecutionExceptionIfthecomputationthrewanexception.
- *@throwsInterruptedExceptionIfthecurrentthreadwasinterrupted
- *whilewaiting.
- */
- publicfinalResultget()throwsInterruptedException,ExecutionException{
- returnmFuture.get();
- }
- /**
- *Waitsifnecessaryforatmostthegiventimeforthecomputation
- *tocomplete,andthenretrievesitsresult.
- *
- *@paramtimeoutTimetowaitbeforecancellingtheoperation.
- *@paramunitThetimeunitforthetimeout.
- *
- *@returnThecomputedresult.
- *
- *@throwsCancellationExceptionIfthecomputationwascancelled.
- *@throwsExecutionExceptionIfthecomputationthrewanexception.
- *@throwsInterruptedExceptionIfthecurrentthreadwasinterrupted
- *whilewaiting.
- *@throwsTimeoutExceptionIfthewaittimedout.
- */
- publicfinalResultget(longtimeout,TimeUnitunit)throwsInterruptedException,
- ExecutionException,TimeoutException{
- returnmFuture.get(timeout,unit);
- }
- /**
- *Executesthetaskwiththespecifiedparameters.Thetaskreturns
- *itself(this)sothatthecallercankeepareferencetoit.
- *
- *ThismethodmustbeinvokedontheUIthread.
- *
- *@paramparamsTheparametersofthetask.
- *
- *@returnThisinstanceofAsyncTask.
- *
- *@throwsIllegalStateExceptionIf{@link#getStatus()}returnseither
- *{@linkFifoAsyncTask.Status#RUNNING}or{@linkFifoAsyncTask.Status#FINISHED}.
- */
- publicfinalPreReadTask<Params,Progress,Result>execute(Params...params){
- if(mStatus!=Status.PENDING){
- switch(mStatus){
- caseRUNNING:
- thrownewIllegalStateException("Cannotexecutetask:"
- +"thetaskisalreadyrunning.");
- caseFINISHED:
- thrownewIllegalStateException("Cannotexecutetask:"
- +"thetaskhasalreadybeenexecuted"
- +"(ataskcanbeexecutedonlyonce)");
- }
- }
- mStatus=Status.RUNNING;
- onPreExecute();
- mWorker.mParams=params;
- sExecutor.execute(mFuture);
- returnthis;
- }
- /**
- *Thismethodcanbeinvokedfrom{@link#doInBackground}to
- *publishupdatesontheUIthreadwhilethebackgroundcomputationis
- *stillrunning.Eachcalltothismethodwilltriggertheexecutionof
- *{@link#onProgressUpdate}ontheUIthread.
- *
- *@paramvaluesTheprogressvaluestoupdatetheUIwith.
- *
- *@see#onProgressUpdate
- *@see#doInBackground
- */
- protectedfinalvoidpublishProgress(Progress...values){
- sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
- newPreReadTaskResult<Progress>(this,values)).sendToTarget();
- }
- privatevoidfinish(Resultresult){
- if(isCancelled())result=null;
- onPostExecute(result);
- mStatus=Status.FINISHED;
- }
- privatestaticclassInternalHandlerextendsHandler{
- @SuppressWarnings({"unchecked","RawUseOfParameterizedType"})
- @Override
- publicvoidhandleMessage(Messagemsg){
- PreReadTaskResultresult=(PreReadTaskResult)msg.obj;
- switch(msg.what){
- caseMESSAGE_POST_RESULT:
- //Thereisonlyoneresult
- result.mTask.finish(result.mData[0]);
- break;
- caseMESSAGE_POST_PROGRESS:
- result.mTask.onProgressUpdate(result.mData);
- break;
- caseMESSAGE_POST_CANCEL:
- result.mTask.onCancelled();
- break;
- }
- }
- }
- privatestaticabstractclassWorkerRunnable<Params,Result>implementsCallable<Result>{
- Params[]mParams;
- }
- @SuppressWarnings({"RawUseOfParameterizedType"})
- privatestaticclassPreReadTaskResult<Data>{
- finalPreReadTaskmTask;
- finalData[]mData;
- PreReadTaskResult(PreReadTasktask,Data...data){
- mTask=task;
- mData=data;
- }
- }
- }
对比AsyncTask我们实际只修改了一个地方
- privatestaticfinalExecutorServicesExecutor=Executors.newSingleThreadExecutor(sThreadFactory);//只有一个工作线程的线程池
通过Executors.newSingleThreadExecutor,我们把PreReadTask的的线程池设置成只有一个工作线程,并且带有一个无边界的缓冲队列,这一个工作线程以先进先出的顺序不断从缓冲队列中取出并执行任务。
创建完后台预读的线程。我们通过一个例子介绍如何使用这个后台预读线程。
这个例子由两个Activity组成,WelcomeActivity是欢迎界面,在欢迎界面中会停留三秒,在此时我们对数据进行预读,预读成功后保存到一个全局的静态hashmap中。MainActivity是主界面,在主界面中显示一个listview,listview中的图片是模拟从网络获取的,当静态hashmap中存在数据(也就是已经成功预读的数据)的时,从hashmap中取,如果不存在,才从网络获取。
WelcomeActivity.java 欢迎界面,停留三秒,预读数据
- packagecom.zhuozhuo;
- importandroid.app.Activity;
- importandroid.content.Intent;
- importandroid.graphics.BitmapFactory;
- importandroid.os.AsyncTask;
- importandroid.os.Bundle;
- importandroid.os.Handler;
- importandroid.widget.Toast;
- publicclassWelcomeActivityextendsActivity{
- privateHandlerhandler=newHandler();
- @Override
- publicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.welcome);
- for(inti=0;i<15;i++){//预读15张图片
- ReadImgTasktask=newReadImgTask();
- task.execute(String.valueOf(i));
- }
- Toast.makeText(getApplicationContext(),"PreReading...",Toast.LENGTH_LONG).show();
- handler.postDelayed(newRunnable(){
- @Override
- publicvoidrun(){
- startActivity(newIntent(WelcomeActivity.this,MainActivity.class));//启动MainActivity
- finish();
- }
- },3000);//显示三秒钟的欢迎界面
- }
- classReadImgTaskextendsPreReadTask<String,Void,Void>{
- @Override
- protectedVoiddoInBackground(String...arg0){
- try{
- Thread.sleep(200);//模拟网络延时
- }catch(InterruptedExceptione){
- //TODOAuto-generatedcatchblock
- e.printStackTrace();
- }
- Data.putData(arg0[0],BitmapFactory.decodeResource(getResources(),R.drawable.icon));//把预读的数据放到hashmap中
- returnnull;
- }
- }
- }
MainActivity.java 主界面,有一个listview,listview中显示图片和文字,模拟图片从网络异步获取
- packagecom.zhuozhuo;
- importjava.util.ArrayList;
- importjava.util.Collection;
- importjava.util.HashMap;
- importjava.util.Iterator;
- importjava.util.List;
- importjava.util.ListIterator;
- importjava.util.Map;
- importandroid.app.Activity;
- importandroid.app.AlertDialog;
- importandroid.app.Dialog;
- importandroid.app.ListActivity;
- importandroid.app.ProgressDialog;
- importandroid.content.Context;
- importandroid.content.DialogInterface;
- importandroid.content.Intent;
- importandroid.database.Cursor;
- importandroid.graphics.Bitmap;
- importandroid.graphics.BitmapFactory;
- importandroid.os.AsyncTask;
- importandroid.os.Bundle;
- importandroid.provider.ContactsContract;
- importandroid.util.Log;
- importandroid.view.LayoutInflater;
- importandroid.view.View;
- importandroid.view.ViewGroup;
- importandroid.widget.AbsListView;
- importandroid.widget.AbsListView.OnScrollListener;
- importandroid.widget.Adapter;
- importandroid.widget.AdapterView;
- importandroid.widget.AdapterView.OnItemClickListener;
- importandroid.widget.BaseAdapter;
- importandroid.widget.GridView;
- importandroid.widget.ImageView;
- importandroid.widget.ListAdapter;
- importandroid.widget.ListView;
- importandroid.widget.SimpleAdapter;
- importandroid.widget.TextView;
- importandroid.widget.Toast;
- publicclassMainActivityextendsActivity{
- privateListViewmListView;
- privateList<HashMap<String,Object>>mData;
- privateBaseAdaptermAdapter;
- @Override
- publicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mListView=(ListView)findViewById(R.id.listview);
- mData=newArrayList<HashMap<String,Object>>();
- mAdapter=newCustomAdapter();
- mListView.setAdapter(mAdapter);
- for(inti=0;i<100;i++){//初始化100项数据
- HashMapdata=newHashMap<String,Object>();
- data.put("title","title"+i);
- mData.add(data);
- }
- }
- classCustomAdapterextendsBaseAdapter{
- CustomAdapter(){
- }
- @Override
- publicintgetCount(){
- returnmData.size();
- }
- @Override
- publicObjectgetItem(intposition){
- returnmData.get(position);
- }
- @Override
- publiclonggetItemId(intposition){
- return0;
- }
- @Override
- publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
- Viewview=convertView;
- ViewHoldervh;
- if(view==null){
- view=LayoutInflater.from(MainActivity.this).inflate(R.layout.list_item,null);
- vh=newViewHolder();
- 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"));
- Bitmapbitmap=(Bitmap)mData.get(position).get("pic");
- if(bitmap!=null){
- vh.iv.setImageBitmap(bitmap);
- }
- else{
- vh.iv.setImageBitmap(null);
- }
- AsyncTasktask=(AsyncTask)mData.get(position).get("task");
- if(task==null||task.isCancelled()){
- mData.get(position).put("task",newGetItemImageTask(position).execute(null));//启动线程异步获取图片
- }
- returnview;
- }
- }
- staticclassViewHolder{
- TextViewtv;
- ImageViewiv;
- }
- classGetItemImageTaskextendsAsyncTask<Void,Void,Void>{//获取图片仍采用AsyncTask,这里的优化放到下篇再讨论
- intid;
- GetItemImageTask(intid){
- this.id=id;
- }
- @Override
- protectedVoiddoInBackground(Void...params){
- Bitmapbm=(Bitmap)Data.getData(String.valueOf(id));
- if(bm!=null){//如果hashmap中已经有数据,
- mData.get(id).put("pic",bm);
- }
- else{//模拟从网络获取
- try{
- Thread.sleep(200);//模拟网络延时
- }catch(InterruptedExceptione){
- e.printStackTrace();
- }
- mData.get(id).put("pic",BitmapFactory.decodeResource(getResources(),R.drawable.icon));
- }
- returnnull;
- }
- protectedvoidonPostExecute(Voidresult){
- mAdapter.notifyDataSetChanged();
- }
- }
Data.java 静态的Hashmap保存预读数据
- packagecom.zhuozhuo;
- importjava.util.AbstractMap;
- importjava.util.HashMap;
- importjava.util.concurrent.ConcurrentHashMap;
- publicclassData{
- privatestaticAbstractMap<String,Object>mData=newConcurrentHashMap<String,Object>();
- privateData(){
- }
- publicstaticvoidputData(Stringkey,Objectobj){
- mData.put(key,obj);
- }
- publicstaticObjectgetData(Stringkey){
- returnmData.get(key);
- }
- }
运行结果:
从执行结果可以看到,当进入MainActivity时,listview中的第一屏的图片已经加载好了。
这个简单例子中还不能很好地体现预读带来的用户体验的优势,不过一些应用(如前面提到过的新闻阅读类应用),实现了预读机制,使响应性大大提高,增强了用户体验。
总结:
1、通过实现自定义的AsyncTask来避免AsyncTask引起的FC风险和满足特定的后台异步任务需求
2、实现后台预读可以提高应用的响应性。
3、使用Executors.newSingleThreadExecutor()创建只有一个工作队列的线程池来实现预读需求。
引申:
1、预读队列的工作线程可以不止一个,请根据需求配置自己的线程池。
2、adapter的getview()方法中,我们仍然采用了AsyncTask实现异步获取图片,下篇我们将探讨更好的解决办法,在提高响应性的同时,避免了AyncTask带来的FC风险
3、预读也要控制成本,存储空间、耗电和流量都是要考虑的因素。