美文网首页半栈工程师程序员手机移动程序开发
OkHttp3-Android网络请求框架常用用法介绍与实例(m

OkHttp3-Android网络请求框架常用用法介绍与实例(m

作者: Donkor | 来源:发表于2017-01-05 19:59 被阅读0次

    前言:OkHttp是Square开发的第三方库,用于发送和接收基于HTTP的网络请求。它建立在Okio库之上,通过创建共享内存池,它尝试通过标准Java I / O库更高效地读取和写入数据。它还是Retrofit库的底层库,为使用基于REST的API提供类型安全性。Square公司是不是看着很 眼红 (眼熟),是的没错,这家公司在开源的道路上做足了贡献,造福了无数程序员。除了OkHttp外,还有Picasso、Retrofit、otto等著名的开源项目。目前OkHttp最新版本是3.x,支持Android 2.3+,所以以下所讲内容都是基于OkHttp3.x。

    OkHttp项目开源地址 :https://github.com/square/okhttp

    基本使用
    ●配置与导入
    在AndroidManifest.xml文件中打开了联网的权限:

    <uses-permission android:name="android.permission.INTERNET"/>
    

    在Android Studio 中配置gradle:

    compile 'com.squareup.okhttp3:okhttp:3.5.0'
    

    ●发送和接收网络请求
    实例化一个OkHttpClient并创建一个Request对象。

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).build();
    

    如果有任何需要添加的查询参数,OkHttp提供的HttpUrl类可以用来构造URL:

    HttpUrl.Builder urlBuilder = HttpUrl.parse("http://blog.csdn.net/donkor_").newBuilder();
    //addQueryParameter    添加查询参数
    urlBuilder.addQueryParameter("name", "donkor");
    urlBuilder.addQueryParameter("blog", "okhttp3");
    urlBuilder.addQueryParameter("number", "8");
    String url = urlBuilder.build().toString();
    Request request = new Request.Builder()
                         .url(url)
                         .build();
    

    ●同步Get
    因为Android不允许主线程上的网络调用,所以只能在单独的线程或后台服务上进行同步调用。

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder().url(url).build();
                //newCall方法会得到一个Call对象,表示一个新的网络请求
                //execute方法是同步方法,会阻塞当前线程,并返回Response对象
                okhttp3.Response response = client.newCall(request).execute();
                String data=response.body().string();
                if (response.isSuccessful()) {
                     Log.e("asd","okHttp is request success");
                } else {
                    Log.e("asd", "okHttp is request error");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
    

    ※ 注:通过Response对象的body()方法可以得到响应体ResponseBody对象,调用其string()方法可以很方便地将响应体中的数据转换为字符串,该方法会将所有的数据放入到内存之中,所以如果数据超过1M,最好不要调用string()方法以避免占用过多内存,这种情况下可以考虑将数据当做Stream流处理。

    ●异步Get

    //enqueue方法调用异步请求网络,该方法接收一个okhttp3.Callback对象,
    // 且不会阻塞当前线程,会新开一个工作线程,让实际的网络请求在工作线程中执行。
    //当异步请求成功后,会回调Callback对象的onResponse方法,在该方法中可以获取Response对象。
    // 当异步请求失败或者调用了Call对象的cancel方法时,会回调Callback对象的onFailure方法。
    // onResponse和onFailure这两个方法都是在工作线程中执行的。
    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            Log.e("asd", "okHttp is request erro");
            Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            Log.e("asd", "okHttp is request success");
            String data=response.body().string();
            //在主线程中进行UI修改操作
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                //do something
                }
            });
        }
    });
    

    ●Post方式发送String
    使用HTTP POST提交请求到服务。这个例子提交了一个markdown文档到web服务,以HTML方式渲染markdown。因为请求体会放置在内存中,所以应该避免用该API发送超过1M的数据。

        public static final MediaType MEDIA_TYPE_MARKDOWN
          = MediaType.parse("text/x-markdown; charset=utf-8");
             
        private final OkHttpClient client = new OkHttpClient();
             
        public void run() throws Exception {
            String postBody = ""
                + "Releases\n"
                + "--------\n"
                + "\n"
                + " * _1.0_ May 6, 2013\n"
                + " * _1.1_ June 15, 2013\n"
                + " * _1.2_ August 11, 2013\n";
            //post方法接收一个RequestBody对象
            //create方法第一个参数都是MediaType类型,create方法的第二个参数可以是String、File、byte[]或okio.ByteString
            Request request = new Request.Builder()
                .url("https://api.github.com/markdown/raw")
                .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
                .build();
             
            Response response = client.newCall(request).execute();
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
             
            System.out.println(response.body().string());
        }
    

    ●POST方式发送Stream流
    这里我们将请求主体作为流。 请求体的内容由流写入产生。 此示例直接流入Okio缓冲接收器。 您的程序可能更喜欢OutputStream,您可以从BufferedSink.outputStream()获取。

        public static final MediaType MEDIA_TYPE_MARKDOWN
              = MediaType.parse("text/x-markdown; charset=utf-8");
    
          private final OkHttpClient client = new OkHttpClient();
    
          public void run() throws Exception {
        RequestBody requestBody = new RequestBody() {
          //重写contentType()方法,返回markdown类型的MediaType
          @Override public MediaType contentType() {
            return MEDIA_TYPE_MARKDOWN;
          }
    
          //重写writeTo()方法,该方法会传入一个Okia的BufferedSink类型的对象,
          //可以通过BufferedSink的各种write方法向其写入各种类型的数据,
          //此例中用其writeUtf8方法向其中写入UTF-8的文本数据。
          //也可以通过它的outputStream()方法,得到输出流OutputStream,
          //从而通过OutputSteram向BufferedSink写入数据。
          @Override public void writeTo(BufferedSink sink) throws IOException {
            sink.writeUtf8("Numbers\n");
            sink.writeUtf8("-------\n");
            for (int i = 2; i <= 997; i++) {
              sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
            }
          }
    
          private String factor(int n) {
            for (int i = 2; i < n; i++) {
              int x = n / i;
              if (x * i == n) return factor(x) + " × " + i;
            }
            return Integer.toString(n);
          }
        };
    
        Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(requestBody)
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
        }
    

    ●POST方式发送文件File
    上传文件在实际开发中也经常用到,这里比较简单,直接看代码:

        public static final MediaType MEDIA_TYPE_MARKDOWN
            = MediaType.parse("text/x-markdown; charset=utf-8");
    
        private final OkHttpClient client = new OkHttpClient();
    
        public void run() throws Exception {
          //File("/你的文件路径/名称")
          File file = new File("README.md");
    
          Request request = new Request.Builder()
              .url("https://api.github.com/markdown/raw")
              .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
              .build();
    
          Response response = client.newCall(request).execute();
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
          System.out.println(response.body().string());
        }
    

    ●POST方式Form表单中的键值对
    使用FormBody.Builder来构建类似于HTML <form>标签的请求体。键值对将使用一种HTML兼容形式的URL编码来进行编码。

        private final OkHttpClient client = new OkHttpClient();
    
        public void run() throws Exception {
          RequestBody formBody = new FormBody.Builder()
              .add("search", "Jurassic Park")
              .build();
          Request request = new Request.Builder()
              .url("https://en.wikipedia.org/w/index.php")
              .post(formBody)
              .build();
    
          Response response = client.newCall(request).execute();
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
          System.out.println(response.body().string());
        }
    

    ●响应缓存
    要想缓存响应,就需要配置一个可以读写的缓存目录,以及缓存大小的限制。 并且缓存目录应该是私有的,不受信任的应用程序不应该能够读取其内容!
    一个缓存目录同时被多个缓存访问是错误的。 大多数应用程序应该只需调用一次OkHttpClient(),并配置它的缓存,之后只需要调用这个实例即可。 否则,两个缓存实例会互相干扰,破坏响应缓存,并可能导致应用程序崩溃。

        private final OkHttpClient client;
    
        public CacheResponse(File cacheDirectory) throws Exception {
          //设置缓存上限为10M
          int cacheSize = 10 * 1024 * 1024;
          Cache cache = new Cache(cacheDirectory, cacheSize);
    
          //new OkHttpClient只实例化一次,避免多个缓存实例互相干扰
          client = new OkHttpClient.Builder()
              .cache(cache)
              .build();
        }
    
        public void run() throws Exception {
          Request request = new Request.Builder()
              .url("http://publicobject.com/helloworld.txt")
              .build();
    
          Response response1 = client.newCall(request).execute();
          if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
    
          String response1Body = response1.body().string();
          System.out.println("Response 1 response:          " + response1);
          //对于同一个url地址,第一次获得缓存数据 为null
          System.out.println("Response 1 cache response:    " + response1.cacheResponse());
          //对于同一个url地址,第一次获得请求数据 不为null
          System.out.println("Response 1 network response:  " + response1.networkResponse());
    
          Response response2 = client.newCall(request).execute();
          if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
    
          String response2Body = response2.body().string();
          System.out.println("Response 2 response:          " + response2);
          //对于同一个url地址,第二次获得缓存数据 不为null
          System.out.println("Response 2 cache response:    " + response2.cacheResponse());
          //对于同一个url地址,第二次获得请求数据 为null
          System.out.println("Response 2 network response:  " + response2.networkResponse());
    
          System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
        }
    

    ※ 注:如果想让某次请求禁用缓存,可以调用request.cacheControl(CacheControl.FORCE_NETWORK)方法,这样即便缓存目录有对应的缓存,也会忽略缓存,强制发送网络请求,这对于获取最新的响应结果很有用。如果想强制某次请求使用缓存的结果,可以调用request.cacheControl(CacheControl.FORCE_CACHE),这样不会发送实际的网络请求,而是读取缓存,即便缓存数据过期了,也会强制使用该缓存作为响应数据,如果缓存不存在,那么就返回504 Unsatisfiable Request错误。

    ●取消请求Call
    当请求不再需要的时候,我们应该中止请求,比如退出当前的Activity了,那么在Activity中发出的请求应该被中止。可以通过调用Call的cancel方法立即中止请求,如果线程正在写入Request或读取Response,那么会抛出IOException异常。使用这个api可以节约网络资源。同步请求和异步请求都可以被取消。

        private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        private final OkHttpClient client = new OkHttpClient();
    
        public void run() throws Exception {
          Request request = new Request.Builder()
              .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.服务器端会有两秒的延时
              .build();
    
          final long startNanos = System.nanoTime();
          final Call call = client.newCall(request);
    
          // Schedule a job to cancel the call in 1 second.
          //客户端发出请求1秒之后,请求还未完成,这时候通过cancel方法中止了Call,请求中断,并触发IOException异常
          executor.schedule(new Runnable() {
            @Override public void run() {
              System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
              call.cancel();
              System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
            }
          }, 1, TimeUnit.SECONDS);
    
          try {
            System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
            Response response = call.execute();
            System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
                (System.nanoTime() - startNanos) / 1e9f, response);
          } catch (IOException e) {
            System.out.printf("%.2f Call failed as expected: %s%n",
                (System.nanoTime() - startNanos) / 1e9f, e);
          }
        }
    

    ●设置超时
    没有响应时使用超时结束call。没有响应的原因可能是客户点链接问题、服务器可用性问题或者这之间的其他东西。OkHttp支持连接,读取和写入超时。

          private final OkHttpClient client;
    
          public ConfigureTimeouts() throws Exception {
            //connectTimeout方法设置客户端和服务器建立连接的超时时间
            //writeTimeout方法设置客户端上传数据到服务器的超时时间
            //readTimeout方法设置客户端从服务器下载响应数据的超时时间
            client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .build();
          }
    
          public void run() throws Exception {
            Request request = new Request.Builder()
                .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
                .build();
    
            Response response = client.newCall(request).execute();
            System.out.println("Response completed: " + response);
          }
    

    实例:Gson处理复杂JSON字符串,并获取天气情况
    1 . 在使用免费的天气预报api,这里用的是mob平台提供的,有需要的直接在下面链接注册使用,这里不再过多赘述。
    http://api.mob.com/#/apiwiki/weather
    2 . mob平台获取天气状况请求方式是get,这里我们使用同步与异步get分别进行请求
    3 . 请求成功,返回的数据是json格式,所以我们使用Gson进行解析,并显示在文本上
    4 . 配置gradle与请求网络权限

    compile files('libs/gson-2.8.0.jar') 
    compile files('libs/okhttp-3.5.0.jar') 
    compile files('libs/okio-1.11.0.jar')
    
    <uses-permission android:name="android.permission.INTERNET"/>
    

    5 . 添加请求天气预报的url地址,这里我们以深圳为例

    //url为请求地址 key=19d6b7c760314 key为注册应用成功后获得 
    private final String url = "http://apicloud.mob.com/v1/weather/query?key=19d6b7c760314&city=深圳";
    

    6 . 初始化Gson与OkHttpClient

    private Gson gson = new Gson(); 
    private OkHttpClient client= new OkHttpClient();
    

    7 . 根据json字符串的复杂程序,定义需要序列化的bean。下图是我获取得到天气预报并打印成字符串的截图


    8 . 根据上图获取的json字符串,要得到future最近四天的天气,这里我们定义三个类。
    CommWeather.java

    public class CommWeather {
    
        private String retCode;
        private List<Results> result;
    
    
        public String getRetCode() {
            return retCode;
        }
    
        public void setRetCode(String retCode) {
            this.retCode = retCode;
        }
    
        public List<Results> getResult() {
            return result;
        }
    
        public void setResult(List<Results> result) {
            this.result = result;
        }
    }
    

    Results.java

    public class Results {
    
        private String city;
        private String sunrise;
        private String sunset;
        private List<Future> future;
    
        public List<Future> getFuture() {
            return future;
        }
    
        public void setFuture(List<Future> future) {
            this.future = future;
        }
    
        public String getCity() {
            return city;
        }
    
        public void setCity(String city) {
            this.city = city;
        }
    
        public String getSunrise() {
            return sunrise;
        }
    
        public void setSunrise(String sunrise) {
            this.sunrise = sunrise;
        }
    
        public String getSunset() {
            return sunset;
        }
    
        public void setSunset(String sunset) {
            this.sunset = sunset;
        }
    }
    

    Future.java

    public class Future {
        private String date;
        private String dayTime;
        private String night;
        private String temperature;
        private String wind;
    
        public String getDate() {
            return date;
        }
    
        public void setDate(String date) {
            this.date = date;
        }
    
        public String getDayTime() {
            return dayTime;
        }
    
        public void setDayTime(String dayTime) {
            this.dayTime = dayTime;
        }
    
        public String getNight() {
            return night;
        }
    
        public void setNight(String night) {
            this.night = night;
        }
    
        public String getTemperature() {
            return temperature;
        }
    
        public void setTemperature(String temperature) {
            this.temperature = temperature;
        }
    
        public String getWind() {
            return wind;
        }
    
        public void setWind(String wind) {
            this.wind = wind;
        }
    
    }
    

    9.使用okhttp同步/异步get获取得到天气预报的json数据,并进行解析,并显示在UI上。这里我们看下MainActivity中异步操作并解析的主要代码:

        private void getWeatherAsync() {
            //CacheControl.FORCE_NETWORK 不进行缓存
            Request request = new Request.Builder().url(url).cacheControl(CacheControl.FORCE_NETWORK).build();
    
            //enqueue方法调用异步请求网络,该方法接收一个okhttp3.Callback对象,
            // 且不会阻塞当前线程,会新开一个工作线程,让实际的网络请求在工作线程中执行。
            //当异步请求成功后,会回调Callback对象的onResponse方法,在该方法中可以获取Response对象。
            // 当异步请求失败或者调用了Call对象的cancel方法时,会回调Callback对象的onFailure方法。
            // onResponse和onFailure这两个方法都是在工作线程中执行的。
            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Log.e("asd", "okHttp is request error");
                    Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    Log.e("asd", "okHttp is request success");
                    //获取服务器返回的json字符串
                    String responseString = response.body().string();
                    Log.e("asd", "responseString: " + responseString);
                    //使用Gson解析json字符串
                    CommWeather commWeather = gson.fromJson(responseString, CommWeather.class);
    
                    //retCode==200 请求成功
                    if (commWeather.getRetCode().equals("200")) {
                        List<Results> listResult = commWeather.getResult();
                        //根据mob的api文档可以确定,result始终size为1
                        for (int i = 0; i < listResult.size(); i++) {
                            final String city = listResult.get(i).getCity();
                            final String sunrise = listResult.get(i).getSunrise();
                            final String sunset = listResult.get(i).getSunset();
                            //在主线程中修改UI
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    tvCity.setTextColor(android.graphics.Color.RED);
                                    tvCity.setText("Aysnc异步获得的"+city + " 的日出时间: " + sunrise + " 和日落时间" + sunset);
                                }
                            });
    
                            //这里我们只需要获取最近四天的天气
                            final List<Future> listFuture = listResult.get(i).getFuture();
                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            //修改UI操作
                                        }
                                    });
                        }
                    }
                }
            });
        }
    

    最后看下效果图


    结尾
    参考:
    https://github.com/square/okhttp/wiki/Recipes

    CSDN下载(内含最新jar包): http://download.csdn.net/detail/donkor_/9710663

    关于我

    相关文章

      网友评论

        本文标题:OkHttp3-Android网络请求框架常用用法介绍与实例(m

        本文链接:https://www.haomeiwen.com/subject/rngzvttx.html