以前发在 CSDN 上, blog.csdn.net/u014443348/…
掘金氛围感觉更好一点,打算慢慢转过来。
源码基于 Retrofit 2.6.0
变量扩展
因为最近负责开发几个应用需要加上一个功能:通过访问服务器端,对比服务器端上软件的版本号与当前应用的版本号,如果版本号大于当前应用就进行升级。
既然是要几个应用都需要这个功能,那我们想的是肯定首先做成一个通用型的,每个应用都拷贝一份,然后调用不同的配置就好。
那按照我们常规的思路,就可以这样,虚拟下代码。
静态变量
java 代码解读复制代码
//声明一个检查类
public class UpdateChecker{
//应用1
public static final String TYPE_APP1 = "xxx1";
//应用2
public static final String TYPE_APP3 = "xxx3";
//应用3
public static final String TYPE_APP2 = "xxx2";
//针对不同的升级类型,进行调用
public void startCheck(String type){
switch (type){
case TYPE_APP1:
break;
case TYPE_APP2:
break;
case TYPE_APP3:
break;
default:
}
}
……
}
当然此处的的类型也可以换成 int ,我们在查找对应应用是否需要升级时调用对应的类别就行。
这种如果是自己用的话,肯定也是没有问题的,如果要把程序交给别人使用,怎么让别人一眼就看到有哪些值可以进行调用。
那接手的人可以尝试输入 UpdateChecker.
之后,对应编程软件弹出对应的静态属性或者方法提示,从中寻找看着比较像的选项。
更负责任的做法,就是在对应的方法上,加上注释,并且使用 {@link }
进行标注,这样一看注释,就可以有相关链接,跳转到对应的参数,查看对应参数的注释。
java 代码解读复制代码
/**
* 传入不同的应用类型进行对应版本升级检测。
* 可以使用这些值。
*
* {@link #TYPE_APP1} 应用1
* {@link #TYPE_APP2} 应用2
* {@link #TYPE_APP3} 应用3
*
* @param type 需要检测的类型
*/
public void startCheck(String type){
switch (type){
case TYPE_APP1:
break;
case TYPE_APP2:
break;
case TYPE_APP3:
break;
default:
}
}
使用带链接注释很容易让使用者快速找到相关的类,特别是如果此时的 TYPE 是在另一个专用的常量类里,那不进行链接注释,怕是不好找了。
当然此处也可能存在传入值不适当的情况,比如传了一个 abc ,那么肯定是不合适的。
虽然这种事情发生概率极极极小,但是很多 Java 书中仍然推荐使用枚举。因为你只能选那几个,随便输入值是不行的。
枚举
java 代码解读复制代码
public class UpdateChecker{
public void startCheck(AppType type){
switch (type){
case TYPE_APP1:
break;
case TYPE_APP2:
break;
case TYPE_APP3:
break;
default:
}
}
……
}
public enum AppType {
//应用1
TYPE_APP1,
//应用2
TYPE_APP2,
//应用3
TYPE_APP1;
}
其实从这看,也没什么差别,无法从枚举中看出有什么优势,不过如果我们如果需要把某些固定的信息和对应的类型进行绑定时。
如果是用静态变量进行扩展时,那就需要加更多的静态变量,在前缀或者后缀上进行区别,多了还是会有眼花的风险;那如果换成枚举就很简单了。
比如我们需要绑定每个类别的名字和网络地址。
java 代码解读复制代码
public enum AppType {
//需要使用的应用类型
//TYPE_APP1
TYPE_APP1(AppName.TYPE_APP1,AppUrl.AppUrl1),
//TYPE_APP2
TYPE_APP2(AppName.TYPE_APP2,AppUrl.AppUrl2),
//TYPE_APP3
TYPE_APP3(AppName.TYPE_APP3,AppUrl.AppUrl3);
//需要存储的对应信息
private final String name;
private final String url;
//构造函数中进行配置
AppType(String name,Strng url){
this.name = name;
this.url = url;
}
String getName(){
return name;
}
String getUrl(){
return url;
}
//名字常量类
public static class AppName{
public final static String AppType1 = "AppType1";
public final static String AppType2 = "AppType2";
public final static String AppType3 = "AppType3";
}
//网址常量类
public static class AppUrl{
public final static String AppUrl1 = "AppUrl1";
public final static String AppUrl2 = "AppUrl2";
public final static String AppUrl3 = "AppUrl3";
}
}
这样看起来是不是清爽很多,首先在内部对不同属性定义一个静态类,然后添加不同的常量变量在类中。
在枚举类中声明需要存储的对应 final 变量(因为我们不希望这个值在后期进行变化),构造函数中声明需要配置的一些变量,此处例子就是一个名字和网址。
那我们在声明不同的枚举类型时,加入不同的变量进行配置。
我们 UpdateChecker.startCheck()
时只需要传入一个类型,在需要使用名字的时候使用 type.getName()
,在需要使用网址时,使用 type.getUrl()
,那么就很简单的把各种类型进行绑定。有效减少了填错值的情况。
当然也可以试着用一个普通类来完成这种枚举类的形式,在此就不写了。
请求网络检测的时候使用的 Retrofit ,因为注解看着很华丽,哈哈。
一般处理网络回复分为异步和同步,其实 … 写法最后也差不多。
我们就分析异步的情况。进行请求时,需要将逻辑处理的回调作为参数进行传入。先来看看这个回调。
java 代码解读复制代码
public interface Callback {
void onResponse(Call call, Response response) ;
void onFailure(Call call, Throwable t) ;
}
onResponse()
方法是拿到回复了,但是不一定是成功的,而且 response.body()
有可能是空,那么我们就拆成三部分,分为请求失败,回复失败,回复成功,我们新建一个类来实现。
封装
java 代码解读复制代码
public abstract class BaseCallback implements Callback {
@Override
public void onResponse(Call call, Response response) {
//如果返回成功,并且body 也不为空。那么就算成功,其他算回复失败。
if(response.isSuccessful() && response.body() != null){
responseSuccess(call, response);
}else{
responseFail(call, response);
}
}
@Override
public void onFailure(Call call, Throwable t) {
requestFail(call, t);
}
public abstract void responseSuccess(Call call, Response response) ;
public abstract void responseFail(Call call, Response response) ;
public abstract void requestFail(Call call, Throwable t) ;
}
看着好像还阔以,虽然只是封装了一小步 …
接下来在使用这个抽象类,肯定需要返回结果进行一些日志的打印,或者是发出对应的事件。比如究竟是没网,还是 json 解析失败。
java 代码解读复制代码
private BaseCallback updateCallback =
new BaseCallback() {
@Override
public void responseSuccess(Call call,
Response response) {
……
mListener.onUpdateCheck(Result.CHECK_RESPONSE_JSON_FAIL);
}
@Override
public void responseFail(Call call,
Response response) {
……
mListener.onUpdateCheck(Result.CHECK_RESPONSE_FAIL);
}
@Override
public void requestFail(Call call,
Throwable t) {
……
mListener.onUpdateCheck(Result.CHECK_REQUEST_FAIL);
}
};
public interface OnUpdateListener{
void onUpdateCheck(Result errorCode);
void onDownloadProgress(int progress);
}
这我们做了一个监听,对请求结果进行回调,此处的 Result 类也是定义的一个枚举类。用来通知不同的请求结果。其实刚开始还行,就定义了几个 Result 类型,然后在 responseSuccess()
对数据进行一些更细的校验时,那么就对应需要返回更多的 Result 类型。
我想到了,如果是给的 Java 源码还好,你可以直接在 Result 类进行扩展,那如果是拿到的库文件,那想加都没办法了,只能选择一个 Result 类型使用。
这样又不能准确的传达出想要表达的信息,这样看来使用枚举在这种情况下还是有一点的缺陷。
那么想来想去,可以把 Result 类型从 enum 换成 interface 类型。
java 代码解读复制代码
public interface Result {
String getMsg();
Result NO_INTERNET_PERMISSION = new Result() {
@Override
public String getMsg() {
return MSG_NO_INTERNET_PERMISSION;
}
};
Result NO_WRITE_FILE_PERMISSION = new Result() {
@Override
public String getMsg() {
return MSG_NO_WRITE_FILE_PERMISSION;
}
};
Result CHECK_RESPONSE_JSON_FAIL = new Result() {
@Override
public String getMsg() {
return MSG_CHECK_RESPONSE_JSON_FAIL;
}
};
static final String MSG_NO_INTERNET_PERMISSION = "没有获取网络权限";
static final String MSG_NO_WRITE_FILE_PERMISSION = "没有获取读写权限";
static final String MSG_CHECK_RESPONSE_JSON_FAIL = "JSON 字符解析失败";
}
这样,除了自己定义的一些类型,如果后期打包给别人用,别人使用时,直接 new 一个接口类,并且复写 getMsg()
就可以定义出自己想要的 Result 类。
枚举与接口对比
那什么情况下使用枚举合适,什么时候使用接口合适 ?
我的想法是:如果你是需要封装一个东西,但是又不希望人家进行改动或者扩展时,使用枚举。如果你希望人家可以在此基础上进行扩展,那么就可以使用接口,并且写一些相关的类,给他人参考用。
回调收尾
完成了请求过程中关于信息的传输,我们肯定需要把监听,或者引用的一些其他资源置空,防止内存泄漏。我们可以这么操作 …
java 代码解读复制代码
private BaseCallback updateCallback =
new BaseCallback() {
@Override
public void responseSuccess(Call call,
Response response) {
……
mListener.onUpdateCheck(Result.CHECK_RESPONSE_JSON_FAIL);
clear();
}
@Override
public void responseFail(Call call,
Response response) {
……
mListener.onUpdateCheck(Result.CHECK_RESPONSE_FAIL);
clear();
}
@Override
public void requestFail(Call call,
Throwable t) {
……
mListener.onUpdateCheck(Result.CHECK_REQUEST_FAIL);
clear();
}
};
private void clear(){
……
mListener = null;
}
这样虽然没什么问题,但是一点也不优雅,还要加三个地方,而且以后新的 BaseCallback 都要加,那我们就把这个 clear()
放在基类里。
java 代码解读复制代码
public abstract class BaseCallback implements Callback {
@Override
public void onResponse(Call call, Response response) {
//如果返回成功,并且body 也不为空。那么就算成功,其他算回复失败。
if(response.isSuccessful() && response.body() != null){
responseSuccess(call, response);
clear();
}else{
responseFail(call, response);
clear();
}
}
@Override
public void onFailure(Call call, Throwable t) {
requestFail(call, t);
clear();
}
……
public abstract void clear();
}
那么在 new 之后,我们需要多复写一个 clear()
方法。
现在问题来了,如果按照现在的逻辑,在执行中会不会有什么风险 ?
当然 ! 因为我们 listener 在使用时并没有进行 != null
判断,如果在执行 responseSuccess()
, responseFail()
, requestFail()
回调时,进行了异步的操作,那么很可能会出现空指针异常。
除了在进行非空判断以后,还有一个小的技巧,就是调整三个方法的返回值,让使用者来决定是否需要执行 clear()
。
此处用 responseSuccess()
进行演示,因为其他情况失败了,一般默认是输出信息,不会进行异步操作,所以暂时就不用那两个方法演示。
java 代码解读复制代码
public abstract class BaseCallback implements Callback {
@Override
public void onResponse(Call call, Response response) {
if(response.isSuccessful() && response.body() != null){
//如果返回 true ,证明没有异步操作,直接进行清除。
if(responseSuccess(call, response ,this)){
clear();
}else{
//如果返回 false 的话,需要使用者自己找时机清除。
//可以把此 baseCallback 对象传到异步方法等执行完调用clear()
//不过稍微有点麻烦.
}
}else{
responseFail(call, response);
clear();
}
}
@Override
public void onFailure(Call call, Throwable t) {
requestFail(call, t);
clear();
}
//如果返回 true,表明可以直接调用 clear ,如果返回 false ,则表明剩下还有异步操作。
//传入 BaseCallback 是方便异步方法中仍然拿到 callback 引用。
public abstract boolean responseSuccess(Call call,
Response response,
BaseCallback callback);
……
}
responseSuccess()
通过返回 true 或者 false ,来决定是否需要 clear()
,如果是同步操作,那么顺序执行就可以执行 clear()
,反之就不执行。
如果其他地方有持有该 BaseCallback 对象时,也可以在异步操作完成后调用 clear()
,而不需要在 responseSuccess()
把此对象进行传递。
至于 responseFail()
与 requestFail()
,我们可以考虑做一个通用的日志输出方法,并且方法为 protected ,那么 new BaseCallback()
时只需要复写一个方法,如果需要更改的话,再进行复写,那么不会看着比较臃肿。
此分析纯属个人见解,如果有不对之处或者欠妥地方,欢迎指出一起讨论。
评论记录:
回复评论: