App网络框架封装
本文介绍对App网络框架封装的一些建议,只是个人观点欢迎大家讨论。
刚开始开发Android github上没有那么多好用的开源网络框架,就连async-http-client都没有,更不用说现在火的没朋友的okhttp+retrofit,开发者只能自己封装相对底层的库apache-httpclient,本人在Retrofit出现之前也一直在封装网络框架,最开始封装HttpClient,后来封装async-http-client,总结了一点小小经验来和大家分享。
Retrofit也不是完美的也需要根据App的实际使用情况进行封装,只不过要比之前框架的好太多了,尤其是可以和RxJava连用。
目录
- 访问网络Api
- CallBack监听
- Response支持FastJson解析
- 缓存机制
- Https网络安全
1.访问网络Api
App通过网络框架进行访问服务器,我们目前用的最多的Api是Get和Post请求,有的App干脆全部都用Post请求,这样设计接口的原因很大程度上是由于当时开发时间紧没有时间细化接口,或者某些历史遗留问题。
如果服务端是RESTful Api 那你的网络架构需要支持更多的请求方式。
- GET:只用于从服务器获取数据
- POST:在服务器上创建资源
- PUT:更新服务器上的资源(对应资源全部字段)
- PATCH:更新服务器上的资源(对应资源部分字段)
- DELETE:删除服务器上的资源
以下方法不常用解释的详细一些 - HEAD:HEAD方法跟GET方法相同,只不过服务器响应时不会返回消息体。
1、只请求资源的首部;
2、检查超链接的有效性;
3、检查网页是否被修改;
4、多用于自动搜索机器人获取网页的标志信息,获取rss种子信息,或者传递安全认证信息等 - OPTIONS:
1、获取服务器支持的HTTP请求方法;
2、用来检查服务器的性能。 - TRACE:回显服务器接收到的header。支持该方法的服务器可能存在XST安全隐患。
Response body中包含整个请求消息。 - CONNECT:用来实现代理服务器。
代理服务器和真正的服务器之间建立起 TCP 连接,然后在客户端和真正服务器端进行数据的直接转发。
1、客户端先发送 CONNECT 请求到隧道代理服务器,告诉它建立和服务器的 TCP 连接(因为是 TCP 连接,只需要 ip 和端口就行,不需要关注上层的协议类型)
2、代理服务器成功和后端服务器建立 TCP 连接
3、代理服务器返回 HTTP 200 Connection Established 报文,告诉客户端连接已经成功建立
4、这个时候就建立起了连接,所有发给代理的 TCP 报文都会直接转发,从而实现服务器和客户端的通信
2.CallBack监听
Android 3.0 开始系统要求网络访问必须在子线程中进行, 否则网络访问将会失败并抛出NetworkOnMainThreadException这个异常, 这样做是为了避免主线程由于耗时操作所阻塞从而出现ANR现象。对于多线程来说网络框架的回调就非常重要了。
- IBaseStateListener
状态监听,当请求开始、上传或下载进度、请求完成(包括处理完Error之后)
public interface IBaseStateListener {
/**
* 网络请求之前回调,可以进行一些预处理或者弹出网络等待对话框
*/
public void onStart();
/**
* 文件上传进度,文件下载进度
* @param bytesWritten
* @param totalSize
*/
public void onProgress(int bytesWritten, int totalSize);
/**
* 网络请求完成回调,请求成功,或者Error之后调用,和onStart成对出现
*/
public void onFinish();
}
- IBaseErrorListener
错误监听:
网络方面:网络超时、网络错误等。
返回数据:返回数据错误代码。例如返回Json格式,当state=-1回调Error监听
{
"state":-1 , //-1错误,0返回空,1返回正确
"msg":"缺少字段",
"data"{}
}
public interface IBaseErrorListener {
/**
* 自定义错误监听
* @param statusCode
* @param errCode
* @param headers
* @param responseBody
* @param error
*/
public void onFailure(int statusCode, int errCode, Header[] headers, Throwable error);
}
- IBaseResponse
请求成功返回监听。
public interface IBaseResponse {
/**
* 用于返回文本,在内存中存储
* @param statusCode
* @param headers
* @param responseBody
* @throws Exception
*/
public void onResponse(int statusCode, Header[] headers, byte[] responseBody) throws Exception;
/**
* 用于返回文件,保存在硬盘上
* @param statusCode
* @param headers
* @param file
* @throws Exception
*/
public void onResponse(int statusCode, Header[] headers, File file) throws Exception;
}
以上是三个主要的监听:
网络框架中回调监听也需要做判断,当前请求是否取消,如果取消了跳过所有监听直接回调IBaseStateListener. onFinish()
。当前页面是否关闭,如果关闭了不回调所有监听,否则你在监听中的操作有可能会因为当前界面关闭(Activity.finish()
)导致NullPointerException
。
网络框架发起Url请求都是在子线程中,同时用ThreadLocal保存监听对象,这就意味着每个Url请求对应的都是不同的监听对象,这样就会避免在同一个页面并发请求导致监听丢失的问题,例如:在一个Activity页面并发获取数据,如果网络框架内部多个线程共用一个监听,就会导致只能接收到最后一次回调,之前设置的都丢失了,用ThreadLocal保存就不会出现这种情况,每个线程都是保存一份监听。
3.Response支持FastJson解析
网络框架Response支持自动解析,目前用的最多的就是Gson和FastJson
继承返回监听来实现,三种返回:
返回对象BaseEntityResponse
。
返回列表BaseListResponse
。
返回状态BaseSuccessResponse
(成功或失败)。
/**
*
* @Description: 网络返回对象
* @version V1.0.0
*/
public abstract class BaseEntityResponse<T> extends IBaseResponse {
/**表示返回json文件中需要取出的Object对象的key,这个在网络协议中必须事先定义好,返回对象的协议都是统一的*/
private static final String RESULT_OBJ = "result";
private Class<T> clazz;
private T object;
public BaseEntityResponse(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public void onSuccess(int statusCode, Header[] headers,
JSONObject mainJsonObject) throws Exception {
object = mainJsonObject.getObject(RESULT_OBJ, clazz);
onSuccess(object);
}
public abstract void onSuccess(T t) throws Exception;
}
/**
*
* @Description: 网络返回,List
* @version V1.0.0
*/
public abstract class BaseListResponse<T> extends BaseHttpJsonResponse {
/**表示返回json文件中需要取出的List Object对象的key,这个在网络协议中必须事先定义好,返回对象的协议都是统一的*/
private static final String RESULT_OBJ = "result";
private Class<T> clazz;
private List<T> list;
// 消息记录数
public int total;
public BaseListResponse(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public void onSuccess(int statusCode, Header[] headers,
JSONObject mainJsonObject) throws Exception {
JSONArray jsonArray = mainJsonObject.getJSONArray(RESULT_OBJ);
list = new ArrayList<T>();
if (null == jsonArray || 0 == jsonArray.size()) {
// 返回空
} else {
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
T t = JSON.toJavaObject(jsonObject, clazz);
list.add(t);
}
}
onSuccess(list);
}
public abstract void onSuccess(List<T> list) throws Exception;
}
/**
*
* @Description: 返回正确还是错误
* @version V1.0.0
*/
public abstract class BaseSuccessResponse extends BaseHttpJsonResponse {
@Override
public void onSuccess(int statusCode, Header[] headers,
JSONObject mainJsonObject) throws Exception {
onSuccess();
}
public abstract void onSuccess() throws Exception;
}
4. 缓存机制
网络框架的缓存机制也是非常重要的。
缓存策略:
- force_network:强制网络
- network_cache:先从网络获取数据缓存起来,每次都更新缓存数据,如果无网络、获取数据解析错误,等其他原因,如果有缓存读取缓存。
栗子:App没有网络进来一样能展示上一次的缓存,提高用户体验。 - cache_network:先读取缓存,如果没有缓存从网络获取,缓存下来如果有缓存每次判断缓存有效期,如果在有效期内直接读取,如果超过有效期,先链接网络在刷新缓存。
栗子:用于那些不经常变化的页面,例如城市列表,不会经常变化但是也会变化,可以将缓存有效期设置成1个月这样一个月之内都会取缓存。
缓存保存形式:缓存保存形式有很多种,数据库,sharepreference,序列化对象,文件等。目前我用的是文件保存网络返回的json字符串,文件的名字是URL+param的MD5,这样保证一个请求对应一个缓存文件,将缓存文件统一保存在预先设置好的目录中。这样做的好处是保存和读取缓存比较简单,读出的json字符串和网络返回走同一套逻辑在返回监听中解析返回对应的mode。
5. Https网络安全
Http请求不安全可以轻松的通过软件(fiddler charles)进行拦截,查看Request和Response的信息。
服务器可以通过配置Https证书,客户端发起Https请求来解决拦截,但是仍然无法阻止中间人攻击的拦截方式。
可以将服务器配置的Https证书的公钥写在客户端中每次请求都去校验这样可以有效防止拦截,但是客户端一旦被破解其他人拿到公钥仍然会拦截信息,因此我们对Apk进行混淆用第三方平台进行加固防止被恶意破解。
如下代码是对HttpsURLConnection
设置Https
public static String CERT = "-----BEGIN CERTIFICATE-----\n" +
"-----END CERTIFICATE-----\n";
public static KeyStore getKeyStore(){
try {
InputStream inputStream = new ByteArrayInputStream(CERT.getBytes("UTF-8"));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca;
try {
ca = cf.generateCertificate(inputStream);
// System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
inputStream.close();
}
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
return keyStore;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
public synchronized static void trustCertificate(HttpsURLConnection httpsURLConnection) {
try {
KeyStore keyStore = getKeyStore();
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession sslSession) {
//对域名进行校验
if (SystemFunction.getHost().equalsIgnoreCase(hostname)) {
return true;
}
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
总结
以下几点只是我对网络框架封装的一些心得,可能写的不全或者有考虑不周到的地方希望和大家讨论。
- 访问网络Api
- CallBack监听
- Response支持FastJson解析
- 缓存机制
- Https网络安全