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这个框架 中也有明确的体现。
git clone https://android.googlesource.com/platform/frameworks/volley
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);
1.public StringRequest(int method, String url, Listener<String> listener,ErrorListener errorListener); 参数说明:从左到右分别是请求方法(都封装在Request中的Method接口内),请求URL,响应监听接口实例,错误监听接口实例。
ALPHA_8 | |
ARGB_4444 | 由于质量低,已经被弃用,推荐用ARGB_8888 |
ARGB_8888 | 每个像素用4byte存储 |
RGB_565 | 每个像素用2byte存储,红色占5位,绿色占6位,蓝色占5位 |
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);
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);
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.ImageRequest加载图片
1 RequestQueue newRequestQueue = Volley.newRequestQueue(MainActivity.this); 2 ImageRequest imageRequest = new ImageRequest( 3 "http://img0.imgtn.bdimg.com/it/u=2470748499,1329594201&fm=26&gp=0.jpg", 4 new Response.Listener<Bitmap>() { 5 @Override 6 public void onResponse(Bitmap response){ 7 mBitmap = response; 8 mImageView.setImageBitmap(mBitmap); 9 // load the data from the web 10 dataFragment.setData(mBitmap); 11 } 12 }, 0, 0, Bitmap.Config.RGB_565, null); 13 14 newRequestQueue.add(imageRequest);
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 JSON 12 = 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 @FormUrlEncoded 2 @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 @FormUrlEncoded 2 @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 @Multipart 11 @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 @NonNull 10 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 // 添加其他的part 13 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 @Override 19 public void onResponse(Call<ResponseBody> call, 20 Response<ResponseBody> response) { 21 Log.v("Upload", "success"); 22 } 23 @Override 24 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 @Override 11 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 }