終於找到了一篇一看就懂的 OKHttp 原理解析
一、概述
最近在群裡聽到各種討論okhttp的話題,可見okhttp的口碑相當好了。再加上Google貌似在6.0版本里面刪除了HttpClient相關API,對於這個行為不做評價。為了更好的在應對網路訪問,學習下okhttp還是蠻必要的,本篇部落格首先介紹okhttp的簡單使用,主要包含:
-
一般的get請求
-
一般的post請求
-
基於Http的檔案上傳
-
檔案下載
-
載入圖片
使用前,對於Android Studio的使用者,可以選擇新增
compile 'com.squareup.okhttp:okhttp:2.4.0'
或者Eclipse的使用者,可以下載最新的jar
注意:okhttp內部依賴okio,別忘了同時匯入okio:
gradle: compile 'com.squareup.okio:okio:1.5.0'
二、使用教程
(一)Http Get
對了網路載入庫,那麼最常見的肯定就是http get請求了,比如獲取一個網頁的內容。
//建立okHttpClient物件 OkHttpClient mOkHttpClient = new OkHttpClient(); //建立一個Request final Request request = new Request.Builder() .url("https://github.com/hongyangAndroid") .build(); //new call Call call = mOkHttpClient.newCall(request); //請求加入排程 call.enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { } @Override public void onResponse(final Response response) throws IOException { //String htmlStr = response.body().string(); } });
-
以上就是傳送一個get請求的步驟,首先構造一個Request物件,引數最起碼有個url,當然你可以通過Request.Builder設定更多的引數比如:header、method等。
-
然後通過request的物件去構造得到一個Call物件,類似於將你的請求封裝成了任務,既然是任務,就會有execute()和cancel()等方法。
-
最後,我們希望以非同步的方式去執行請求,所以我們呼叫的是call.enqueue,將call加入排程佇列,然後等待任務執行完成,我們在Callback中即可得到結果。
看到這,你會發現,整體的寫法還是比較長的,所以封裝肯定是要做的,不然每個請求這麼寫,得累死。
ok,需要注意幾點:
-
onResponse回撥的引數是response,一般情況下,比如我們希望獲得返回的字串,可以通過response.body().string()獲取;如果希望獲得返回的二進位制位元組陣列,則呼叫response.body().bytes();如果你想拿到返回的inputStream,則呼叫response.body().byteStream()
看到這,你可能會奇怪,竟然還能拿到返回的inputStream,看到這個最起碼能意識到一點,這裡支援大檔案下載,有inputStream我們就可以通過IO的方式寫檔案。不過也說明一個問題,這個onResponse執行的執行緒並不是UI執行緒。的確是的,如果你希望操作控制元件,還是需要使用handler等,例如:
-
@Override public void onResponse(final Response response) throws IOException { final String res = response.body().string(); runOnUiThread(new Runnable() { @Override public void run() { mTv.setText(res); } }); }
-
我們這裡是非同步的方式去執行,當然也支援阻塞的方式,上面我們也說了Call有一個execute()方法,你也可以直接呼叫call.execute()通過返回一個Response。
(二) Http Post 攜帶引數
看來上面的簡單的get請求,基本上整個的用法也就掌握了,比如post攜帶引數,也僅僅是Request的構造的不同。
Request request = buildMultipartFormRequest(
url, new File[]{file}, new String[]{fileKey}, null);
FormEncodingBuilder builder = new FormEncodingBuilder();
builder.add("username","張鴻洋");
Request request = new Request.Builder()
.url(url)
.post(builder.build())
.build();
mOkHttpClient.newCall(request).enqueue(new Callback(){});
大家都清楚,post的時候,引數是包含在請求體中的;所以我們通過FormEncodingBuilder。新增多個String鍵值對,然後去構造RequestBody,最後完成我們Request的構造。
後面的就和上面一樣了。
(三)基於Http的檔案上傳
接下來我們在介紹一個可以構造RequestBody的Builder,叫做MultipartBuilder。當我們需要做類似於表單上傳的時候,就可以使用它來構造我們的requestBody。
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()
{
//...
});
上述程式碼向伺服器傳遞了一個鍵值對username:張鴻洋和一個檔案。我們通過MultipartBuilder的addPart方法可以新增鍵值對或者檔案。
ok,對於我們最開始的目錄還剩下圖片下載,檔案下載;這兩個一個是通過回撥的Response拿到byte[]然後decode成圖片;檔案下載,就是拿到inputStream做寫檔案操作,我們這裡就不贅述了。
接下來我們主要看如何封裝上述的程式碼。
三、封裝
由於按照上述的程式碼,寫多個請求肯定包含大量的重複程式碼,所以我希望封裝後的程式碼呼叫是這樣的:
(一)使用
-
一般的get請求
-
OkHttpClientManager.getAsyn("https://github.com/hongyangAndroid", new OkHttpClientManager.StringCallback() { @Override public void onFailure(Request request, IOException e) { e.printStackTrace(); } @Override public void onResponse(String bytes) { mTv.setText(bytes);//注意這裡是UI執行緒回撥,可以直接操作控制元件 } });
對於一般的請求,我們希望給個url,然後CallBack裡面直接操作控制元件。
-
檔案上傳且攜帶引數
我們希望提供一個方法,傳入url,params,file,callback即可。
OkHttpClientManager.postAsyn("http://192.168.1.103:8080/okHttpServer/fileUpload",//
new OkHttpClientManager.StringCallback()
{
@Override
public void onFailure(Request request, IOException e)
{
e.printStackTrace();
}
@Override
public void onResponse(String result)
{
}
},//
file,//
"mFile",//
new OkHttpClientManager.Param[]{
new OkHttpClientManager.Param("username", "zhy"),
new OkHttpClientManager.Param("password", "123")}
);
鍵值對沒什麼說的,引數3為file,引數4為file對應的name,這個name不是檔案的名字;
對應於http中的
<input type="file" name="mFile" >
對應的是name後面的值,即mFile.
-
檔案下載
對於檔案下載,提供url,目標dir,callback即可。
OkHttpClientManager.downloadAsyn(
"http://192.168.1.103:8080/okHttpServer/files/messenger_01.png",
Environment.getExternalStorageDirectory().getAbsolutePath(),
new OkHttpClientManager.StringCallback()
{
@Override
public void onFailure(Request request, IOException e)
{
}
@Override
public void onResponse(String response)
{
//檔案下載成功,這裡回撥的reponse為檔案的absolutePath
}
});
-
展示圖片
展示圖片,我們希望提供一個url和一個imageview,如果下載成功,直接幫我們設定上即可。
OkHttpClientManager.displayImage(mImageView, "http://images.csdn.net/20150817/1.jpg");
內部會自動根據imageview的大小自動對圖片進行合適的壓縮。雖然,這裡可能不適合一次性載入大量圖片的場景,但是對於app中偶爾有幾個圖片的載入,還是可用的。
(二)原始碼
ok,基本介紹完了,對於封裝的程式碼其實也很簡單,我就直接貼出來了,因為也沒什麼好介紹的,如果你看完上面的用法,肯定可以看懂:
package com.zhy.utils.http.okhttp;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.Map;
import java.util.Set;
/**
* Created by zhy on 15/8/17.
*/
public class OkHttpClientManager
{
private static OkHttpClientManager mInstance;
private OkHttpClient mOkHttpClient;
private Handler mDelivery;
private static final String TAG = "OkHttpClientManager";
private OkHttpClientManager()
{
mOkHttpClient = new OkHttpClient();
mDelivery = new Handler(Looper.getMainLooper());
}
public static OkHttpClientManager getInstance()
{
if (mInstance == null)
{
synchronized (OkHttpClientManager.class)
{
if (mInstance == null)
{
mInstance = new OkHttpClientManager();
}
}
}
return mInstance;
}
/**
* 同步的Get請求
*
* @param url
* @return Response
*/
private Response _getAsyn(String url) throws IOException
{
final Request request = new Request.Builder()
.url(url)
.build();
Call call = mOkHttpClient.newCall(request);
Response execute = call.execute();
return execute;
}
/**
* 同步的Get請求
*
* @param url
* @return 字串
*/
private String _getAsString(String url) throws IOException
{
Response execute = _getAsyn(url);
return execute.body().string();
}
/**
* 非同步的get請求
*
* @param url
* @param callback
*/
private void _getAsyn(String url, final StringCallback callback)
{
final Request request = new Request.Builder()
.url(url)
.build();
deliveryResult(callback, request);
}
/**
* 同步的Post請求
*
* @param url
* @param params post的引數
* @return
*/
private Response _post(String url, Param... params) throws IOException
{
Request request = buildPostRequest(url, params);
Response response = mOkHttpClient.newCall(request).execute();
return response;
}
/**
* 同步的Post請求
*
* @param url
* @param params post的引數
* @return 字串
*/
private String _postAsString(String url, Param... params) throws IOException
{
Response response = _post(url, params);
return response.body().string();
}
/**
* 非同步的post請求
*
* @param url
* @param callback
* @param params
*/
private void _postAsyn(String url, final StringCallback callback, Param... params)
{
Request request = buildPostRequest(url, params);
deliveryResult(callback, request);
}
/**
* 非同步的post請求
*
* @param url
* @param callback
* @param params
*/
private void _postAsyn(String url, final StringCallback callback, Map<String, String> params)
{
Param[] paramsArr = map2Params(params);
Request request = buildPostRequest(url, paramsArr);
deliveryResult(callback, request);
}
/**
* 同步基於post的檔案上傳
*
* @param params
* @return
*/
private Response _post(String url, File[] files, String[] fileKeys, Param... params) throws IOException
{
Request request = buildMultipartFormRequest(url, files, fileKeys, params);
return mOkHttpClient.newCall(request).execute();
}
private Response _post(String url, File file, String fileKey) throws IOException
{
Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, null);
return mOkHttpClient.newCall(request).execute();
}
private Response _post(String url, File file, String fileKey, Param... params) throws IOException
{
Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, params);
return mOkHttpClient.newCall(request).execute();
}
/**
* 非同步基於post的檔案上傳
*
* @param url
* @param callback
* @param files
* @param fileKeys
* @throws IOException
*/
private void _postAsyn(String url, StringCallback callback, File[] files, String[] fileKeys, Param... params) throws IOException
{
Request request = buildMultipartFormRequest(url, files, fileKeys, params);
deliveryResult(callback, request);
}
/**
* 非同步基於post的檔案上傳,單檔案不帶引數上傳
*
* @param url
* @param callback
* @param file
* @param fileKey
* @throws IOException
*/
private void _postAsyn(String url, StringCallback callback, File file, String fileKey) throws IOException
{
Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, null);
deliveryResult(callback, request);
}
/**
* 非同步基於post的檔案上傳,單檔案且攜帶其他form引數上傳
*
* @param url
* @param callback
* @param file
* @param fileKey
* @param params
* @throws IOException
*/
private void _postAsyn(String url, StringCallback callback, File file, String fileKey, Param... params) throws IOException
{
Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, params);
deliveryResult(callback, request);
}
/**
* 非同步下載檔案
*
* @param url
* @param destFileDir 本地檔案儲存的資料夾
* @param callback
*/
private void _downloadAsyn(final String url, final String destFileDir, final StringCallback callback)
{
final Request request = new Request.Builder()
.url(url)
.build();
final Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback()
{
@Override
public void onFailure(final Request request, final IOException e)
{
sendFailedStringCallback(request, e, callback);
}
@Override
public void onResponse(Response response)
{
InputStream is = null;
byte[] buf = new byte[2048];
int len = 0;
FileOutputStream fos = null;
try
{
is = response.body().byteStream();
File file = new File(destFileDir, getFileName(url));
fos = new FileOutputStream(file);
while ((len = is.read(buf)) != -1)
{
fos.write(buf, 0, len);
}
fos.flush();
//如果下載檔案成功,第一個引數為檔案的絕對路徑
sendSuccessStringCallback(file.getAbsolutePath(), callback);
} catch (IOException e)
{
sendFailedStringCallback(response.request(), e, callback);
} finally
{
try
{
if (is != null) is.close();
} catch (IOException e)
{
}
try
{
if (fos != null) fos.close();
} catch (IOException e)
{
}
}
}
});
}
private String getFileName(String path)
{
int separatorIndex = path.lastIndexOf("/");
return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
}
/**
* 載入圖片
*
* @param view
* @param url
* @throws IOException
*/
private void _displayImage(final ImageView view, final String url, final int errorResId)
{
final Request request = new Request.Builder()
.url(url)
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback()
{
@Override
public void onFailure(Request request, IOException e)
{
setErrorResId(view, errorResId);
}
@Override
public void onResponse(Response response)
{
InputStream is = null;
try
{
is = response.body().byteStream();
ImageUtils.ImageSize actualImageSize = ImageUtils.getImageSize(is);
ImageUtils.ImageSize imageViewSize = ImageUtils.getImageViewSize(view);
int inSampleSize = ImageUtils.calculateInSampleSize(actualImageSize, imageViewSize);
try
{
is.reset();
} catch (IOException e)
{
response = _getAsyn(url);
is = response.body().byteStream();
}
BitmapFactory.Options ops = new BitmapFactory.Options();
ops.inJustDecodeBounds = false;
ops.inSampleSize = inSampleSize;
final Bitmap bm = BitmapFactory.decodeStream(is, null, ops);
mDelivery.post(new Runnable()
{
@Override
public void run()
{
view.setImageBitmap(bm);
}
});
} catch (Exception e)
{
setErrorResId(view, errorResId);
} finally
{
if (is != null) try
{
is.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
});
}
private void setErrorResId(final ImageView view, final int errorResId)
{
mDelivery.post(new Runnable()
{
@Override
public void run()
{
view.setImageResource(errorResId);
}
});
}
//*************對外公佈的方法************
public static Response getAsyn(String url) throws IOException
{
return getInstance()._getAsyn(url);
}
public static String getAsString(String url) throws IOException
{
return getInstance()._getAsString(url);
}
public static void getAsyn(String url, StringCallback callback)
{
getInstance()._getAsyn(url, callback);
}
public static Response post(String url, Param... params) throws IOException
{
return getInstance()._post(url, params);
}
public static String postAsString(String url, Param... params) throws IOException
{
return getInstance()._postAsString(url, params);
}
public static void postAsyn(String url, final StringCallback callback, Param... params)
{
getInstance()._postAsyn(url, callback, params);
}
public static void postAsyn(String url, final StringCallback callback, Map<String, String> params)
{
getInstance()._postAsyn(url, callback, params);
}
public static Response post(String url, File[] files, String[] fileKeys, Param... params) throws IOException
{
return getInstance()._post(url, files, fileKeys, params);
}
public static Response post(String url, File file, String fileKey) throws IOException
{
return getInstance()._post(url, file, fileKey);
}
public static Response post(String url, File file, String fileKey, Param... params) throws IOException
{
return getInstance()._post(url, file, fileKey, params);
}
public static void postAsyn(String url, StringCallback callback, File[] files, String[] fileKeys, Param... params) throws IOException
{
getInstance()._postAsyn(url, callback, files, fileKeys, params);
}
public static void postAsyn(String url, StringCallback callback, File file, String fileKey) throws IOException
{
getInstance()._postAsyn(url, callback, file, fileKey);
}
public static void postAsyn(String url, StringCallback callback, File file, String fileKey, Param... params) throws IOException
{
getInstance()._postAsyn(url, callback, file, fileKey, params);
}
public static void displayImage(final ImageView view, String url, int errorResId) throws IOException
{
getInstance()._displayImage(view, url, errorResId);
}
public static void displayImage(final ImageView view, String url)
{
getInstance()._displayImage(view, url, -1);
}
public static void downloadAsyn(String url, String destDir, StringCallback callback)
{
getInstance()._downloadAsyn(url, destDir, callback);
}
//****************************
private Request buildMultipartFormRequest(String url, File[] files,
String[] fileKeys, Param[] params)
{
params = validateParam(params);
MultipartBuilder builder = new MultipartBuilder()
.type(MultipartBuilder.FORM);
for (Param param : params)
{
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + param.key + "\""),
RequestBody.create(null, param.value));
}
if (files != null)
{
RequestBody fileBody = null;
for (int i = 0; i < files.length; i++)
{
File file = files[i];
String fileName = file.getName();
fileBody = RequestBody.create(MediaType.parse(guessMimeType(fileName)), file);
//TODO 根據檔名設定contentType
builder.addPart(Headers.of("Content-Disposition",
"form-data; name=\"" + fileKeys[i] + "\"; filename=\"" + fileName + "\""),
fileBody);
}
}
RequestBody requestBody = builder.build();
return new Request.Builder()
.url(url)
.post(requestBody)
.build();
}
private String guessMimeType(String path)
{
FileNameMap fileNameMap = URLConnection.getFileNameMap();
String contentTypeFor = fileNameMap.getContentTypeFor(path);
if (contentTypeFor == null)
{
contentTypeFor = "application/octet-stream";
}
return contentTypeFor;
}
private Param[] validateParam(Param[] params)
{
if (params == null)
return new Param[0];
else return params;
}
private Param[] map2Params(Map<String, String> params)
{
if (params == null) return new Param[0];
int size = params.size();
Param[] res = new Param[size];
Set<Map.Entry<String, String>> entries = params.entrySet();
int i = 0;
for (Map.Entry<String, String> entry : entries)
{
res[i++] = new Param(entry.getKey(), entry.getValue());
}
return res;
}
private void deliveryResult(final StringCallback callback, Request request)
{
mOkHttpClient.newCall(request).enqueue(new Callback()
{
@Override
public void onFailure(final Request request, final IOException e)
{
sendFailedStringCallback(request, e, callback);
}
@Override
public void onResponse(final Response response)
{
try
{
final String string = response.body().string();
sendSuccessStringCallback(string, callback);
} catch (IOException e)
{
sendFailedStringCallback(response.request(), e, callback);
}
}
});
}
private void sendFailedStringCallback(final Request request, final IOException e, final StringCallback callback)
{
mDelivery.post(new Runnable()
{
@Override
public void run()
{
if (callback != null)
callback.onFailure(request, e);
}
});
}
private void sendSuccessStringCallback(final String string, final StringCallback callback)
{
mDelivery.post(new Runnable()
{
@Override
public void run()
{
if (callback != null)
callback.onResponse(string);
}
});
}
private Request buildPostRequest(String url, Param[] params)
{
if (params == null)
{
params = new Param[0];
}
FormEncodingBuilder builder = new FormEncodingBuilder();
for (Param param : params)
{
builder.add(param.key, param.value);
}
RequestBody requestBody = builder.build();
return new Request.Builder()
.url(url)
.post(requestBody)
.build();
}
public interface StringCallback
{
void onFailure(Request request, IOException e);
void onResponse(String response);
}
public static class Param
{
public Param()
{
}
public Param(String key, String value)
{
this.key = key;
this.value = value;
}
String key;
String value;
}
}
ok ,最後一段程式碼好長,建議下載原始碼檢視~~