Android — 网络请求

 

一. HttpURLConnection

二. HttpClient  

三.Volley

四.OkHttp

五. Retrofit

-------------------------------------------------------------

一. HttpURLConnection

 

1. get请求方式

 1 public static void requestByGet() throws Exception {  2 String path = "https://reg.163.com/logins.jsp?id=helloworld&pwd=android";  3 // 新建一个URL对象  4 URL url = new URL(path);  5 // 打开一个HttpURLConnection连接  6 HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();  7 // 设置连接超时时间  8 urlConn.setConnectTimeout(5 * 1000);  9 // 开始连接 10  urlConn.connect(); 11 // 判断请求是否成功 12 if (urlConn.getResponseCode() == HTTP_200) { 13 // 获取返回的数据 14 byte[] data = readStream(urlConn.getInputStream()); 15 Log.i(TAG_GET, "Get方式请求成功,返回数据如下:"); 16 Log.i(TAG_GET, new String(data, "UTF-8")); 17 } else { 18 Log.i(TAG_GET, "Get方式请求失败"); 19  } 20 // 关闭连接 21  urlConn.disconnect(); 22 } 

 

2.  post请求方式

 1 public static void requestByPost() throws Throwable {  2 String path = "https://reg.163.com/logins.jsp";  3 // 请求的参数转换为byte数组  4 String params = "id=" + URLEncoder.encode("helloworld", "UTF-8")  5 + "&pwd=" + URLEncoder.encode("android", "UTF-8");  6 byte[] postData = params.getBytes();  7 // 新建一个URL对象  8 URL url = new URL(path);  9 // 打开一个HttpURLConnection连接 10 HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); 11 // 设置连接超时时间 12 urlConn.setConnectTimeout(5 * 1000); 13 // Post请求必须设置允许输出 14 urlConn.setDoOutput(true); 15 // Post请求不能使用缓存 16 urlConn.setUseCaches(false); 17 // 设置为Post请求 18 urlConn.setRequestMethod("POST"); 19 urlConn.setInstanceFollowRedirects(true); 20 // 配置请求Content-Type 21 urlConn.setRequestProperty("Content-Type", 22 "application/x-www-form-urlencode"); 23 // 开始连接 24  urlConn.connect(); 25 // 发送请求参数 26 DataOutputStream dos = new DataOutputStream(urlConn.getOutputStream()); 27  dos.write(postData); 28  dos.flush(); 29  dos.close(); 30 // 判断请求是否成功 31 if (urlConn.getResponseCode() == HTTP_200) { 32 // 获取返回的数据 33 byte[] data = readStream(urlConn.getInputStream()); 34 Log.i(TAG_POST, "Post请求方式成功,返回数据如下:"); 35 Log.i(TAG_POST, new String(data, "UTF-8")); 36 } else { 37 Log.i(TAG_POST, "Post方式请求失败"); 38  } 39 }

 

二. HttpClient

1. get请求方式

 1 public static void requestByHttpGet() throws Exception {  2 String path = "https://reg.163.com/logins.jsp?id=helloworld&pwd=android";  3 // 新建HttpGet对象  4 HttpGet httpGet = new HttpGet(path);  5 // 获取HttpClient对象  6 HttpClient httpClient = new DefaultHttpClient();  7 // 获取HttpResponse实例  8 HttpResponse httpResp = httpClient.execute(httpGet);  9 // 判断是够请求成功 10 if (httpResp.getStatusLine().getStatusCode() == HTTP_200) { 11 // 获取返回的数据 12 String result = EntityUtils.toString(httpResp.getEntity(), "UTF-8"); 13 Log.i(TAG_HTTPGET, "HttpGet方式请求成功,返回数据如下:"); 14  Log.i(TAG_HTTPGET, result); 15 } else { 16 Log.i(TAG_HTTPGET, "HttpGet方式请求失败"); 17  } 18 } 

 

 2. post请求方式

 1 public static void requestByHttpPost() throws Exception {  2 String path = "https://reg.163.com/logins.jsp";  3 // 新建HttpPost对象  4 HttpPost httpPost = new HttpPost(path);  5 // Post参数  6 List<NameValuePair> params = new ArrayList<NameValuePair>();  7 params.add(new BasicNameValuePair("id", "helloworld"));  8 params.add(new BasicNameValuePair("pwd", "android"));  9 // 设置字符集 10 HttpEntity entity = new UrlEncodedFormEntity(params, HTTP.UTF_8); 11 // 设置参数实体 12  httpPost.setEntity(entity); 13 // 获取HttpClient对象 14 HttpClient httpClient = new DefaultHttpClient(); 15 // 获取HttpResponse实例 16 HttpResponse httpResp = httpClient.execute(httpPost); 17 // 判断是够请求成功 18 if (httpResp.getStatusLine().getStatusCode() == HTTP_200) { 19 // 获取返回的数据 20 String result = EntityUtils.toString(httpResp.getEntity(), "UTF-8"); 21 Log.i(TAG_HTTPGET, "HttpPost方式请求成功,返回数据如下:"); 22  Log.i(TAG_HTTPGET, result); 23 } else { 24 Log.i(TAG_HTTPGET, "HttpPost方式请求失败"); 25  } 26 }

 

三 . Volley

Volley是Google在2003年的I/O大会上推出的通信框架,结合了AsyncHttpClient和Universal- Image-Loader的优点——简化了http的使用 + 异步加载图片的神奇能力。Android中的Http实现主要有HttpUrlConnection和HttpClient两种,关于二者的选择 Google在Blog中表示推荐在姜饼小人(API level = 9)及以上的版本中使用Java的HttpUrlConnection而在之前的版本使用Apache的HttpClient,这在Volley这个框架 中也有明确的体现。

 
获取Volley
git clone https://android.googlesource.com/platform/frameworks/volley

把它编译成jar文件就可以加入libs了
 
1. 简单的请求(以StringRequest为例)
  Http的通信最主要的部分应
该就是发出请求和接收响应 了,所以Volley的比较核心的一个类就是RequestQueue,一个请求队列。它负责管理工作线程,读写缓存,和解析、分发响应(具体操作还是由 具体的类实现),即将发出的Http请求都会首先聚集在这里等待工作线程来实现请求。RequestQueue可以被看成一艘载满Http请求的航空母 舰,而工作线程就是弹射器喽。
  所以按照航母起飞飞机的步骤,我们可以猜到利用Volley进行Http通信的简单步骤:
    1.获取RequestQueue(得到一艘航母,可以是自己造的,也可以是委托别人造的,下面会提到)
    2.实例化一个Request(得到一架飞机,你也知道飞机又很多类型啦)
    3.将Request加入RequestQueue,等待工作线程将其发送出去(把飞机从机库升上起飞甲板,等待弹射器把它扔出去)
 
       起飞侦察机-发出GET请求
  按照上面的步骤,第一步就是建立一个请求队列,最简单的方法就是用Volley.newRequestQueue(),这是一个特别方便的 静态方法,替我们默认实现了所有需要的东西(网络、缓存等,这些在Volley中都有默认实现),它会返回一个已经开始运行的 RequestQueue(相当于别人帮忙造了艘航母)。之后我们需要的只是设置好请求的响应监听接口,把请求加入到这个队列中就可以等着响应数据来敲门 了。下面是Google文档中的示例代码:
复制代码
 1 //初始化一个请求队列  2 RequestQueue queue = Volley.newRequestQueue(this);  3 String url ="http://www.google.com";  4  5 //根据给定的URL新建一个请求  6 StringRequest stringRequest = new StringRequest(Request.Method.GET, url,  7 new Response.Listener() {  8  @Override  9 public void onResponse(String response) { 10 //在这里操作UI组件是安全的,因为响应返回时这个函数会被post到UI线程来执行 11 // 在这里尽情蹂躏响应的String。 12  } 13 }, new Response.ErrorListener() { 14  @Override 15 public void onErrorResponse(VolleyError error) { 16 // 出错了怎么办?凉拌!并且在这里拌。 17  } 18 }); 19 // 把这个请求加入请求队列 20 queue.add(stringRequest);
复制代码

StringRequest是Request的具体实现之一,代表解析后的响应数据是一个字符串,相似的还有JsonRequest(包括 JsonObjectRequest和JsonArrayRequest两个可以使用的子类)、ImageRequest来满足基本的使用,用法大同小 异。主要是构造参数不一样,分别如下:

  1.public StringRequest(int method, String url, Listener<String> listener,ErrorListener errorListener);      参数说明:从左到右分别是请求方法(都封装在Request中的Method接口内),请求URL,响应监听接口实例,错误监听接口实例。

  2.public JsonObjectRequest(int method, String url, JSONObject jsonRequest,Listener<JSONObject> listener, ErrorListener errorListener);
     public JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener,ErrorListener errorListener);
     参数说明:如果是GET请求的话,jsonRequest传入null就可以了,否则在未指明请求方法的情况下(也就是第二个构造函数)会默认为POST请求。其他同上。
  3.public JsonArrayRequest(String url, Listener<JSONArray> listener, ErrorListener errorListener);
     参数说明:同上。
  4.public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,Config decodeConfig, Response.ErrorListener errorListener);
     参数说明:decodeConfig是图片的颜色属性,下面的几个值都可以使用。
 
Bitmap.Config中的颜色属性(枚举类型)
ALPHA_8 
ARGB_4444由于质量低,已经被弃用,推荐用ARGB_8888
ARGB_8888每个像素用4byte存储
RGB_565每个像素用2byte存储,红色占5位,绿色占6位,蓝色占5位
 
       起飞战斗机-发出POST请求
  基本方式和上面一样,但是怎么装导弹,啊不,是怎么提交的数据呢?
Volley会在Request的请求方法是POST(还有PUT和PATCH)的情况下调用Request类(就是XXXRequest的父 类)的getParam()函数来获取参数,提前剧透,如果使用的是HttpUrlConnection的话,调用getParam()是在 HurlStatck中的addBodyIfExists()函数实现的,感兴趣的话可以去看一下哈。所以,POST请求像下面这样就可以了。
复制代码
 1 //初始化一个请求队列  2 RequestQueue queue = Volley.newRequestQueue(this);  3 String url ="http://www.google.com";  4  5 //根据给定的URL新建一个请求  6 StringRequest stringRequest = new StringRequest(Request.Method.POST, url,  7 new Response.Listener() {  8  @Override  9 public void onResponse(String response) { 10 // 在这里处理请求得到的String类型的响应 11  } 12 }, new Response.ErrorListener() { 13  @Override 14 public void onErrorResponse(VolleyError error) { 15 // 在这里进行出错之后的处理 16  } 17 }) { 18 @Override 19 protected Map<String, String> getParams() throws AuthFailureError { 20 21 Map<String, String> map = new HashMap<String, String>(); 22 map.put("params1", "value1"); 23 map.put("params2", "value2"); 24 return map 25  }; 26 // 把这个请求加入请求队列 27 queue.add(stringRequest);
复制代码
 
后悔药-取消请求
  Request中有一个cancel()方法,调用这个就可以取消当前请求了,但是取消到哪一个层次就不一定了,但是Volley可以保证 响应处理函数(就是onResponse()和onErroeResponse())不会被调用。还有一个一起取消多个请求,就是在发出请求前调用 Request的setTag()方法为每个请求加一个标签,这个方法的参数是Object,所以我们可以使用任何类型作为标签。这样就可以调用 ReqiestQueue的cancelAll()函数取消一群标签了。比较常用的方法就是,将发出这个请求的Activity或者Fragment作为 标签,并在onStop()中调用cancelAll()。
 
2. 使用ImageLoader加载图片
  ImageLoader 是一个可以实现图片异步加载的类,但已经不是继承与Request了。ImageLoader虽然是头神兽,但必须在主线程召唤它,否则会抛出错误 IllegalStateException,可能是因为ImageLoader在图片返回时要直接操作ImageView,在主线程里操作UI组件才是 安全的,so~
  用ImageLoader加载图片分三步
    1.创建ImageLoader
    2.获取一个ImageListener对象
    3.调用ImageLoader的get()方法获取图片
  ImageLoader的构造函数长成这样:public ImageLoader(RequestQueue queue, ImageCache imageCache);
所以实例化一个ImageLoader需要一个RequestQueue(之前建立的就行),还有一个ImageCache,这是一个 ImageLoader内部定义的接口,用来实现L1缓存——内存缓存(Volley在RequestQueue中已经实现了L2缓存——文件缓存)。 ImageLoader中并没有对传入的ImageCache在使用前判空的代码,传null进去会出错的。如果实在不想弄内存缓存,实现一个什么都不做 的ImageCache就好了。下面是代码:
复制代码
 1 ImageLoader imageLoader = new ImageLoader(mRequestQueue, new ImageCache() {  2  @Override  3 public void putBitmap(String url, Bitmap bitmap) {  4  }  5  6  @Override  7 public Bitmap getBitmap(String url) {  8 return null;  9  } 10 }); 11 12 //default_image是正在加载图片时占位用的 13 //error_image是加载不成功时显示的图片 14 ImageListener listener = ImageLoader.getImageListener(imageView, R.drawable.default_image, R.drawable.error_image); 15imageLoader.get("your image url", listener); 
复制代码

  除了ImageLoader之外Volley中还有一个Image加载的神器——NetworkImageView,使用步骤如下:
    1.在布局文件中加入控件,并在Java代码中获取实例
    2.设置default_image,error_image,图片URL,一个ImageLoader对象
  代码如下:
1 networkImageView = (NetworkImageView) findViewById(R.id.network_image_view); 2 networkImageView.setDefaultImageResId(R.drawable.default_image); 3 networkImageView.setErrorImageResId(R.drawable.error_image); 4 networkImageView.setImageUrl("your image url", imageLoader); 

 

3. Google推荐的用法
  上面就是Volley的基本用法了,但是如果一个App需要频繁的网络通信的话,建立多个RequestQueue是件很奇怪的事儿(谁会 因为临时有飞机要在海上起飞就去新建一艘航母呢,这得多有钱啊),所以Google推荐我们只实例化一个RequestQueue来应付频繁的Http通 信,当然,要保证队列的寿命和App一样长。如何实现呢?Google又说了,不推荐在App的Application.onCretae()方法中实例 化一个RequestQueue(不过确实是个简单的方法哈),最好是建立一个单例模式的类,并把所有我们需要用到的Volley的瓶瓶罐罐都放进去,这 样显得更模块化。下面就是示例代码。这段代码中最重要的就是RequestQueue要用Application的Context实例化,要不然就会随着 Activity的生命周期不停重建。其实,像AsyncHttpClient中的纯静态使用方法也不错(详情见:http://loopj.com /android-async-http/)
PS:下面还实现了一个简单的ImageCache
复制代码
 1 private static MySingleton mInstance;  2 private RequestQueue mRequestQueue;  3 private ImageLoader mImageLoader;  4 private static Context mCtx;  5  6 private MySingleton(Context context) {  7 mCtx = context;  8 mRequestQueue = getRequestQueue();  9 10 mImageLoader = new ImageLoader(mRequestQueue, 11 new ImageLoader.ImageCache() { 12 private final LruCache<String, Bitmap> 13 cache = new LruCache<String, Bitmap>(20); 14 15  @Override 16 public Bitmap getBitmap(String url) { 17 return cache.get(url); 18  } 19 20  @Override 21 public void putBitmap(String url, Bitmap bitmap) { 22  cache.put(url, bitmap); 23  } 24  }); 25  } 26 27 public static synchronized MySingleton getInstance(Context context) { 28 if (mInstance == null) { 29 mInstance = new MySingleton(context); 30  } 31 return mInstance; 32  } 33 34 public RequestQueue getRequestQueue() { 35 if (mRequestQueue == null) { 36 // getApplicationContext()是关键, 它会避免 37 // Activity或者BroadcastReceiver带来的缺点. 38 mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext()); 39  } 40 return mRequestQueue; 41  } 42 43 public <T> void addToRequestQueue(Request<T> req) { 44  getRequestQueue().add(req); 45  } 46 47 public ImageLoader getImageLoader() { 48 return mImageLoader; 49  } 50 }

 

四.OkHttp

HTTP是现代应用网络的方式。这是我们如何交换数据和媒体。有效地进行HTTP使您的东西加载更快,并节省带宽。
OkHttp是默认情况下高效的HTTP客户端
    HTTP / 2支持允许同一主机的所有请求共享套接字。
    连接池减少请求延迟(如果HTTP / 2不可用)。
    透明GZIP缩小下载大小。
    响应缓存可以避免重复请求的网络。

当网络麻烦时,OkHttp坚持不懈:它将从常见的连接问题中静默地恢复。如果您的服务有多个IP地址,如果第一个连接失败,OkHttp将尝试替代地址。这对于IPv4 + IPv6以及在冗余数据中心中托管的服务是必需的。 OkHttp启动与现代TLS功能(SNI,ALPN)的新连接,如果握手失败,则返回TLS 1.0。
使用OkHttp很容易它的请求/响应API设计有流畅的构建器和不变性。它支持同步阻塞调用和具有回调的异步调用。
OkHttp支持Android 2.3及以上版本。对于Java,最低要求是1.7。

1. 下载URL并将其内容作为字符串打印

 1 package okhttp3.guide; 2  3 import java.io.IOException; 4 import okhttp3.OkHttpClient; 5 import okhttp3.Request; 6 import okhttp3.Response; 7  8 public class GetExample { 9 OkHttpClient client = new OkHttpClient();10 11 String run(String url) throws IOException {12 Request request = new Request.Builder()13  .url(url)14  .build();15 16 try (Response response = client.newCall(request).execute()) {17 return response.body().string();18  }19  }20 21 public static void main(String[] args) throws IOException {22 GetExample example = new GetExample();23 String response = example.run("https://raw.github.com/square/okhttp/master/README.md");24  System.out.println(response);25  }26 }

 

2. 将数据发送到服务

 1 package okhttp3.guide; 2  3 import java.io.IOException; 4 import okhttp3.MediaType; 5 import okhttp3.OkHttpClient; 6 import okhttp3.Request; 7 import okhttp3.RequestBody; 8 import okhttp3.Response; 9 10 public class PostExample {11 public static final MediaType JSON12 = MediaType.parse("application/json; charset=utf-8");13 14 OkHttpClient client = new OkHttpClient();15 16 String post(String url, String json) throws IOException {17 RequestBody body = RequestBody.create(JSON, json);18 Request request = new Request.Builder()19  .url(url)20  .post(body)21  .build();22 try (Response response = client.newCall(request).execute()) {23 return response.body().string();24  }25  }26 27  String bowlingJson(String player1, String player2) {28 return "{‘winCondition‘:‘HIGH_SCORE‘,"29 + "‘name‘:‘Bowling‘,"30 + "‘round‘:4,"31 + "‘lastSaved‘:1367702411696,"32 + "‘dateStarted‘:1367702378785,"33 + "‘players‘:["34 + "{‘name‘:‘" + player1 + "‘,‘history‘:[10,8,6,7,8],‘color‘:-13388315,‘total‘:39},"35 + "{‘name‘:‘" + player2 + "‘,‘history‘:[6,10,5,10,10],‘color‘:-48060,‘total‘:41}"36 + "]}";37  }38 39 public static void main(String[] args) throws IOException {40 PostExample example = new PostExample();41 String json = example.bowlingJson("Jesse", "Jake");42 String response = example.post("http://www.roundsapp.com/post", json);43  System.out.println(response);44  }45 }

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

五. Retrofit

 

Retrofit是Square公司开发的一款针对Android网络请求的框架,Retrofit2底层基于OkHttp实现的,OkHttp现在已经得到Google官方认可,大量的app都采用OkHttp做网络请求,其源码详见OkHttp Github。

首先先来看一个完整Get请求是如何实现:

1. 创建业务请求接口

1 public interface BlueService {2 @GET("book/search")3 Call<BookSearchResponse> getSearchBooks(@Query("q") String name, 4 @Query("tag") String tag, @Query("start") int start, 5 @Query("count") int count);6 }

这里需要稍作说明,@GET注解就表示get请求,@Query表示请求参数,将会以key=value的方式拼接在url后面。

 

2.  需要创建一个Retrofit的示例,并完成相应的配置

1 Retrofit retrofit = new Retrofit.Builder()2 .baseUrl("https://api.douban.com/v2/")3  .addConverterFactory(GsonConverterFactory.create())4  .build();5 6 BlueService service = retrofit.create(BlueService.class);

        这里的baseUrl就是网络请求URL相对固定的地址,一般包括请求协议(如Http)、域名或IP地址、端口号等,当然还会有很多其他的配置,下文会详细介绍。还有addConverterFactory方法表示需要用什么转换器来解析返回值,GsonConverterFactory.create()表示调用Gson库来解析json返回值,具体的下文还会做详细介绍。

 3.  调用请求方法,并得到Call实例

1 Call<BookSearchResponse> call = mBlueService.getSearchBooks("小王子", "", 0, 3);

        Call其实在Retrofit中就是行使网络请求并处理返回值的类,调用的时候会把需要拼接的参数传递进去,此处最后得到的url完整地址为
        https://api.douban.com/v2/book/search?q=%E5%B0%8F%E7%8E%8B%E5%AD%90&tag=&start=0&count=3

 4.   使用Call实例完成同步或异步请求

        同步请求

1 BookSearchResponse response = call.execute().body();

       这里需要注意的是网络请求一定要在子线程中完成,不能直接在UI线程执行,不然会crash

        异步请求

 1 call.enqueue(new Callback<BookSearchResponse>() { 2  @Override 3 public void onResponse(Call<BookSearchResponse> call, Response<BookSearchResponse> response) { 4 asyncText.setText("异步请求结果: " + response.body().books.get(0).altTitle); 5  } 6  @Override 7 public void onFailure(Call<BookSearchResponse> call, Throwable t) { 8  9  }10  });11 

 

----------------------------------------------------------------------------------------------------------------

然后看看是如何使用的。

首先需要在build.gradle文件中引入需要的第三包,配置如下:

compile ‘com.squareup.retrofit2:retrofit:2.1.0‘compile ‘com.squareup.retrofit2:converter-gson:2.1.0‘compile ‘com.squareup.retrofit2:adapter-rxjava:2.1.0‘

引入完第三包接下来就可以使用Retrofit来进行网络请求了。接下来会对不同的请求方式做进一步的说明。

Get方法

1. @Query

Get方法请求参数都会以key=value的方式拼接在url后面,Retrofit提供了两种方式设置请求参数。第一种就是像上文提到的直接在interface中添加@Query注解,还有一种方式是通过Interceptor实现,直接看如何通过Interceptor实现请求参数的添加。

 1 public class CustomInterceptor implements Interceptor { 2  @Override 3 public Response intercept(Chain chain) throws IOException { 4 Request request = chain.request(); 5 HttpUrl httpUrl = request.url().newBuilder() 6 .addQueryParameter("token", "tokenValue") 7  .build(); 8 request = request.newBuilder().url(httpUrl).build(); 9 return chain.proceed(request);10  }11 }

addQueryParameter就是添加请求参数的具体代码,这种方式比较适用于所有的请求都需要添加的参数,一般现在的网络请求都会添加token作为用户标识,那么这种方式就比较适合。
创建完成自定义的Interceptor后,还需要在Retrofit创建client处完成添加
addInterceptor(new CustomInterceptor())

2. @QueryMap

如果Query参数比较多,那么可以通过@QueryMap方式将所有的参数集成在一个Map统一传递,还以上文中的get请求方法为例

1 public interface BlueService {2 @GET("book/search")3 Call<BookSearchResponse> getSearchBooks(@QueryMap Map<String, String> options);4 }

调用的时候将所有的参数集合在统一的map中即可

1 Map<String, String> options = new HashMap<>();2 map.put("q", "小王子");3 map.put("tag", null);4 map.put("start", "0");5 map.put("count", "3");6 Call<BookSearchResponse> call = mBlueService.getSearchBooks(options);

3. Query集合

假如你需要添加相同Key值,但是value却有多个的情况,一种方式是添加多个@Query参数,还有一种简便的方式是将所有的value放置在列表中,然后在同一个@Query下完成添加,实例代码如下:

1 public interface BlueService {2 @GET("book/search")3 Call<BookSearchResponse> getSearchBooks(@Query("q") List<String> name);4 }

最后得到的url地址为
https://api.douban.com/v2/book/search?q=leadership&q=beyond%20feelings

4. Query非必填

如果请求参数为非必填,也就是说即使不传该参数,服务端也可以正常解析,那么如何实现呢?其实也很简单,请求方法定义处还是需要完整的Query注解,某次请求如果不需要传该参数的话,只需填充null即可。

针对文章开头提到的get的请求,加入按以下方式调用

1 Call<BookSearchResponse> call = mBlueService.getSearchBooks("小王子", null, 0, 3);

那么得到的url地址为
https://api.douban.com/v2/book/search?q=%E5%B0%8F%E7%8E%8B%E5%AD%90&start=0&count=3

5. @Path

如果请求的相对地址也是需要调用方传递,那么可以使用@Path注解,示例代码如下:

1 @GET("book/{id}")2 Call<BookResponse> getBook(@Path("id") String id);

业务方想要在地址后面拼接书籍id,那么通过Path注解可以在具体的调用场景中动态传递,具体的调用方式如下:

1 Call<BookResponse> call = mBlueService.getBook("1003078");

此时的url地址为
https://api.douban.com/v2/book/1003078
@Path可以用于任何请求方式,包括Post,Put,Delete等等

Post请求

1. @field

Post请求需要把请求参数放置在请求体中,而非拼接在url后面,先来看一个简单的例子

1  @FormUrlEncoded2 @POST("book/reviews")3 Call<String> addReviews(@Field("book") String bookId, @Field("title") String title,4 @Field("content") String content, @Field("rating") String rating);

这里有几点需要说明的
    @FormUrlEncoded将会自动将请求参数的类型调整为application/x-www-form-urlencoded,假如content传递的参数为Good Luck,那么最后得到的请求体就是

1 content=Good+Luck

    FormUrlEncoded不能用于Get请求
    @Field注解将每一个请求参数都存放至请求体中,还可以添加encoded参数,该参数为boolean型,具体的用法为

1 @Field(value = "book", encoded = true) String book

    encoded参数为true的话,key-value-pair将会被编码,即将中文和特殊字符进行编码转换

2. @FieldMap

上述Post请求有4个请求参数,假如说有更多的请求参数,那么通过一个一个的参数传递就显得很麻烦而且容易出错,这个时候就可以用FieldMap

1  @FormUrlEncoded2 @POST("book/reviews")3 Call<String> addReviews(@FieldMap Map<String, String> fields);

3. @Body

如果Post请求参数有多个,那么统一封装到类中应该会更好,这样维护起来会非常方便

 1 @FormUrlEncoded 2 @POST("book/reviews") 3 Call<String> addReviews(@Body Reviews reviews); 4  5 public class Reviews { 6 public String book; 7 public String title; 8 public String content; 9 public String rating;10 }

上传

上传因为需要用到Multipart,所以需要单独拿出来介绍,先看一个具体上传的例子

首先还是需要新建一个interface用于定义上传方法

 1 public interface FileUploadService {  2 // 上传单个文件 3  @Multipart 4 @POST("upload") 5 Call<ResponseBody> uploadFile( 6 @Part("description") RequestBody description, 7  @Part MultipartBody.Part file); 8  9 // 上传多个文件10  @Multipart11 @POST("upload")12 Call<ResponseBody> uploadMultipleFiles(13 @Part("description") RequestBody description,14  @Part MultipartBody.Part file1,15  @Part MultipartBody.Part file2);16 }

接下来我们还需要在Activity和Fragment中实现两个工具方法,代码如下:

 1 public static final String MULTIPART_FORM_DATA = "multipart/form-data"; 2  3 @NonNull 4 private RequestBody createPartFromString(String descriptionString) {  5 return RequestBody.create( 6  MediaType.parse(MULTIPART_FORM_DATA), descriptionString); 7 } 8  9 @NonNull10 private MultipartBody.Part prepareFilePart(String partName, Uri fileUri) { 11 File file = FileUtils.getFile(this, fileUri);12 13 // 为file建立RequestBody实例14 RequestBody requestFile =15  RequestBody.create(MediaType.parse(MULTIPART_FORM_DATA), file);16 17 // MultipartBody.Part借助文件名完成最终的上传18 return MultipartBody.Part.createFormData(partName, file.getName(), requestFile);19 }

好了,接下来就是最终的上传文件代码了

 1 Uri file1Uri = ... // 从文件选择器或者摄像头中获取 2 Uri file2Uri = ... 3  4 // 创建上传的service实例 5 FileUploadService service =  6 ServiceGenerator.createService(FileUploadService.class); 7  8 // 创建文件的part (photo, video, ...) 9 MultipartBody.Part body1 = prepareFilePart("video", file1Uri); 10 MultipartBody.Part body2 = prepareFilePart("thumbnail", file2Uri);11 12 // 添加其他的part13 RequestBody description = createPartFromString("hello, this is description speaking");14 15 // 最后执行异步请求操作16 Call<ResponseBody> call = service.uploadMultipleFiles(description, body1, body2); 17 call.enqueue(new Callback<ResponseBody>() { 18  @Override19 public void onResponse(Call<ResponseBody> call,20 Response<ResponseBody> response) {21 Log.v("Upload", "success");22  }23  @Override24 public void onFailure(Call<ResponseBody> call, Throwable t) {25 Log.e("Upload error:", t.getMessage());26  }27 });

其他必须知道的事项

1. 添加自定义的header

Retrofit提供了两个方式定义Http请求头参数:静态方法和动态方法,静态方法不能随不同的请求进行变化,头部信息在初始化的时候就固定了。而动态方法则必须为每个请求都要单独设置。

    静态方法

1 public interface BlueService {2 @Headers("Cache-Control: max-age=640000")3 @GET("book/search")4 Call<BookSearchResponse> getSearchBooks(@Query("q") String name,5 @Query("tag") String tag, @Query("start") int start,6 @Query("count") int count);7 }

 

    当然你想添加多个header参数也是可以的,写法也很简单

 1 public interface BlueService { 2  @Headers({ 3 "Accept: application/vnd.yourapi.v1.full+json", 4 "User-Agent: Your-App-Name" 5  }) 6 @GET("book/search") 7 Call<BookSearchResponse> getSearchBooks(@Query("q") String name, 8 @Query("tag") String tag, @Query("start") int start, 9 @Query("count") int count);10 }

    此外也可以通过Interceptor来定义静态请求头

 1 public class RequestInterceptor implements Interceptor { 2  @Override 3 public Response intercept(Chain chain) throws IOException { 4 Request original = chain.request(); 5 Request request = original.newBuilder() 6 .header("User-Agent", "Your-App-Name") 7 .header("Accept", "application/vnd.yourapi.v1.full+json") 8  .method(original.method(), original.body()) 9  .build();10 return chain.proceed(request);11  }12 }

        添加header参数Request提供了两个方法,一个是header(key, value),另一个是.addHeader(key, value),两者的区别是,header()如果有重名的将会覆盖,而addHeader()允许相同key值的header存在

    然后在OkHttp创建Client实例时,添加RequestInterceptor即可

1 private static OkHttpClient getNewClient(){2 return new OkHttpClient.Builder()3 .addInterceptor(new RequestInterceptor())4  .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)5  .build();6 }

    动态方法

1 public interface BlueService {2 @GET("book/search")3 Call<BookSearchResponse> getSearchBooks(4 @Header("Content-Range") String contentRange,5 @Query("q") String name, @Query("tag") String tag,6 @Query("start") int start, @Query("count") int count);7 }

2. 网络请求日志

调试网络请求的时候经常需要关注一下请求参数和返回值,以便判断和定位问题出在哪里,Retrofit官方提供了一个很方便查看日志的Interceptor,你可以控制你需要的打印信息类型,使用方法也很简单。

首先需要在build.gradle文件中引入logging-interceptor

compile ‘com.squareup.okhttp3:logging-interceptor:3.4.1‘

同上文提到的CustomInterceptor和RequestInterceptor一样,添加到OkHttpClient创建处即可,完整的示例代码如下:

1 private static OkHttpClient getNewClient(){2 HttpLoggingInterceptor logging = new HttpLoggingInterceptor();3  logging.setLevel(HttpLoggingInterceptor.Level.BODY);4 return new OkHttpClient.Builder()5 .addInterceptor(new CustomInterceptor())6  .addInterceptor(logging)7  .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)8  .build();9 }

HttpLoggingInterceptor提供了4中控制打印信息类型的等级,分别是NONE,BASIC,HEADERS,BODY,接下来分别来说一下相应的打印信息类型。

    NONE

    没有任何日志信息

    Basic

    打印请求类型,URL,请求体大小,返回值状态以及返回值的大小

    D/HttpLoggingInterceptor$Logger: --> POST /upload HTTP/1.1 (277-byte body)  
    D/HttpLoggingInterceptor$Logger: <-- HTTP/1.1 200 OK (543ms, -1-byte body)  

    Headers

    打印返回请求和返回值的头部信息,请求类型,URL以及返回值状态码

    <-- 200 OK https://api.douban.com/v2/book/search?q=%E5%B0%8F%E7%8E%8B%E5%AD%90&start=0&count=3&token=tokenValue (3787ms)
    D/OkHttp: Date: Sat, 06 Aug 2016 14:26:03 GMT
    D/OkHttp: Content-Type: application/json; charset=utf-8
    D/OkHttp: Transfer-Encoding: chunked
    D/OkHttp: Connection: keep-alive
    D/OkHttp: Keep-Alive: timeout=30
    D/OkHttp: Vary: Accept-Encoding
    D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
    D/OkHttp: Pragma: no-cache
    D/OkHttp: Cache-Control: must-revalidate, no-cache, private
    D/OkHttp: Set-Cookie: bid=D6UtQR5N9I4; Expires=Sun, 06-Aug-17 14:26:03 GMT; Domain=.douban.com; Path=/
    D/OkHttp: X-DOUBAN-NEWBID: D6UtQR5N9I4
    D/OkHttp: X-DAE-Node: dis17
    D/OkHttp: X-DAE-App: book
    D/OkHttp: Server: dae
    D/OkHttp: <-- END HTTP

    Body

    打印请求和返回值的头部和body信息

    <-- 200 OK https://api.douban.com/v2/book/search?q=%E5%B0%8F%E7%8E%8B%E5%AD%90&tag=&start=0&count=3&token=tokenValue (3583ms)
    D/OkHttp: Connection: keep-alive
    D/OkHttp: Date: Sat, 06 Aug 2016 14:29:11 GMT
    D/OkHttp: Keep-Alive: timeout=30
    D/OkHttp: Content-Type: application/json; charset=utf-8
    D/OkHttp: Vary: Accept-Encoding
    D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
    D/OkHttp: Transfer-Encoding: chunked
    D/OkHttp: Pragma: no-cache
    D/OkHttp: Connection: keep-alive
    D/OkHttp: Cache-Control: must-revalidate, no-cache, private
    D/OkHttp: Keep-Alive: timeout=30
    D/OkHttp: Set-Cookie: bid=ESnahto1_Os; Expires=Sun, 06-Aug-17 14:29:11 GMT; Domain=.douban.com; Path=/
    D/OkHttp: Vary: Accept-Encoding
    D/OkHttp: X-DOUBAN-NEWBID: ESnahto1_Os
    D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
    D/OkHttp: X-DAE-Node: dis5
    D/OkHttp: Pragma: no-cache
    D/OkHttp: X-DAE-App: book
    D/OkHttp: Cache-Control: must-revalidate, no-cache, private
    D/OkHttp: Server: dae
    D/OkHttp: Set-Cookie: bid=5qefVyUZ3KU; Expires=Sun, 06-Aug-17 14:29:11 GMT; Domain=.douban.com; Path=/
    D/OkHttp: X-DOUBAN-NEWBID: 5qefVyUZ3KU
    D/OkHttp: X-DAE-Node: dis17
    D/OkHttp: X-DAE-App: book
    D/OkHttp: Server: dae
    D/OkHttp: {"count":3,"start":0,"total":778,"books":[{"rating":{"max":10,"numRaters":202900,"average":"9.0","min":0},"subtitle":"","author":["[法] 圣埃克苏佩里"],"pubdate":"2003-8","tags":[{"count":49322,"name":"小王子","title":"小王子"},{"count":41381,"name":"童话","title":"童话"},{"count":19773,"name":"圣埃克苏佩里","title":"圣埃克苏佩里"}
    D/OkHttp: <-- END HTTP (13758-byte body)

3. 为某个请求设置完整的URL

? 假如说你的某一个请求不是以base_url开头该怎么办呢?别着急,办法很简单,看下面这个例子你就懂了

 1 public interface BlueService {  2  @GET 3 public Call<ResponseBody> profilePicture(@Url String url); 4 } 5  6 Retrofit retrofit = Retrofit.Builder()  7 .baseUrl("https://your.api.url/"); 8  .build(); 9 10 BlueService service = retrofit.create(BlueService.class); 11 service.profilePicture("https://s3.amazon.com/profile-picture/path");

? 直接用@Url注解的方式传递完整的url地址即可。

4. 取消请求

Call提供了cancel方法可以取消请求,前提是该请求还没有执行

 1 String fileUrl = "http://futurestud.io/test.mp4";  2 Call<ResponseBody> call =  3  downloadService.downloadFileWithDynamicUrlSync(fileUrl); 4 call.enqueue(new Callback<ResponseBody>() {  5  @Override 6 public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { 7 Log.d(TAG, "request success"); 8  } 9 10  @Override11 public void onFailure(Call<ResponseBody> call, Throwable t) {12 if (call.isCanceled()) {13 Log.e(TAG, "request was cancelled");14 } else {15 Log.e(TAG, "other larger issue, i.e. no network connection?");16  }17  }18 });19  }20 21 // 触发某个动作,例如用户点击了取消请求的按钮22 call.cancel(); 23 }

 

相关文章