回顾
前两章我们用Vollery+FastJson很方便的获得了数据,Java8的Lambda表达式
可以将initData方法的代码显示得如下图般精简。
Java8 Lambda表达式教程
添加Header
真实的项目中,有时会遇到加上token,统计信息等请求头,那么怎么办呢,看下代码。
可以看到我们仅仅是重写了Request的getHeaders()方法。
@Override
public Map getHeaders() throws AuthFailureError {
Map map = new HashMap<>();
map.put("versionName", BuildConfig.VERSION_NAME);
return map;
}
向请求添加了一个Header参数,参数名称为versionName,参数值是当前应用的版本名称。
缺点
但是个人认为还不够完美,如果你对Volley比较熟悉的话,就能发现每一次请求都要创建一个Request,然后将其add到RequestQueue,如果项目中请求非常多,那么将会看到遍地都是onResponse,onError。而且如果项目进行到一半,突然说每个请求头都要加上某字段(如果有值)。那将是一件多么痛苦的事情。
接下来分析我是如何将Volley封装为更适合该类项目需求的。
修改分析
接下来要做的,应该是将重复的代码整合到一起,整个网络流程可以归纳为三句话:
- 我想要做什么(url)
- 我能提供什么(headers, params)
- 结果返回后要做什么
这里要说一下为什么我用Volley,为什么是使用StringRequest而不是JsonObjectRequest,最大的原因就是为了接下来的封装。而请求结果返回后,应当将结果返回给请求发起的地方。接下来看代码。
先在com.joyin.volleydemo.utils.network
包下新建RequestHandler
类,里面创建addRequestByGet方法:
/**
* @param method Request.Method.GET 或 Request.Method.POST
* @param handler 请求结束后将结果作为Message.obj发送到该Handler
* @param what 请求结束后发送的Message.what
* @param bundle 不参与网络请求,仅携带参数
* (请求结束后,通过Message.setData设置到Message对象,数据原样返回)
* @param url 请求地址
* @param params 请求参数
* @param header 请求头
*/
public static void addRequest(
final int method, final Handler handler, final int what,
final Bundle bundle, final String url,
final Map params,
final Map header) {
}
其中bundle对象在请求结束后,可通过Message.getData()可得到,并不参与网络流程。合理利用该参数,可以将多种操作串起来。
接下来首先新建com.joyin.volleydemo.utils.network.NetworkHelper
类,并且提供三个方法,完整代码如下:
package com.joyin.volleydemo.utils.network;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by joyin on 16-4-2.
*/
public class NetworkHelper {
/**
* 将参数转换为字符串
*/
public static String convertMapToString(Map params) {
StringBuffer content = new StringBuffer();
List keys = new ArrayList<>(params.keySet());
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (value != null) {
content.append((i == 0 ? "" : "&") + key + "=" + value);
} else {
content.append((i == 0 ? "" : "&") + key + "=");
}
}
return content.toString();
}
/**
* 講參數进行URLEncode编码转换
* @param params
* @return
*/
public static Map getURLEncodeParams(Map params) {
Map map = new HashMap();
for (String key : params.keySet()) {
String value = params.get(key);
try {
value = URLEncoder.encode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
map.put(key, value);
}
return map;
}
/**
* 拼接url和参数,用于Get请求
* @param url
* @param params
* @return
*/
public static String getUrlWithParams(String url, Map params) {
if (params == null || params.isEmpty()) {
return url;
}
params = getURLEncodeParams(params);
String paramsStr = convertMapToString(params);
if (!url.endsWith("?")) {
url += "?";
}
url += paramsStr;
return url;
}
}
代码比较简单,在Get请求的时候,调用getUrlWithParams方法,将url和params一起传入,即可自动生成拼接好参数的url。
新建com.joyin.volleydemo.app.MyApplication
类,完整代码:
package com.joyin.volleydemo.app;
import android.app.Application;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
/**
* Created by joyin on 16-4-3.
*/
public class MyApplication extends Application {
private RequestQueue mRequestQueue;
private static MyApplication mInstance;
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
mRequestQueue = Volley.newRequestQueue(this);
}
public static MyApplication getInstance() {
return mInstance;
}
public static RequestQueue getRequestQueue() {
return mInstance.mRequestQueue;
}
}
修改AndroidManifest.xml
某些条件下,网络请求是需要显示loading动画的,在这里也考虑到了,现在用默认的ProgressDialog,先把逻辑实现了,最后再实现自定义的loading框。
再来看整理好功能的RequestHandler.java
package com.joyin.volleydemo.utils.network;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.joyin.volleydemo.BuildConfig;
import com.joyin.volleydemo.app.MyApplication;
import java.util.HashMap;
import java.util.Map;
/**
* Created by joyin on 16-4-2.
*/
public class RequestHandler {
public static final int NET_ERROR_VOLLEY = -2;
private static void addRequest(
int method,
final Handler handler, final int what,
final Bundle bundle, String url, final Map params, final Map header,
final NetWorkRequestListener listener) {
if (method == Request.Method.GET) {
url = NetworkHelper.getUrlWithParams(url, params);
}
listener.onPreRequest();
StringRequest request = new StringRequest(method, url, new Response.Listener() {
@Override
public void onResponse(String response) {
onVolleyResponse(response, handler, what, bundle);
listener.onResponse();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
onVolleyErrorResponse(volleyError, listener, handler, bundle);
}
}) {
@Override
public Map getHeaders() throws AuthFailureError {
Map map = header;
if (map == null) {
map = new HashMap<>();
}
// 在此统一添加header
map.put("versionName", BuildConfig.VERSION_NAME);
return map;
}
/**
* Volley仅在post的情况下会回调该方法,获取form表单参数
* @return
* @throws AuthFailureError
*/
@Override
protected Map getParams() throws AuthFailureError {
return params;
}
};
MyApplication.getRequestQueue().add(request);
}
private static void onVolleyErrorResponse(VolleyError volleyError, NetWorkRequestListener listener, Handler handler, Bundle bundle) {
if (listener.retry()) {
listener.onFailed();
return;
}
Message msg = handler.obtainMessage(NET_ERROR_VOLLEY);
msg.setData(bundle);
handler.sendMessage(msg);
listener.onFailed();
}
private static void onVolleyResponse(String response, Handler handler, int what, Bundle bundle) {
Message msg = handler.obtainMessage(what, response);
msg.setData(bundle);
handler.sendMessage(msg);
}
/**
* @param method Request.Method.GET 或 Request.Method.POST
* @param handler 请求结束后将结果作为Message.obj发送到该Handler
* @param what 请求结束后发送的Message.what
* @param bundle 不参与网络请求,仅携带参数
* (请求结束后,通过Message.setData设置到Message对象,数据原样返回)
* @param url 请求地址
* @param params 请求参数
* @param header 请求头
*/
public static void addRequest(
final int method, final Handler handler, final int what, final Bundle bundle,
final String url, final Map params, final Map header) {
addRequest(method, handler, what, bundle, url, params, header, new DefaultRequestListener() {
@Override
public boolean retry() {
addRequest(method, handler, what, bundle, url, params, header,
retryTimer++ >= MAX_RETRY_TIME ? new DefaultRequestListener() : this);
return true;
}
});
}
public static void addRequestWithDialog(
final int method, Context context, final Handler handler, final int what, final Bundle bundle,
final String url, final Map params, final Map header) {
addRequest(method, handler, what, bundle, url, params, header, new DefaultDialogRequestListener(context) {
@Override
public boolean retry() {
addRequest(method, handler, what, bundle, url, params, header,
retryTimer++ >= MAX_RETRY_TIME ? new DefaultDialogRequestListener(context) : this);
return true;
}
});
}
/**
* 请求过程中显示加载对话框,且自动处理其生命周期
*/
private static class DefaultDialogRequestListener extends DefaultRequestListener {
Context context;
ProgressDialog dialog;
public DefaultDialogRequestListener(Context context) {
this.context = context;
dialog = new ProgressDialog(context);
}
@Override
public void onPreRequest() {
dialog.show();
}
@Override
public void onResponse() {
dialog.dismiss();
}
@Override
public void onFailed() {
dialog.dismiss();
}
}
private static class DefaultRequestListener implements NetWorkRequestListener {
int retryTimer;
static final int MAX_RETRY_TIME = 3;
@Override
public void onPreRequest() {
}
@Override
public void onResponse() {
}
@Override
public void onFailed() {
}
@Override
public boolean retry() {
return false;
}
}
/**
* 用于所有网络请求,在不同时机回调的接口
*/
private static interface NetWorkRequestListener {
void onPreRequest();
void onResponse();
void onFailed();
boolean retry();
}
}
这个类里面的代码有点多,但是都不难,想必熟悉Android的人都可以很好的理解。
onVolleyResponse里可以对数据做处理,然后再分发下去,比如服务端api所有返回结果都是以{"code":1,"message":{}}
格式,根据code判断各项认证是否成功等,都可以在这拦截处理。这点下一章会讲到。
现在看修改代码后的MainActivity
package com.joyin.volleydemo.activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import com.alibaba.fastjson.JSON;
import com.android.volley.Request;
import com.joyin.volleydemo.R;
import com.joyin.volleydemo.data.api.IpInfo;
import com.joyin.volleydemo.utils.network.RequestHandler;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
TextView mTvCountry, mTvCountryId, mTvIP;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
initData();
}
private void initViews() {
mTvCountry = (TextView) findViewById(R.id.tv_country);
mTvCountryId = (TextView) findViewById(R.id.tv_country_id);
mTvIP = (TextView) findViewById(R.id.tv_ip);
}
private void initData() {
String url = "http://ip.taobao.com/service/getIpInfo.php";
Map params = new HashMap<>();
params.put("ip", "21.22.11.33");
RequestHandler.addRequest(Request.Method.GET, mHandler, RESULT_GET_IP_INFO, null, url, params, null);
}
private void setIpInfoToView(IpInfo ipInfo) {
mTvCountry.setText(ipInfo.getData().getCountry());
mTvCountryId.setText(ipInfo.getData().getCountry_id());
mTvIP.setText(ipInfo.getData().getIp());
}
private static final int RESULT_GET_IP_INFO = 101;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case RESULT_GET_IP_INFO:
String result = (String) msg.obj;
Log.d("demo", result);
IpInfo ipInfo = JSON.parseObject(result, IpInfo.class);
setIpInfoToView(ipInfo);
break;
}
}
};
}
看initData方法里,网络请求是不是很容易。
将initData里最后一行
RequestHandler.addRequest(Request.Method.GET, mHandler, RESULT_GET_IP_INFO, null, url, params, null);
修改为:
RequestHandler.addRequestWithDialog(Request.Method.GET, MainActivity.this, mHandler, RESULT_GET_IP_INFO, null, url, params, null);
就会在请求过程中显示loading框了。
目前我们的网络请求分工已经很明确,后续章节将会在现在的基础上进行优化,包括Handler的优化,网络请求错误码统一处理,loading对话框的定制等。