Volley完全解析之進階最佳實踐與二次封裝
來自: http://www.lcode.org/volley完全解析之進階最佳實踐與二次封裝/
(一).前言:
上一講我們已經對Volley使用基礎階段涉及到字符串,JSON,圖片等等網絡數據請求相關方法的使用。今天我們對于Volley框架來一個進階使用擴展封裝,以及對上一篇遺留的問題做一下具體修改使用。主要涉及新增GsonRequest,ImageLoader列表圖片加載,ImageCache,Volley框架StringRequest二次封裝以及post請求新增設置請求參數方法。
FastDev4Android框架項目地址: https://github.com/jiangqqlmj/FastDev4Android
(二).post請求擴展請求參數方法
還記得前面文章我們只是使用Request的GET請求方法,當然作為完善的網絡框架POST請求必須支持,不過很可惜的是Volley框架沒有給我們顯示的提供設置POST請求參數的方法,不過我們有兩種方法來進行設置。不過在做之前我們需要先從源碼角度來分析一下。
查看源碼,我們發現Volley請求最終會被封裝成HttpRequest子類對象HttpUrlRequest,該方法在HttpClientStack中的createHttpRequest()方法,代碼和相關注釋如下:
/* 進行創建httprequest,這邊獲取httprequest的子類,httpurlrequest Creates the appropriate subclass of HttpUriRequest for passed in request. / @SuppressWarnings("deprecation") / protected / static HttpUriRequest createHttpRequest(Request< ?> request, Map
additionalHeaders) throws AuthFailureError { switch (request.getMethod()) { case Method.DEPRECATED_GET_OR_POST: { // This is the deprecated way that needs to be handled for backwards compatibility. // If the request's post body is null, then the assumption is that the request is // GET. Otherwise, it is assumed that the request is a POST. byte[] postBody = request.getPostBody(); if (postBody != null) { HttpPost postRequest = new HttpPost(request.getUrl()); postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType()); HttpEntity entity; entity = new ByteArrayEntity(postBody); postRequest.setEntity(entity); return postRequest; } else { return new HttpGet(request.getUrl()); } } case Method.GET: return new HttpGet(request.getUrl()); case Method.DELETE: return new HttpDelete(request.getUrl()); case Method.POST: { HttpPost postRequest = new HttpPost(request.getUrl()); postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); //設置請求體 body信息 -如果請求體不為空---[注意] setEntityIfNonEmptyBody(postRequest, request); return postRequest; } case Method.PUT: { HttpPut putRequest = new HttpPut(request.getUrl()); putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); setEntityIfNonEmptyBody(putRequest, request); return putRequest; } case Method.HEAD: return new HttpHead(request.getUrl()); case Method.OPTIONS: return new HttpOptions(request.getUrl()); case Method.TRACE: return new HttpTrace(request.getUrl()); case Method.PATCH: { HttpPatch patchRequest = new HttpPatch(request.getUrl()); patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); setEntityIfNonEmptyBody(patchRequest, request); return patchRequest; } default: throw new IllegalStateException("Unknown request method."); } }</string></pre>
注意查看以上case Method.POST部分中的setEntityIfNonEmptyBody();中進行設置請求體相關數據:
/** * 如果request的請求體不為空,進行設置請求體信息 * @param httpRequest * @param request * @throws AuthFailureError */ private static void setEntityIfNonEmptyBody(HttpEntityEnclosingRequestBase httpRequest, Request< ?> request) throws AuthFailureError { byte[] body = request.getBody(); if (body != null) { HttpEntity entity = new ByteArrayEntity(body); httpRequest.setEntity(entity); } }然后會執行Request中getBody()方法來獲取請求體。
/** * Returns the raw POST or PUT body to be sent. * 如果請求是POST或者PUT方法,去獲取請求參數信息,然后設置到請求中 *By default, the body consists of the request parameters in * application/x-www-form-urlencoded format. When overriding this method, consider overriding * {@link #getBodyContentType()} as well to match the new body format. * * @throws AuthFailureError in the event of auth failure */ public byte[] getBody() throws AuthFailureError { //獲取請求參數信息 Map
params = getParams(); if (params != null && params.size() > 0) { return encodeParameters(params, getParamsEncoding()); } return null; } 最后會獲取XxxRequest的基類Requst類中的getParams()來獲取請求參數信息,然后設置進去。OK這個設置POST請求參數信息的流程大家應該能清楚吧。但是Volley源代碼這邊就直接返回null,如下:
protected Map
getParams() throws AuthFailureError { return null; } 又因為Volley是開源的,那么下面我們開源對Request和XxxRequest類進行改造修改源代碼,往外提高POST請求參數設置的方法,修改步驟如下:
2.1.Request類一個Map對象mParams,然后添加set方法,然后在getParams()進行return 該mParams。具體修改如下:
/** * 自定義修改 新增POST請求參數map */ protected Map</string>mParams=null; public void setParams(Map params) { this.mParams = params; } /** * 進行獲取post請求參數數據 * Returns a Map of parameters to be used for a POST or PUT request. Can throw * {@link AuthFailureError} as authentication may be required to provide these values. * * Note that you can directly override {@link #getBody()} for custom data.
* * @throws AuthFailureError in the event of auth failure */ protected MapgetParams() throws AuthFailureError { //return null; //重新進行返回params return mParams; } 2.2.間接著我們對StringRequest類進行擴展一個構造方法,加入請求參數Map對象,具體代碼如下:
/** * 擴展POST請求構造函數 * @param url 請求地址 * @param listener 數據請求加載成功監聽器 * @param errorListener 數據請求加載失敗監聽器 * @param params POST請求參數 */ public StringRequest(String url, Listener</string>listener, ErrorListener errorListener,Map params) { this(Method.POST, url, listener, errorListener); //進行初始化Request中post 請求參數 setParams(params); } OK這兩步修改就完成了POST請求參數設置擴展,下面我們來一看一下具體使用方法:
RequestQueue requestQueue=Volley.newRequestQueue(this); //修改Volley源代碼,擴展StringRequest支持post參數設置 Map</string>params=new HashMap (); params.put("username","zhangsan"); params.put("password","12345"); StringRequest post_stringRequest=new StringRequest("http://10.18.3.123:8080/SalesWebTest/TestVolleyPost", new Response.Listener () { @Override public void onResponse(String response) { tv_result.setVisibility(View.VISIBLE); img_result.setVisibility(View.GONE); tv_result.setText(response.toString()); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } },params); requestQueue.add(post_stringRequest); 這樣就完成POST請求參數設置的擴展,另外的一種方案是創建StringRequest對象的時候實匿名類中重寫getParams()方法,然后構造Map對象設置參數信息return該對象即可。不過個人覺的這種方案不推薦還是使用設置的方法比較好。
(三).Imageloader和ImageCache
在前面使用ImageLoder對象加載圖片的時候,有一個優點我們沒有深入講解,因為ImageLoader是支持緩存功能,所以我們在創建ImageLoader需要傳入一個緩存管理器,里面傳入我們自定義的緩存策略,Fdv_ImageCache我們這邊引入LruCache算法具體實現如下:
package com.chinaztt.fdv; import android.graphics.Bitmap; import android.util.LruCache; import com.android.volley.toolbox.ImageLoader; /** * 當前類注釋:圖片緩存器,實現ImageLoder.ImageCache實現其中的方法,具體圖片怎么樣緩存讓我們自己來實現 * 這樣可以考慮到將來的擴展性 * 項目名:FastDev4Android * 包名:com.chinaztt.fdv * 作者:江清清 on 15/11/12 12:31 * 郵箱:jiangqqlmj@163.com * QQ: 781931404 * 公司:江蘇中天科技軟件技術有限公司 */ public class Fdv_ImageCache implements ImageLoader.ImageCache { private LruCache</string>mCache=null; private static final int CACHE_MAX_SIZE = 8 * 1024 * 1024; //默認緩存大小為8M public Fdv_ImageCache(){ if(mCache==null){ mCache = new LruCache (CACHE_MAX_SIZE) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getRowBytes() bitmap.getHeight(); } }; } } /** 從緩存中獲取圖片 @param url 獲取圖片key 當然該key可以根據實際情況 使用url進行變換修改 @return / @Override public Bitmap getBitmap(String url) { return mCache.get(url); } /** 向緩存中添加圖片 @param url 緩存圖片key,當然該key可以根據實際情況 使用url進行變換修改 不過規格需要和上面方法的key保持一致 @param bitmap 需要緩存的圖片 */ @Override public void putBitmap(String url, Bitmap bitmap) { mCache.put(url,bitmap); } } 有了圖片緩存機制,然后我們在配合ImageLoader來進行列表上面加載異步圖片的實現,具體實現方式如下:
/* 當前類注釋:使用ImageLoader來進行測試列表圖片異步加載以及緩存 項目名:FastDev4Android 包名:com.chinaztt.fda.test 作者:江清清 on 15/11/12 15:19 郵箱:jiangqqlmj@163.com QQ: 781931404 公司:江蘇中天科技軟件技術有限公司 */ @EActivity(R.layout.base_adapter_test_layout) public class VolleyLoaderActivity extends BaseActivity { @ViewById ListView lv_base_adapter; @ViewById TextView tv_title; private QuickAdapter
mAdapter; private List</modulebean>
moduleBeans; private RequestQueue requestQueue; private ImageLoader imageLoader; private ImageLoader.ImageListener listener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestQueue= Volley.newRequestQueue(this); imageLoader=new ImageLoader(requestQueue,new Fdv_ImageCache()); } @AfterViews public void setViews(){ tv_title.setText("Loader 列表圖片"); } @AfterViews public void bindLvData(){ moduleBeans=DataUtils.getAdapterData(); if(mAdapter==null) { mAdapter = new QuickAdapter</modulebean>
(this, R.layout.lv_item_base_layout,moduleBeans) { @Override protected void convert(BaseAdapterHelper helper, ModuleBean item) { //列表底下顯示進度 mAdapter.showIndeterminateProgress(true); helper.setText(R.id.text_lv_item_title, item.getModulename()) .setText(R.id.text_lv_item_description, item.getDescription()); //setImageUrl(R.id.img_lv_item, item.getImgurl()); //使用ImageLoader進行加載圖片 ImageView loader_img=helper.getView(R.id.img_lv_item); listener=ImageLoader.getImageListener(loader_img,R.drawable.ic_loading,R.drawable.ic_loading); imageLoader.get(item.getImgurl(),listener); } }; lv_base_adapter.setAdapter(mAdapter); } } }</modulebean></pre>
運行效果如下:
![]()
(四).GsonRequest封裝
前面我們已經使用過JsonObjectRequest,JsonArrayRequest兩個工具,這邊是采用Android自身提供的JSONObject和JSONArray來進行實現解析JSON數據的,不過我們也知道如果我們已經有JSONObject和JSONArray對象然后一步步的解析還是挺麻煩的,所以我們這邊可以使用JSON 快速解析框架Gson來進行解決這個問題。現在我們自定義一個GsonRequest來專門處理請求GSON解析。在做之前我們先要看一下StringRequest的實現,首先StringRequest是繼承Request類然后實現兩個抽象方法,間接著提供若干個構造方法(進行數據初始化,請求類型GET/POST…,請求服務器地址,相應成功是否回調接口)。因為StringRequest的實現代碼很少而且較簡單,那么我們模仿StringRequest類的實現可以寫出來一下GsonRequest代碼如下:
/* 當前類注釋:進行擴展GSON數據解析json數據 項目名:FastDev4Android 包名:com.android.volley.toolbox 作者:江清清 on 15/11/12 18:28 郵箱:jiangqqlmj@163.com QQ: 781931404 公司:江蘇中天科技軟件技術有限公司 */ public class GsonRequest
extends Request</t>
{ private final Response.Listener</t>
listener; private Gson gson; private Class</t>
mClass;/* GsonRequest 構造函數 @param method 請求方法 @param url 請求地址 @param listener 數據請求成功回調接口 @param errorListener 數據請求失敗回調接口 @param pClass 需要進行解析的類 / public GsonRequest(int method,String url,Response.Listener
</t>
listener,Response.ErrorListener errorListener,Class</t>
pClass){ super(method,url,errorListener); this.listener=listener; gson=new Gson(); mClass=pClass; }/* GsonRequest 構造函數 默認使用GET請求方法 @param url @param listener @param errorListener @param pClass */ public GsonRequest(String url,Response.Listener
</t>
listener,Response.ErrorListener errorListener,Class</t>
pClass){ super(Method.GET,url,errorListener); this.listener=listener; gson=new Gson(); mClass=pClass; }/* 數據解析 @param response Response from the network 網絡請求返回數據 @return */ @Override protected Response
</t>
parseNetworkResponse(NetworkResponse response) { try { String jsonStr=new String(response.data,HttpHeaderParser.parseCharset(response.headers)); T data=gson.fromJson(jsonStr,mClass); return Response.success(data,HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } }/* 數據分發 @param response The parsed response returned by / @Override protected void deliverResponse(T response) { listener.onResponse(response); } }
</t></pre>
以上實現詳解:GsonRequest繼承Request,同樣提供兩個構造函數,然后在parseNetworkResponse()方法中進行解析數據,最后通過Gson組件來封裝數據成Bean對象,最終數據回調即可。具體使用方法如下:
GsonRequest
gsonRequest=new GsonRequest
![]()
(五).Volley二次封裝(StringRequest為例)
我們在使用Volley框架請求網絡數據獲取的時候,一般就是以下三步驟:
1.創建RequestQueue對象
2.創建XXRequest對象(XX代表String,JSON,Image等等)
3.把XXRequest對象添加到RequestQueue中即可
雖然代碼量不是不多,不過也還是需要做創建隊列對象和請求對象加入到隊列中的操作,而創建XxxRequest對象的構造函數中的參數又比較多,所以這邊想了一種方案就是想進行網絡請求的時候創建和add操作在內部然后構造函數中的參數減少。所以這邊只是以StringRequest為例,簡單的封裝了一個Get方法請求的方法,其他的功能還有待繼續添加更新。我們已StringRequest為例二次封裝了一個類Fdv_StringRequst,我們來看以下該類的使用方法代碼:
new Fdv_StringRequest
(VolleyTestActivity.this).get("</string>
() { @Override public void onSuccessResponse(String response) { } @Override public void onErrorResponse(VolleyError error) {} });
</string></pre>
這樣即可,不在需要以前的RequestQueue requestQueue=Volley.newRequestQueue(this);和
requestQueue.add(object)這兩句代碼了,直接在內部已經做了。然后如果Get請求直接調用get()方法即可,不需要顯示傳入請求方法。下面我們來看一下具體實現:
![]()
5.1.定義Fdv_Volley類,里邊是一個單例方法,來獲取RequestQueue請求隊列對象.
/** * 當前類注釋:全局Fdv_Volley封裝類管理類 * 項目名:FastDev4Android * 包名:com.chinaztt.fdv * 作者:江清清 on 15/11/11 23:02 * 郵箱:jiangqqlmj@163.com * QQ: 781931404 * 公司:江蘇中天科技軟件技術有限公司 */ public class Fdv_Volley { private static RequestQueue instance; public static RequestQueue getInstace(Context pContext){ if(instance==null){ instance= Volley.newRequestQueue(pContext); } return instance; } }5.2.定義XxxRequest的基類Fdv_BaseRequest,主要功能獲取請求隊列對象,然后定義一個addRequest()方法,讓子類進行調用,添加當前請求對象到請求隊列中。
/** * 當前類注釋: * 項目名:FastDev4Android * 包名:com.chinaztt.fdv * 作者:江清清 on 15/11/11 22:59 * 郵箱:jiangqqlmj@163.com * QQ: 781931404 * 公司:江蘇中天科技軟件技術有限公司 */ public class Fdv_BaseRequest{ protected static RequestQueue requestQueue; private Context mContext; protected Fdv_BaseRequest(Context pContext){ this.mContext=pContext; } /** * 請求加入到Volley Request請求隊列中 * @param request */ protected void addRequest(Request request){ Fdv_Volley.getInstace(mContext).add(request); }5.3.請求結果回調接口Fdv_CallBackListener,這邊暫時只加了兩個很簡單的方法,后期會進行擴展
/* 當前類注釋: 項目名:FastDev4Android 包名:com.chinaztt.fdv 作者:江清清 on 15/11/11 23:18 郵箱:jiangqqlmj@163.com QQ: 781931404 公司:江蘇中天科技軟件技術有限公司 */ public interface Fdv_CallBackListener
{ void onSuccessResponse(T response); void onErrorResponse(VolleyError error); }</t></pre>
5.4.最后是我們的核心類封裝過后的StringRequest,Fdv_StringRequest該類繼承Fdv_BaseRequest類,在該類中我們暫時只是提供一個get()方法來獲取數據中,get()方法的實現還是使用原來的StringRequest進行獲取數據,得到數據使用接口回調即可,實現代碼如下:
/* 當前類注釋:Volley 字符串、文本數據請求封裝類 項目名:FastDev4Android 包名:com.chinaztt.fdv 作者:江清清 on 15/11/11 13:43 郵箱:jiangqqlmj@163.com QQ: 781931404 公司:江蘇中天科技軟件技術有限公司 */ public class Fdv_StringRequest
extends Fdv_BaseRequest{ public Fdv_StringRequest(Context pContext){ super(pContext); } public void get(String url, final Fdv_CallBackListener</t>
listener){ StringRequest stringRequest=new StringRequest(Request.Method.GET, url, new Response.Listener<string> () { @Override public void onResponse(String response) { if(listener!=null){ listener.onSuccessResponse((T) response); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { if(listener!=null){ listener.onErrorResponse(error); } } }); addRequest(stringRequest); } } </string>
</t></pre>
OK有了以上的幾步驟,我們的對于StringRequest的get方法請求簡單封裝就完成了,有了這樣的封裝我們的功能實現就會變得代碼稍微少了一點,當然我們現在的封裝的功能還非常的弱,后面我這邊會繼續更新封裝功能以及重構,讓整個工具類變得更加使用。具體更新代碼都會FastDev4Android項目中。以上的功能測試代碼如下:
@EActivity(R.layout.volley_test_layout) public class VolleyTestActivity extends BaseActivity { private static final String TAG=VolleyTestActivity.class.toString(); @ViewById LinearLayout top_bar_linear_back; @ViewById TextView top_bar_title,tv_result; @ViewById ImageView img_result; @ViewById Button btn_string,btn_json,btn_image_request,btn_image_loader,btn_image_network,btn_string_post,btn_loader_list,btn_gson; @ViewById NetworkImageView img_result_network; private RequestQueue requestQueue; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestQueue=Volley.newRequestQueue(this); } @Click({R.id.top_bar_linear_back,R.id.btn_string,R.id.btn_json,R.id.btn_image_request,R.id.btn_image_loader,R.id.btn_image_network,R.id.btn_string_post,R.id.btn_loader_list,R.id.btn_gson}) public void backLinearClick(View view){ switch (view.getId()){ case R.id.top_bar_linear_back: this.finish(); break; case R.id.btn_string: //獲取字符串 Log.d(TAG, "點擊獲取字符串..."); new Fdv_StringRequest</string>(VolleyTestActivity.this).get("http://www.baidu.com", new Fdv_CallBackListener () { @Override public void onSuccessResponse(String response) { tv_result.setVisibility(View.VISIBLE); img_result.setVisibility(View.GONE); tv_result.setText(response.toString()); } @Override public void onErrorResponse(VolleyError error) { } }); // StringRequest stringRequest=new StringRequest(Request.Method.GET, "http://www.baidu.com", new Response.Listener () { // @Override // public void onResponse(String response) { // tv_result.setVisibility(View.VISIBLE); // img_result.setVisibility(View.GONE); // tv_result.setText(response.toString()); // } // }, new Response.ErrorListener() { // @Override // public void onErrorResponse(VolleyError error) { // // } // }); // requestQueue.add(stringRequest); break; case R.id.btn_json: //獲取json Log.d(TAG, "點擊獲取json..."); JsonObjectRequest jsonObjectRequest=new JsonObjectRequest(Request.Method.GET , "http://interface.zttmall.com/update/mallUpdate", null,new Response.Listener () { @Override public void onResponse(JSONObject response) { Gson gson=new Gson(); tv_result.setVisibility(View.VISIBLE); img_result.setVisibility(View.GONE); tv_result.setText(gson.fromJson(response.toString(),UpdateBean.class).toString()); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }); requestQueue.add(jsonObjectRequest); break; case R.id.btn_image_request: //獲取圖片 //http:\/\/interface.zttmall.com\/Images\/upload\/image\/20150325\/20150325083110_0898.jpg Log.d(TAG, "點擊獲取圖片..."); ImageRequest imageRequest=new ImageRequest("http://interface.zttmall.com/Images/upload/image/20150325/20150325083110_0898.jpg" , new Response.Listener () { @Override public void onResponse(Bitmap response) { tv_result.setVisibility(View.GONE); img_result.setVisibility(View.VISIBLE); img_result.setImageBitmap(response); } }, 0, 0, ImageView.ScaleType.FIT_XY, Bitmap.Config.ARGB_8888, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }); requestQueue.add(imageRequest); break; case R.id.btn_image_loader: //使用imageloader進行獲取圖片 ImageLoader imageLoader=new ImageLoader(requestQueue, new Fdv_ImageCache()); tv_result.setVisibility(View.GONE); img_result.setVisibility(View.VISIBLE); ImageLoader.ImageListener listener=ImageLoader.getImageListener(img_result,R.drawable.ic_loading,R.drawable.ic_loading); imageLoader.get("http://interface.zttmall.com//Images//upload//image//20150328//20150328105404_2392.jpg", listener); break; case R.id.btn_image_network: //采用NetworkImageView imageview控件 ImageLoader network_imageLoader=new ImageLoader(requestQueue, new Fdv_ImageCache()); img_result_network.setVisibility(View.VISIBLE); img_result_network.setImageUrl("http://interface.zttmall.com//Images//upload//image//20150325//20150325083214_8280.jpg",network_imageLoader); break; case R.id.btn_string_post: //修改Volley源代碼,擴展StringRequest支持post參數設置 Map params=new HashMap (); params.put("username","zhangsan"); params.put("password","12345"); StringRequest post_stringRequest=new StringRequest("http://10.18.3.123:8080/SalesWebTest/TestVolleyPost", new Response.Listener () { @Override public void onResponse(String response) { tv_result.setVisibility(View.VISIBLE); img_result.setVisibility(View.GONE); tv_result.setText(response.toString()); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } },params); requestQueue.add(post_stringRequest); break; case R.id.btn_loaderlist: //進行使用ImageLoader加載圖片列表 openActivity(VolleyLoaderActivity.class); break; case R.id.btn_gson: //使用擴展工具 GsonRequest進行請求 GsonRequest gsonRequest=new GsonRequest ("http://interface.zttmall.com/update/mallUpdate", new Response.Listener () { @Override public void onResponse(UpdateBean response) { tv_result.setVisibility(View.VISIBLE); img_result.setVisibility(View.GONE); tv_result.setText(response.toString()); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }, UpdateBean.class); requestQueue.add(gsonRequest); break; } } @AfterViews public void setViews(){ top_bar_title.setText("Volley網絡框架測試實例"); } } 整個功能測試功能點如下圖:
<p> <img src="https://simg.open-open.com/show/3d1c0e6aa93dc8ce28ec549f069133d9.jpg" class="alignCenter" width="455" height="272" /> </p> <p>(六).結束語:</p> <p>到此為止我們已經講完了Volley框架進階部分:POST請求參數修改,圖片緩存器,使用最佳實踐,二次封裝等相關高級進階。具體實例和框架注釋過的全部代碼已經上傳到FastDev4Android項目中了。同時歡迎大家去Github站點進行clone或者下載瀏覽:</p> <p> <a href="/misc/goto?guid=4959655574556554105" rel="nofollow,noindex" target="_blank">https://github.com/jiangqqlmj/FastDev4Android</a> 同時歡迎大家star和fork整個開源快速開發框架項目~ </p> <p>最后對于Volley框架二次封裝的代碼庫,我這邊會繼續進行更新,后面會放入單獨的Git庫中,庫地址后面會放出來的,敬請期待~</p> <p> 尊重原創,轉載請注明:From Sky丶清( <a href="/misc/goto?guid=4959655601131384132" target="_blank" rel="nofollow,noindex">http://www.lcode.org/</a> ) 侵權必究! </p> <p>關注我的訂閱號(codedev123),每天分享移動開發技術(Android/IOS),項目管理以及博客文章!(歡迎關注,第一時間推送精彩文章)</p> <p> <img src="https://simg.open-open.com/show/2dc894ab594fca699f3efcef6878e369.jpg" width="300" height="300" class="alignCenter" /> </p> <p>關注我的微博,可以獲得更多精彩內容</p> <p> <img src="https://simg.open-open.com/show/488b1c8c1311a3fa5383affc63e9637f.png" class="alignCenter" width="392" height="112" /> </p> </updatebean> </string>
</bitmap> </jsonobject> </string>
</div>