zoukankan      html  css  js  c++  java
  • 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);  

    3ImageRequest加载图片

     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);
    4. 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 }
  • 相关阅读:
    iOS 谁说程序猿不懂浪漫之 爱心
    iOS 星星评价视图 3行搞定
    iOS 柱状图的定制
    iOS 跑马灯 一句话集成
    代理的使用
    发送的网络请求中含有中文 转码的实现
    杂记
    使用纯代码实现UICollectionView(UITableView)需要注册
    NSASSert的使用
    iOS进阶第三节 数据处理之CoreData
  • 原文地址:https://www.cnblogs.com/neo-java/p/6841343.html
Copyright © 2011-2022 走看看