說明:
在開發Android項目時自己寫的一個網絡連接框架,基於valley框架的使用建立了一個支持多線程的、異步下載的、多數據格式的網絡框架
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結束時同時取消所有網絡請求)
關鍵類介紹:
ConnectBase:
/**
* 安卓與後臺連接類,本類中所有方法必須在多線程中執行
* (靜態方法將影響封裝與後期拓展,此處用普通方法)
*該類中包括了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<NameValuePair> 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();
}
}
ConnectEasy
/**
*基於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);
}
ConnectList
/**
*處理服務端與前端之間多數據的傳輸,
*將多個數據或者文件以list的形式進行處理
*
*/
public class ConnectList {
private List<NameValuePair> list = null;
private Map<String, String> map = null;
private List<String> list_key = null;
private List<File> 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<File> 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<NameValuePair> getList() {
return list;
}
public Map<String, String> getMap() {
return map;
}
public List<String> getListKey() {
return list_key;
}
public List<File> 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;
}
}
ConnectFile
/**
* 文件處理,使之可以通過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;
}
}
ConnectListener
/**
*網絡請求時以監聽的方式來控制網絡參數、網絡執行情況等
*在移動端向服務端發起網絡請求時需要註冊監聽器
*
*/
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);
}
ConnectSign
/**
*網絡簽名,在網絡傳輸時用於校對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端網絡框架,上述是框架中主要的代碼,供大家參考。