使用Volley+OkHttp+Gson加速Android网络开发
来源:互联网 发布:淘宝做什么项目好 编辑:程序博客网 时间:2024/06/04 18:08
在Android应用开发中,我们不可避免的需要网络操作,尤其是在与服务器频繁的交互过程中,更是需要大量的重复编码:HttpURLConnection,Thread, AsyncTask, Service等,十分复杂又容易出错。在2013年Google I/O大会上推出了网络通信框架——Volley。它可以极大简化HTTP通信,加载网络图片的操作,并且在小数据量的频繁网络交互中性能表现良好。不过,它不适合进行大文件的上传下载。
基于上述原因,在最近的项目中我学习使用了Volley网络库,然而Volley在使用中也存在一些问题:
比如,Volley自带的JsonRequest只支持JSONObject,而如果想要使用Gson,还需要自己定制Request。
另外,默认情况下,Volley在Froyo使用Apache Http stack作为其传输层,而在Gingerbread及之后的版本上使用HttpURLConnection stack作为传输层。很自然我们想到,能不能将口碑十分好的OkHttp替换为Volley的传输层呢。
最后,很多时候我们需要一些小文件的上传操作(如用户头像),能不能使用Volley完成这个任务呢。
所以,本文描述一种结合使用Volley+OkHttp+Gson进行快速网络开发的方法,并给出一些网络接口封装的例子。
首先,要将OkHttp替换为Volley的传输层,需要实现一个OkHttpStack类,然后在实例化Volley的RequestQueue时将之替换即可:
OkHttpStack类的代码如下(来自GitHub,详见文后参考资料部分,这里为了方便网络不佳的读者贴出来):
为了方便调用,封装Response接口如下:
至此,对Volley+OkHttp+Gson的封装就完成了。在使用时,我们可以在自定义Application中初始化Queue,再封装一个专门的NetworkManager类来提供所有的网络访问操作,比如,在我最近的项目中是这样用的:
OkHttpStack.java -- from GitHub
Android Networking I: OkHttp, Volley and Gson
Android Volley完全解析(三),定制自己的Request
Android volley 解析(三)之文件上传篇
Android OkHttp完全解析 是时候来了解OkHttp了
基于上述原因,在最近的项目中我学习使用了Volley网络库,然而Volley在使用中也存在一些问题:
比如,Volley自带的JsonRequest只支持JSONObject,而如果想要使用Gson,还需要自己定制Request。
另外,默认情况下,Volley在Froyo使用Apache Http stack作为其传输层,而在Gingerbread及之后的版本上使用HttpURLConnection stack作为传输层。很自然我们想到,能不能将口碑十分好的OkHttp替换为Volley的传输层呢。
最后,很多时候我们需要一些小文件的上传操作(如用户头像),能不能使用Volley完成这个任务呢。
所以,本文描述一种结合使用Volley+OkHttp+Gson进行快速网络开发的方法,并给出一些网络接口封装的例子。
首先,要将OkHttp替换为Volley的传输层,需要实现一个OkHttpStack类,然后在实例化Volley的RequestQueue时将之替换即可:
RequestQueue mQueue = Volley.newRequestQueue(this, new OkHttpStack(new OkHttpClient()));
OkHttpStack类的代码如下(来自GitHub,详见文后参考资料部分,这里为了方便网络不佳的读者贴出来):
/** * The MIT License (MIT) * * Copyright (c) 2015 Circle Internet Financial * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */import com.android.volley.AuthFailureError;import com.android.volley.Request;import com.android.volley.toolbox.HttpStack;import com.squareup.okhttp.Call;import com.squareup.okhttp.Headers;import com.squareup.okhttp.MediaType;import com.squareup.okhttp.OkHttpClient;import com.squareup.okhttp.Protocol;import com.squareup.okhttp.RequestBody;import com.squareup.okhttp.Response;import com.squareup.okhttp.ResponseBody;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.ProtocolVersion;import org.apache.http.StatusLine;import org.apache.http.entity.BasicHttpEntity;import org.apache.http.message.BasicHeader;import org.apache.http.message.BasicHttpResponse;import org.apache.http.message.BasicStatusLine;import java.io.IOException;import java.util.Map;import java.util.concurrent.TimeUnit;/** * OkHttp backed {@link com.android.volley.toolbox.HttpStack HttpStack} that does not * use okhttp-urlconnection */public class OkHttpStack implements HttpStack { private final OkHttpClient mClient; public OkHttpStack(OkHttpClient client) { this.mClient = client; } @Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { OkHttpClient client = mClient.clone(); int timeoutMs = request.getTimeoutMs(); client.setConnectTimeout(timeoutMs, TimeUnit.MILLISECONDS); client.setReadTimeout(timeoutMs, TimeUnit.MILLISECONDS); client.setWriteTimeout(timeoutMs, TimeUnit.MILLISECONDS); com.squareup.okhttp.Request.Builder okHttpRequestBuilder = new com.squareup.okhttp.Request.Builder(); okHttpRequestBuilder.url(request.getUrl()); Map<String, String> headers = request.getHeaders(); for (final String name : headers.keySet()) { okHttpRequestBuilder.addHeader(name, headers.get(name)); } for (final String name : additionalHeaders.keySet()) { okHttpRequestBuilder.addHeader(name, additionalHeaders.get(name)); } setConnectionParametersForRequest(okHttpRequestBuilder, request); com.squareup.okhttp.Request okHttpRequest = okHttpRequestBuilder.build(); Call okHttpCall = client.newCall(okHttpRequest); Response okHttpResponse = okHttpCall.execute(); StatusLine responseStatus = new BasicStatusLine(parseProtocol(okHttpResponse.protocol()), okHttpResponse.code(), okHttpResponse.message()); BasicHttpResponse response = new BasicHttpResponse(responseStatus); response.setEntity(entityFromOkHttpResponse(okHttpResponse)); Headers responseHeaders = okHttpResponse.headers(); for (int i = 0, len = responseHeaders.size(); i < len; i++) { final String name = responseHeaders.name(i), value = responseHeaders.value(i); if (name != null) { response.addHeader(new BasicHeader(name, value)); } } return response; } private static HttpEntity entityFromOkHttpResponse(Response r) throws IOException { BasicHttpEntity entity = new BasicHttpEntity(); ResponseBody body = r.body(); entity.setContent(body.byteStream()); entity.setContentLength(body.contentLength()); entity.setContentEncoding(r.header("Content-Encoding")); if (body.contentType() != null) { entity.setContentType(body.contentType().type()); } return entity; } @SuppressWarnings("deprecation") private static void setConnectionParametersForRequest(com.squareup.okhttp.Request.Builder builder, Request<?> request) throws IOException, AuthFailureError { switch (request.getMethod()) { case Request.Method.DEPRECATED_GET_OR_POST: // Ensure backwards compatibility. Volley assumes a request with a null body is a GET. byte[] postBody = request.getPostBody(); if (postBody != null) { builder.post(RequestBody.create(MediaType.parse(request.getPostBodyContentType()), postBody)); } break; case Request.Method.GET: builder.get(); break; case Request.Method.DELETE: builder.delete(); break; case Request.Method.POST: builder.post(createRequestBody(request)); break; case Request.Method.PUT: builder.put(createRequestBody(request)); break; case Request.Method.HEAD: builder.head(); break; case Request.Method.OPTIONS: builder.method("OPTIONS", null); break; case Request.Method.TRACE: builder.method("TRACE", null); break; case Request.Method.PATCH: builder.patch(createRequestBody(request)); break; default: throw new IllegalStateException("Unknown method type."); } } private static ProtocolVersion parseProtocol(final Protocol p) { switch (p) { case HTTP_1_0: return new ProtocolVersion("HTTP", 1, 0); case HTTP_1_1: return new ProtocolVersion("HTTP", 1, 1); case SPDY_3: return new ProtocolVersion("SPDY", 3, 1); case HTTP_2: return new ProtocolVersion("HTTP", 2, 0); } throw new IllegalAccessError("Unkwown protocol"); } private static RequestBody createRequestBody(Request r) throws AuthFailureError { final byte[] body = r.getBody(); if (body == null) return null; return RequestBody.create(MediaType.parse(r.getBodyContentType()), body); }}
然后,我们需要实现使用Gson的Request类:
import com.android.volley.AuthFailureError;import com.android.volley.NetworkResponse;import com.android.volley.ParseError;import com.android.volley.Request;import com.android.volley.Response;import com.android.volley.toolbox.HttpHeaderParser;import com.google.gson.Gson;import java.io.UnsupportedEncodingException;import java.lang.reflect.Type;import java.util.Map;import me.zq.youjoin.utils.LogUtils;import me.zq.youjoin.utils.StringUtils;public class PostObjectRequest<T> extends Request<T> { private ResponseListener listener; private Gson gson; private Type clazz; private Map<String, String> params; public PostObjectRequest(String url, Map<String, String> params, Type type, ResponseListener listener){ super(Method.POST, url, listener); this.listener = listener; this.clazz = type; this.params = params; this.gson = new Gson(); } @Override protected Response<T> parseNetworkResponse(NetworkResponse response){ try{ T result; String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); LogUtils.d("hehe", jsonString); result = gson.fromJson(StringUtils.FixJsonString(jsonString), clazz); return Response.success(result, HttpHeaderParser.parseCacheHeaders(response)); }catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } } @Override protected void deliverResponse(T response){ listener.onResponse(response); } @Override protected Map<String, String> getParams() throws AuthFailureError{ return params; }}
最后,我们来实现图片上传部分。由于我们希望在上传图片的时候添加一些参数,例如用户id等,导致这部分的实现比较复杂。虽然我们可以将这些信息存储在图片文件名中实现传递,但这总归是取巧的办法。比较正规一点的方法还是研究网页表单提交时的Post请求的格式,然后自己构造一个合法的Post请求。我们可以使用Chorme的开发者工具来抓取Post请求,研究其格式,下图为我抓取的一个带图片和参数的表单Post请求:
从图中可以看出,我们只需要重写getBody()方法,逐行仿照Post格式写入我们自己的图片和参数,就可实现参数+图片的上传。多张图片的上传原理相同。另外,图片要注意在上传之前进行压缩处理。另外,为了方便服务器解析多张图片,将图片参数设置为uploadedfile[index]格式。代码如下:
import android.util.Log;import com.android.volley.AuthFailureError;import com.android.volley.DefaultRetryPolicy;import com.android.volley.NetworkResponse;import com.android.volley.ParseError;import com.android.volley.Request;import com.android.volley.Response;import com.android.volley.toolbox.HttpHeaderParser;import com.google.gson.Gson;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.lang.reflect.Type;import java.util.List;import java.util.Map;import me.zq.youjoin.model.ImageInfo;import me.zq.youjoin.utils.StringUtils;public class PostUploadRequest<T> extends Request<T> { /** * 正确数据的时候回掉用 */ private ResponseListener mListener ; /*请求 数据通过参数的形式传入*/ private List<ImageInfo> mListItem ; private static final String BOUNDARY = "--------------520-13-14"; //数据分隔线 private static final String MULTIPART_FORM_DATA = "multipart/form-data";//使用表单数据方法提交 private static final String TAG = "hehe_upload_request"; private static final String PARAM = "uploadedfile";//图片的参数,<span style="font-family: Arial, Helvetica, sans-serif;">为了上传多张图片,在下面的封装中使用uploadedfile[index]格式作为图片参数</span> private Map<String, String> params; private Gson gson; private Type clazz; public PostUploadRequest(String url, List<ImageInfo> listItem, Map<String, String> params, Type type, ResponseListener listener) { super(Method.POST, url, listener); this.mListener = listener ; this.params = params; this.gson = new Gson(); this.clazz = type; setShouldCache(false); mListItem = listItem ; //设置请求的响应事件,因为文件上传需要较长的时间,所以在这里加大了,设为5秒 setRetryPolicy(new DefaultRetryPolicy(5000,DefaultRetryPolicy.DEFAULT_MAX_RETRIES,DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); } /** * 这里开始解析数据 * @param response Response from the network * @return */ @Override protected Response<T> parseNetworkResponse(NetworkResponse response) { try { String mString = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); Log.v(TAG, "mString = " + mString); T result = gson.fromJson(StringUtils.FixJsonString(mString), clazz); return Response.success(result, 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) { mListener.onResponse(response); }//重写getBody()方法,封装Post包 @Override public byte[] getBody() throws AuthFailureError { if (mListItem == null||mListItem.size() == 0){ return super.getBody() ; } ByteArrayOutputStream bos = new ByteArrayOutputStream() ; if(!params.isEmpty()) { StringBuilder sbParams = new StringBuilder(); for (Map.Entry<String, String> o : params.entrySet()) { Map.Entry entry = (Map.Entry) o; /*第一行*/ //`"--" + BOUNDARY + "\r\n"` sbParams.append("--" + BOUNDARY); sbParams.append("\r\n"); /*第二行*/ //Content-Disposition: form-data; name="参数的名称"; + 参数 + "\r\n" sbParams.append("Content-Disposition: form-data;"); sbParams.append(" name=\""); sbParams.append((String) entry.getKey()); sbParams.append("\""); sbParams.append("\r\n"); sbParams.append("\r\n"); sbParams.append((String) entry.getValue()); sbParams.append("\r\n"); } try { bos.write(sbParams.toString().getBytes("utf-8"));// bos.write("\r\n".getBytes("utf-8")); } catch (IOException e) { e.printStackTrace(); } } int N = mListItem.size() ; ImageInfo imageInfo ; for (int i = 0; i < N ;i++){ imageInfo = mListItem.get(i) ; StringBuilder sb= new StringBuilder() ; /*第一行*/ //`"--" + BOUNDARY + "\r\n"` sb.append("--"+BOUNDARY); sb.append("\r\n") ; /*第二行*/ //Content-Disposition: form-data; name="参数的名称"; filename="上传的文件名" + "\r\n" sb.append("Content-Disposition: form-data;"); sb.append(" name=\""); sb.append(PARAM + "[" + Integer.toString(i) + "]"); //为了上传多张图片,使用uploadedfile[index]格式作为图片参数 sb.append("\"") ; sb.append("; filename=\"") ; sb.append(imageInfo.getFileName()) ; sb.append("\""); sb.append("\r\n") ; /*第三行*/ //Content-Type: 文件的 mime 类型 + "\r\n" sb.append("Content-Type: "); sb.append(imageInfo.getMime()) ; sb.append("\r\n") ; /*第四行*/ //"\r\n" sb.append("\r\n") ; try { bos.write(sb.toString().getBytes("utf-8")); /*第五行*/ //文件的二进制数据 + "\r\n" bos.write(imageInfo.getValue()); bos.write("\r\n".getBytes("utf-8")); } catch (IOException e) { e.printStackTrace(); } } /*结尾行*/ //`"--" + BOUNDARY + "--" + "\r\n"` String endLine = "--" + BOUNDARY + "--" + "\r\n" ; try { bos.write(endLine.getBytes("utf-8")); } catch (IOException e) { e.printStackTrace(); } // Log.v(TAG,"imageInfo =\n"+bos.toString()) ; return bos.toByteArray(); } //Content-Type: multipart/form-data; boundary=----------8888888888888 @Override public String getBodyContentType() { return MULTIPART_FORM_DATA+"; boundary="+BOUNDARY; }// @Override //注意一旦重写getBody()方法,则使用此方法添加参数无效。// protected Map<String, String> getParams() throws AuthFailureError{// return params;// }}
为了方便调用,封装Response接口如下:
import com.android.volley.Response;public interface ResponseListener<T> extends Response.ErrorListener,Response.Listener<T> {}
至此,对Volley+OkHttp+Gson的封装就完成了。在使用时,我们可以在自定义Application中初始化Queue,再封装一个专门的NetworkManager类来提供所有的网络访问操作,比如,在我最近的项目中是这样用的:
/** * 网络管理类,封装网络操作接口 */public class NetworkManager { /** * 网络接口相关常量 */ public static final String USER_USERNAME = "user_name"; public static final String USER_PASSWORD = "user_password"; public static final String USER_EMAIL = "user_email"; public static final String USER_ID = "user_id"; public static final String USER_WORK = "user_work"; public static final String USER_BIRTH = "user_birth"; public static final String USER_SIGN = "user_sign"; public static final String USER_LOCATION = "user_location"; public static final String USER_SEX = "user_sex"; public static final String TWEETS_CONTNET = "tweets_content"; public static final String FRIEND_ID = "friend_id"; public static final String SEND_USERID = "send_userid"; public static final String RECEIVE_USERID = "receive_userid"; public static final String MESSAGE_CONTENT = "message_content"; /** * 服务器接口URL */ public static final String BASE_API_URL = "http://192.168.0.103:8088/youjoin-server/controllers/"; public static final String API_SIGN_IN = BASE_API_URL + "signin.php"; public static final String API_SIGN_UP = BASE_API_URL + "signup.php"; public static final String API_UPDATE_USERINFO = BASE_API_URL + "update_userinfo.php"; public static final String API_SEND_TWEET = BASE_API_URL + "send_tweet.php"; public static final String API_REQUEST_USERINFO = BASE_API_URL + "request_userinfo.php"; public static final String API_ADD_FRIEND = BASE_API_URL + "add_friend.php"; public static final String API_SEND_MESSAGE = BASE_API_URL + "send_message.php"; public static final String API_RECEIVE_MESSAGE = BASE_API_URL + "receive_message.php"; public static final String API_COMMENT_TWEET = BASE_API_URL + "comment_tweet.php"; public static final String API_UPVOTE_TWEET = BASE_API_URL + "upvote_tweet.php"; private static RequestQueue mRequestQueue ; /** * 发送动态接口 * @param listener ResponseListener */ public static void postSendTweet(String content, List<ImageInfo> images, ResponseListener listener){ Map<String, String> params = new HashMap<>(); params.put(USER_ID, YouJoinApplication.getCurrUser().getId()); params.put(TWEETS_CONTNET, content); Request request = new PostUploadRequest(API_SEND_TWEET, images, params, new TypeToken<UpdateUserInfoResult>(){}.getType(), listener); NetworkManager.getRequestQueue().add(request); } /** * 发送私信接口 * @param receiveUserId 接收方用户id * @param content 私信内容 * @param listener ResponseListener */ public static void postSendMessage(String receiveUserId, String content, ResponseListener listener){ Map<String, String> params = new HashMap<>(); params.put(SEND_USERID, YouJoinApplication.getCurrUser().getId()); params.put(RECEIVE_USERID, receiveUserId); params.put(MESSAGE_CONTENT, content); Request request = new PostObjectRequest( API_SEND_MESSAGE, params, new TypeToken<ResultInfo>(){}.getType(), listener); NetworkManager.getRequestQueue().add(request); } /** * 添加好友接口 * @param friendUserId 要添加的好友id * @param listener ResponseListener */ public static void postAddFriend(String friendUserId, ResponseListener listener){ Map<String, String> params = new HashMap<>(); params.put(USER_ID, YouJoinApplication.getCurrUser().getId()); params.put(FRIEND_ID, friendUserId); Request request = new PostObjectRequest( API_ADD_FRIEND, params, new TypeToken<ResultInfo>(){}.getType(), listener); NetworkManager.getRequestQueue().add(request); } /** * 个人资料请求(下载)接口 * @param userId 要获取的用户id * @param listener ResponseListener */ public static void postRequestUserInfo(String userId, ResponseListener listener){ Map<String, String> params = new HashMap<>(); params.put(USER_ID, userId); Request request = new PostObjectRequest( API_REQUEST_USERINFO, params,new TypeToken<UserInfo>(){}.getType(), listener); NetworkManager.getRequestQueue().add(request); } /** * 个人资料更新(上传)接口 * @param userInfo 用户实体类 * @param picPath 头像的本地路径 * @param listener ResponseListener */ public static void postUpdateUserInfo(UserInfo userInfo, String picPath, ResponseListener listener){ if(userInfo.getId() == null) return; List<ImageInfo> imageList = new ArrayList<>(); imageList.add(new ImageInfo(picPath)); Map<String, String> params = new HashMap<>(); params.put(USER_ID, userInfo.getId()); params.put(USER_WORK, userInfo.getWork()); params.put(USER_LOCATION, userInfo.getLocation()); params.put(USER_SEX, userInfo.getSex()); params.put(USER_BIRTH, userInfo.getBirth()); params.put(USER_SIGN, userInfo.getUsersign()); Request request = new PostUploadRequest(API_UPDATE_USERINFO, imageList, params, new TypeToken<UpdateUserInfoResult>(){}.getType(), listener); NetworkManager.getRequestQueue().add(request); } /** * 登陆接口 * @param username 登录用户名 * @param password 登陆密码 * @param listener ResponseListener */ public static void postSignIn(String username, String password, ResponseListener listener){ Map<String, String> param = new HashMap<>(); param.put(USER_USERNAME, username); param.put(USER_PASSWORD, Md5Utils.getMd5(password)); Request request = new PostObjectRequest( API_SIGN_IN, param, new TypeToken<UserInfo>(){}.getType(), listener); NetworkManager.getRequestQueue().add(request); } /** * 注册接口 * @param username 注册用户名 * @param password 注册密码 * @param email 注册邮箱 * @param listener ResponseListener */ public static void postSignUp(String username, String password, String email, ResponseListener listener){ Map<String, String> param = new HashMap<>(); param.put(USER_USERNAME, username); param.put(USER_PASSWORD, Md5Utils.getMd5(password)); param.put(USER_EMAIL, email); Request request = new PostObjectRequest( API_SIGN_UP, param, new TypeToken<UserInfo>(){}.getType(), listener); NetworkManager.getRequestQueue().add(request); } /**初始化Volley 使用OkHttpStack * @param context 用作初始化Volley RequestQueue */ public static synchronized void initialize(Context context){ if (mRequestQueue == null){ synchronized (NetworkManager.class){ if (mRequestQueue == null){ mRequestQueue = Volley.newRequestQueue(context, new OkHttpStack(new OkHttpClient())); } } } mRequestQueue.start(); } /**获取RequestQueue实例 * @return 返回RequestQueue实例 */ public static RequestQueue getRequestQueue(){ if (mRequestQueue == null) throw new RuntimeException("请先初始化mRequestQueue") ; return mRequestQueue ; }}
import android.app.Application;import android.content.Context;import me.zq.youjoin.model.UserInfo;import me.zq.youjoin.network.NetworkManager;public class YouJoinApplication extends Application { public static float sScale; public static int sHeightPix; private static Context context; private static UserInfo currUser; @Override public void onCreate(){ super.onCreate(); context = getApplicationContext(); NetworkManager.initialize(context); sScale = getResources().getDisplayMetrics().density; sHeightPix = getResources().getDisplayMetrics().heightPixels; } public static Context getAppContext(){ return context; } public static UserInfo getCurrUser() { return currUser; } public static void setCurrUser(UserInfo currUser) { YouJoinApplication.currUser = currUser; }}
参考资料:
OkHttpStack.java -- from GitHub
Android Networking I: OkHttp, Volley and Gson
Android Volley完全解析(三),定制自己的Request
Android volley 解析(三)之文件上传篇
Android OkHttp完全解析 是时候来了解OkHttp了
2 0
- 使用Volley+OkHttp+Gson加速Android网络开发
- android网络操作I: OkHttp, Volley以及Gson
- Android网络okhttp/Volley
- OkHttp使用教程——网络操作之OkHttp, Volley以及Gson
- Android Networking I: OkHttp, Volley and Gson
- Android 网络--Volley+OkHttp+Https
- Android 网络: Volley+OkHttp+Https
- Android 使用OkHttp扩展Volley
- Android 使用OkHttp扩展Volley
- Volley+OkHttp+Gson自定义框架
- Android 网络之 Volley+OkHttp+Https
- Android网络请求XUtils、Volley、OkHttp、Retrofit
- Android开发使用Volley加载网络图片
- Android网络开发库Volley使用教程
- Android开发 Volley通讯与Gson解析
- 快速Android开发系列网络篇之Retrofit, Retrofit OKHttp GSON
- Retrofit 2.0使用详解,配合OkHttp、Gson,Android最强网络请求框架
- Retrofit 2.0使用详解,配合OkHttp、Gson,Android最强网络请求框架
- Pig_10. 常见的数据简化模式 -- 待完善
- android SQLite更新数据库版本最佳写法
- Numpy and Theano broadcasting
- 7.6.1 内部排序算法的比较
- 利用alembic进行sqlalchemy ORM数据库模型版本管理
- 使用Volley+OkHttp+Gson加速Android网络开发
- android增加自定义公用资源包customer-framework-res.apk
- Native Hibernate与Hibernate JPA
- 从指针类型获取原类型
- 分清iOS中的OC和CF概念
- Android FloatingActionButton: FloatingActionsMenu向下伸展弹出及删除包含的FloatingActionButton【4】
- Java - 面向对象设计六大基本原则-以Volley为例
- valgrind官方手册翻译(一)_20151128
- HDU-1541 Stars(树状数组+逆序数)