zl程序教程

您现在的位置是:首页 >  移动开发

当前栏目

Android开发笔记之:深入理解多线程AsyncTask

Android多线程笔记开发 深入 理解 AsyncTask
2023-06-13 09:14:54 时间
UnderstandingAsyncTask
AsyncTask是Android1.5Cubake加入的用于实现异步操作的一个类,在此之前只能用JavaSE库中的Thread来实现多线程异步,AsyncTask是Android平台自己的异步工具,融入了Android平台的特性,让异步操作更加的安全,方便和实用。实质上它也是对JavaSE库中Thread的一个封装,加上了平台相关的特性,所以对于所有的多线程异步都强烈推荐使用AsyncTask,因为它考虑,也融入了Android平台的特性,更加的安全和高效。
AsyncTask可以方便的执行异步操作(doInBackground),又能方便的与主线程进行通信,它本身又有良好的封装性,可以进行取消操作(cancel())。关于AsyncTask的使用,文档说的很明白,下面直接上实例。
实例
这个实例用AsyncTask到网络上下载图片,同时显示进度,下载完图片更新UI。
复制代码代码如下:

packagecom.hilton.effectiveandroid.concurrent;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.OutputStream;
importjava.net.HttpURLConnection;
importjava.net.MalformedURLException;
importjava.net.URL;
importandroid.app.Activity;
importandroid.content.Context;
importandroid.graphics.Bitmap;
importandroid.graphics.BitmapFactory;
importandroid.os.AsyncTask;
importandroid.os.Bundle;
importandroid.os.SystemClock;
importandroid.view.View;
importandroid.widget.Button;
importandroid.widget.ImageView;
importandroid.widget.ProgressBar;
importcom.hilton.effectiveandroid.R;
/*
 *AsyncTaskcannotbereused,i.e.ifyouhaveexecutedoneAsyncTask,youmustdiscardit,youcannotexecuteitagain.
 *IfyoutrytoexecuteanexecutedAsyncTask,youwillget"java.lang.IllegalStateException:Cannotexecutetask:thetaskisalreadyrunning"
 *Inthisdemo,ifyouclick"gettheimage"buttontwiceatanytime,youwillreceive"IllegalStateException".
 *Aboutcancellation:
 *YoucancallAsyncTask#cancel()atanytimeduringAsyncTaskexecuting,buttheresultisonPostExecute()isnotcalledafter
 *doInBackground()finishes,whichmeansdoInBackground()isnotstopped.AsyncTask#isCancelled()returnstrueaftercancel()getting
 *called,soifyouwanttoreallycancelthetask,i.e.stopdoInBackground(),youmustcheckthereturnvalueofisCancelled()in
 *doInBackground,whenthereareloopsindoInBackgroundinparticular.
 *ThisisthesametoJavathreading,inwhichisnoeffectivewaytostoparunningthread,onlywaytodoissetaflagtothread,andcheck
 *theflageverytimeinThread#run(),ifflagisset,run()aborts.
 */
publicclassAsyncTaskDemoActivityextendsActivity{
   privatestaticfinalStringImageUrl="http://i1.cqnews.net/sports/attachement/jpg/site82/2011-10-01/2960950278670008721.jpg";
   privateProgressBarmProgressBar;
   privateImageViewmImageView;
   privateButtonmGetImage;
   privateButtonmAbort;

   @Override
   publicvoidonCreate(Bundleicicle){
 super.onCreate(icicle);
 setContentView(R.layout.async_task_demo_activity);
 mProgressBar=(ProgressBar)findViewById(R.id.async_task_progress);
 mImageView=(ImageView)findViewById(R.id.async_task_displayer);
 finalImageLoaderloader=newImageLoader();
 mGetImage=(Button)findViewById(R.id.async_task_get_image);
 mGetImage.setOnClickListener(newView.OnClickListener(){
    publicvoidonClick(Viewv){
  loader.execute(ImageUrl);
    }
 });
 mAbort=(Button)findViewById(R.id.asyc_task_abort);
 mAbort.setOnClickListener(newView.OnClickListener(){
    publicvoidonClick(Viewv){
  loader.cancel(true);
    }
 });
 mAbort.setEnabled(false);
   }

   privateclassImageLoaderextendsAsyncTask<String,Integer,Bitmap>{
 privatestaticfinalStringTAG="ImageLoader";
 @Override
 protectedvoidonPreExecute(){
    //Initializeprogressandimage
    mGetImage.setEnabled(false);
    mAbort.setEnabled(true);
    mProgressBar.setVisibility(View.VISIBLE);
    mProgressBar.setProgress(0);
    mImageView.setImageResource(R.drawable.icon);
 }

 @Override
 protectedBitmapdoInBackground(String...url){
    /*
     *Fuckingridiculousthinghappenedhere,touseanyInternetconnections,eitherviaHttpURLConnection
     *orHttpClient,youmustdeclareINTERNETpermissioninAndroidManifest.xml.Otherwiseyouwillget
     *"UnknownHostException"whenconnectingorothertcp/ip/httpexceptionsratherthan"SecurityException"
     *whichtellsyouneedtodeclareINTERNETpermission.
     */
    try{
  URLu;
  HttpURLConnectionconn=null;
  InputStreamin=null;
  OutputStreamout=null;
  finalStringfilename="local_temp_image";
  try{
     u=newURL(url[0]);
     conn=(HttpURLConnection)u.openConnection();
     conn.setDoInput(true);
     conn.setDoOutput(false);
     conn.setConnectTimeout(20*1000);
     in=conn.getInputStream();
     out=openFileOutput(filename,Context.MODE_PRIVATE);
     byte[]buf=newbyte[8196];
     intseg=0;
     finallongtotal=conn.getContentLength();
     longcurrent=0;
     /*
      *WithoutcheckingisCancelled(),theloopcontinuesuntilreadingwholeimagedone,i.e.theprogress
      *continuesgoupto100.ButonPostExecute()willnotbecalled.
      *BycheckingisCancelled(),wecanstopimmediately,i.e.progressstopsimmediatelywhencancel()iscalled.
      */
     while(!isCancelled()&&(seg=in.read(buf))!=-1){
   out.write(buf,0,seg);
   current+=seg;
   intprogress=(int)((float)current/(float)total*100f);
   publishProgress(progress);
   SystemClock.sleep(1000);
     }
  }finally{
     if(conn!=null){
   conn.disconnect();
     }
     if(in!=null){
   in.close();
     }
     if(out!=null){
   out.close();
     }
  }
  returnBitmapFactory.decodeFile(getFileStreamPath(filename).getAbsolutePath());
    }catch(MalformedURLExceptione){
  e.printStackTrace();
    }catch(IOExceptione){
  e.printStackTrace();
    }
    returnnull;
 }

 @Override
 protectedvoidonProgressUpdate(Integer...progress){
    mProgressBar.setProgress(progress[0]);
 }

 @Override
 protectedvoidonPostExecute(Bitmapimage){
    if(image!=null){
  mImageView.setImageBitmap(image);
    }
    mProgressBar.setProgress(100);
    mProgressBar.setVisibility(View.GONE);
    mAbort.setEnabled(false);
 }
   }
}

运行结果

先后顺序分别是下载前,下载中和下载后
总结
关于怎么使用看文档和这个例子就够了,下面说下,使用时的注意事项:
1.AsyncTask对象不可重复使用,也就是说一个AsyncTask对象只能execute()一次,否则会有异常抛出"java.lang.IllegalStateException:Cannotexecutetask:thetaskisalreadyrunning"

2.在doInBackground()中要检查isCancelled()的返回值,如果你的异步任务是可以取消的话。
cancel()仅仅是给AsyncTask对象设置了一个标识位,当调用了cancel()后,发生的事情只有:AsyncTask对象的标识位变了,和doInBackground()执行完成后,onPostExecute()不会被回调了,而doInBackground()和onProgressUpdate()还是会继续执行直到doInBackground()结束。所以要在doInBackground()中不断的检查isCancellled()的返回值,当其返回true时就停止执行,特别是有循环的时候。如上面的例子,如果把读取数据的isCancelled()检查去掉,图片还是会下载,进度也一直会走,只是最后图片不会放到UI上(因为onPostExecute()没被回调)!

这里的原因其实很好理解,想想JavaSE的Thread吧,是没有方法将其直接Cacncel掉的,那些线程取消也无非就是给线程设置标识位,然后在run()方法中不断的检查标识而已。

3.如果要在应用程序中使用网络,一定不要忘记在AndroidManifest中声明INTERNET权限,否则会报出很诡异的异常信息,比如上面的例子,如果把INTERNET权限拿掉会抛出"UnknownHostException"。刚开始很疑惑,因为模拟器是可以正常上网的,后来Google了下才发现原来是没权限,但是疑问还是没有消除,既然没有声明网络权限,为什么不直接提示无网络权限呢?

对比JavaSE的Thread
Thread是非常原始的类,它只有一个run()方法,一旦开始,无法停止,它仅适合于一个非常独立的异步任务,也即不需要与主线程交互,对于其他情况,比如需要取消或与主线程交互,都需添加额外的代码来实现,并且还要注意同步的问题。
而AsyncTask是封装好了的,可以直接拿来用,如果你仅执行独立的异步任务,可以仅实现doInBackground()。
所以,当有一个非常独立的任务时,可以考虑使用Thread,其他时候,尽可能的用AsyncTask。