前面几篇博文简单的介绍了一些常见的Http的操作,这些操作几乎都是在新开的线程中进行的网络请求,并在日志中打印出获取到的网络数据。那么,问题来了!(呃~感觉下一句是蓝翔有木有?)如何在把获取到的网络数据显示在UI界面上呢?如果按照前几篇博文的例子,并在主线程中直接对子线程获取的网络数据直接进行操作就会发现一个问题,那就是在主线程中根本就获取不到子线程得到的从服务器返回的数据。因为,网络操作属于耗时操作,为了不阻塞主线程而放在子线程中,当主线程中的代码执行完后子线程未必就获取到服务器返回的数据了。所以,为了解决这样的问题我们通常在Http的操作中加上异步消息机制,并且为了简化操作对其进行简单的封装,加上回调机制。
这篇博文就以HttpClient访问百度首页为例子,对之前博文中的Http操作进一步的完善。
首先,先回忆一下使用HttpClient的最简单的步骤或者说是过程:
(这里的strurl为"http://www.baidu.com",str为从服务器返回的数据。)
HttpClient client = new DefaultHttpClient(); HttpGet request = new HttpGet(strurl); HttpResponse response= client.execute(request); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { String str = EntityUtils.toString(response.getEntity()); }
下面将基于上面的这些操作来实现简易封装:
首先加入回调机制:
(如果对回调还不太熟悉,请参考博文《java回调机制解析》)
public interface HttpCallback { void onSuccess(Object result); void onFailure(Exception result); }
接下来,实现一个Request类:
这个类一共有三个属性:
method是在该类中用enum类型限定的RequestMethod类型的对象,用于设置请求方式。
url就是网络请求的路径。
callback为回调接口对象,用于在网络请求中获取到数据后将数据传递至调用处。
public class Request { public RequestMethod method; public String url; public HttpCallback callback; public Request(String url, RequestMethod method) { this.method = method; this.url = url; } public Request(String url) { this.method = RequestMethod.GET; this.url = url; } public enum RequestMethod { GET, POST, DELETE, PUT } public void setCallBack(HttpCallback callback) { this.callback = callback; } public void execute() { RequstTask task = new RequstTask(this); task.execute(); } }
从上面的代码可以看出,该类的两个构造函数,都需要传入URL,其中一个构造函数可以设置请求方式,另一个设置默认请求方式GET。
在该类中有一个execute()方法,在这个方法中RequestTask类继承于AsyncTask,它的作用就是在RequestTask中进行网络请求。
RequestTask代码如下:
public class RequstTask extends AsyncTask<Void, integer, Object> { private Request requset; public RequstTask(Request requset) { this.requset = requset; } @Override protected void onPreExecute() { // TODO Auto-generated method stub super.onPreExecute(); } @Override protected Object doInBackground(Void... params) { try { HttpResponse response = HttpClientUtils.execute(requset); if (requset.callback != null) { //如果进一步抽象化回调接口,使其子抽象类能分别处理多种格式的数据(如:JSON、XML、String、File),可以更方便 //这里直接使用返回String类型的数据 if(response.getStatusLine().getStatusCode()==HttpStatus.SC_OK){ return EntityUtils.toString(response.getEntity()); } return "请求失败"; } else { return null; } } catch (Exception e) { return e; } } @Override protected void onPostExecute(Object result) { if (requset.callback != null) { if (result instanceof Exception) { requset.callback.onFailure((Exception) result); } else { requset.callback.onSuccess(result); } } } @Override protected void onProgressUpdate(integer... values) { // TODO Auto-generated method stub super.onProgressUpdate(values); } }
这个类的构造函数需要传递一个Request对象,会根据这个Request对象的method属性确定请求方式、url属性确定请求路径,根据callback属性的有无来判断是否是否将获取到的网络数据传递至调用接口处。
在doInBackground()中如果Request对象的callback属性为null则返回null:
当Request对象的callback属性不为null,则先取出服务器返回的状态码(这里的response为服务器返回的信息),如果等于200(也就是HttpStatus.SC_OK)那么就说明响应成功了。再调用getEntity()方法获取到一个HttpEntity实例,然后再用EntityUtils.toString()这个静态方法将HttpEntity转换成字符串并return。返回后的数据将传入onPostExecute()方法中作为参数。
if (requset.callback != null) { //如果进一步抽象化回调接口,使其子抽象类能分别处理多种格式的数据(如:JSON、XML、String、File),可以更方便 //这里直接使用返回String类型的数据 if(response.getStatusLine().getStatusCode()==HttpStatus.SC_OK){ return EntityUtils.toString(response.getEntity()); } return "请求失败"; } else { return null; }
在onPostExecute()中Request对象的callback属性为null根本就没返回。反之,doInBackground()的结果result将会传递至回调接口的调用处:
if (requset.callback != null) { if (result instanceof Exception) { requset.callback.onFailure((Exception) result); } else { requset.callback.onSuccess(result); } }
在doInBackground()中有一句代码:
HttpResponse response = HttpClientUtils.execute(requset);
这里的HttpClientUtils其实就是一个简单的封装,其代码如下:
public class HttpClientUtils { public static HttpResponse execute(Request requst) throws Exception { switch (requst.method) { case GET: return get(requst); } return null; } private static HttpResponse get(Request requst) throws ClientProtocolException, IOException { HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(requst.url); HttpResponse response = client.execute(get); return response; } }
从代码中可以看出,execute()和get()方法都是static类型并且返回类型都是HttpResponse。在execute()方法中根据传入的Request对象的method属性结合switch语句执行相对应的网络请求方式,并返回从服务器获得HttpResponse类型的信息。这个信息在RequstTask类中被直接赋值使用。
大体的过程就是这样了。
最后的最后,在Activity中的使用:
public class MainActivity extends Activity { private Button btn; private TextView tv; private Request request; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btn = (Button) findViewById(R.id.btn); tv = (TextView) findViewById(R.id.tv); request = new Request("http://www.baidu.com", RequestMethod.GET); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { try { request.setCallBack(new HttpCallback() { @Override public void onSuccess(Object result) { tv.setText((String) result); } @Override public void onFailure(Exception result) { tv.setText("请求失败"); } }); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } request.execute(); } }); } }
Activity的布局文件如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="请求" /> <ScrollView android:id="@+id/sv" android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/tv" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </ScrollView> </LinearLayout>
运行程序,效果如下: