Android Volley 超實用簡易封裝
寫在前面
本文僅針對有一定volley基礎的朋友們。如果你之前完全沒有接觸過volley,推薦去看下郭霖大神的blog。Android Volley完全解析(一),初識Volley的基本用法
本文心法傳授於群裡大佬,大佬QQ:864009106,讓我對於volley以及封裝有了新的認識,十分感謝。
準備工作
Manifest.xml配置網路許可權
<!-- 網路請求許可權 --> <uses-permission android:name="android.permission.INTERNET" />
JSON處理
本文采用fastjson進行解析
//fastJson compile 'com.alibaba:fastjson:1.2.33'
進度條顯示(可選)
本文采用SweetAlertDialog
//sweet-alert-dialog compile 'cn.pedant.sweetalert:library:1.3'
匯入SweetAlertDialog時,專案會報Manifest merge failed錯誤,在Manifest的application中加入tools:replace=”android:icon”即可解決。
初始化
確保RequestQueue單例。
新建RequestQueueUtil工具類, 單例獲取RequestQueue物件。
public class RequestQueueUtil { private static RequestQueue sRequestQueue; public static RequestQueue getRequestQueue(Context context) { if (sRequestQueue == null) { synchronized (RequestQueue.class) { if (sRequestQueue == null) { sRequestQueue = Volley.newRequestQueue(context); } } } return
在Application中初始化請求佇列
public class VolleyApplication extends Application { public static RequestQueue sRequestQueue; @Override public void onCreate() { super.onCreate(); sRequestQueue = RequestQueueUtil.getRequestQueue(this); } }
開始封裝
開始之前,我們先來想一想,一個好的網路請求框架需要哪些東西?
首先要有可拓展性,可以根據專案的情況靈活修改。
其次要有統一的資料處理以及錯誤處理。
最後一定要結構整齊美觀,便於後期維護。
咱們先來看一個基本的Request用法:
private void getInfo() {
StringRequest request = new StringRequest(Request.Method.GET, "url", new Response.Listener<String>() {
@Override
public void onResponse(String s) {
Bean bean = JSON.parseObject(s, Bean.class);
switch (bean.getStatus()) {
//成功邏輯處理
case 1000:
//do something...
break;
//case xxxx:
//其他邏輯處理...
//break;
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
Toast.makeText(MainActivity.this, "error", Toast.LENGTH_SHORT).show();
}
});
request.setRetryPolicy(new DefaultRetryPolicy(
5 * 1000,//連結超時時間
0,//重新嘗試連線次數
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
));
requestQueue.addRequest(request);
}
再來看一個我們封裝好的用法:
private void getNewInfo() {
NetworkUtil.getInstance().get(MainActivity.this, "url", map, new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case TAG:
Log.i("result", msg.obj.toString());
//do something...
break;
}
}
}, TAG);
}
比較起來,封裝之後是不是看起來更加的清晰明瞭呢?話不多說,咱們正式開始。
為了更好的應對專案的變化,我們來建立一個工具類來對請求進行封裝:
public class NetworkUtil{ private static NetworkUtil instance; //單例獲取工具類 public static NetworkUtil getInstance() { if (instance == null) { synchronized (NetworkUtil.class) { if (instance == null) { instance = new NetworkUtil(); } } } return instance; } //獲取請求佇列 private RequestQueue mRequestQueue = VolleyApplication.sRequestQueue; }
確定BaseBean:
所有請求傳送成功之後的返回json都應該有著相同的結構,例如我們的專案中會返回:
{ "status": 1000, "desc": "success", "data": { "key": "value", "key2": 100, "key3": false //xxxx:yyyy .... } }
為了統一資料,新建BaseBean類:
public class BaseBean<T> { //返回碼 private int status; //返回資訊 private String desc; //我們需要的資料 private T data; //getter and setter here... }
這裡使用了泛型T來對data進行處理,這樣不管返回的結果如何,我們都不用再新建類去處理status以及desc,直接解析data即可。
完整方法:
public void post(final Activity activity, String url, final Map<String, String> map, final Handler handler, final int tag) { final SweetAlertDialog dialog = new SweetAlertDialog(activity); dialog.getProgressHelper().setBarColor(Color.parseColor("#66CCFF")); dialog.setTitleText("Loading"); dialog.setCancelable(false); if (!dialog.isShowing()) { dialog.show(); } final Message message = Message.obtain(); StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() { @Override public void onResponse(String s) { if (dialog.isShowing()) { dialog.dismiss(); } BaseBean baseBean = JSON.parseObject(s, BaseBean.class); int status = baseBean.getStatus(); switch (status) { case 1000: message.what = tag; message.obj = baseBean.getData(); handler.sendMessage(message); break; //case xxxx: //處理其他邏輯,例如簽名錯誤、token失效、toast錯誤資訊等 //break; } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { if (dialog.isShowing()) { dialog.dismiss(); } handlerErrorMessage(activity, volleyError); } }) { //如有公參請求頭之類的在這裡設定就好 //@Override //public Map<String, String> getHeaders() throws AuthFailureError { //return super.getHeaders(); //} @Override protected Map<String, String> getParams() throws AuthFailureError { return map; } }; request.setRetryPolicy(new DefaultRetryPolicy( 5 * 1000,//連結超時時間 0,//重新嘗試連線次數 DefaultRetryPolicy.DEFAULT_BACKOFF_MULT//曲線增長因子 )); mRequestQueue.add(request); }
先貼出一個完整的post請求方法,接下來我們逐步分析:
引數
/** * @param activity 當前Activity Context * @param url 請求地址 * @param map 請求引數 * @param handler 返回結果處理 * @param tag 請求標識 */
activity:
傳入當前Activity作為Context,由於存在progress,必須依託在ActivityContext中,所以不能使用一般Context。另外在Log時也可以通過列印activity.getClass().getSimpleName()進行追蹤。
url:
請求的連結地址
map:
請求引數,通過Map進行引數處理
handler:
通過handler.sendMessage()傳送資料,在Activity中拿到msg.obj進行資料處理。
tag:
當前請求標識。由於存有一個頁面發起多個請求的情況,用一個自定義tag來對請求進行標記,在Activity的handler中,通過判斷tag來確保請求不發生混亂。
進度顯示
此項為可選項,但從互動角度考慮需要加上進度條顯示。這裡使用了SweetAlertDialog,大家也可以選用自定義進度條或者其他控制元件。在請求發起時顯示,請求成功或失敗後關閉,並做對應的邏輯處理。
傳送請求
一個基本的StringRequest,如果專案需求公參或者Header,在這裡統一設定。
(最開始嫌麻煩沒有封裝,專案中期要求加公參,真的是一個一個改。。。都是淚)此外由於使用者操作,介面響應等多種因素,會造成傳送多次請求的情況,所以在將請求新增到佇列之前,使用setRetryPolicy來統一設定,確保不會重複傳送。資料處理
利用Message傳遞資料,成功響應後,將tag放入msg.what中,並將解析後的data放入msg.obj中,再由handler傳送。其中具體的邏輯按照具體的專案來修改。在失敗響應中,根據volleyError做了統一處理,直接在UI介面中Toast出相應資訊。
具體使用
一個基本的Button點擊發送請求,成功後在TextView中進行顯示。由於沒找到合適的post請求連結,就從網上找了個天氣api介面,但是不曉得為嘛返回亂碼。。。get請求的封裝稍後會在原始碼中放出。
public class MainActivity extends AppCompatActivity {
private Button mButton;
private TextView mContent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.test_button);
mContent = (TextView) findViewById(R.id.test_content);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
NetworkUtil.getInstance().get(MainActivity.this, "http://wthrcdn.etouch.cn/weather_mini?citykey=101010100", null, new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case WEATHER_TAG:
Log.i("result", msg.obj.toString());
mContent.setText(msg.obj.toString());
break;
}
}
}, WEATHER_TAG);
}
});
}
}