每个公司的项目都有各自具体的业务,市面上大多数第三方网络请求框架都不能完全满足公司业务的具体需求,所以自己动手一步步写一个网络加载框架。
说到网络请求,无非是把子线程中请求到的数据解析出来,并发送回主线程去,期间Activity中处理的逻辑越少越好,美其名曰:一行代码完成,其实也就是如下图所示的类图:
根据上方的逻辑图,先把每个分支都写好,这里需要导入两个jar包
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.google.code.gson:gson:2.8.0'
并且在Manifest文件中加入联网权限
<uses-permission android:name="android.permission.INTERNET" />
底层还是使用okhttp框架,用一个HttpUtil将其封装起来
public class HttpUtil {
private static final String TAG = HttpUtil.class.getSimpleName();
private static OkHttpClient sOkHttpClient;
public static Response get(String url) {
LogUtils.e("HttpUtil", url);
Request request= new Request.Builder()
.url(url)
.build();
try {
return getOkHttpClient().newCall(request).execute();
} catch (IOException e) {
LogUtils.e("HttpUtil", e.toString());
}
return null;
}
public static Response get(String url, Map<String,String> headers) {
LogUtils.e("HttpUtil", url);
Request request;
Request.Builder builder = new Request.Builder();
if (headers != null && headers.size() > 0){
for (Object o : headers.entrySet()) {
Map.Entry entry = (Map.Entry) o;
String key = (String) entry.getKey();
String value = (String) entry.getValue();
builder.addHeader(key, value);
}
}
request = builder.url(url).build();
Response response;
try {
return getOkHttpClient().newCall(request).execute();
} catch (IOException e) {
LogUtils.e("HttpUtil", e.toString());
}
return null;
}
public static Response post(String url, Map<String, String> postParameters, Map<String, String> headers) {
LogUtils.e("HttpUtil", url);
try {
Request request;
FormBody.Builder builder = new FormBody.Builder();
if (postParameters == null) {
postParameters = new HashMap<>();
}
Set<String> set = postParameters.keySet();
for (String key : set) {
builder.add(key,postParameters.get(key));
}
Request.Builder requestBuilder = new Request.Builder();
if (headers != null && headers.size() > 0){
Set<String> headerSet = headers.keySet();
for (String key : headerSet) {
requestBuilder.addHeader(key,headers.get(key));
}
}
RequestBody formBody = builder.build();
request = requestBuilder
.url(url)
.post(formBody)
.build();
return getOkHttpClient().newCall(request).execute();
} catch (Exception e) {
LogUtils.e("HttpUtil", e.toString());
}
return null;
}
public static Response put(String url, Map<String, String> postParameters) {
LogUtils.e("HttpUtil", url);
try {
FormBody.Builder builder = new FormBody.Builder();
if (postParameters == null) {
postParameters = new HashMap<>();
}
Set<String> set = postParameters.keySet();
for (String key : set) {
builder.add(key,postParameters.get(key));
}
RequestBody formBody = builder.build();
Request request = new Request.Builder()
.url(url)
.put(formBody)
.build();
return getOkHttpClient().newCall(request).execute();
} catch (Exception e) {
LogUtils.e("HttpUtil", e.toString());
}
return null;
}
public static Response delete(String url, Map<String, String> postParameters) {
LogUtils.e("HttpUtil", url);
try {
FormBody.Builder builder = new FormBody.Builder();
if (postParameters == null) {
postParameters = new HashMap<>();
}
Set<String> set = postParameters.keySet();
for (String key : set) {
builder.add(key,postParameters.get(key));
}
RequestBody formBody = builder.build();
Request request = new Request.Builder()
.url(url)
.method("DELETE", formBody)
.build();
return getOkHttpClient().newCall(request).execute();
} catch (Exception e) {
LogUtils.e("HttpUtil", e.toString());
}
return null;
}
private static OkHttpClient getOkHttpClient() {
if (sOkHttpClient == null) {
synchronized (HttpUtil.class) {
if (sOkHttpClient == null) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(10, TimeUnit.SECONDS);
builder.writeTimeout(10, TimeUnit.SECONDS);
builder.readTimeout(30, TimeUnit.SECONDS);
sOkHttpClient = builder.build();
}
}
}
return sOkHttpClient;
}
}
HttpUtil中代码没什么好说的,就是将一些基础的Get,Post,Put,Delete这些常见的请求封装成为工具方法
RequestManager:请求统一管理类,可以包括发起请求,取消请求等一系列网络请求相关生命周期,可以看到网络请求中所必需的参数是Url,Params,还有请求方式(HttpMethod),还需要一个Callback,用于取到数据后回调给Activity(controller),目前先想到这么多,就先把这些写出来
public class RequestManager {
private static RequestManager sRequestManager;
public static RequestManager getRequestManager(){
if(sRequestManager == null){
synchronized (RequestManager.class){
if(sRequestManager == null){
sRequestManager = new RequestManager();
}
}
}
return sRequestManager;
}
public void performRequest(String url, Map<String,String> params,HttpMethod httpMethod,JsonCallback callback){
RequestTask task = new RequestTask(url,params,httpMethod,callback);
task.execute();
}
}
由于RequestManager是个管理类,别忘了用单例模式,这里我使用了双加锁模式,其中performRequest就是启动一个任务,用于请求数据,并且将参数发送过去,task.execute()就是执行这个网络请求。
Callback:用于将数据传回到Activity中
IResponseCallback:最上层接口IResponseCallback包含请求成功和失败两个回调,可以实现此接口对此进行扩展,比如onStart(),onComple().......
public interface IResponseCallback<T> {
/**
* 请求成功回调
* @param result
*/
void onSuccess(T result);
/**
* 请求失败回调
* @param code
* @param msg
*/
void onFailure(int code,String msg);
}
JsonCallback:我这里只做了一个小实现,在JsonCallback中解析Json数据,别的扩展大家可以发挥想象空间......
public abstract class JsonCallback<T> implements IResponseCallback<T> {
public T bindData(String json) {
return new Gson().fromJson(json,
((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
}
}
既然是统一对数据包装并且返回,就必须用到泛型,因为每个请求的实体类Bean不是同一个,Gson框架解析没什么好说的,基本属于模板代码
HttpMethod:简单用一个枚举类表示一下~
public enum HttpMethod {
GET,
POST,
PUT,
DELETE
}
RequestTask:网络请求任务类,用于子线程请求数据并处理相关逻辑,拿到数据后,需要用Handler切换到主线程
public class RequestTask implements Runnable {
private static final String TAG = RequestTask.class.getSimpleName();
private String url;
private Map<String,String> params;
private HttpMethod httpMethod;
private static InternalHandler sHandler;
private JsonCallback mJsonCallback;
public RequestTask(String url, Map<String, String> params,HttpMethod method,JsonCallback callback) {
this.url = url;
this.params = params;
this.httpMethod = method;
this.mJsonCallback = callback;
}
@Override
public void run() {
Response response;
String json;
if(httpMethod == HttpMethod.GET){
response = HttpUtil.get(url);
}else if(httpMethod == HttpMethod.POST){
response = HttpUtil.post(url,params,null);
}else if(httpMethod == HttpMethod.PUT){
response = HttpUtil.put(url,params);
}else {
response = HttpUtil.delete(url,params);
}
try {
if(response != null){
if(response.body() != null){
json = response.body().string();
if (!TextUtils.isEmpty(json)) {
LogUtils.i(TAG,json);
JSONObject jsonObject = new JSONObject(json);
if (jsonObject.has("code")) {
int code = jsonObject.optInt("code");
String message = jsonObject.optString("msg");
String type = jsonObject.optString("type");
String data = jsonObject.optString("data");
LogUtils.i(TAG,"需要解析的数据 :" + data);
if (code == 1) {
Object o = mJsonCallback.bindData(data);
onSuccess(o);
} else {
onFailure(code, message);
}
} else {
onFailure(-1, "json error");
}
} else {
//Json为空
onFailure(-1,"json empty");
return;
}
}
}else {
onFailure(-1,"response empty");
}
} catch (Exception e) {
LogUtils.e(TAG, "error---" + e.toString());
e.printStackTrace();
onFailure(-1,"Exception");
}
}
public void execute() {
new Thread(this).start();
}
private void onFailure(final int code, final String msg) {
getHandler().post(new Runnable() {
@Override
public void run() {
mJsonCallback.onFailure(code,msg);
}
});
}
private void onSuccess(final Object o) {
getHandler().post(new Runnable() {
@Override
public void run() {
mJsonCallback.onSuccess(o);
}
});
}
private static class InternalHandler extends Handler {
InternalHandler() {
super(Looper.getMainLooper());
}
}
private static InternalHandler getHandler() {
if (sHandler == null) {
synchronized (RequestTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
}
}
return sHandler;
}
}
其中的execute其实就是启动一个子线程,并且start(),传入this,因为RequestTask实现了Runnable接口,在run()中处理一系列判断逻辑,其中关于code的判断是我们与后台服务器定义好的逻辑,拿到json后code为1是请求成功,其余为失败,失败的类型可以自己根据情况返回,打Log也好,回调也好,总之就是越能快速定位错误越好,这一块可以自己自定义。比如有的后台喜欢code定义为200为成功,其余为失败......(这里感谢后台大神为我专门写了两个测试接口)
到这里,网络框架的雏形就基本搭建完成了,对照上面的类图,该有的都有了,写一个Activity,整两个Button,测试一下看看行不行:
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private String getUrl = "http://39.100.224.84:6100/api/system/open/module/selectCCGet";
private String postUrl = "http://39.100.224.84:6100/api/system/open/module/selectCCPost";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_test_get).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
RequestManager.getRequestManager().performRequest(getUrl,null,HttpMethod.GET,
new JsonCallback<User>() {
@Override
public void onSuccess(User result) {
LogUtils.i(TAG,result.toString());
}
@Override
public void onFailure(int code, String msg) {
LogUtils.i(TAG,"code = " + code + " msg = " + msg);
}
});
}
});
findViewById(R.id.btn_test_post).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Map<String,String> params = new HashMap<>();
params.put("userId","123");
RequestManager.getRequestManager().performRequest(postUrl, params, HttpMethod.POST, new JsonCallback<User>() {
@Override
public void onSuccess(User result) {
LogUtils.i(TAG,result.toString());
}
@Override
public void onFailure(int code, String msg) {
LogUtils.i(TAG,"code = " + code + " msg = " + msg);
}
});
}
});
}
}
这里有个User类,就是返回的实体Bean,里面就三个参数,为了方便看返回结果,重写一下toString()方法
public class User {
/**
* name : ly
* id : 1
* sex : 男
*/
private String name;
private String id;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id='" + id + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
这里一个Get请求,一个Post请求,简单测试一下(可以看到onClick方法中就两个步骤:1.拼接参数 2.用RequestManager一句话发送请求,并且创建好Callback等待接收结果),运行一下,看看Callback中返回的结果
2021-02-07 17:45:38.510 25446-25855/com.wzw.baseframwork E/HttpUtil: http://39.100.224.84:6100/api/system/open/module/selectCCGet
2021-02-07 17:45:38.627 25446-25855/com.wzw.baseframwork I/RequestTask: {"data":{"name":"ly","id":"1","sex":"男"},"msg":"成功","type":"NO","code":1}
2021-02-07 17:45:38.629 25446-25855/com.wzw.baseframwork I/RequestTask: 需要解析的数据 :{"name":"ly","id":"1","sex":"男"}
2021-02-07 17:45:38.634 25446-25446/com.wzw.baseframwork I/MainActivity: User{name='ly', id='1', sex='男'}
OK,大功告成,可以看到MainActivity中的Log中打印出了User的信息,说明上面的代码测试正确。至此,一个网络请求框架就初步成型了,每次在需要用到它的地方拼几个参数,再用RequestManager一句话发送请求,就可以拿到需要的数据了。
功能是完成了,回顾一下上面的代码,发现有很多细节上的地方可以优化,这篇文章就到这了,下一篇文章再来考虑具体的优化。