移動端網絡框架--基於valley實現

說明:

在開發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端網絡框架,上述是框架中主要的代碼,供大家參考。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章