笔者将通过11篇博客对个人开源框架进行讲解,本篇为第1篇,讲解需求,思想。
如果有兴趣一起讨论本框架的内容,请加QQ群:271335749
从代码说起吧。对于初学者,我们写一个网络请求,通常的,会是下面这样
package com.chenjian.testandroid;
import android.app.Activity;
import android.os.Handler;
import android.os.Message;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
request();
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Object object = msg.obj;
// 拿着object进行操作,可能要更新ui
break;
}
}
};
private void request() {
new Thread() {
@Override
public void run() {
String url = "";
Object object = null;
// Object object = Http.get(url);
Message message = Message.obtain();
message.what = 0;
message.obj = object;
mHandler.sendMessage(message);
}
}.start();
}
}
先定义一个Handler,因为多数情况下要进行ui操作。
接着是request方法,里面首先是拿到url,url可能经过各种拼接。
再用url进行http请求,这里省略了http请求的细节部分,但对整体逻辑的理解并没影响。
再接着,我们把请求的结果,通过handler,返回给ui线程。
这样,ui线程就可以拿着数据进行更新ui了。
问题出现了:
1.每个类里面都要写一个Handler。试想一下如果整个项目就写一个全局静态的,那在handleMessage里面的处理会多麻烦。
2.每次请求都要开一个线程,或者说是一个类里面至少要开一个。开线程并不算什么,关键是,你的代码,重复了。
3.线程执行方法里,url的拼接,多数项目是有公共参数的,也就是每一个http请求都要带的参数。且url拼接,直接用普通字符串进行相加,不够符合面向对象思想,也不灵活。
4.发起http请求并将结果包装成Message返回给ui线程,同样存在代码重复的问题。
当然一些开发者会采用另一种方式,可以少写些代码,也就是AsyncTask
package com.chenjian.testandroid;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
public class OtherActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
request();
}
private void request() {
new AsyncTask<String, Integer, Object>() {
@Override
protected Object doInBackground(String... params) {
String url = "";
Object object = null;
// Object object = Http.get(url);
return object;
}
@Override
protected void onPostExecute(Object o) {
Object object = o;
// 拿着object进行操作,可能要更新ui
}
}.execute();
}
}
以上的代码分析,还漏掉了至关重要的两点:
1.解析,通常是将json字符串解析为我们代码中的实体
2.错误处理,包括请求的过程中,解析的过程中,ui线程拿到实体后还需要判断是否为null等
1.解析
解析是繁琐的,对于json,android的sdk里面就有相应的类和方法,也有一些第三方解析框架,比如gson,fastjson。
笔者的开源框架,完全兼容第三解析框架,且第三方解析的内容,只是本框架的一小部分内容。
现在来说本篇中至关重要的一个思想。
笔者到现在也接触过不少项目了,发现了一个共性,也就是服务端返回的数据,多数情况下会是以一定格式的,比如下面这样:
{code, message, data : { 这里才是具体数据 } }
可能你在外层会增加一些数据,比如time之类的,但是总归是类似于上面那样的。
因此,我们在解析的时候,可以分为两部分,一部分是外层的解析,一部分是内层具体数据的解析。
对于外层的解析,可以抽成公共部分,这样代码就省了很多。
内层的解析,需要具体分析,因为内层可能返回的是单个Bean(实体),也可能是List<Bean>,更有可能是多种不同的Bean和不同的List<Bean>组合而成。当然,也可能是空字符串。
在编程上,我们可以在一个类里面解析外层数据,此类定义一个抽象方法,再由他的子类实现抽象方法去解析内层数据。来看看下面这段代码,应该就很清晰了
abstract public class ParseJson {
private void parseJson(String jsonString) throws JSONException {
JSONObject jsonObject = new JSONObject(jsonString);
String code = jsonObject.getString("code");
String message = jsonObject.getString("message");
Object data = parseData(jsonObject.getString("data"));
}
abstract Object parseData(String jsonString) throws JSONException;
}
子类在parseData方法中,可以选择使用Android自带的Json相关类进行解析,也可以使用gson,或者fastjson等第三方解析框架。
2.错误处理
错误处理通常是麻烦在判断,比如ui线程拿到实体后还需要判断是否为null,确实是够蛋疼的但又不得不去做。
还有一个比较让人头疼的地方,当ui线程拿到数据后,如果判断为错误,ui线程很难知道是哪个步骤出错了,必须在之前要做很多的记录才行。
说到最后,那我们到底需要一个怎样的网络请求解析框架呢?本框架又能达到怎样的效果呢?
1.必须是一步到位的,通过一个入口,我传入url等参数,如果请求成功,返回给我时,是已经解析好的Bean
2.所有环节出错,都要归类到一个地方,且在那个地方,可以判断是哪个步骤出错了,以及出错的相关信息
3.请求后,不管成功失败,我得到结果时,必须已经回到主线程
用代码来表达最好了,我希望是这样的
Util.getXXBean("url") {
onSuccess(XXBean xxBean) {
// 这里是ui线程
}
onError(int errorCode, ErrorBean errorBean) {
// 这里是ui线程
}
};
Util.getYYBean("url") {
onSuccess(YYBean yyBean) {
}
onError(int errorCode, ErrorBean errorBean) {
}
};
通过util工具类,把url传进去。
首先,回调onSuccess时,已经是实体Bean了,且是不为null的,如果为null,就会回调onError。
这里的Bean是已经去掉了外层的数据,用data里面的内层数据所解析出来的。
当然你如果需要在成功的时候也要得到外层数据,可以在onSuccess方法里面加上一些参数,或者一个参数实体来包含那些数据。
其次,回调onError方法时,通过errorCode,就可以判断错误类型,
而且这个方法带了一个ErrorBean参数,里面记录了一些错误的信息,比如json解析出错,http请求的status码不为200,连接超时,以及所有可能的错误信息。
在这里可以把错误信息提示给用户,或者你的代码逻辑需要,总之更加清楚错误来源。
最后,onSuccess和onError都必须是已经回到ui线程。
本框架也提供了同步请求,即直接在当前线程中请求和返回的方法,后面的章节会讲到。
细心的你也许发现了,以上的代码有一个致命的缺点,也就是一种Bean需要在Util类里面写一个方法。
怎么办?你应该想到了:泛型。使用泛型吧,感谢这个伟大的发明。
使用泛型后,代码修改,会简洁成下面这个样子
Util<T>.getBean("url") {
onSuccess(T t) {
}
onError(int errorCode, ErrorBean ErrorBean) {
}
};
当然,前面也说过了返回的类型不一定是单个Bean的,也可能是List<Bean>
Util<T>.getListBean("url") {
onSuccess(List<T> ts) {
}
onError(int errorCode, ErrorBean ErrorBean) {
}
};
Util<A, B, C>.getCustomBean("url") {
onSuccess(A a, List<B> bs, C c) {
}
onError(int errorCode, ErrorBean ErrorBean) {
}
};
或者你不想解析成实体,或者你不关心返回结果,都可以写成下面这样
Util.getString("url") {
onSuccess(String string) {
}
onError(int errorCode, ErrorBean ErrorBean) {
}
};
以上的几种模式,几乎满足了所有网络请求解析需求。
鉴于本篇是讲解思想,以上代码都比较简陋,跟源码中有所差异,url的拼接,这里也没介绍。
本篇也不想细讲代码,总体上就是阐述一个思想,不写重复的代码,让使用框架的开发者方便,把最有用的信息都能采集并归类到一个地方。
下篇将讲解整个框架的构造