在开发Android项目时自己写的一个网络连接框架,基于valley框架的使用建立了一个支持多线程的、异步下载的、多数据格式的网络框架
在这之前,我们在程序中需要和网络通信的时候,大体使用的东西莫过于AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient(Apache)等,今年的Google I/O 2013上,Volley发布了。Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮。
这是Volley名称的由来: a burst or emission of many things or a large amount at once
1.1. Volley引入的背景
在以前,我们可能面临如下很多麻烦的问题。
比如以前从网上下载图片的步骤可能是这样的流程:
在ListAdapter#getView()里开始图像的读取。
通过AsyncTask等机制使用HttpURLConnection从服务器去的图片资源
在AsyncTask#onPostExecute()里设置相应ImageView的属性。
而在Volley下,只需要一个函数即可,详细见后面的例子。
再比如,屏幕旋转的时候,有时候会导致再次从网络取得数据。为了避免这种不必要的网络访问,我们可能需要自己写很多针对各种情况的处理,比如cache什么的。
再有,比如ListView的时候,我们滚动过快,可能导致有些网络请求返回的时候,早已经滚过了当时的位置,根本没必要显示在list里了,虽然我们可以通过ViewHolder来保持url等来实现防止两次取得,但是那些已经没有必须要的数据,还是会浪费系统的各种资源。
1.2. Volley提供的功能
简单来说,它提供了如下的便利功能:
JSON,图像等的异步下载;
网络请求的排序(scheduling)
网络请求的优先级处理
缓存
多级别取消请求
和Activity和生命周期的联动(Activity结束时同时取消所有网络请求)
/**
* 安卓与后台连接类,本类中所有方法必须在多线程中执行
* (静态方法将影响封装与后期拓展,此处用普通方法)
*该类中包括了cookie的检查、超时连接的处理、网络检测等方法
*/`
@SuppressWarnings("deprecation")
public class ConnectBase {
private static final String DECODE_UNICODE = "\\\\u([0-9a-zA-Z]{4})";
private static String JSP_COOKIE = null;// 维持会话的cookie
private Context context = null;
/**
* 使用旧cookie
*/
public ConnectBase(Context context) {
this.context = context;
JSP_COOKIE = ConnectTool.getCookie(context);
}
/**
* 刷新cookie
*/
public ConnectBase(Context context, boolean refreash) {
this.context = context;
}
/**
* 以协定方式执行post连接后台,发送head与body并接收后台返回。
*
* @param url 连接地址
* @param list 主体信息
* @return 后台返回的字符串信息
*/
public String executePost(String url, ConnectList list) {
if (!isNetworkEnable(context)) {// 网络不可用,直接返回null
return null;
}
if (!url.startsWith("http")) {
url = ServerURL.getIP() + url;
}
final int COONECT_TIME_OUT = 10000;// 设定连接超时10秒
final int READ_TIME_OUT = 10000;// 设定读取超时为10秒,仅仅用于登录
BufferedReader in = null;
try {
// 定义HttpClient,实例化Post方法
HttpClient client = new DefaultHttpClient();
HttpPost request = new HttpPost(url);
// 设定超时,超时将以异常形式提示
client.getParams().setParameter(
CoreConnectionPNames.CONNECTION_TIMEOUT, COONECT_TIME_OUT);// 请求超时
client.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT,
READ_TIME_OUT);// 读取超时
// 添加cookie信息
if (JSP_COOKIE != null && !JSP_COOKIE.equals("")) {// 若为null,则不添加,等待服务器返回
request.addHeader("Cookie", JSP_COOKIE);
Log.e("EEEE", "EEEE cookie:" + JSP_COOKIE);
}
// 添加body信息
List body = list.getList();
UrlEncodedFormEntity formEntiry = new UrlEncodedFormEntity(body);
request.setEntity(formEntiry);
// 执行请求
HttpResponse response = client.execute(request);
// cookie处理,维护会话
Header head = response.getFirstHeader("set-Cookie");
if (head != null) {
JSP_COOKIE = head.getValue();
ConnectTool.saveCookie(context, JSP_COOKIE);
Log.e("EEEE", "EEEE new-cookie:" + JSP_COOKIE);
}
// 接收返回
in = new BufferedReader(new InputStreamReader(response.getEntity()
.getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
while ((line = in.readLine()) != null) {
sb.append(line + "\n");
}
in.close();
String result = sb.toString();
int code = response.getStatusLine().getStatusCode();
Log.e("EEEE", "EEEE url:" + url);
Log.e("EEEE", "EEEE code:" + code);
Log.e("EEEE", "EEEE size:" + list.getList().size());
Log.e("EEEE", "EEEE result:" + result);
if (code == 500 || code == 404) {
return null;
}
result = URLDecoder.decode(result, "UTF-8");
result = URLDecoder.decode(result, "UTF-8");
result = result.trim();
if (result.equals(""))
return null;// 后台返回""则返回null。
return result;
} catch (Exception e) {// 很有可能是请求超时了
// e.printStackTrace();
if (e != null)
Log.e("EEEE", "EEEE " + e.getMessage());
return null;
} finally {// 这个在finally中很有必要
if (in != null) {
try {
in.close();
} catch (Exception e) {
// e.printStackTrace();
}
}
}
}
// 解决\\u问题
public static String decode(String s) {
Pattern reUnicode = Pattern.compile(DECODE_UNICODE);
Matcher m = reUnicode.matcher(s);
StringBuffer sb = new StringBuffer(s.length());
while (m.find()) {
m.appendReplacement(sb,
Character.toString((char) Integer.parseInt(m.group(1), 16)));
}
m.appendTail(sb);
return sb.toString();
}
// 解决\\u问题,简便方法
public static String decodeEasy(String s) {
try {// json自带解析
return new JSONObject(s).toString();
// JSONObject.parse(s).toString();//更好,尤其是带引号的
} catch (JSONException e) {
return s;
}
}
// 检测网络是否可用
private static boolean isNetworkEnable(Context context) {
ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ni = cm.getActiveNetworkInfo();
return ni != null && ni.isConnectedOrConnecting();
}
}
/**
*基于valley框架,实现前端与后台的连接
*前端可以向后台发出post请求,包括单请求和多请求的处理
*/
public class ConnectEasy extends AsyncTask<Void, Void, String> {
private Context context;
private String url;
private ConnectList list;
private ConnectDialog dialog;
private ConnectListener listener;
private static RequestQueue queue;
public ConnectEasy(Context context, String url, ConnectListener listener) {
this.context = context;
this.url = url;
this.listener = listener;
list = new ConnectList();
dialog = new ConnectDialog();
if (listener != null) {
list = listener.setParam(list);
dialog = listener.showDialog(dialog);
}
if (list == null)// 防止listener返回错了
list = new ConnectList();
if (dialog == null)
dialog = listener.showDialog(dialog);
}
@Override
protected void onPreExecute() {
if (dialog != null) {
dialog.show();
}
}
@Override
protected String doInBackground(Void... params) {
ConnectBase con = new ConnectBase(context);
return con.executePost(url, list);
}
@Override
protected void onPostExecute(String result) {
if (listener != null)
listener.onResponse(result);
if (dialog != null)
dialog.hide();
}
// ///基于回调的方法///
/**
* 向指定网址发起post请求
*
* @param context context
* @param url 网址
* @param listener 监听回调
*/
public static void POST(Context context, String url,
ConnectListener listener) {
//刷新URL
url = ServerURL.getSignedURL(url);
VOLLEY(context, url, listener);
// //发起请求
// ConnectEasy connect = new ConnectEasy(context, url, listener);
// connect.execute();
}
/**
* 向指定网址发起post请求,强制使用原来的方法
*
* @param context context
* @param url 网址
* @param listener 监听回调
*/
public static void POSTLOGIN(Context context, String url,
ConnectListener listener) {
//刷新URL
url = ServerURL.getSignedURL(url);
//发起请求
ConnectEasy connect = new ConnectEasy(context, url, listener);
connect.execute();
}
// ///基于Volley的方法///
public static void VOLLEY(final Context context, final String url,
final ConnectListener listener) {
if (queue == null) {
queue = Volley.newRequestQueue(context);
}
final ConnectDialog dialog;
final ConnectList list;
if (listener == null) {
dialog = null;
list = new ConnectList();
} else {
dialog = listener.showDialog(new ConnectDialog());
if (dialog != null)
dialog.show();
ConnectList list_temp = listener.setParam(new ConnectList());
if (list_temp == null)
list = new ConnectList();
else
list = list_temp;
}
Request request = null;
if (!list.hasFile()) {
StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
try {//编码处理
if (ServerURL.isTest()) {
Log.e("EEE-VOLLEY-url", url + "");
if (list != null && list.getMap() != null)
Log.e("EEE-VOLLEY-params", list.getMap().size() + "");
if (!TextUtils.isEmpty(response))
Log.e("EEE-VOLLEY-response1", response);
}
if (!TextUtils.isEmpty(response)) {
response = new String(response.getBytes("ISO-8859-1"), "utf-8");
if (ServerURL.isTest()) {
Log.e("EEE-VOLLEY-response2", response + "");
response = ConnectBase.decode(response);//便于查看(此方法更加稳定)
Log.e("EEE-VOLLEY-response3", response + "");
}
}
} catch (Exception e) {
if (ServerURL.isTest()) {
Log.e("EEE-VOLLEY-error", "response other error");
if (e != null)
Log.e("EEE-VOLLEY-error", e.getMessage() + "");
}
}
if (listener != null)
listener.onResponse(response);
if (dialog != null)
dialog.hide();
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (ServerURL.isTest()) {
Log.e("EEE-VOLLEY-error", "response error");
if (error != null)
Log.e("EEE-VOLLEY-error", error.getMessage() + "");
}
if (listener != null)
listener.onResponse("");
if (dialog != null)
dialog.hide();
}
}) {
@Override
protected Map<String, String> getParams() {
if (listener != null) {
if (list != null) {
return list.getMap();
}
}
return new HashMap<String, String>();
}
@Override
public Map<String, String> getHeaders() {
Map<String, String> map = new HashMap<>();
String cookie = ConnectTool.getCookie(context);
map.put("Cookie", cookie == null ? "" : cookie);
if (ServerURL.isTest())
Log.e("EEE-VOLLEY-cookie", "" + cookie);
return map;
}
};
request = stringRequest;
} else {
MultipartRequest multipartRequest = new MultipartRequest(url,
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
if (listener != null)
listener.onResponse("");
if (dialog != null)
dialog.hide();
}
},
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
try {//编码处理
if (ServerURL.isTest()) {
Log.e("EEE-VOLLEY-url", url + "");
if (list != null && list.getMap() != null)
Log.e("EEE-VOLLEY-params", list.getMap().size() + "");
if (!TextUtils.isEmpty(response))
Log.e("EEE-VOLLEY-response1", response + "");
}
if (!TextUtils.isEmpty(response)) {
response = new String(response.getBytes("ISO-8859-1"), "utf-8");
response = JSONObject.parse(response).toString();
if (ServerURL.isTest())
Log.e("EEE-VOLLEY-response2", response + "");
}
} catch (Exception e) {
if (ServerURL.isTest()) {
Log.e("EEE-VOLLEY-error", "response other error");
if (e != null)
Log.e("EEE-VOLLEY-error", e.getMessage() + "");
}
}
if (listener != null)
listener.onResponse(response);
if (dialog != null)
dialog.hide();
}
}, list.getListKey(), list.getListFile(), list.getMap());
request = multipartRequest;
}
request.setRetryPolicy(
new DefaultRetryPolicy(
500000,//默认超时时间,应设置一个稍微大点儿的,例如本处的500000
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,//默认最大尝试次数
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
)
);
queue.add(request);
}
/**
*处理服务端与前端之间多数据的传输,
*将多个数据或者文件以list的形式进行处理
*
*/
public class ConnectList {
private List list = null;
private Map map = null;
private List list_key = null;
private List list_file = null;
private boolean has_file = false;
public ConnectList() {
list = new ArrayList<>();
map = new HashMap<>();
list_key = new ArrayList<>();
list_file = new ArrayList<>();
}
/**
* 添加一个文件,服务器收到File类
*/
public ConnectList put(String key, File file) {
list_key.add(key);
list_file.add(file);
has_file = true;
return this;
}
/**
* 添加一个文件,服务器收到Set-File类
*/
public ConnectList put(String key, List files) {
for (File file : files)
put(key, file);
return this;
}
/**
* 添加一个键值对,是否强制encode,影响list,不影响map
*/
public ConnectList put(String key, String value, boolean should_encode) {
map.put(key, value == null ? "" : value);//map直接put。
try {
if (should_encode)
value = URLEncoder.encode(value == null ? "" : value, "UTF-8");
} catch (Exception e) {
return this;
}
NameValuePair item = new BasicNameValuePair(key, value);
list.add(item);
return this;
}
/**
* 添加一个文件,服务器收到File类
* 防止null冲突
*/
public ConnectList putFile(String key, File file) {
return put(key, file);
}
/**
* 添加一个键值对,String值
*/
public ConnectList put(String key, String value) {
return put(key, value, true);
}
/**
* 添加一个键值对,Long值
*/
public ConnectList put(String key, long value) {
return put(key, value + "");
}
public List getList() {
return list;
}
public Map getMap() {
return map;
}
public List getListKey() {
return list_key;
}
public List getListFile() {
return list_file;
}
/**
* 是否包含文件
*/
public boolean hasFile() {
return has_file;
}
// ///静态方法
/**
* 直接获取网络数据类
*
* @param key_value 键1,值1,键2,值2,……键n,值n
* @return 网络数据类
*/
public static ConnectList getSimpleList(String... key_value) {
if (key_value.length % 2 == 1)
return null;
ConnectList list = new ConnectList();
for (int i = 0; i < key_value.length; i += 2) {
list.put(key_value[i], key_value[i + 1]);
}
return list;
}
}
/**
* 文件处理,使之可以通过json传输
*
*
*/
public class ConnectFile {
/**
* TODO:将以Base64方式编码的字符串解码为byte数组
*
* @param encodeString
* 待解码的字符串
* @return 解码后的byte数组,解码失败返回null
*/
public static byte[] decodeFile(String encodeString) {
byte[] filebyte = null;
try {
filebyte = Base64Coder.decode(encodeString);
} catch (Exception e) {
filebyte = null;
}
return filebyte;
}
/**
* TODO:将文件以Base64方式编码为字符串
*
* @param filepath
* 文件的绝对路径
* @return 编码后的字符串,编码失败返回null
* */
public static String encodeFile(String filepath) {
String result = "";
try {
FileInputStream fis = new FileInputStream(filepath);
byte[] filebyte = new byte[fis.available()];
fis.read(filebyte);
fis.close();
result = new String(Base64Coder.encode(filebyte));
} catch (IOException e) {
result = null;
}
return result;
}
}
/**
*网络请求时以监听的方式来控制网络参数、网络执行情况等
*在移动端向服务端发起网络请求时需要注册监听器
*
*/
public interface ConnectListener {
/**
* 网络请求的参数
*
* @param list 默认的参数列表(空表,直接put然后返回即可)
* @return 添加参数后的参数列表
*/
public ConnectList setParam(ConnectList list);
/**
* 是否显示忙碌对话框
*
* @param dialog 默认的对话框(不显示,调用config将显示并在onResponse结束后自动隐藏)
* @return 配置后的对话框
*/
public ConnectDialog showDialog(ConnectDialog dialog);
/**
* 网络执行完毕后自动回调
*
* @param response 服务器返回的数据,错误将返回null
*/
public void onResponse(String response);
}
/**
*网络签名,在网络传输时用于校对Android端与服务端的信息比对
*这里主要针对于两端服务时间的比对,用于矫正时间差
*
*/
public class ConnectSign {
public static String KEY_TIME = "timestamp", KEY_SIGN = "signature";
private static long TIME_SPACE = 0;
private static String SECRET_KEY = "qianxun";
/**
* 获取签名的MD5
*
* @param time 时间戳
* @return 时间戳+秘钥,取两次MD5
*/
public static String getSignMD5(long time) {
String all = time + SECRET_KEY;
String result = getMD5(all);
result = getMD5(result);
return result;
}
/**
* 处理时间差,安卓专用
*
* @param time 服务器时间
*/
public static void dealTimeSpace(long time) {
TIME_SPACE = time - System.currentTimeMillis();
}
public static void dealTimeSpace(String time) {
if (TextUtils.isEmpty(time))
return;
if(time.length()!=13)
return;
try {
long time_long = Long.parseLong(time);
dealTimeSpace(time_long);
} catch (Exception e) {
}
}
/**
* 获取签名的URL后缀,安卓专用
*
* @return 以"?"开头的URL后缀
*/
public static String getSignURL() {
String result = "?";
long time = getTimeSnap();
result += KEY_TIME + "=" + time;
result += "&";
result += KEY_SIGN + "=" + getSignMD5(time);
return result;
}
/**
* 获取时间戳
*
* @return 当前的服务器时间
*/
private static long getTimeSnap() {
return System.currentTimeMillis() + TIME_SPACE;
}
/**
* 获取文本数据的MD5编码(注:安卓没有直接就按MD5的包)
*
* @param text 要编码的文本数据
* @return 数据的32位MD5字符串值
*/
private static String getMD5(String text) {// 返回32位MD5数组
String result = "";
MessageDigest message = null;
byte[] bytes = null;
try {
message = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
}
bytes = message.digest(text.getBytes());
result = new String(toHexString(bytes));
return result;
}
/**
* 将byte数组转换为Hex字符串,这其实是HttpClient里面的codec.jar中Hex类中的encodeHex方法
* (这里没有必要导入整个包,所以只拿出来这个方法)
*
* @param md 要转换的byte数组
* @return 转换后的字符串
*/
private static String toHexString(byte[] md) {
char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f'};
int j = md.length;
char str[] = new char[j * 2];
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[2 * i] = hexDigits[byte0 >>> 4 & 0xf];
str[i * 2 + 1] = hexDigits[byte0 & 0xf];
}
return new String(str);
}
}
以上就是我所实现的一个Android端网络框架,上述是框架中主要的代码,供大家参考。