文章转载只能用于非商业性质,且不能带有虚拟货币、积分、注册等附加条件,转载须注明出处:http://blog.csdn.net/flowingflying/
之前,我们直接在activity中执行http通信,在通信过程中可能会出现连接超时、socket超时等情况,超时阈值一般是秒级,例如AndroidHttpClient中设置的20秒,如果出现超时,就会停在execute()语句中,等待20秒仍没有response,才给出异常。整个activity UI就会在这20秒内冻结,这样的用户体验显然不能接受,因此http应当放置在后台线程中处理。后台线程的处理可以参考Android学习笔记(三一):线程:Message和Runnable和Android学习笔记(三二):线程:后台异步任务AsyncTask。本次我们将聚焦如何AsyncTask中实现http通信,关于AsyncTask,还会在未来中详细讨论。
之前的例子,我们都是在onCreate()中进行http的通信,如果出现连接超时,会一直等到异常出现,但是如果我们在onStart()之后,即UI可以和用户进行交互后再进行http通信,情况又会如何?
我们将之前的小例子改改,通过按Button来触发http通信,连接一个空的IP地址来测试一下。在等待连接的过程中,如果用户进行了UI操作(例如按返回键或者menu键),Android的UI处理是在且只在main thread(也成为UI线程)中进行,系统如果在5秒内不能对用户的UI操作进行响应,则会进行ANR(Application Not Responsing)处理,弹框让用户选择是否强制关闭activity。
如果后台线程需要更新UI,可以考虑使用AsyncTask,它可以向主线程提供Callback,很方便进行UI处理。callback可以在后台线程运行的开始、中间和结束。
小例子有一个activity和一个AsyncTask类组成,用户按activity上button触发AsyncTask在后台线程从互联网中下载一个大图片。AsyncTask在下载开始、中间和结束后台线程均可以和主线程交互,将进度信息以及获取的图片在activity中显示。(这鱼是年前1.27和同事在外面打野食所拍,4.5斤的鲩鱼,一条鱼一顿饭。饭店没什么人,说做完今天就放假,难得清静,哈哈。)
AsyncTask是一个抽象类,需要具体实现,这个类的参数有些特别,就是参数的类型可以自行设定。在若干方法中参数带有“…”,这表示参数的数目不固定,例如(String… arg),可以是(“hello”),也可以是(“hello”,“world”),也可以是三个参数、四个参数,分别用arg[0],arg[1],...来表示。
AsyncTask非常适合需要将中间值和最终结果返回给用户,在UI呈现的情况,起执行过程如图所示。
类的参数有三,本例为<String,Integer,Bitmap>。
/** AsyncTask是一个抽象类,需要具体实现。*/
public class DownloadImageTask extends AsyncTask<String, Integer,Bitmap>{
//【步骤0】AsyncTask适合于在UI中有反馈,因此,通过构造函数要获的相应的activity的句柄
private Context mContext = null; //context就是activity,通过它可以在AsyncTask中对activity中的view进行操作
public DownloadImageTask(Context context){
mContext = context;
}
//步骤4: 后台线程执行完后,将结果带给主线程的onPostExcute(),在UI上对后台运行结果进行处理
protected void onPostExecute(Bitmap result) {
Log.v("DownloadImageTask","onPostExecute() is called, run in " + Thread.currentThread().getName());
if(result != null){
ImageView image = (ImageView)((Activity)mContext).findViewById(R.id.image);
image.setImageBitmap(result);
}else{
TextView errorTv = (TextView)((Activity)mContext).findViewById(R.id.error_msg);
errorTv.setText("Error: Can't get image!");
}
}
//步骤1: Override onPreExecute(),在中处理setup所需的工作。要注意,这是在main thread中运行的。
protected void onPreExecute() {
Log.v("DownloadImageTask","onPreExecute() is called, run in " + Thread.currentThread().getName());
}
//步骤3:后台线程中运行代码库通过publicProgress()触发UI线程的onProgressUpdate(),将信息反馈给UI。
protected void onProgressUpdate(Integer... values) {
Log.v("DownloadImageTask","onProgressUpdate(" + values[0] +") is called, run in " + Thread.currentThread().getName());
TextView tv = (TextView)((Activity)mContext).findViewById(R.id.text);
tv.setText("Progress so far:" + values[0]);
}
//步骤2:Override doInBackground(),在进行初始化的工作后,将创建线程,并在线程中运行doInBackground()中的代码,至于线程建立和操作的相关代码均以封装好,开发者不需要处理。如果在线程的运行中,我们需要有些信息反馈给UI,由于不能够在线程中去操作主线程的UI,通过publishProgress(),它将触发运行在主线程中onProgressUpdate()来进行UI操控。
protected Bitmap doInBackground(String... arg0) {
Log.v("DownloadImageTask","doInBackground() is called, run in " + Thread.currentThread().getName());
return downloadImage(arg0);
}
private Bitmap downloadImage(String... urls){
HttpClient httpClient = CustomHttpClient.getCustomHttpClient();
try{
HttpGet request = new HttpGet(urls[0]);
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 60000);
request.setParams(params);
publishProgress(25);
HttpResponse response = httpClient.execute(request);
publishProgress(50);
...... //这里本应进行resposne code的判断,小例子目标是考察AsyncTask,一切从简,就略过这部分
byte[] image = EntityUtils.toByteArray(response.getEntity());
publishProgress(75);
Bitmap mBitmap = BitmapFactory.decodeByteArray(image, 0, image.length);
publishProgress(100);
return mBitmap;
}catch(Exception e){
e.printStackTrace();
}
return null;
}
}
小例子的Activity代码如下:
public class DownloadImageActivity extends Activity{
private DownloadImageTask download = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_image);
}
//触发下载图片,通过状态监测避免重复创建后台线程。此外,需要注意,AsyncTask指定被执行一次,即一个对象只能运行execute()方法一次,否则会抛出异常。在本例,我们可以再次下载图片,此时,我们需要创建一个新对象。
public void onClickedAction(View v){
if(download != null){
//可以获取AsyncTask的运行情况,包括RUNNING、PENDING、FINISHED。
AsyncTask.Status status = download.getStatus();
Log.v("DownloadImageActivity","status is " + status);
if(status != AsyncTask.Status.FINISHED){
Log.v("DownloadImageActivity","It is doing, not need to start again");
return;
}
}
ImageView image = (ImageView)findViewById(R.id.image);
image.setImageBitmap(null);
//创建AsyncTask的实例,并执行execute()。
download = new DownloadImageTask(this);
download.execute("http://ww1.sinaimg.cn/large/5cf79a90jw1ecy18vfwlrj20xc18gtgd.jpg");
}
}
我们通过LogCat来查看代码具体运行在哪个线程。
在某种情况下,我们可能需要终止后台进行的运行,这样可以调用cancel(),例如task.cancel(true);,我们可以用task.isCanceled()来询问是否已经药企后台线程AsynTask停止,避免重复叫停。执行cancel(),将会触发AysnTask中的onCanceled(),我们可以在当中设置某些状态,例如bool isCancel=true,在后台运行的doInBackground()中,检测该状态位来判断是否继续运行。
在上面的例子,后台线程运行结束后,通过onPostExcute()的方式,向UI线程返回结果,这是常规的方式。AsynTask还提供get(),主动获取结果的方式。我们将小例子改一下:
public void onClickedAction(View v){
……
checkDownload();
}
//这是测试主线程主动查询结果
private void checkDownload(){
try{
Bitmap bp = download.get(); //主线程将在此主动获取后台线程运行的结果,并一直等待到有结果。因此如果后台线程此时有onProgressUpdate()处理UI,这些处理将会被block,直至download()等到结果,这些被阻塞的UI处理才会被执行,也包括最后的onPostExcute(),所以如果采用get()方式,一般不在onPostExcute()中进行UI操作。
ImageView image = (ImageView)findViewById(R.id.image);
image.setImageBitmap(bp);
}catch(Exception e){
e.printStackTrace();
}
}
为了避免get()中等待的时间过长,可以设置超时,如下:
Bitmap bp = download.get(2000,TimeUnit.MILLISECONDS);
上面例子是如果在2秒内仍得不到结果,将抛出java.uril.concurrent.TimeoutException的异常,在异常处理中,我们可能会主动cancel掉后台线程。
本博文涉及的例子代码,可以在Pro Android学习:Http service小例子中下载。
相关链接: 我的Android开发相关文章