Android -- 网络请求

Posted 浪里小白龙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 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 }

 






















































































































































































以上是关于Android -- 网络请求的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段14——Vue的axios网络请求封装

Android 使用两个不同的代码片段获取当前位置 NULL

Android主流视频播放及缓存实现原理调研

在android中动态创建选项卡并使用传入的参数加载片段

Android:处理同步网络调用

Android实现优雅快速的网络请求!