[转载]Android Asynchronous HTTPClient的实现和优化-Android开发实例教程-eoe Android开发者社区_Android开发论坛

[转载]Android Asynchronous HTTPClient的实现和优化-Android开发实例教程-eoe Android开发者社区_Android开发论坛.

大家知道Android对UI线程的反应时间要求很高,超过5秒钟直接ANR掉,根本不给你机会多等。


Android应用与后端系统的交互是最基本的需求之一,如何实现高效的Asynchronous HTTPClient,确保UI线程在启动任务后交由后端异步处理与服务器端的通信,尤为关键。


Google过几个方案,要么太复杂要么不符合要求,基本都淘汰了,最后发现这一版本的实现不错,就拿来用了。
链接:Android Asynchronous HTTPClient tutorial (如果链接错误,请到网上搜索一下)


后来发现了几个严重的问题,罗列如下:
1. 启用单独的线程后,简直如脱缰的野马,难以驾驭。
现象是:在调试的时候经常发现某个线程死掉(比如在服务器down掉的时候,由于线程无法连接而挂掉)
后果是:只能关掉模拟器,甚至还要重启eclipse,否者两者通信出现问题,再也不能继续联机调试


2. 异常的处理非常弱,Activity层难以捕捉并加以处理。
这个问题跟实现的机制有一定的关系,此实现根本就没提供好的异常处理机制,以便捕捉、反馈、处理合理的可预见性的异常,诸如:

1)UnknownHostException – 谁能确保手机的网络连接一直正常,信号一直满格?
2)HttpResponseException – 后端500的错误,说不定就蹦出来了
3)SocketTimeoutException 超时也是太正常不过了,如果人家在荒山野岭(no 3G)摆弄超大的通信请求
4)诸如此类吧

所以改造就再说难免了。下面我贴出相关代码(import就省了吧这里),并加以简单注释说明,方面大家的理解。

首先定义AsyncHttpClient.java。这里的重点是超时的设置。另外我加了个cancelRequest,用 以在切换Activity后取消掉原有Activity发出的所有的异步请求,因为一般情况下,切换了Activity后是不能再更新那个UI了,否则会 抛出异常,直接导致应用crash掉,不过话说回来,这个cancel我发现好像不是那么给力(any feedback?)。

public class AsyncHttpClient {
private static DefaultHttpClient httpClient;

public static int CONNECTION_TIMEOUT = 2*60*1000;
public static int SOCKET_TIMEOUT  = 2*60*1000;

private static ConcurrentHashMap<Activity,AsyncHttpSender> tasks = new ConcurrentHashMap<Activity,AsyncHttpSender>();
  
public static void sendRequest(
   final Activity currentActitity,
   final HttpRequest request,
   AsyncResponseListener callback) {
  
  sendRequest(currentActitity, request, callback, CONNECTION_TIMEOUT, SOCKET_TIMEOUT);
}

public static void sendRequest(
   final Activity currentActitity,
   final HttpRequest request,
   AsyncResponseListener callback,
   int timeoutConnection,
   int timeoutSocket) {
  
  InputHolder input = new InputHolder(request, callback);
  AsyncHttpSender sender = new AsyncHttpSender();
  sender.execute(input);
  tasks.put(currentActitity, sender);
}

public static void cancelRequest(final Activity currentActitity){
  if(tasks==null || tasks.size()==0) return;
  for (Activity key : tasks.keySet()) {
      if(currentActitity == key){
       AsyncTask<?,?,?> task = tasks.get(key);
       if(task.getStatus()!=null && task.getStatus()!=AsyncTask.Status.FINISHED){
        Log.i(TAG, "AsyncTask of " + task + " cancelled.");
        task.cancel(true);
       }
       tasks.remove(key);
      }
  }
}

public static synchronized HttpClient getClient() {
  if (httpClient == null){   
   //use following code to solve Adapter is detached error
   //refer: [url=http://stackoverflow.com/questions/5317882/android-handling-back-button-during-asynctask]http://stackoverflow.com/questions/5317882/android-handling-back-button-during-asynctask[/url]
   BasicHttpParams params = new BasicHttpParams();
   
   SchemeRegistry schemeRegistry = new SchemeRegistry();
   schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
   final SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory();
   schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
   
   // Set the timeout in milliseconds until a connection is established.
   HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
   // Set the default socket timeout (SO_TIMEOUT) 
   // in milliseconds which is the timeout for waiting for data.
   HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
   
   ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
   httpClient = new DefaultHttpClient(cm, params); 
  }
  return httpClient;
}

}

然后是AsyncHttpSender。这里我用了InputHolder和OutputHolder来进行对象传递,简单包装了下:


/**
* AsyncHttpSender is the AsyncTask implementation
* 
* @author bright_zheng
*
*/
public class AsyncHttpSender extends AsyncTask<InputHolder, Void, OutputHolder> {[/size]
@Override
protected OutputHolder doInBackground(InputHolder... params) {
  HttpEntity entity = null;
  InputHolder input = params[0];
  try {
   HttpResponse response = AsyncHttpClient.getClient().execute((HttpUriRequest) input.getRequest());
   StatusLine status = response.getStatusLine();
   
         if(status.getStatusCode() >= 300) {
          return new OutputHolder(
            new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()),
            input.getResponseListener());
         }
         
   entity = response.getEntity();
   Log.i(TAG, "isChunked:" + entity.isChunked());
            if(entity != null) {
             try{
              entity = new BufferedHttpEntity(entity);
             }catch(Exception e){
              Log.e(<SPAN style="BACKGROUND-COLOR: #ffffff">TAG</SPAN>, e.getMessage(), e);
              //ignore?
             }
            }   
  } catch (ClientProtocolException e) {
   Log.e(<SPAN style="BACKGROUND-COLOR: #ffffff">TAG</SPAN>, e.getMessage(), e);
   return new OutputHolder(e, input.getResponseListener());
  } catch (IOException e) {
   Log.e(<SPAN style="BACKGROUND-COLOR: #ffffff">TAG</SPAN>, e.getMessage(), e);
   return new OutputHolder(e, input.getResponseListener());
  }
  return new OutputHolder(entity, input.getResponseListener());
}

@Override
    protected void onPreExecute(){
  Log.i(<SPAN style="BACKGROUND-COLOR: #ffffff">TAG</SPAN>, "AsyncHttpSender.onPreExecute()");
  super.onPreExecute();
}

@Override
protected void onPostExecute(OutputHolder result) {
  Log.i(<SPAN style="BACKGROUND-COLOR: #ffffff">TAG</SPAN>, "AsyncHttpSender.onPostExecute()");
  super.onPostExecute(result);
  
  if(isCancelled()){
   Log.i(<SPAN style="BACKGROUND-COLOR: #ffffff">TAG</SPAN>, "AsyncHttpSender.onPostExecute(): isCancelled() is true");
   return; //Canceled, do nothing
  }
  
  AsyncResponseListener listener = result.getResponseListener();
  HttpEntity response = result.getResponse();
  Throwable exception = result.getException();
  if(response!=null){
   Log.i(<SPAN style="BACKGROUND-COLOR: #ffffff">TAG</SPAN>, "AsyncHttpSender.onResponseReceived(response)");
   listener.onResponseReceived(response);
  }else{
   Log.i(<SPAN style="BACKGROUND-COLOR: #ffffff">TAG</SPAN>, "AsyncHttpSender.onResponseReceived(exception)");
   listener.onResponseReceived(exception);
  }
}

@Override
    protected void onCancelled(){
  Log.i(<SPAN style="BACKGROUND-COLOR: #ffffff">TAG</SPAN>, "AsyncHttpSender.onCancelled()");
  super.onCancelled();
  //this.isCancelled = true;
}
}

public class OutputHolder{
private HttpEntity response;
private Throwable exception;
private AsyncResponseListener responseListener;

public OutputHolder(HttpEntity response, AsyncResponseListener responseListener){
  this.response = response;
  this.responseListener = responseListener;
}

public OutputHolder(Throwable exception, AsyncResponseListener responseListener){
  this.exception = exception;
  this.responseListener = responseListener;
}
 public HttpEntity getResponse() {
  return response;
}
 public Throwable getException() {
  return exception;
}

public AsyncResponseListener getResponseListener() {
  return responseListener;
}

}

再来看看我们的Call back接口定义, AsyncResponseListener.java:


/**
* The call back interface for  
* 
* @author bright_zheng
*
*/
public interface AsyncResponseListener {
/** Handle successful response */
public void onResponseReceived(HttpEntity response);

/** Handle exception */
public void onResponseReceived(Throwable response);
}

以及抽象Call back的实现,AbstractAsyncResponseListener.java:


/**
* Abstract Async Response Listener implementation
* 
* Subclass should implement at lease two methods.
* 1. onSuccess() to handle the corresponding successful response object
* 2. onFailure() to handle the exception if any
* 
* @author bright_zheng
*
*/
public abstract class AbstractAsyncResponseListener implements AsyncResponseListener{
public static final int RESPONSE_TYPE_STRING = 1;
public static final int RESPONSE_TYPE_JSON_ARRAY = 2;
public static final int RESPONSE_TYPE_JSON_OBJECT = 3;
public static final int RESPONSE_TYPE_STREAM = 4;
private int responseType;

public AbstractAsyncResponseListener(){
  this.responseType = RESPONSE_TYPE_STRING; // default type
}

public AbstractAsyncResponseListener(int responseType){
  this.responseType = responseType;
}

public void onResponseReceived(HttpEntity response){
  try {
   switch(this.responseType){
          case RESPONSE_TYPE_JSON_ARRAY:{
           String responseBody = EntityUtils.toString(response); 
           Log.i(<SPAN style="BACKGROUND-COLOR: #ffffff">TAG</SPAN>, "Return JSON String: " + responseBody);
           JSONArray json = null;
           if(responseBody!=null && responseBody.trim().length()>0){
            json = (JSONArray) new JSONTokener(responseBody).nextValue();
           }
        onSuccess(json);
           break;
          }
          case RESPONSE_TYPE_JSON_OBJECT:{
           String responseBody = EntityUtils.toString(response); 
           Log.i(<SPAN style="BACKGROUND-COLOR: #ffffff">TAG</SPAN>, "Return JSON String: " + responseBody);
           JSONObject json = null;
           if(responseBody!=null && responseBody.trim().length()>0){
            json = (JSONObject) new JSONTokener(responseBody).nextValue();
           }
        onSuccess(json); 
           break;
          }
          case RESPONSE_TYPE_STREAM:{
           onSuccess(response.getContent());
           break;
          }
          default:{
           String responseBody = EntityUtils.toString(response);
           onSuccess(responseBody);
          }         
   }
     } catch(IOException e) {
      onFailure(e);
     } catch (JSONException e) {
      onFailure(e);
  } 
}

public void onResponseReceived(Throwable response){
  onFailure(response);
}

protected void onSuccess(JSONArray response){}

protected void onSuccess(JSONObject response){}

protected void onSuccess(InputStream response){}

protected void onSuccess(String response) {}[/size]
protected void onFailure(Throwable e) {}
}

这样我们使用起来就非常清晰、简单了。

下面贴个简单的客户端用法代码片段:
1、这个是把服务器端响应当stream用的,用以诸如文件、图片下载之类的场景:


AsyncHttpClient.sendRequest(this, request,  
          new AbstractAsyncResponseListener(AbstractAsyncResponseListener.RESPONSE_TYPE_STREAM){
   
   @Override
   protected void onSuccess(InputStream response){
    Bitmap bmp = null;
    try {
     //bmp = decodeFile(response, _facial.getWidth());
     bmp = BitmapFactory.decodeStream(response);
     
     //resize to fit screen
     bmp = resizeImage(bmp, _facial.getWidth(), true);
           
     candidateCache.put(candidate_id, bmp);
           ((ImageView) v).setImageBitmap(bmp);
           
           dialog.dismiss();
    } catch (Exception e) {
     onFailure(e);
    }
   }
   
   @Override
   protected void onFailure(Throwable e) {
    Log.i(TAG, "Error: " + e.getMessage(), e);
    updateErrorMessage(e);
    
    dialog.dismiss();
   }
  });

2、这个是把服务器端响应当JSON用的,用以诸如获取基本文本信息之类的场景:


// Async mode to get hit result
AsyncHttpClient.sendRequest(this, request, 
          new AbstractAsyncResponseListener(AbstractAsyncResponseListener.RESPONSE_TYPE_JSON_ARRAY){
   @Override
   protected void onSuccess(JSONArray response){
    Log.i(TAG, "UploadAndMatch.onSuccess()...");
    candidates = response;
    if(candidates!=null && candidates.length()>0){
           hit_count = candidates.length();
              Log.i(TAG, "HIT: " + hit_count);
              updateStatus(String.format(context.getString(R.string.msg_got_hit), hit_count));
           
     //update UI
           refreshCurrentUI(1);
          }else{
              Log.i(TAG, "No HIT!");
              updateStatus(context.getString(R.string.msg_no_hit));
           
     //update UI
           refreshCurrentUI(0);
          }
   }
   
   @Override
   protected void onFailure(Throwable e) {
    Log.e(TAG, "UploadAndMatch.onFailure(), error: " + e.getMessage(), e);
    updateErrorMessage(e);
    //update UI
          refreshCurrentUI(-1);
   }
   
  });

取自:http://www.iteye.com/topic/1117362

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏