您的位置:首页 > 理论基础 > 计算机网络

Android通用网络请求解析框架.5(使用框架)

2017-01-06 15:55 726 查看
笔者将通过11篇博客对个人开源框架进行讲解,本篇为第5篇,讲解使用框架。

开源库github地址 https://github.com/qq296216078/Android-Universial-NetFrame

如果有兴趣一起讨论本框架的内容,请加QQ群:271335749

在之前的几篇博文中,其实已经对使用本框架有所介绍过了,本篇进行更全面的介绍

假设服务端返回的数据是这样的:
{
"code":"00001",
"message":"login success",
"time":"1479807260",
"data":{
"id":"123",
"name":"chenjian"
}
}


定义一个Bean,他继承NetBaseBean
public class NetUserBean extends NetBaseBean {
private String id;
private String name;

@Override
public void initByJson(JSONObject jsonObject) throws JSONException {
this.id = jsonObject.optString("id");
this.name = jsonObject.optString("name");
}
}
initByJson方法中,我们用的是api里面的类来解析

再来看看使用

NetHelper.get("url", new NetSingleBeanListener<NetUserBean>() {
@Override
protected void onCommon() {
super.onCommon();
}

@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {
// 这里是ui线程
}

@Override
protected void onSuccess(NetUserBean userBean) {
// 这里是ui线程
}
});

NetHelper.post("url", "param", new NetSingleBeanListener<NetUserBean>() {
@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {

}

@Override
protected void onSuccess(NetUserBean userBean) {

}
});


使用的代码和之前介绍的没差多少,这里在get请求中重写了onCommon方法,当开发者在onSuccess和onError中有重复代码时,特别是有大量重复代码时,可以把重复的代码在onCommon里面写一次就行了。比如请求前在界面中加了进度条以及其它动画,在请求完成时,不管是成功还是失败,进度条和动画都需要先取消掉。这个时候onCommon方法就发挥作用了。onCommon方法不是抽象方法,子类可以不重写,如果开发者不需要,就要以不重写。

此外,这里再来引入两个问题:
1.此处的url哪来的?
2.onError方法是否经常做重复操作?

问题1,url通常是开发者拼接而来的,通常会是这样的形式 url = BASE_URL + URL + "?" + key1=value1 + "&" + key2=value2 + ...
后面还可能有很多参数,开发者一个一个的去拼接,
如果是post的时候,参数部分要放到param里面去,url部分只保留 url = BASE_URL + URL
我们需要一个工具类,让url拼接更符合面向对象的编程思想。还有,一些时候服务端需要每个请求都带一些固定参数。

问题2,一些时候多个网络请求的onError会做同样的操作,
笔者就假设开发者是弹一个Toast。网络未打开时提示toast1,未请求到数据时提示toast2,请求到数据但解析失败时提示toast3
这样的话其实可以把帮onError做一个ToastUtil工具类,
或者继承我们的xxxListener,实现onError方法。这样的话子类只要实现自己的onSuccess就行了。

来看看问题1中需要的工具类
package com.chenjian.net.url;

import android.text.TextUtils;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Url工具类,提供Url操作的基本方法单元
* <p>
* 作者: ChenJian
* 时间: 2016.12.14 11:24
*/

public class UrlParse {

private Map<String, String> mMap = new LinkedHashMap<>();
private StringBuilder mHeaderBuilder;
private boolean mUseCommonParam = true;

public UrlParse() {

}

public boolean isUseCommonParam() {
return mUseCommonParam;
}

public UrlParse setUseCommonParam(boolean useCommonParam) {
this.mUseCommonParam = useCommonParam;
return this;
}

public UrlParse(String url) {
initUrl(url);
}

private void initUrl(String url) {
if (TextUtils.isEmpty(url)) {
mHeaderBuilder = new StringBuilder("");
return;
}

mMap.clear();
int pos = url.indexOf("?");
if (pos == -1) {
mHeaderBuilder = new StringBuilder(url);
return;
}

mHeaderBuilder = new StringBuilder(url.substring(0, pos));
String temp = url.substring(pos + 1);
StringTokenizer token = new StringTokenizer(temp, "&", false);
while (token.hasMoreElements()) {
String[] str = token.nextToken().split("=");
if (str.length == 2) {
putValue(str[0], str[1]);
}
}
}

public boolean containsKey(String key) {
return mMap.containsKey(key.toLowerCase());
}

private String getValue(String key) {
return mMap.get(key.toLowerCase());
}

private String decodeUtf8(String str) {
try {
if (str == null || "".equals(str)) {
return str;
}
return URLDecoder.decode(str, "utf-8");
} catch (Exception e) {
e.printStackTrace();
}
return str;
}

public String getUtf8Value(String key) {
String temp = mMap.get(key.toLowerCase());
return decodeUtf8(temp);
}

public int getInteger(String key, int def) {
String value = getValue(key);
if (TextUtils.isEmpty(value)) {
return def;
}
try {
return Integer.valueOf(value);
} catch (Exception ex) {
return def;
}
}

public UrlParse putValue(String key, String value) {
if (key == null || value == null) {
return this;
}
mMap.put(key.toLowerCase(), value);
return this;
}

private UrlParse putValue(String key, int value) {
return putValue(key, String.valueOf(value));
}

public void removeValue(String key) {
mMap.remove(key.toLowerCase());
}

public void optRemoveValue(String key) {
if (TextUtils.isEmpty(key)) {
return;
}

if (mMap.containsKey(key)) {
mMap.remove(key.toLowerCase());
}
}

@Override
public String toString() {
if (mUseCommonParam) {
addCommonParam();
}

return toStringWithParam();
}

public String toStringOnlyHeader() {
return mHeaderBuilder.toString();
}

public String toStringOnlyParam() {
if (mUseCommonParam) {
addCommonParam();
}

return getUrlParam();
}

private void addCommonParam() {
Map<String, String> paramMap = UrlManager.getInstance().getCommonParam();
if (paramMap != null) {
for (Map.Entry<String, String> entry : paramMap.entrySet()) {
putValue(entry.getKey(), entry.getValue());
}
}
}

private String toStringWithParam() {
String param = getUrlParam();
if (param == null || param.equals("")) {
return mHeaderBuilder.toString();
} else {
return mHeaderBuilder + "?" + getUrlParam();
}
}

private String getUrlParam() {
StringBuilder sb = new StringBuilder();
Iterator<String> iterator = mMap.keySet().iterator();
for (; iterator.hasNext(); ) {
String key = iterator.next();
sb.append(key);
sb.append("=");
sb.append(mMap.get(key));
sb.append("&");
}
if (sb.length() > 0) {
return sb.substring(0, sb.length() - 1);
}
return sb.toString();
}

/**
* @param region host + / + region
* @return 返回this
*/
public UrlParse appendRegion(String region) {
String str = mHeaderBuilder.toString();
if (str.endsWith("/")) {
mHeaderBuilder.append(region);
} else {
mHeaderBuilder.append("/").append(region);
}
return this;
}

public static String encode(String url) {
Pattern pattern = Pattern.compile("[\\u4E00-\\u9FA5]");
Matcher m = pattern.matcher(url);
while (m.find()) {
String cn = m.group();
try {
url = url.replace(cn, URLEncoder.encode(cn, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return url;
}
}


先看三个性属,分别为参数map,url头,是否使用公共参数,
再看看三个toString方法,分别提供了只返回url头,只返回参数,返回url头+参数的形式。

类的addCommonParam方法中用到了UrlManager类,他是与本类相关的一个类
package com.chenjian.net.url;

import com.chenjian.net.demo.app.DemoApplication;
import com.chenjian.net.demo.util.PackageUtil;

import java.util.HashMap;
import java.util.Map;

/**
* Url管理类
* <p>
* 作者: ChenJian
* 时间: 2016.12.14 11:24
*/
public class UrlManager {

private static UrlManager mInstance = null;
private Map<String, String> mCommonParam = null;
private boolean mUseCommonParam = true;

private UrlManager() {

}

public static UrlManager getInstance() {
if (mInstance == null) {
synchronized (UrlManager.class) {
if (mInstance == null) {
mInstance = new UrlManager();
}
}
}

return mInstance;
}

public Map<String, String> getCommonParam() {
return mCommonParam;
}

public void setCommonParam(Map<String, String> commonParam) {
this.mCommonParam = commonParam;
}

public boolean isUseCommonParam() {
return mUseCommonParam;
}

public void setUseCommonParam(boolean useCommonParam) {
this.mUseCommonParam = useCommonParam;
}

/**
* 返回默认的公共参数
*
* @return
*/
public Map<String, String> getDefaultCommonParam() {
Map<String, String> paramMap = new HashMap<>();
paramMap.put("app_version_name", PackageUtil.getAppVersionName(DemoApplication.context));
paramMap.put("app_version_code", String.valueOf(PackageUtil.getAppVersionCode(DemoApplication.context)));
return paramMap;
}
}


一起来看看使用,我们一般将url常量放在一个类里
public class UrlConst {

public static final String BASE_URL = "10.0.12.39/net/";

public static final String LOGIN = "login";
}


然后在使用get请求的时候,这样
UrlParse urlParse = new UrlParse(UrlConst.BASE_URL)
.appendRegion(UrlConst.LOGIN)
.putValue("username", "chenjian")
.putValue("password", "12345678");

NetHelper.get(urlParse.toString(), new NetStringListener() { ... };


这个时候,urlParse.toString()的返回值为"10.0.12.39/net/login?username=chenjian&password=12345678"

在使用post请求时,我们这样
UrlParse urlParse = new UrlParse(UrlConst.BASE_URL).appendRegion(UrlConst.LOGIN);
UrlParse paramParse = new UrlParse().putValue("username", "chenjian").putValue("password", "12345678");

NetHelper.post(urlParse.toStringOnlyHeader(), paramParse.toStringOnlyParam(), new NetStringListener() { ... };


这个时候,urlParse.toStringOnlyHeader()的返回值为"10.0.12.39/net/login",
paramParse.toStringOnlyParam()的返回值为"username=chenjian&password=12345678"

如果你的项目需要公共参数,那么可以使用UrlManager类,一般我们在Application类里面做一下初始化
package com.chenjian.net.demo.app;

import android.app.Application;
import android.content.Context;

import com.chenjian.net.demo.util.PackageUtil;
import com.chenjian.net.url.UrlManager;

import java.util.HashMap;
import java.util.Map;

/**
* 例子:定义一个Application
* <p>
* 作者: ChenJian
* 时间: 2016.12.14 12:26
*/

public class DemoApplication extends Application {

public static Context context;

@Override
public void onCreate() {
super.onCreate();

context = getApplicationContext();

initNet();
}

private void initNet() {
/**
* 设置网络请求公共参数。注意以下设置的参数为只是get请求的参数
*/
Map<String, String> paramMap = new HashMap<>();
paramMap.put("app_version_name", PackageUtil.getAppVersionName(context));
paramMap.put("app_version_code", String.valueOf(PackageUtil.getAppVersionCode(context)));
UrlManager.getInstance().setCommonParam(paramMap);
}
}


假设服务端要求每个请求都要加上app的版本名和版本号,那么在Application类中就可以将公共参数初始化,
这样的话,我们每个请求都会带上这两个参数,刚才的get请求的url,就会变成
"10.0.12.39/net/login?username=chenjian&password=12345678&app_version_name=1.0&app_version_code=1"
刚才的post请求的url头不变,参数部分变为
"username=chenjian&password=12345678&app_version_name=1.0&app_version_code=1"

当然,UrlManager类可以设置是否使用公共参数,UrlParse类也提供了方法设置当前这个url是否使用公共参数。
可以看出,这样做比较符合面向对象的编程思想,但如果开发者不喜欢这样,有自己的方法,或者还是喜欢用字符串相加的方式,也是可以的。

笔者还模仿UrlParse和UrlManager写了另外两个类,为JsonParse和JsonManager,功能与UrlManager和UrlManager类似,只不过他是对json字符串进行操作,具体内容可以查看源码中的两个类,这里不做讲解。

问题2,显然也是工具类来实现。先是一个Toast的工具类
package com.chenjian.net.demo.util;

import android.content.Context;
import android.widget.Toast;

/**
* Created by ChenJian
* 2016.7.1 16:54:54.
*/
public class ToastUtil {
public static void prompt(Context context, String prompt) {
Toast.makeText(context, prompt, Toast.LENGTH_SHORT).show();
}

public static void prompt(Context context, int strId) {
prompt(context, context.getString(strId));
}

public static void longPrompt(Context context, String prompt) {
Toast.makeText(context, prompt, Toast.LENGTH_LONG).show();
}

public static void longPrompt(Context context, int strId) {
longPrompt(context, context.getString(strId));
}
}


再写一个针对Net的ToastUtil
package com.chenjian.net.demo.util;

import android.content.Context;

import com.chenjian.net.R;
import com.chenjian.net.bean.NetRetBean;

/**
* Created by ChenJian
* 2016.7.1 16:54:54.
*/
public class NetToastUtil {

/**
* 解析错误码
*/
public static void requestError(Context context, NetRetBean netRetBean) {
switch (netRetBean.getCallbackCode()) {
case CODE_ERROR_SERVER_DATA_ERROR:
case CODE_ERROR_JSON_EXP:
serverError(context);
break;

case CODE_ERROR_HTTP_NOT_200:
case CODE_ERROR_REQUEST_EXP:
case CODE_ERROR_UNKNOWN:
default:
requestError(context);
break;
}
}

/**
* 服务器返回数据异常
*/
private static void serverError(Context context) {
ToastUtil.longPrompt(context, R.string.request_failed_server);
}

/**
* 请求出错,可能是 网络请求发生异常 或者 网络未打开
*/
private static void requestError(Context context) {
if (DeviceUtil.isNetConnect(context)) {
netError(context);
} else {
noOpenNet(context);
}
}

/**
* 网络请求发生异常
*/
private static void netError(Context context) {
ToastUtil.longPrompt(context, R.string.request_failed);
}

/**
* 网络未打开
*/
private static void noOpenNet(Context context) {
ToastUtil.longPrompt(context, R.string.no_open_network);
}
}


<resources>
<string name="no_open_network">网络未打开</string>
<string name="request_failed_server">服务器返回数据异常</string>
<string name="request_failed">网络请求发生异常</string>
</resources>


在网络未打开时,我们提示no_open_network,
在请求时发生异常的,比如http的返回码不为200,我们提示request_failed
在请求到数据后,但是服务端返回的code不为00001,或者json解析失败时,我们提示request_failed_server

使用起来就方便了,如果你的某个网络请求的错误提示没有特别的要求的话,直接在onError里面调用一行就行
NetHelper.get(urlParse.toString(), new NetStringListener() {
@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {
requestError(netRetBean);
}
});


当然,如果你不想写onError方法的话,也行,因为每次都写这行代码,也相当于是重复的代码。
我们再写一个Listener,他继承自NetStringListener,他把onError方法给实现了
package com.chenjian.net.demo.listener.async;

import android.content.Context;

import com.chenjian.net.bean.NetRetBean;
import com.chenjian.net.demo.util.NetToastUtil;
import com.chenjian.net.listener.async.NetStringListener;
import com.chenjian.net.listener.common.CallbackCode;

/**
* 作者: ChenJian
* 时间: 2017.1.9 20:12
*/

abstract public class NetSimpleStringListener extends NetStringListener {
private Context mContext;

public NetSimpleStringListener(Context context) {
this.mContext = context;
}

@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {
NetToastUtil.requestError(mContext, netRetBean);
}
}


子类要使用的话,只要实现onSuccess方法就行了,因为onError方法已经在本类中实现了,而且采用了是用户自己业务上通用的解决方案。至于onError方法里面要写哪些代码,开发者自己定义。关于问题2的解决,其实只是给大家说一下编程思想。

来看看使用
NetHelper.get("url", new NetSimpleStringListener(this) {
@Override
protected void onSuccess(String string) {

}
});


用起来是如此的方便。你甚至可以这样
NetHelper.get("url", new NetSimpleStringListener(this) {
@Override
protected void onSuccess(String string) {

}

@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {
if (errorCode == CallbackCode.CODE_ERROR_JSON_EXP) {
// 在json解析失败时,你需要做特殊的处理,其它时候,用默认的错误处理
} else {
super.onError(errorCode, netRetBean);
}
}
});


当然,你也可能需要模仿NetSimpleStringListener,实现NetSimpleSingleBeanListener,还有NetSimpleListBeanListener。

关于更多的使用方法,请参考源码中的GetActivity和PostActivity两个类。此处不再做更多介绍

最后再说个知识,其实本框架的Listener的使用,是可以变通的

对于这样格式的返回:
{
"code":"00001",
"message":"login success",
"time":"1479807260",
"data":{
"id":"123",
"name":"chenjian"
}
}


你也可以使用NetStringListener
NetHelper.get("url", new NetStringListener() {
@Override
protected void onSuccess(String string) {
/**
* 这里返回的数据就会是 "id":"123", "name":"chenjian",
* 然后你在这里再进行解析,不过这里已经是ui线程了,
* 在ui线程如果做大量复杂的解析,可能不太好。少量的话,并没影响
*/
}

@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {

}
});


对于这样格式的返回:
{
"code":"00001",
"message":"login success",
"time":"1479807260",
"data":{
"list":[
{
"id":"123",
"name":"chenjian"
},
{
"id":"123",
"name":"chenjian"
},
{
"id":"123",
"name":"chenjian"
}
]
}
}


之前的做法是定义NetUserBean,然后使用NetListBeanListener。

其实在这个地方,你也可以使用NetStringListener。
和上面一样,使用NetStringListener时候,在onSuccess里面返回的是data字段里面的数据,你再进行解析。

可以说NetStringListener,基本上是万能的。

至此,框架的基本使用已经讲解完了。

下一篇将讲解自定义解析器
Android通用网络请求解析框架.6(自定义解析器)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: