1. 程式人生 > >從零開始學習OkHttp

從零開始學習OkHttp

這裡寫圖片描述

前言:OkHttp從零開始學習,首先是來自OkHttpClient文件註釋的簡單翻譯,簡單瞭解下注意事項和用法

1、 Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their
responses.

想要傳送和接收http請求,需要call這個類

2、OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for
all of your HTTP calls. This is because each client holds its own connection pool and thread
pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a
client for each request wastes resources on idle pools.

okhttp的最好方式是建立一個單例模式,並且重複使用它來進行http請求。因為每一個http客戶端擁有自己連線池和執行緒池。重複利用連線池和執行緒池可以減少延遲和記憶體。

3 Use {@code new OkHttpClient()} to create a shared instance with the default settings:
*

   {@code 
*
* // The singleton HTTP client.
* public final OkHttpClient client = new OkHttpClient();
* }

*
*

Or use {@code new OkHttpClient.Builder()} to create a shared instance with custom settings:
*

   {@code 
*
* // The singleton HTTP client.
* public final OkHttpClient client = new OkHttpClient.Builder()
* .addInterceptor(new HttpLoggingInterceptor())
* .cache(new Cache(cacheDir, cacheSize))
* .build();
* }

建立一個http客戶端例項,可以使用預設配置也可以自定義配置,比如快取和攔截器

4、 This example shows a call with a short 500 millisecond timeout:

   {@code 
*
* OkHttpClient eagerClient = client.newBuilder()
* .readTimeout(500, TimeUnit.MILLISECONDS)
* .build();
* Response response = eagerClient.newCall(request).execute();
* }

一個簡單的http訪問的例子,設定了讀取失效時間。

通過簡單的閱讀註釋,可以發現OkHttpClient有執行緒池和連線池,最好使用單例模式,所以我們肯定要對其進行封裝,不要像以前apach的HttpClient一樣,每一個請求都new一個client物件,而且我們可以配置自己響應超時時間、快取、攔截器等等配置。

好了,從零開始進行一下功能的實現:

  1. 單例一個OkHttpClient
  2. 簡單的Get請求
  3. 簡單的Post請求
  4. 快取實現
  5. seesion保持
  6. 檔案上傳

    引入 compile ‘com.squareup.okhttp3:okhttp:3.4.2’

    一、實現一個單例

    public class OkHttpUtil {
    //讀超時長,單位:毫秒
    public static final int READ_TIME_OUT = 7676;
    //連線時長,單位:毫秒
    public static final int CONNECT_TIME_OUT = 7676;

    private static OkHttpUtil okHttpUtil;
    private static OkHttpClient okHttpClient;

    private OkHttpUtil() {
    okHttpClient = new OkHttpClient();
    }
    public static OkHttpUtil getInstance() {
    if (null == okHttpUtil) {
    synchronized (OkHttpUtil.class) {
    if (null == okHttpUtil) {
    okHttpUtil = new OkHttpUtil();
    }
    }
    }
    return okHttpUtil;
    }

    public void doGet(String url) {

    }
    }

二、get請求,傳入url,返回call,呼叫者拿到call可以進去取消:call.cancel();

  public Call doGetBackCall(String url,  Callback callback) {
    Request request = new Request.Builder().url(url).build();

    Call call = okHttpClient.newCall(request);
    call.enqueue(callback);
    return call;
}

三、post請求,以表單的形式

 public Call doPostBackCall(HashMap<String, String> params, String url,  Callback callback) {
    FormBody.Builder formBodybuilder = new FormBody.Builder();
    Iterator iter = params.entrySet().iterator();
    while (iter.hasNext()) {
        Map.Entry<String, String> entry = (Map.Entry<String, String>) iter.next();
        String key = entry.getKey();
        String val = entry.getValue();
        formBodybuilder.add(key, val);
    }
    RequestBody requestBody = formBodybuilder.build();
    Request request = new Request.Builder().url(url).post(requestBody).build();
    Call call = okHttpClient.newCall(request);

    call.enqueue(callback);
    return call;
}

呼叫:

  public void doPost(){
    HashMap<String,String> map= new HashMap<>();
    map.put("username","lmj");
    map.put("pwd","1234556");
    try {
        OkHttpUtil.getInstance().doPostBackCall(map,"http://192.168.3.171:8080/LmjWeb2/test", new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i("aaa", "載入失敗:");
                runOnUiThread(
                        new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(MainActivity.this, "載入失敗:",Toast.LENGTH_SHORT).show();
                            }
                        }
                );
            }
            @Override
            public void onResponse(Call call,  Response response) throws IOException {
                final String str = response.body().string();
                Log.i("aaa", "載入成功:" + str);
                runOnUiThread(
                        new Runnable() {
                            @Override
                            public void run() {

                                Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();
                            }
                        }
                );
            }
        });

    } catch (Exception e) {
        e.printStackTrace();
    }
}

四、快取的實現,我現在測試出來的是有網路進去網路請求,不管請求失敗還是成功都不會使用快取,在沒有網路的時候根據需要判斷要不要使用快取,並且這個快取時間我還控制不了。。。求大神

(1)首先是實現一個攔截器

  private static final long CACHE_STALE_SEC = 60 * 1;
private static final Interceptor mRewriteCacheControlInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        String cacheControl = request.cacheControl().toString();//根據請求的header,來判斷要不要使用快取,retrofit可
        //以輕鬆實現在請求上加header
        if (!NetWorkUtils.isNetConnected(BaseApplication.getAppContext())) {
            request = request.newBuilder()
                    .cacheControl(TextUtils.isEmpty(cacheControl) ? CacheControl.FORCE_NETWORK : CacheControl.FORCE_CACHE)
                    .build();
        }
        Response originalResponse = chain.proceed(request);
        if (NetWorkUtils.isNetConnected(BaseApplication.getAppContext())) {
            //有網的時候讀介面上的@Headers裡的配置,你可以在這裡進行統一的設定
            return originalResponse.newBuilder()
                    .header("Cache-Control", cacheControl)
                    .removeHeader("Pragma")
                    .build();
        } else {
            return originalResponse.newBuilder()
                    .header("Cache-Control", "public, only-if-cached, max-stale=" + CACHE_STALE_SEC)
                    .removeHeader("Pragma")
                    .build();
        }
    }
};

(2)配置我們的OkHttpClient客戶端

   cacheFile = new File(BaseApplication.getAppContext().getCacheDir(), "cache");
    cache = new Cache(cacheFile, 1024 * 1024 * 10); //10Mb
 okHttpClient = new OkHttpClient.Builder()
            .readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS)
            .connectTimeout(CONNECT_TIME_OUT, TimeUnit.MILLISECONDS)
            .addInterceptor(mRewriteCacheControlInterceptor)
            .addNetworkInterceptor(mRewriteCacheControlInterceptor)
            .cache(cache)
            .build();

(3)改造一下get請求,新增設定快取的header

public Call doGetBackCall(String url,int cacheTime , final Callback callback) {
    Request request = new Request.Builder().url(url)
    .addHeader("Cache-Control",cacheTime<1?"":"max-stale="+cacheTime).build();

    Call call = okHttpClient.newCall(request);
    call.enqueue(callback);

    return call;
}

五、sesson的保持:在很多時候需要客戶端本地儲存session值,然後在每個介面上面傳送給伺服器,所以我們要在返回中拿到sesseion,儲存session,在每個請求頭中新增session。這裡我們只需要實現攔截器就可以了,然後給單例的客戶端配置好這個攔截器。

 private String sessionID = "";
private Interceptor receivedCookiesInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Response originalResponse = chain.proceed(chain.request());

        if (!originalResponse.headers("Set-Cookie").isEmpty()) {
            HashSet<String> cookies = new HashSet<>();

            for (String header : originalResponse.headers("Set-Cookie")) {
                Log.i("aaa", "session:" + header);
                sessionID = header;
                cookies.add(header);
            }
        }

        return originalResponse;
    }
};
private Interceptor addCookiesInterceptor = new Interceptor() {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request.Builder builder = chain.request().newBuilder();
        builder.addHeader("Cookie", sessionID);

        return chain.proceed(builder.build());
    }
};
--給原來的client新增攔截器
okHttpClient = new OkHttpClient.Builder()
            .readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS)
            .connectTimeout(CONNECT_TIME_OUT, TimeUnit.MILLISECONDS)
            .addInterceptor(addCookiesInterceptor)
            .addInterceptor(receivedCookiesInterceptor)
            .addInterceptor(mRewriteCacheControlInterceptor)
            .addNetworkInterceptor(mRewriteCacheControlInterceptor)
            .cache(cache)
            .build();

六、對於http檔案上傳,其實就是post請求了,只是請求body的問題,在請求的時候可以在body裡面放置表單、json資料、file檔案等等。直接貼上hongyang大神程式碼了,因為這個屬於http協議部分了。。。

File file = new File(Environment.getExternalStorageDirectory(), "balabala.mp4");

RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);

RequestBody requestBody = new MultipartBuilder()
 .type(MultipartBuilder.FORM)
 .addPart(Headers.of(
      "Content-Disposition", 
          "form-data; name=\"username\""), 
      RequestBody.create(null, "張鴻洋"))
 .addPart(Headers.of(
     "Content-Disposition", 
     "form-data; name=\"mFile\"; 
     filename=\"wjd.mp4\""), fileBody)
 .build();

Request request = new Request.Builder()
.url("http://192.168.1.103:8080/okHttpServer/fileUpload")
.post(requestBody)
.build();

Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback()
{
//...
});

注意攔截器
應用攔截器

不需要關心像重定向和重試這樣的中間響應。
總是呼叫一次,即使HTTP響應從快取中獲取服務。
監視應用原始意圖。不關心OkHttp注入的像If-None-Match頭。
允許短路並不呼叫Chain.proceed()。
允許重試並執行多個Chain.proceed()呼叫。
網路攔截器

可以操作像重定向和重試這樣的中間響應。
對於短路網路的快取響應不會呼叫。
監視即將要通過網路傳輸的資料。
訪問運輸請求的Connection。

在網路請求結果response在攔截器被讀取,會導致回撥時候流被關閉了讀取不到資料
只有用通過peekBody()或者
MediaType contentType = null;
String bodyString = null;
if (originalResponse.body() != null) {
contentType = originalResponse.body().contentType();
bodyString = originalResponse.body().string();
Log.i(“AAA”,bodyString);
}
okhttp3.ResponseBody body = okhttp3.ResponseBody.create(contentType, bodyString);
return originalResponse.newBuilder().body(body).build();

原始碼下載