Android通用网络请求解析框架.5(使用框架)
2017-01-06 15:55
726 查看
笔者将通过11篇博客对个人开源框架进行讲解,本篇为第5篇,讲解使用框架。
开源库github地址 https://github.com/qq296216078/Android-Universial-NetFrame
如果有兴趣一起讨论本框架的内容,请加QQ群:271335749
在之前的几篇博文中,其实已经对使用本框架有所介绍过了,本篇进行更全面的介绍
假设服务端返回的数据是这样的:
定义一个Bean,他继承NetBaseBean
再来看看使用
使用的代码和之前介绍的没差多少,这里在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中需要的工具类
先看三个性属,分别为参数map,url头,是否使用公共参数,
再看看三个toString方法,分别提供了只返回url头,只返回参数,返回url头+参数的形式。
类的addCommonParam方法中用到了UrlManager类,他是与本类相关的一个类
一起来看看使用,我们一般将url常量放在一个类里
然后在使用get请求的时候,这样
这个时候,urlParse.toString()的返回值为"10.0.12.39/net/login?username=chenjian&password=12345678"
在使用post请求时,我们这样
这个时候,urlParse.toStringOnlyHeader()的返回值为"10.0.12.39/net/login",
paramParse.toStringOnlyParam()的返回值为"username=chenjian&password=12345678"
如果你的项目需要公共参数,那么可以使用UrlManager类,一般我们在Application类里面做一下初始化
假设服务端要求每个请求都要加上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的工具类
再写一个针对Net的ToastUtil
在网络未打开时,我们提示no_open_network,
在请求时发生异常的,比如http的返回码不为200,我们提示request_failed
在请求到数据后,但是服务端返回的code不为00001,或者json解析失败时,我们提示request_failed_server
使用起来就方便了,如果你的某个网络请求的错误提示没有特别的要求的话,直接在onError里面调用一行就行
当然,如果你不想写onError方法的话,也行,因为每次都写这行代码,也相当于是重复的代码。
我们再写一个Listener,他继承自NetStringListener,他把onError方法给实现了
子类要使用的话,只要实现onSuccess方法就行了,因为onError方法已经在本类中实现了,而且采用了是用户自己业务上通用的解决方案。至于onError方法里面要写哪些代码,开发者自己定义。关于问题2的解决,其实只是给大家说一下编程思想。
来看看使用
用起来是如此的方便。你甚至可以这样
当然,你也可能需要模仿NetSimpleStringListener,实现NetSimpleSingleBeanListener,还有NetSimpleListBeanListener。
关于更多的使用方法,请参考源码中的GetActivity和PostActivity两个类。此处不再做更多介绍
最后再说个知识,其实本框架的Listener的使用,是可以变通的
对于这样格式的返回:
你也可以使用NetStringListener
对于这样格式的返回:
之前的做法是定义NetUserBean,然后使用NetListBeanListener。
其实在这个地方,你也可以使用NetStringListener。
和上面一样,使用NetStringListener时候,在onSuccess里面返回的是data字段里面的数据,你再进行解析。
可以说NetStringListener,基本上是万能的。
至此,框架的基本使用已经讲解完了。
下一篇将讲解自定义解析器
Android通用网络请求解析框架.6(自定义解析器)
开源库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(自定义解析器)
相关文章推荐
- CS231n:神经网络一例
- Not found org.springframework.http.converter.json.MappingJacksonHttpMessageConverter
- [置顶] HTTPDNS域名解析场景下如何使用Cookie?
- centos6.5源码安装和centos7.2使用yum安装httpd后,如何添加模块;以及怎么设置Cache-Control:max-age=?浏览器缓存时间
- 基于协程的Python网络库gevent介绍
- If you are behind an HTTP proxy, please configure the proxy settings either in IDE or Gradle.
- ImageLoader 加载https图片报异常 SSLCertificateSocketFactory
- 神经网络的Dropout正则化
- XMLHttpRequest cannot load http://xxx. Response for preflight has invalid HTTP status code 404
- HTTP 协议详解
- 基于TCP的Socket通信传文件
- linux c++ 高并发tcp服务器架构
- 最浓缩的基于TCP协议的网络通信详解
- iOS HTTP网络请求,没有使用框架的request
- http 和 https 总结
- http 和 https 总结
- http 和 https 总结
- http 和 https 总结
- http 和 https 总结
- http 和 https 总结