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

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

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

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

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

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

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


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


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

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

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yangfuhai.simplefactory"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".ClientActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

       <uses-permission android:name="android.permission.INTERNET" />
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	
</manifest>
里面主要是添加了访问网络的权限

<uses-permission android:name="android.permission.INTERNET" />
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

     <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" 
        android:id="@+id/textView"
        />

</LinearLayout>

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

package com.yangfuhai.simplefactory.http;
/**
 * @title http模块接口定义
 * @description 描述
 * @company 探索者网络工作室(www.tsz.net)
 * @author michael Young (www.YangFuhai.com)
 * @version 1.0
 * @created 2012-8-21
 */
public interface HttpApi {
	
	public String getUrlContext(String strUrl);

}

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

HttpUtils:

package com.yangfuhai.simplefactory.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;

import android.util.Log;

/**
 * @title Http请求工具类
 * @description 请求http数据,单例模式
 * @company 探索者网络工作室(www.tsz.net)
 * @author michael Young (www.YangFuhai.com)
 * @version 1.0
 * @created 2012-8-19
 */
public class HttpUtils implements HttpApi{
	private static final String DEBUG_TAG = "HttpUtils";
	private HttpUtils() {} //单例模式中,封闭创建实例接口
	
	private static HttpUtils httpUtils = null; 
	
	//提供了一个可以访问到它自己的全局访问点
	public static HttpUtils get(){
		if(httpUtils == null)
			httpUtils = new HttpUtils();
		return httpUtils;
	}
	
	/**
	 * 获取某个url的内容
	 * @param strUrl
	 * @return
	 */
	@Override
	public String getUrlContext(String strUrl){
		 InputStream is = null;
		    int len = 500;
		        
		    try {
		        URL url = new URL(strUrl);
		        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		        conn.setReadTimeout(10000 /* milliseconds */);
		        conn.setConnectTimeout(15000 /* milliseconds */);
		        conn.setRequestMethod("GET");
		        conn.setDoInput(true);
		        conn.connect();
		        int response = conn.getResponseCode();
		        Log.d(DEBUG_TAG, "The response is: " + response);
		        is = conn.getInputStream();
		        
		        //这里指获取了500(len=500)字节,如果想
		        //整个网页全部获取可以用conn.getContentLength()来代替len
		        String contentAsString = readInputStream(is, len);
		        return contentAsString;
		       
		    } catch (Exception e) {
		    	e.printStackTrace();
		    } finally {
		        if (is != null) {
		            try {
						is.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
		        } 
		    }
		    return null;
	}
	
	/**
	 * 读取 InputStream 内容
	 * @param stream
	 * @param len
	 * @return
	 * @throws IOException
	 * @throws UnsupportedEncodingException
	 */
	private String readInputStream(InputStream stream, int len) throws IOException, UnsupportedEncodingException {
	    Reader reader = null;
	    reader = new InputStreamReader(stream, "UTF-8");        
	    char[] buffer = new char[len];
	    reader.read(buffer);
	    return new String(buffer);
	}

}

HttpClientUtils.java 类:

package com.yangfuhai.simplefactory.http;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.SSLHandshakeException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import android.util.Log;

/**
 * @title HttpClient获得网络信息
 * @description 描述
 * @company 探索者网络工作室(www.tsz.net)
 * @author michael Young (www.YangFuhai.com)
 * @version 1.0
 * @created 2012-8-21
 */
public class HttpClientUtils implements HttpApi {

	private static final String DEBUG_TAG = "HttpClientUtils";
	private static final String CHARSET_UTF8 = "UTF-8";

	private HttpClientUtils() {} // 单例模式中,封闭创建实例接口

	private static HttpClientUtils httpClientUtils = null;

	/**
	 * 提供了一个可以访问到它自己的全局访问点
	 * @return
	 */
	public static HttpClientUtils get() {
		if (httpClientUtils == null)
			httpClientUtils = new HttpClientUtils();
		return httpClientUtils;
	}
	
	

	@Override
	public String getUrlContext(String strUrl) {
		String responseStr = null;// 发送请求,得到响应
		DefaultHttpClient httpClient = null;
		HttpGet httpGet = null;
		try {
			strUrl = urlEncode(strUrl.trim(), CHARSET_UTF8);
			httpClient = getDefaultHttpClient(null);
			httpGet = new HttpGet(strUrl);
			responseStr = httpClient.execute(httpGet, strResponseHandler);
		} catch (ClientProtocolException e) {
			Log.e(DEBUG_TAG, e.getMessage());
		} catch (IOException e) {
			Log.e(DEBUG_TAG, e.getMessage());
		} catch (Exception e) {
			Log.e(DEBUG_TAG, e.getMessage());
		} finally {
			abortConnection(httpGet, httpClient);
		}
		return responseStr;
	}

	
	
	/**
	 * 转码http的网址,只对中文进行转码
	 * 
	 * @param str
	 * @param charset
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	private static String urlEncode(String str, String charset)
			throws UnsupportedEncodingException {
		Pattern p = Pattern.compile("[\u4e00-\u9fa5]+");
		Matcher m = p.matcher(str);
		StringBuffer b = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(b, URLEncoder.encode(m.group(0), charset));
		}
		m.appendTail(b);
		return b.toString();
	}

	
	
	/**
	 * 设置重连机制和异常自动恢复处理
	 */
	private static HttpRequestRetryHandler requestRetryHandler = new HttpRequestRetryHandler() {
		// 自定义的恢复策略
		public boolean retryRequest(IOException exception, int executionCount,
				HttpContext context) {
			// 设置恢复策略,在Http请求发生异常时候将自动重试3次
			if (executionCount >= 3) {
				// Do not retry if over max retry count
				return false;
			}
			if (exception instanceof NoHttpResponseException) {
				// Retry if the server dropped connection on us
				return true;
			}
			if (exception instanceof SSLHandshakeException) {
				// Do not retry on SSL handshake exception
				return false;
			}
			HttpRequest request = (HttpRequest) context
					.getAttribute(ExecutionContext.HTTP_REQUEST);
			boolean idempotent = (request instanceof HttpEntityEnclosingRequest);
			if (!idempotent) {
				// Retry if the request is considered idempotent
				return true;
			}
			return false;
		}
	};

	
	// 使用ResponseHandler接口处理响应,HttpClient使用ResponseHandler会自动管理连接的释放,
	//解决对连接的释放管理
	private static ResponseHandler<String> strResponseHandler = new ResponseHandler<String>() {
		// 自定义响应处理
		public String handleResponse(HttpResponse response)
				throws ClientProtocolException, IOException {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				String charset = EntityUtils.getContentCharSet(entity) == null ? CHARSET_UTF8
						: EntityUtils.getContentCharSet(entity);
				return new String(EntityUtils.toByteArray(entity), charset);
			} else {
				return null;
			}
		}
	};
	
	
	

	/**
	 * 获取DefaultHttpClient实例
	 * 
	 * @param charset
	 * @return
	 */
	private static DefaultHttpClient getDefaultHttpClient(final String charset) {
		DefaultHttpClient httpclient = new DefaultHttpClient();
		// httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
		// HttpVersion.HTTP_1_1);
		// 模拟浏览器(有些服务器只支持浏览器访问,这个可以模拟下~~~)
		// httpclient.getParams().setParameter(CoreProtocolPNames.USER_AGENT,"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)");
		// httpclient.getParams().setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE,Boolean.FALSE);
		
		// 请求超时
		httpclient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 5000);
		// 读取超时
		httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,5000);
		httpclient.getParams().setParameter(
				CoreProtocolPNames.HTTP_CONTENT_CHARSET,
				charset == null ? CHARSET_UTF8 : charset);
		httpclient.setHttpRequestRetryHandler(requestRetryHandler);
		return httpclient;
	}

	/**
	 * 释放HttpClient连接
	 * 
	 * @param hrb
	 * @param httpclient
	 */
	private static void abortConnection(final HttpRequestBase httpRequestBase,
			final HttpClient httpclient) {
		if (httpRequestBase != null) {
			httpRequestBase.abort();
		}
		if (httpclient != null) {
			httpclient.getConnectionManager().shutdown();
		}
	}

}

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

package com.yangfuhai.simplefactory.http;

/**
 * @title Http模块工厂
 * @description 获取http模块
 * @company 探索者网络工作室(www.tsz.net)
 * @author michael Young (www.YangFuhai.com)
 * @version 1.0
 * @created 2012-8-21
 */
public class HttpFactory {
	
	private static final int HTTP_CONFIG= 1 ;//http调用方式,0是httpConnect,1是httpclient
	
	public static HttpApi getHttp(){
		if(HTTP_CONFIG == 0)
			return HttpUtils.get();
		else if(HTTP_CONFIG == 1)
			return HttpClientUtils.get();
		
		return null;
	}

}

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


客户端代码如下:

package com.yangfuhai.simplefactory;

import com.yangfuhai.simplefactory.http.HttpFactory;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

/**
 * @title 单例模式
 * @description 单例模式
 * @company 探索者网络工作室(www.tsz.net)
 * @author michael Young (www.YangFuhai.com)
 * @version 1.0
 * @created 2012-8-19
 */
public class ClientActivity extends Activity {
	
	TextView mTv;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mTv = (TextView) findViewById(R.id.textView);
        
        mTv.setText("正在加载www.devchina.com数据。。。");
        new Thread(new Runnable() {
			
			@Override
			public void run() {
				final String strContext = HttpFactory.getHttp().getUrlContext("http://www.devchina.com");
				runOnUiThread(new Runnable() {
					
					@Override
					public void run() {
						if(strContext!=null)
							mTv.setText(strContext);
						else
							mTv.setText("加载失败。。。");
					}
				});
			}
		}).start();
        
    }
    
    
}

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

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

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




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也架构之三:简单工厂模式优化网络请求)