Android也架构之三:简单工厂模式优化网络请求

很悲催,我们在《Android也架构之二:单例模式访问网络》 用httpConnect的方式去访问网络,而且能够正常获取数据了,可是老板能,技术出生,他要求我重新用httpClient去获取获取网络数据,童鞋们,是不是头快爆炸了?你是否也遇见过这样的或者类似这样的情况呢?

       拥抱变化,让我们从现在开始吧,上一篇文章《Android也架构之二:单例模式访问网络》中,我们学会用了单例模式,单例模式一般解决的是和程序相关的问题,和业务逻辑无关,今天开始,我们就开始学习和业务相关的设计模式,拥抱变化,让业务和需求的变化放马过来吧。。。。

今天,通过这篇文章,你讲学习到以下这些知识点:

1、学会用简单工厂模式,基于api接口开发的工作模式,接口的特点是可插拔,不会影响到客户端数据。

2、学会用httpclient框架请求http数据,涉及到android的httpclient框架的细节知识点,比如httpclient自动重连机制,连接自动释放等

3、学会在子线程如何更新主线程的android基础知识

首先我们来看一下项目的目录结构:

Android也架构之三:简单工厂模式优化网络请求_第1张图片

刚才说了,基于接口开发应该是可插拔的,就是说无论服务端(http包下的类)是怎么变化,都不会影响到客户端(clientActivity)的使用,服务端添加或者修改功能的时候不能修改客户端(clientActivity)代码。

我们首先来看下AndroidManifest.xml的代码,很简单

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.yangfuhai.simplefactory"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk android:minSdkVersion="8" />  
  8.   
  9.     <application  
  10.         android:icon="@drawable/ic_launcher"  
  11.         android:label="@string/app_name" >  
  12.         <activity  
  13.             android:name=".ClientActivity"  
  14.             android:label="@string/app_name" >  
  15.             <intent-filter>  
  16.                 <action android:name="android.intent.action.MAIN" />  
  17.   
  18.                 <category android:name="android.intent.category.LAUNCHER" />  
  19.             </intent-filter>  
  20.         </activity>  
  21.     </application>  
  22.   
  23.        <uses-permission android:name="android.permission.INTERNET" />  
  24.     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  
  25.       
  26. </manifest>  
里面主要是添加了访问网络的权限

[html]  view plain copy
  1. <uses-permission android:name="android.permission.INTERNET" />  
  2.     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  

接下来我们来看下布局文件main.xml,也非常简单。就一个textview用来显示网络的http数据

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.      <TextView  
  8.         android:layout_width="fill_parent"  
  9.         android:layout_height="wrap_content"  
  10.         android:text="@string/hello"   
  11.         android:id="@+id/textView"  
  12.         />  
  13.   
  14. </LinearLayout>  

接下来,我们先来给网络模块定义一个接口,这个接口主要是用来读取网络,很简单,就只有一个方法。

[java]  view plain copy
  1. package com.yangfuhai.simplefactory.http;  
  2. /** 
  3.  * @title http模块接口定义 
  4.  * @description 描述 
  5.  * @company 探索者网络工作室(www.tsz.net) 
  6.  * @author michael Young (www.YangFuhai.com) 
  7.  * @version 1.0 
  8.  * @created 2012-8-21 
  9.  */  
  10. public interface HttpApi {  
  11.       
  12.     public String getUrlContext(String strUrl);  
  13.   
  14. }  

接下来有两个类,放别实现了这个接口,一个是HttpURLConnection来实现,一个是httpclient来实现的。代码如下:

HttpUtils:

[java]  view plain copy
  1. package com.yangfuhai.simplefactory.http;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.io.InputStreamReader;  
  6. import java.io.Reader;  
  7. import java.io.UnsupportedEncodingException;  
  8. import java.net.HttpURLConnection;  
  9. import java.net.URL;  
  10.   
  11. import android.util.Log;  
  12.   
  13. /** 
  14.  * @title Http请求工具类 
  15.  * @description 请求http数据,单例模式 
  16.  * @company 探索者网络工作室(www.tsz.net) 
  17.  * @author michael Young (www.YangFuhai.com) 
  18.  * @version 1.0 
  19.  * @created 2012-8-19 
  20.  */  
  21. public class HttpUtils implements HttpApi{  
  22.     private static final String DEBUG_TAG = "HttpUtils";  
  23.     private HttpUtils() {} //单例模式中,封闭创建实例接口  
  24.       
  25.     private static HttpUtils httpUtils = null;   
  26.       
  27.     //提供了一个可以访问到它自己的全局访问点  
  28.     public static HttpUtils get(){  
  29.         if(httpUtils == null)  
  30.             httpUtils = new HttpUtils();  
  31.         return httpUtils;  
  32.     }  
  33.       
  34.     /** 
  35.      * 获取某个url的内容 
  36.      * @param strUrl 
  37.      * @return  
  38.      */  
  39.     @Override  
  40.     public String getUrlContext(String strUrl){  
  41.          InputStream is = null;  
  42.             int len = 500;  
  43.                   
  44.             try {  
  45.                 URL url = new URL(strUrl);  
  46.                 HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
  47.                 conn.setReadTimeout(10000 /* milliseconds */);  
  48.                 conn.setConnectTimeout(15000 /* milliseconds */);  
  49.                 conn.setRequestMethod("GET");  
  50.                 conn.setDoInput(true);  
  51.                 conn.connect();  
  52.                 int response = conn.getResponseCode();  
  53.                 Log.d(DEBUG_TAG, "The response is: " + response);  
  54.                 is = conn.getInputStream();  
  55.                   
  56.                 //这里指获取了500(len=500)字节,如果想  
  57.                 //整个网页全部获取可以用conn.getContentLength()来代替len  
  58.                 String contentAsString = readInputStream(is, len);  
  59.                 return contentAsString;  
  60.                  
  61.             } catch (Exception e) {  
  62.                 e.printStackTrace();  
  63.             } finally {  
  64.                 if (is != null) {  
  65.                     try {  
  66.                         is.close();  
  67.                     } catch (IOException e) {  
  68.                         e.printStackTrace();  
  69.                     }  
  70.                 }   
  71.             }  
  72.             return null;  
  73.     }  
  74.       
  75.     /** 
  76.      * 读取 InputStream 内容 
  77.      * @param stream 
  78.      * @param len 
  79.      * @return  
  80.      * @throws IOException 
  81.      * @throws UnsupportedEncodingException 
  82.      */  
  83.     private String readInputStream(InputStream stream, int len) throws IOException, UnsupportedEncodingException {  
  84.         Reader reader = null;  
  85.         reader = new InputStreamReader(stream, "UTF-8");          
  86.         char[] buffer = new char[len];  
  87.         reader.read(buffer);  
  88.         return new String(buffer);  
  89.     }  
  90.   
  91. }  

HttpClientUtils.java 类:

[java]  view plain copy
  1. package com.yangfuhai.simplefactory.http;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.UnsupportedEncodingException;  
  5. import java.net.URLEncoder;  
  6. import java.util.regex.Matcher;  
  7. import java.util.regex.Pattern;  
  8.   
  9. import javax.net.ssl.SSLHandshakeException;  
  10.   
  11. import org.apache.http.HttpEntity;  
  12. import org.apache.http.HttpEntityEnclosingRequest;  
  13. import org.apache.http.HttpRequest;  
  14. import org.apache.http.HttpResponse;  
  15. import org.apache.http.NoHttpResponseException;  
  16. import org.apache.http.client.ClientProtocolException;  
  17. import org.apache.http.client.HttpClient;  
  18. import org.apache.http.client.HttpRequestRetryHandler;  
  19. import org.apache.http.client.ResponseHandler;  
  20. import org.apache.http.client.methods.HttpGet;  
  21. import org.apache.http.client.methods.HttpRequestBase;  
  22. import org.apache.http.impl.client.DefaultHttpClient;  
  23. import org.apache.http.params.CoreConnectionPNames;  
  24. import org.apache.http.params.CoreProtocolPNames;  
  25. import org.apache.http.protocol.ExecutionContext;  
  26. import org.apache.http.protocol.HttpContext;  
  27. import org.apache.http.util.EntityUtils;  
  28.   
  29. import android.util.Log;  
  30.   
  31. /** 
  32.  * @title HttpClient获得网络信息 
  33.  * @description 描述 
  34.  * @company 探索者网络工作室(www.tsz.net) 
  35.  * @author michael Young (www.YangFuhai.com) 
  36.  * @version 1.0 
  37.  * @created 2012-8-21 
  38.  */  
  39. public class HttpClientUtils implements HttpApi {  
  40.   
  41.     private static final String DEBUG_TAG = "HttpClientUtils";  
  42.     private static final String CHARSET_UTF8 = "UTF-8";  
  43.   
  44.     private HttpClientUtils() {} // 单例模式中,封闭创建实例接口  
  45.   
  46.     private static HttpClientUtils httpClientUtils = null;  
  47.   
  48.     /** 
  49.      * 提供了一个可以访问到它自己的全局访问点 
  50.      * @return  
  51.      */  
  52.     public static HttpClientUtils get() {  
  53.         if (httpClientUtils == null)  
  54.             httpClientUtils = new HttpClientUtils();  
  55.         return httpClientUtils;  
  56.     }  
  57.       
  58.       
  59.   
  60.     @Override  
  61.     public String getUrlContext(String strUrl) {  
  62.         String responseStr = null;// 发送请求,得到响应  
  63.         DefaultHttpClient httpClient = null;  
  64.         HttpGet httpGet = null;  
  65.         try {  
  66.             strUrl = urlEncode(strUrl.trim(), CHARSET_UTF8);  
  67.             httpClient = getDefaultHttpClient(null);  
  68.             httpGet = new HttpGet(strUrl);  
  69.             responseStr = httpClient.execute(httpGet, strResponseHandler);  
  70.         } catch (ClientProtocolException e) {  
  71.             Log.e(DEBUG_TAG, e.getMessage());  
  72.         } catch (IOException e) {  
  73.             Log.e(DEBUG_TAG, e.getMessage());  
  74.         } catch (Exception e) {  
  75.             Log.e(DEBUG_TAG, e.getMessage());  
  76.         } finally {  
  77.             abortConnection(httpGet, httpClient);  
  78.         }  
  79.         return responseStr;  
  80.     }  
  81.   
  82.       
  83.       
  84.     /** 
  85.      * 转码http的网址,只对中文进行转码 
  86.      *  
  87.      * @param str 
  88.      * @param charset 
  89.      * @return  
  90.      * @throws UnsupportedEncodingException 
  91.      */  
  92.     private static String urlEncode(String str, String charset)  
  93.             throws UnsupportedEncodingException {  
  94.         Pattern p = Pattern.compile("[\u4e00-\u9fa5]+");  
  95.         Matcher m = p.matcher(str);  
  96.         StringBuffer b = new StringBuffer();  
  97.         while (m.find()) {  
  98.             m.appendReplacement(b, URLEncoder.encode(m.group(0), charset));  
  99.         }  
  100.         m.appendTail(b);  
  101.         return b.toString();  
  102.     }  
  103.   
  104.       
  105.       
  106.     /** 
  107.      * 设置重连机制和异常自动恢复处理 
  108.      */  
  109.     private static HttpRequestRetryHandler requestRetryHandler = new HttpRequestRetryHandler() {  
  110.         // 自定义的恢复策略  
  111.         public boolean retryRequest(IOException exception, int executionCount,  
  112.                 HttpContext context) {  
  113.             // 设置恢复策略,在Http请求发生异常时候将自动重试3次  
  114.             if (executionCount >= 3) {  
  115.                 // Do not retry if over max retry count  
  116.                 return false;  
  117.             }  
  118.             if (exception instanceof NoHttpResponseException) {  
  119.                 // Retry if the server dropped connection on us  
  120.                 return true;  
  121.             }  
  122.             if (exception instanceof SSLHandshakeException) {  
  123.                 // Do not retry on SSL handshake exception  
  124.                 return false;  
  125.             }  
  126.             HttpRequest request = (HttpRequest) context  
  127.                     .getAttribute(ExecutionContext.HTTP_REQUEST);  
  128.             boolean idempotent = (request instanceof HttpEntityEnclosingRequest);  
  129.             if (!idempotent) {  
  130.                 // Retry if the request is considered idempotent  
  131.                 return true;  
  132.             }  
  133.             return false;  
  134.         }  
  135.     };  
  136.   
  137.       
  138.     // 使用ResponseHandler接口处理响应,HttpClient使用ResponseHandler会自动管理连接的释放,  
  139.     //解决对连接的释放管理  
  140.     private static ResponseHandler<String> strResponseHandler = new ResponseHandler<String>() {  
  141.         // 自定义响应处理  
  142.         public String handleResponse(HttpResponse response)  
  143.                 throws ClientProtocolException, IOException {  
  144.             HttpEntity entity = response.getEntity();  
  145.             if (entity != null) {  
  146.                 String charset = EntityUtils.getContentCharSet(entity) == null ? CHARSET_UTF8  
  147.                         : EntityUtils.getContentCharSet(entity);  
  148.                 return new String(EntityUtils.toByteArray(entity), charset);  
  149.             } else {  
  150.                 return null;  
  151.             }  
  152.         }  
  153.     };  
  154.       
  155.       
  156.       
  157.   
  158.     /** 
  159.      * 获取DefaultHttpClient实例 
  160.      *  
  161.      * @param charset 
  162.      * @return  
  163.      */  
  164.     private static DefaultHttpClient getDefaultHttpClient(final String charset) {  
  165.         DefaultHttpClient httpclient = new DefaultHttpClient();  
  166.         // httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION,  
  167.         // HttpVersion.HTTP_1_1);  
  168.         // 模拟浏览器(有些服务器只支持浏览器访问,这个可以模拟下~~~)  
  169.         // httpclient.getParams().setParameter(CoreProtocolPNames.USER_AGENT,"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)");  
  170.         // httpclient.getParams().setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE,Boolean.FALSE);  
  171.           
  172.         // 请求超时  
  173.         httpclient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 5000);  
  174.         // 读取超时  
  175.         httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,5000);  
  176.         httpclient.getParams().setParameter(  
  177.                 CoreProtocolPNames.HTTP_CONTENT_CHARSET,  
  178.                 charset == null ? CHARSET_UTF8 : charset);  
  179.         httpclient.setHttpRequestRetryHandler(requestRetryHandler);  
  180.         return httpclient;  
  181.     }  
  182.   
  183.     /** 
  184.      * 释放HttpClient连接 
  185.      *  
  186.      * @param hrb 
  187.      * @param httpclient 
  188.      */  
  189.     private static void abortConnection(final HttpRequestBase httpRequestBase,  
  190.             final HttpClient httpclient) {  
  191.         if (httpRequestBase != null) {  
  192.             httpRequestBase.abort();  
  193.         }  
  194.         if (httpclient != null) {  
  195.             httpclient.getConnectionManager().shutdown();  
  196.         }  
  197.     }  
  198.   
  199. }  

接下来,我们过来看看工厂类:

[java]  view plain copy
  1. package com.yangfuhai.simplefactory.http;  
  2.   
  3. /** 
  4.  * @title Http模块工厂 
  5.  * @description 获取http模块 
  6.  * @company 探索者网络工作室(www.tsz.net) 
  7.  * @author michael Young (www.YangFuhai.com) 
  8.  * @version 1.0 
  9.  * @created 2012-8-21 
  10.  */  
  11. public class HttpFactory {  
  12.       
  13.     private static final int HTTP_CONFIG= 1 ;//http调用方式,0是httpConnect,1是httpclient  
  14.       
  15.     public static HttpApi getHttp(){  
  16.         if(HTTP_CONFIG == 0)  
  17.             return HttpUtils.get();  
  18.         else if(HTTP_CONFIG == 1)  
  19.             return HttpClientUtils.get();  
  20.           
  21.         return null;  
  22.     }  
  23.   
  24. }  

在工厂类中,一般获取http内部模块有几种模式,一种是由客户端传值进来获取某个指定的模块,一种是由HttpFactory内部逻辑实现,会更加需求或者其他一些特定的条件返回不同的接口,一种是通过配置文件获取,比如读取某个文件等。 这里我们简单的模拟了读取网络配置文件 获取的形式,我们可以通过HTTP_CONFIG 的配置返回不同的http模块,不论是httpUrlConnection的方式还是httpclient的形式,只要简单的在这里配置就行了,无论这里怎么修改都不会影响到客户端(httpclient)的代码,哪天老板心血来潮,弄一个icp/ip,socket的形式获取网络信息,我们只需要在这个包(com.yangfuhai.simplefactory.http)下添加一个socket获取网络信息的类,我们也不用修改客户端(clientActivity)的代码啦(鼓掌XXXXX),同时,我们可以移除httpUrlConnection或者httpClient的类,对客户端一点影响都没有(可插拔)。

客户端代码如下:

[java]  view plain copy
  1. package com.yangfuhai.simplefactory;  
  2.   
  3. import com.yangfuhai.simplefactory.http.HttpFactory;  
  4.   
  5. import android.app.Activity;  
  6. import android.os.Bundle;  
  7. import android.widget.TextView;  
  8.   
  9. /** 
  10.  * @title 单例模式 
  11.  * @description 单例模式 
  12.  * @company 探索者网络工作室(www.tsz.net) 
  13.  * @author michael Young (www.YangFuhai.com) 
  14.  * @version 1.0 
  15.  * @created 2012-8-19 
  16.  */  
  17. public class ClientActivity extends Activity {  
  18.       
  19.     TextView mTv;  
  20.       
  21.     @Override  
  22.     public void onCreate(Bundle savedInstanceState) {  
  23.         super.onCreate(savedInstanceState);  
  24.         setContentView(R.layout.main);  
  25.         mTv = (TextView) findViewById(R.id.textView);  
  26.           
  27.         mTv.setText("正在加载www.devchina.com数据。。。");  
  28.         new Thread(new Runnable() {  
  29.               
  30.             @Override  
  31.             public void run() {  
  32.                 final String strContext = HttpFactory.getHttp().getUrlContext("http://www.devchina.com");  
  33.                 runOnUiThread(new Runnable() {  
  34.                       
  35.                     @Override  
  36.                     public void run() {  
  37.                         if(strContext!=null)  
  38.                             mTv.setText(strContext);  
  39.                         else  
  40.                             mTv.setText("加载失败。。。");  
  41.                     }  
  42.                 });  
  43.             }  
  44.         }).start();  
  45.           
  46.     }  
  47.       
  48.       
  49. }  

这里主要强调一下的是这一行代码:

final String strContext = HttpFactory.getHttp().getUrlContext("http://www.devchina.com");

这里必须是通过工厂去获取HttpApi,否则就有代码入侵clientActivity类,就不可以实现 可插拔 啦!

Android也架构之三:简单工厂模式优化网络请求_第2张图片

ok,成功啦。

 

源码下载地址:http://download.csdn.net/detail/michael_yy/4516372  直接导入eclipse就可以用了~~~

大家多多指教,转载请注明来之 http://www.devchina.com/  或者http://blog.csdn.net/michael_yy, 谢谢。

谢谢大家关注,我继续在博客中讲解了经典的23中模式中在android实际项目中灵活运用,下一篇 《Android也架构之四:门面模式解析获取的html代码》敬请关注。

你可能感兴趣的:(android,简单工厂,Android架构)