okhttp是什么?简而言之就是一款优秀的网络框架。
能实现的功能?Get、Post请求,文件上传和下载等等….
从基本功能的调用,看看一篇关于okhttp的封装:
// http Get操作
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
//http Post提交Json数据
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
由于okhttp要在子线程中进行操作,所以要对其进行封装,在github搜索okhttp回看到很多封装库,一个事鸿洋的okhttputils,另一个叫okhttp-OkGo,两个star都在3000以上以上了,可见都很好,可以看一下,依赖指定的jar包,或着clone下来,代码里可以看到几种功能的调用。
先看,前两天我看到的一个okhttp的封装工具:
/**
* Description : OkHttp网络连接封装工具类
* Author : lauren
* Email : lauren.liuling@gmail.com
* Blog : http://www.liuling123.com
* Date : 15/12/17
*/
public class OkHttpUtils {
private static final String TAG = "OkHttpUtils";
private static OkHttpUtils mInstance;
private OkHttpClient mOkHttpClient;
private Handler mDelivery;
private OkHttpUtils() {
mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(10, TimeUnit.SECONDS);
mOkHttpClient.setWriteTimeout(10, TimeUnit.SECONDS);
mOkHttpClient.setReadTimeout(30, TimeUnit.SECONDS);
//cookie enabled
mOkHttpClient.setCookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER));
mDelivery = new Handler(Looper.getMainLooper());
}
private synchronized static OkHttpUtils getmInstance() {
if (mInstance == null) {
mInstance = new OkHttpUtils();
}
return mInstance;
}
private void getRequest(String url, final ResultCallback callback) {
final Request request = new Request.Builder().url(url).build();
deliveryResult(callback, request);
}
private void postRequest(String url, final ResultCallback callback, List<Param> params) {
Request request = buildPostRequest(url, params);
deliveryResult(callback, request);
}
private void deliveryResult(final ResultCallback callback, Request request) {
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Request request, final IOException e) {
sendFailCallback(callback, e);
}
@Override
public void onResponse(Response response) throws IOException {
try {
String str = response.body().string();
if (callback.mType == String.class) {
sendSuccessCallBack(callback, str);
} else {
Object object = JsonUtils.deserialize(str, callback.mType);
sendSuccessCallBack(callback, object);
}
} catch (final Exception e) {
LogUtils.e(TAG, "convert json failure", e);
sendFailCallback(callback, e);
}
}
});
}
private void sendFailCallback(final ResultCallback callback, final Exception e) {
mDelivery.post(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onFailure(e);
}
}
});
}
private void sendSuccessCallBack(final ResultCallback callback, final Object obj) {
mDelivery.post(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onSuccess(obj);
}
}
});
}
private Request buildPostRequest(String url, List<Param> params) {
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();
}
/**********************对外接口************************/
/**
* get请求
* @param url 请求url
* @param callback 请求回调
*/
public static void get(String url, ResultCallback callback) {
getmInstance().getRequest(url, callback);
}
/**
* post请求
* @param url 请求url
* @param callback 请求回调
* @param params 请求参数
*/
public static void post(String url, final ResultCallback callback, List<Param> params) {
getmInstance().postRequest(url, callback, params);
}
/**
* http请求回调类,回调方法在UI线程中执行
* @param <T>
*/
public static abstract class ResultCallback<T> {
Type mType;
public ResultCallback(){
mType = getSuperclassTypeParameter(getClass());
}
static Type getSuperclassTypeParameter(Class<?> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
ParameterizedType parameterized = (ParameterizedType) superclass;
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
/**
* 请求成功回调
* @param response
*/
public abstract void onSuccess(T response);
/**
* 请求失败回调
* @param e
*/
public abstract void onFailure(Exception e);
}
/**
* post请求参数类
*/
public static class Param {
String key;
String value;
public Param() {
}
public Param(String key, String value) {
this.key = key;
this.value = value;
}
}
}
如果不靠工具类,自己去过一遍,因为要在子线程中调用,可能会new一个Thread,启动一个线程,或着来一个asynctask,也方便了更新ui与主线程交互handler了,代码就不写了,刚刚说的很容易实现。由于okhttp提供了一个enqueue这个异步方法,因此省略了,start一个Thread的操作。网络请求成功后,由非主线程–>主线程,需要一个handler.sendMessage()或着handler.post(new Runnable()),runOnUiThread();不在acitivity里或着fragment中,还需要一个接口的监听去返回成功和失败两个方法。成功返回的是结果的值,实体类、String字符串等等。(不看代码我可能还想不出来那么多,哈哈,还是直接分析代码)
private OkHttpUtils() {
//okhttp初始化,配置信息,同时创建一个handler对象
mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(10, TimeUnit.SECONDS);
mOkHttpClient.setWriteTimeout(10, TimeUnit.SECONDS);
mOkHttpClient.setReadTimeout(30, TimeUnit.SECONDS);
//cookie enabled
mOkHttpClient.setCookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER));
mDelivery = new Handler(Looper.getMainLooper());
}
//单例模式,但这样些不太好。
private synchronized static OkHttpUtils getmInstance() {
if (mInstance == null) {
mInstance = new OkHttpUtils();
}
return mInstance;
}
//另一种方式的单例模式
private static OkHttpUtils mInstance=null;
private static OkHttpUtils getmInstance(){
if(mInstance==null){
synchronized(OkHttpUtils.class){
if(mInstance==null){
mInstance=new OkHttpUtils();
}
}
}
return mInstance;
}
Get请求:
/**
* get请求
* @param url 请求url
* @param callback 请求回调
*/
//get方法的调用,在里面对get操作进一步封装,同时指定一个回调
public static void get(String url, ResultCallback callback) {
getmInstance().getRequest(url, callback);
}
//拼接得到request对象,提交网络操作在方法deliveryResult中进行
private void getRequest(String url, final ResultCallback callback) {
final Request request = new Request.Builder().url(url).build();
deliveryResult(callback, request);
}
//实现真正的提交网络操作,在异步中进行enqueue(params)
private void deliveryResult(final ResultCallback callback, Request request) {
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Request request, final IOException e) {
//请求失败,回调自定义接口失败的方法。
sendFailCallback(callback, e);
}
@Override
public void onResponse(Response response) throws IOException {
try {
String str = response.body().string();
if (callback.mType == String.class) {
sendSuccessCallBack(callback, str);
} else {
Object object = JsonUtils.deserialize(str, callback.mType);
//请求成功,回调自定义接口成功的方法。
sendSuccessCallBack(callback, object);
}
} catch (final Exception e) {
LogUtils.e(TAG, "convert json failure", e);
sendFailCallback(callback, e);
}
}
});
}
//对应上面两个回调方法,用handler.post实现与主线程数据交互
private void sendFailCallback(final ResultCallback callback, final Exception e) {
mDelivery.post(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onFailure(e);
}
}
});
}
private void sendSuccessCallBack(final ResultCallback callback, final Object obj) {
mDelivery.post(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onSuccess(obj);
}
}
});
}
看上面代码中的callback这个参数,是在deliveryResult方法中传递的参数,需要我们调用get或者post时,去生成一个callback,看看这个类:
//抽象类,封装了两个方法,当要拿到这个类的引用时,需要复写这两个方法,看下面调用
/**
* http请求回调类,回调方法在UI线程中执行
* @param <T>
*/
public static abstract class ResultCallback<T> {
Type mType;
public ResultCallback(){
mType = getSuperclassTypeParameter(getClass());
}
//通过反射得到想要的返回类型
static Type getSuperclassTypeParameter(Class<?> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
ParameterizedType parameterized = (ParameterizedType) superclass;
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
/**
* 请求成功回调
* @param response
*/
public abstract void onSuccess(T response);
/**
* 请求失败回调
* @param e
*/
public abstract void onFailure(Exception e);
}
//主线程中调用okhttp工具类进行网络请求
//这里就是上面两个方法的回调,使用handler后变为在主线程中
OkHttpUtils.ResultCallback<String> loadNewsCallback = new OkHttpUtils.ResultCallback<String>() {
@Override
public void onSuccess(String response) {
List<NewsBean> newsBeanList = NewsJsonUtils.readJsonNewsBeans(response, getID(type));
listener.onSuccess(newsBeanList);
}
@Override
public void onFailure(Exception e) {
listener.onFailure("load news list failure.", e);
}
};
Post请求:
和Get请求不同的在于requertBody的不同,Post请求要提交一些数据参数之类,另外Post使用起来更安全。
/**
* post请求
* @param url 请求url
* @param callback 请求回调
* @param params 请求参数
*/
//和Get请求一样,去进一步封装
public static void post(String url, final ResultCallback callback, List<Param> params) {
getmInstance().postRequest(url, callback, params);
}
//与get不同的是,多了一个List的参数,这里是用来传递拼接的字段。
private void postRequest(String url, final ResultCallback callback, List<Param> params) {
//调用buildPostRequest方法,去拼接
Request request = buildPostRequest(url, params);
//执行网络操作
deliveryResult(callback, request);
}
//遍历list集合,使用FormEncodingBuilder去add键值对
private Request buildPostRequest(String url, List<Param> params) {
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();
}
/**
* post请求参数类
*/
//List集合中的参数类
public static class Param {
String key;
String value;
public Param() {
}
public Param(String key, String value) {
this.key = key;
this.value = value;
}
}
下面和Get操作一样了,执行网络操作,区别就在于上面的requestbody的不同。同样的调用方法:deliveryResult()
private void deliveryResult(final ResultCallback callback, Request request) {
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Request request, final IOException e) {
sendFailCallback(callback, e);
}
@Override
public void onResponse(Response response) throws IOException {
try {
String str = response.body().string();
if (callback.mType == String.class) {
sendSuccessCallBack(callback, str);
} else {
Object object = JsonUtils.deserialize(str, callback.mType);
sendSuccessCallBack(callback, object);
}
} catch (final Exception e) {
LogUtils.e(TAG, "convert json failure", e);
sendFailCallback(callback, e);
}
}
});
}
实现文件下载和上传:
依赖一个jar包工具类:详细信息点击看这里:https://github.com/jeasonlzy/okhttp-OkGo
compile 'com.lzy.net:okgo:+' //版本号使用 + 可以自动引用最新版
compile 'com.lzy.widget:imagepicker:0.3.2' //这个是一个图片选择器工具类
compile 'com.github.bumptech.glide:glide:3.6.1'
仿微信图片选择imagepicker:https://github.com/jeasonlzy/ImagePicker
文件下载:
/**
* 文件下载
*
* @param view
*/
public void download(View view) {
OkGo.get(Urls.URL_DOWNLOAD)
.tag(this)
.headers("header1", "headerValue1")
.params("paramq", "paramValue1")
.execute(new FileCallback() {
@Override
public void onBefore(BaseRequest request) {
super.onBefore(request);
bt_download.setText("下载中....");
}
@Override
public void onSuccess(File file, Call call, Response response) {
bt_download.setText("下载成功");
tv_download_speed.setText("--");
}
@Override
public void onError(Call call, Response response, Exception e) {
super.onError(call, response, e);
bt_download.setText("下载失败");
tv_download_speed.setText("--");
}
@Override
public void downloadProgress(long currentSize, long totalSize, float progress, long networkSpeed) {
super.downloadProgress(currentSize, totalSize, progress, networkSpeed);
String downloadLength = Formatter.formatFileSize(getApplicationContext(), currentSize);
String totalLength = Formatter.formatFileSize(getApplicationContext(), totalSize);
tv_current_size.setText(downloadLength);
tv_total_size.setText(totalLength);
tv_current_progress.setText((int) (progress * 100) + "%");
String netSpeed = Formatter.formatFileSize(getApplicationContext(), networkSpeed);
tv_download_speed.setText(netSpeed + "/S");
load_progressbar.setProgress((int) (progress * 100));
}
});
}
可以设置多个头部header参数和params参数信息,这里是任意设置的,可以自定义callback,实现依赖jar包中的AbsCallback。AbsCallback里有几个回调方法:代码如下:
public abstract class AbsCallback<T> implements Converter<T> {
/** 请求网络开始前,UI线程 */
public void onBefore(BaseRequest request) {
}
/** 对返回数据进行操作的回调, UI线程 */
public abstract void onSuccess(T t, Call call, Response response);
/** 缓存成功的回调,UI线程 */
public void onCacheSuccess(T t, Call call) {
}
/** 请求失败,响应错误,数据解析错误等,都会回调该方法, UI线程 */
public void onError(Call call, Response response, Exception e) {
}
/** 缓存失败的回调,UI线程 */
public void onCacheError(Call call, Exception e) {
}
/** 网络失败结束之前的回调 */
public void parseError(Call call, Exception e) {
}
/** 请求网络结束后,UI线程 */
public void onAfter(T t, Exception e) {
if (e != null) e.printStackTrace();
}
/**
* Post执行上传过程中的进度回调,get请求不回调,UI线程
*
* @param currentSize 当前上传的字节数
* @param totalSize 总共需要上传的字节数
* @param progress 当前上传的进度
* @param networkSpeed 当前上传的速度 字节/秒
*/
public void upProgress(long currentSize, long totalSize, float progress, long networkSpeed) {
}
/**
* 执行下载过程中的进度回调,UI线程
*
* @param currentSize 当前下载的字节数
* @param totalSize 总共需要下载的字节数
* @param progress 当前下载的进度
* @param networkSpeed 当前下载的速度 字节/秒
*/
public void downloadProgress(long currentSize, long totalSize, float progress, long networkSpeed) {
}
}
这里FileCallback的代码如下:
public abstract class FileCallback extends AbsCallback<File> {
private FileConvert convert; //文件转换类
public FileCallback() {
this(null);
}
public FileCallback(String destFileName) {
this(null, destFileName);
}
public FileCallback(String destFileDir, String destFileName) {
convert = new FileConvert(destFileDir, destFileName);
convert.setCallback(this);
}
@Override
public File convertSuccess(Response response) throws Exception {
File file = convert.convertSuccess(response);
response.close();
return file;
}
}
上传代码如下:
/**
* 文件上传
*
* @param view
*/
public void upload(View view) {
List<File> files = new ArrayList<>();
if (imageItems != null && imageItems.size() > 0) {
for (ImageItem imageItem : imageItems) {
files.add(new File(imageItem.path));
}
}
try {
OkGo.post(Urls.URL_FORM_UPLOAD)
.headers("header1", "heardvalue1")
.headers("header2", "heardvalue2")
.headers("header3", "heardvalue3") //看服务器那边要求去拼接了。
.params("params1", "value1")
.params("params2", "value2")
.params("params3", "value3")
.addFileParams("file", files) //传个集合,一个key,对应一个集合文件上传。
// .params("file1",new File("imagePath1"))
// .params("file2",new File("imagePath2"))
// .params("file3",new File("imagePath3"))
.execute(new StringCallback() {
@Override
public void onBefore(BaseRequest request) {
super.onBefore(request);
bt_upload.setText("正在上传中....");
}
@Override
public void onSuccess(String s, Call call, Response response) {
bt_upload.setText("上传成功");
upload_speed.setText("--");
}
@Override
public void onError(Call call, Response response, Exception e) {
super.onError(call, response, e);
bt_upload.setText("上传失败");
upload_speed.setText("--");
}
@Override
public void upProgress(long currentSize, long totalSize, float progress, long networkSpeed) {
super.upProgress(currentSize, totalSize, progress, networkSpeed);
String uploadSize=Formatter.formatFileSize(MainActivity.this,currentSize);
String totalSizeNum=Formatter.formatFileSize(MainActivity.this,totalSize);
upload_current_progress.setText(uploadSize+"/"+totalSizeNum);
upload_percentage.setText((int) (progress * 100) + "%");
String netSpeed = Formatter.formatFileSize(getApplicationContext(), networkSpeed);
upload_speed.setText(netSpeed);
upload_progressbar.setProgress((int) (progress * 100));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
这块和下载逻辑差不错,headersr和params都可以有多对,在上传时,服务端都会要求拼接许多header和一些params键值对,代码那里有标注,可以直接传一个list集合,里面放图片路径就可以了。图片的选择是跳到相册页,这里就用到了刚刚依赖的imagePicker
跳转到相册代码:
/**
* 去相册选择图片
*
* @param view
*/
public void choose(View view) {
Intent intent = new Intent(this, ImageGridActivity.class);
startActivityForResult(intent, IMAGE_PICKER);
}
回调代码:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == IMAGE_PICKER && resultCode == ImagePicker.RESULT_CODE_ITEMS) {
if (data != null) {
StringBuilder sb = new StringBuilder();
imageItems = (ArrayList<ImageItem>) data.getSerializableExtra(ImagePicker.EXTRA_RESULT_ITEMS);
for (int i = 0; i < imageItems.size(); i++) {
if (i == imageItems.size() - 1) {
sb.append("图片" +(i+1) + ":" + imageItems.get(i).path);
} else {
sb.append("图片" + (i+1) + ":" + imageItems.get(i).path + ",");
}
}
tv_image_path.setText(sb.toString());
}
}
}
这里需要注意的是,依赖的imagepicker需要配置初始化,不清楚点击看这里:
还可以按照我这个demo里的配置来,当然我也是按照它这里配置的:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
ImagePicker imagePicker = ImagePicker.getInstance();
imagePicker.setImageLoader(new GlideImageLoad()); //设置图片加载器
imagePicker.setShowCamera(true); //显示拍照按钮
imagePicker.setCrop(true); //允许裁剪(单选才有效)
imagePicker.setSaveRectangle(true); //是否按矩形区域保存
imagePicker.setSelectLimit(9); //选中数量限制
imagePicker.setStyle(CropImageView.Style.RECTANGLE); //裁剪框的形状
imagePicker.setFocusWidth(800); //裁剪框的宽度。单位像素(圆形自动取宽高最小值)
imagePicker.setFocusHeight(800); //裁剪框的高度。单位像素(圆形自动取宽高最小值)
imagePicker.setOutPutX(1000);//保存文件的宽度。单位像素
imagePicker.setOutPutY(1000);//保存文件的高度。单位像素
}
}
GlideImageLoad代码:
@Override
public void displayImage(Activity activity, String path, ImageView imageView, int width, int height) {
//Glide的配置去加载图片
Glide.with(activity.getApplicationContext()).load(path)
.placeholder(R.mipmap.default_image)
.error(R.mipmap.default_image)
.override(width,height)
.into(imageView);
}
@Override
public void clearMemoryCache() {
//这里去清除缓存
}
这样配置就完成了,最后是xml文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="2dp"
tools:context="user.example.com.okgosample.acitivity.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp"
>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_weight="1"
>
<TextView
android:id="@+id/tv_current_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="/"
/>
<TextView
android:id="@+id/tv_total_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-M"
/>
</LinearLayout>
<TextView
android:id="@+id/tv_current_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="--%"
android:layout_weight="1"
/>
<TextView
android:id="@+id/tv_download_speed"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="kb/s"
android:layout_weight="1"
/>
</LinearLayout>
<ProgressBar
android:id="@+id/load_progressbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
android:progress="0"
android:visibility="visible"
/>
<Button
android:id="@+id/bt_download"
android:onClick="download"
android:text="down_load"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/upload_progressbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
android:progress="0"
android:visibility="visible"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:id="@+id/upload_current_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="-/M"
/>
<TextView
android:id="@+id/upload_percentage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="-%"
/>
<TextView
android:id="@+id/upload_speed"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="kb/s"
/>
</LinearLayout>
<Button
android:id="@+id/bt_choose_images"
android:onClick="choose"
android:text="选择图片"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/bt_upload"
android:onClick="upload"
android:text="up_down"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_image_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
最后再来张效果图吧,效果还不错: (这里是实现3张图片上传,和下载一个文件)
结论: okGo封装类中提供了许多强大的很实用的功能,具体需要哪块就去实现那块吧,demo就不上传了,可以把okGo源码clone下来,里面还是很详细。有问题就拍砖,多多指教~~