【186】okHttp 3.0 封装分析
0.简单说明
对是基于okhttp 2.4.0 封装 参考文章:https://blog.csdn.net/lmj623565791/article/details/47911083 compile 'com.squareup.okhttp:okhttp:2.4.0' compile 'com.squareup.okio:okio:1.5.0'
1.外部调用方法
//[1]同步Get请求-返回原始response public static Response getAsyn(String url) throws IOException { return getInstance()._getAsyn(url); }
//[2]同步Get请求-返回String public static String getAsString(String url) throws IOException { return getInstance()._getAsString(url); }
//[3]异步Get请求-返回原始请求 public static void getAsyn(String url, ResultCallback callback) { getInstance()._getAsyn(url, callback); }
//[4]同步Post请求--返回原始response public static Response post(String url, Param... params) throws IOException { return getInstance()._post(url, params); }
//[5]同步Post请求--返回string public static String postAsString(String url, Param... params) throws IOException { return getInstance()._postAsString(url, params); } //[6]异步post请求封装 public static void postAsyn(String url, final ResultCallback callback, Param... params) { getInstance()._postAsyn(url, callback, params); }
//[7]异步post请求封装,参数的形式是map public static void postAsyn(String url, final ResultCallback callback, Map<String, String> params) { getInstance()._postAsyn(url, callback, params); }
//[8]post同步文件上传-file[]+fileKey[]+params public static Response post(String url, File[] files, String[] fileKeys, Param... params) throws IOException { return getInstance()._post(url, files, fileKeys, params); }
//[9]file+fileKey public static Response post(String url, File file, String fileKey) throws IOException { return getInstance()._post(url, file, fileKey); }
//[10]file+fileKey+params public static Response post(String url, File file, String fileKey, Param... params) throws IOException { return getInstance()._post(url, file, fileKey, params); }
//[11]异步post文件上传 public static void postAsyn(String url, ResultCallback callback, File[] files, String[] fileKeys, Param... params) throws IOException { getInstance()._postAsyn(url, callback, files, fileKeys, params); }
//[12]异步post文件上传,单文件不带参数上传 public static void postAsyn(String url, ResultCallback callback, File file, String fileKey) throws IOException { getInstance()._postAsyn(url, callback, file, fileKey); }
//[13]异步post文件上传,单文件且携带其他form参数上传 public static void postAsyn(String url, ResultCallback callback, File file, String fileKey, Param... params) throws IOException { getInstance()._postAsyn(url, callback, file, fileKey, params); }
//[14]下载图片并加载图片,可以设置默认加载失败的图片 public static void displayImage(final ImageView view, String url, int errorResId) throws IOException { getInstance()._displayImage(view, url, errorResId); }
//[15]下载图片并加载图片 public static void displayImage(final ImageView view, String url) { getInstance()._displayImage(view, url, -1); }
//[16]异步下载文件 public static void downloadAsyn(String url, String destDir, ResultCallback callback) { getInstance()._downloadAsyn(url, destDir, callback); }
2.构造方法
1 private OkHttpClientManager() { 2 mOkHttpClient = new OkHttpClient(); //建立OkHttpClient类; 3 /* 4 【1】使能cookie enabled 5 三种请求: 6 ACCEPT_ALL :accepts all cookies 7 ACCEPT_NONE:accepts no cookies 8 ACCEPT_ORIGINAL_SERVER :only accepts cookies from original server 9 */ 10 mOkHttpClient.setCookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER)); 11 mDelivery = new Handler(Looper.getMainLooper()); //对子线程的的网路请求; 12 mGson = new Gson(); //Gson对象; 13 }
3.单例对象
1 /** 2 * 【2】单例 3 * @return 4 */ 5 public static OkHttpClientManager getInstance() { 6 if (mInstance == null) { 7 synchronized (OkHttpClientManager.class) { 8 if (mInstance == null) { 9 mInstance = new OkHttpClientManager(); 10 } 11 } 12 } 13 return mInstance; 14 }
3.同步Get请求
【返回原始response】
1 /** 2 * 同步的Get请求-返回原始response; 3 * 4 * 说明:okhttp默认是get请求。 5 * @param url 6 * @return Response 7 */ 8 private Response _getAsyn(String url) throws IOException { 9 10 final Request request = new Request.Builder() 11 .url(url) 12 .build(); 13 Call call = mOkHttpClient.newCall(request); 14 Response execute = call.execute(); 15 return execute; 16 }
【返回String类型数据】
1 /** 2 * 同步的Get请求 3 * @param url 4 * @return 字符串 5 * 6 * /直接调用了返回原始response方法,然后解析为string; 7 * 8 */ 9 private String _getAsString(String url) throws IOException { 10 Response execute = _getAsyn(url); 11 return execute.body().string(); 12 }
4. 异步get请求
1 /** 2 * 异步get请求 3 * 4 * @param url 5 * @param callback 封转之后的callback 6 */ 7 private void _getAsyn(String url, final ResultCallback callback) { 8 final Request request = new Request.Builder() 9 .url(url) 10 .build(); 11 deliveryResult(callback, request); //封装了异步请求; 12 }
【异步请求封装-错误信息的返回】
【说明】此处的mDelivery是声明的Handler对象
1 private void sendFailedStringCallback(final Request request, final Exception e, final ResultCallback callback) { 2 //使用handler在子线程中将错误response和error码返回 3 mDelivery.post(new Runnable() { 4 @Override 5 public void run() { 6 if (callback != null) 7 callback.onError(request, e); 8 } 9 }); 10 }
【异步请求封装-原始信息的返回】此处传入的参数是object,后面会根据封装的callback的mType判断不同的情况然后进行不同的处理。
1 private void sendSuccessResultCallback(final Object object, final ResultCallback callback) { 2 //使用handler在子线程中将response结果返回 3 mDelivery.post(new Runnable() { 4 @Override 5 public void run() { 6 if (callback != null) { 7 callback.onResponse(object); 8 } 9 } 10 }); 11 }
【异步请求的完整封装】
1 private void deliveryResult(final ResultCallback callback, Request request) { 2 mOkHttpClient.newCall(request).enqueue(new Callback() { //直接调用okhttp的异步请求 3 @Override 4 public void onFailure(final Request request, final IOException e) { 5 sendFailedStringCallback(request, e, callback); 6 } 7 8 @Override 9 public void onResponse(final Response response) { 10 try { 11 final String string = response.body().string(); 12 //如果结果是String类型,返回string 13 if (callback.mType == String.class) { 14 sendSuccessResultCallback(string, callback); 15 } else { 16 //否则,使用Gson解析返回json数据; 17 Object o = mGson.fromJson(string, callback.mType); 18 sendSuccessResultCallback(o, callback); 19 } 20 } catch (IOException e) { 21 sendFailedStringCallback(response.request(), e, callback);//返回错误信息 22 } catch (com.google.gson.JsonParseException e){ 23 sendFailedStringCallback(response.request(), e, callback);//返回json解析错误; 24 } 25 } 26 }); 27 }
5.ResultCallback 封装
1 /** 2 * 将callback封装成抽象类 3 * @param <T> 4 */ 5 public static abstract class ResultCallback<T> { 6 Type mType; //返回的response数据具有不同的类型,此处必须判断当前的 7 8 public ResultCallback() { 9 mType = getSuperclassTypeParameter(getClass()); 10 } 11 12 static Type getSuperclassTypeParameter(Class<?> subclass) { 13 Type superclass = subclass.getGenericSuperclass(); 14 if (superclass instanceof Class) { 15 throw new RuntimeException("Missing type parameter."); 16 } 17 //对应找到Gson的类型; 18 ParameterizedType parameterized = (ParameterizedType) superclass; 19 return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]); 20 } 21 22 //对应onFailuer方法; 23 public abstract void onError(Request request, Exception e); 24 //对应onResponse方法; 25 public abstract void onResponse(T response); 26 }
6.同步post请求封装--返回原始response
1 /** 2 * 同步的Post请求 3 * 4 * @param url 5 * @param params post的参数 6 * @return 返回原始响应数据 7 */ 8 private Response _post(String url, Param... params) throws IOException { 9 Request request = buildPostRequest(url, params); //对post的Request的封装; 10 Response response = mOkHttpClient.newCall(request).execute(); 11 return response; //返回原始响应数据 12 }
【PostRequest的封装】
1 /** 2 * 生成FormEncodingBuilder/FormBody,然后填充数据; 3 * @param url 4 * @param params 5 * @return 6 */ 7 private Request buildPostRequest(String url, Param[] params) { 8 if (params == null) { 9 params = new Param[0]; 10 } 11 12 /* 13 okHttp3的写法; 14 FormBody.Builder builder1 = new FormBody.Builder(); 15 for (Param param:params){ 16 builder1.add(param.key,param.value); 17 }*/ 18 19 //创建一个fromBody--okHttp3的写法; 20 FormEncodingBuilder builder = new FormEncodingBuilder(); 21 for (Param param : params) { 22 builder.add(param.key, param.value); 23 } 24 RequestBody requestBody = builder.build(); 25 return new Request.Builder() 26 .url(url) 27 .post(requestBody) 28 .build(); 29 }
7.同步post请求封装--返回String
1 /** 2 * 同步的Post请求--返回String 3 * 4 * @param url 5 * @param params post的参数 6 * @return 字符串 7 */ 8 private String _postAsString(String url, Param... params) throws IOException { 9 Response response = _post(url, params); //调用上一个post请求,只是返回的参数进行了String提取; 10 return response.body().string(); //返回String 11 }
8.异步post请求封装
1 /** 2 * 异步post请求封装 3 * 4 * @param url 5 * @param callback 6 * @param params 7 */ 8 private void _postAsyn(String url, final ResultCallback callback, Param... params) { 9 Request request = buildPostRequest(url, params); 10 deliveryResult(callback, request); 11 } 12 13 /** 14 * 异步的post请求 15 * 16 * @param url 17 * @param callback 18 * @param params 19 */ 20 private void _postAsyn(String url, final ResultCallback callback, Map<String, String> params) { 21 Param[] paramsArr = map2Params(params);//params如果是map形式,则转化为数组; 22 Request request = buildPostRequest(url, paramsArr); 23 deliveryResult(callback, request); 24 }
首先认识Parmas类是键值对
1 public static class Param { 2 public Param() { 3 } 4 5 public Param(String key, String value) { 6 this.key = key; 7 this.value = value; 8 } 9 10 String key; 11 String value; 12 }
【map->array转化】
1 private Param[] map2Params(Map<String, String> params) { 2 if (params == null) 3 return new Param[0]; 4 5 int size = params.size(); 6 Param[] res = new Param[size]; 7 Set<Map.Entry<String, String>> entries = params.entrySet(); 8 int i = 0; 9 for (Map.Entry<String, String> entry : entries) { 10 res[i++] = new Param(entry.getKey(), entry.getValue()); 11 } 12 return res; 13 }
9.post同步文件上传
1 /** 2 * 同步基于post的文件上传 3 * 4 * @param params 5 * @return 6 */ 7 //file[]+fileKey[]+params 8 private Response _post(String url, File[] files, String[] fileKeys, Param... params) throws IOException { 9 Request request = buildMultipartFormRequest(url, files, fileKeys, params); 10 return mOkHttpClient.newCall(request).execute(); 11 } 12 13 //file+fileKey 14 private Response _post(String url, File file, String fileKey) throws IOException { 15 Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, null); 16 return mOkHttpClient.newCall(request).execute(); 17 } 18 //file+fileKey+params 19 private Response _post(String url, File file, String fileKey, Param... params) throws IOException { 20 Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, params); 21 return mOkHttpClient.newCall(request).execute(); 22 }
【获取文件的Content-Type(Mime-Type)值】
1 //获取文件的Content-Type(Mime-Type)值 2 private String guessMimeType(String path) { 3 //获取文件Content-Type(Mime-Type) 4 FileNameMap fileNameMap = URLConnection.getFileNameMap(); 5 String contentTypeFor = fileNameMap.getContentTypeFor(path); 6 if (contentTypeFor == null) { 7 contentTypeFor = "application/octet-stream"; //默认为"application/octet-stream" 8 } 9 return contentTypeFor; 10 }
【增加对params为空的赋值】
1 //增加对params为空的赋值 2 private Param[] validateParam(Param[] params) { 3 if (params == null) 4 return new Param[0]; 5 else return params; 6 }
【MultipartFormRequest构建】
1 private Request buildMultipartFormRequest(String url, File[] files, 2 String[] fileKeys, Param[] params) { params = validateParam(params); 4 //构建MultipartBuilder 5 MultipartBuilder builder = new MultipartBuilder() 6 .type(MultipartBuilder.FORM); //设置为表单数据 7 //[1]params的requestBody的构建 8 for (Param param : params) { 9 builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + param.key + "\""), 10 RequestBody.create(null, param.value)); 11 } 12 //[2]file的requestBody的构建 13 if (files != null) { 14 RequestBody fileBody = null; 15 for (int i = 0; i < files.length; i++) { 16 File file = files[i]; 17 String fileName = file.getName(); 18 fileBody = RequestBody.create(MediaType.parse(guessMimeType(fileName)), file); 19 //TODO 根据文件名设置contentType 20 builder.addPart(Headers.of("Content-Disposition", 21 "form-data; name=\"" + fileKeys[i] + "\"; filename=\"" + fileName + "\""), 22 fileBody); 23 } 24 } 25 RequestBody requestBody = builder.build(); 26 return new Request.Builder() 27 .url(url) 28 .post(requestBody) 29 .build(); 30 }
10. post异步文件上传
1 /** 2 * 异步基于post的文件上传 3 * 4 * @param url 5 * @param callback 6 * @param files 7 * @param fileKeys 8 * @throws IOException 9 */ 10 private void _postAsyn(String url, ResultCallback callback, File[] files, String[] fileKeys, Param... params) throws IOException { 11 Request request = buildMultipartFormRequest(url, files, fileKeys, params); 12 deliveryResult(callback, request); 13 } 14 15 /** 16 * 异步基于post的文件上传,单文件不带参数上传 17 * 18 * @param url 19 * @param callback 20 * @param file 21 * @param fileKey 22 * @throws IOException 23 */ 24 private void _postAsyn(String url, ResultCallback callback, File file, String fileKey) throws IOException { 25 Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, null); 26 deliveryResult(callback, request); 27 } 28 29 /** 30 * 异步基于post的文件上传,单文件且携带其他form参数上传 31 * 32 * @param url 33 * @param callback 34 * @param file 35 * @param fileKey 36 * @param params 37 * @throws IOException 38 */ 39 private void _postAsyn(String url, ResultCallback callback, File file, String fileKey, Param... params) throws IOException { 40 Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, params); 41 deliveryResult(callback, request); 42 }
11.get异步下载文件
1 private void _downloadAsyn(final String url, final String destFileDir, final ResultCallback callback) { 2 final Request request = new Request.Builder() 3 .url(url) 4 .build(); 5 6 final Call call = mOkHttpClient.newCall(request); 7 call.enqueue(new Callback() { 8 @Override 9 public void onFailure(final Request request, final IOException e) { 10 sendFailedStringCallback(request, e, callback); 11 } 12 13 @Override 14 public void onResponse(Response response) { 15 InputStream is = null; 16 byte[] buf = new byte[2048]; 17 int len = 0; 18 FileOutputStream fos = null; 19 try { 20 is = response.body().byteStream(); //获取字节流数据; 21 File file = new File(destFileDir, getFileName(url)); 22 fos = new FileOutputStream(file); 23 while ((len = is.read(buf)) != -1) { 24 fos.write(buf, 0, len); 25 } 26 fos.flush(); 27 //如果下载文件成功,第一个参数为文件的绝对路径 28 sendSuccessResultCallback(file.getAbsolutePath(), callback); 29 } catch (IOException e) { 30 sendFailedStringCallback(response.request(), e, callback); 31 } finally { 32 try { 33 if (is != null) 34 is.close(); 35 } catch (IOException e) { 36 } 37 try { 38 if (fos != null) 39 fos.close(); 40 } catch (IOException e) { 41 } 42 } 43 } 44 }); 45 }
【文件名称的获取】
【substring方法】
返回一个字符串,该字符串是此字符串的子字符串。 该substring从指定的{@code beginIndex}开始 扩展到索引{@code endIndex - 1}处的字符。 因此子串的长度是{@code endIndex-beginIndex}。 String substring(int beginIndex, int endIndex) * "hamburger".substring(4, 8) returns "urge" * "smiles".substring(1, 5) returns "mile" * * * * h a m b u r g e r len = 4; 4--7开始;"urge" 0 1 2 3 4 5 6 7 8 * * * * s m i l e s len = 4; 1--4开始;"mile" 0 1 2 3 4 5
1 //一般的文件下载路径: 2 // http://down.360safe.com/360Root/3 6 0 R o o t S e t u p . e x e 3 // 0 32 47 4 // separatorIndex = 31; path.length=47; 5 // 32--47 len = 15; 32-47开始; 6 private String getFileName(String path) { 7 int separatorIndex = path.lastIndexOf("/"); //获取最后一个"/"的索引值; 8 return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length()); 9 }
12. 下载图片并加载图片
1 /** 2 * 下载图片并加载图片 3 * 4 * @param view :设置图片的ImageView 5 * @param url :下载的图片的地址; 6 * @param errorResId :下载失败之后的该ImageView显示的默认图片 7 * @throws IOException 8 */ 9 private void _displayImage(final ImageView view, final String url, final int errorResId) { 10 final Request request = new Request.Builder() 11 .url(url) 12 .build(); 13 Call call = mOkHttpClient.newCall(request); 14 call.enqueue(new Callback() { 15 @Override 16 public void onFailure(Request request, IOException e) { 17 setErrorResId(view, errorResId); 18 } 19 20 @Override 21 public void onResponse(Response response) { 22 InputStream is = null; 23 try { 24 is = response.body().byteStream(); //reponse 字节流,第一次请求stream是为了计算图片的inSampleSize 25 ImageUtils.ImageSize actualImageSize = ImageUtils.getImageSize(is); 26 ImageUtils.ImageSize imageViewSize = ImageUtils.getImageViewSize(view); 27 //计算bitmpa的inSample比例 28 int inSampleSize = ImageUtils.calculateInSampleSize(actualImageSize, imageViewSize); 29 try { 30 is.reset(); 31 } catch (IOException e) { 32 response = _getAsyn(url); 33 is = response.body().byteStream(); //reponse 字节流,第2次请求stream为了加载图片; 34 } 35 36 BitmapFactory.Options ops = new BitmapFactory.Options(); 37 ops.inJustDecodeBounds = false; //计算结束之后设置为false,在ImageUtils时设置为了true; 38 ops.inSampleSize = inSampleSize; 39 final Bitmap bm = BitmapFactory.decodeStream(is, null, ops); 40 //网络请求为耗时操作,必须放在子线程操作,更新ui要放在主线程; 41 mDelivery.post(new Runnable() { 42 @Override 43 public void run() { 44 view.setImageBitmap(bm); 45 } 46 }); 47 } catch (Exception e) { 48 setErrorResId(view, errorResId); 49 } finally { 50 if (is != null) try { 51 is.close(); 52 } catch (IOException e) { 53 e.printStackTrace(); 54 } 55 } 56 } 57 }); 58 }
【根据InputStream获取图片实际的宽度和高度】
1 /** 2 * 根据InputStream获取图片实际的宽度和高度 3 * 4 * @param imageStream 5 * @return 6 */ 7 public static ImageSize getImageSize(InputStream imageStream) { 8 BitmapFactory.Options options = new BitmapFactory.Options(); 9 options.inJustDecodeBounds = true; 10 BitmapFactory.decodeStream(imageStream, null, options); 11 return new ImageSize(options.outWidth, options.outHeight); 12 }
【根据ImageView获适当的压缩的宽和高】
1 /** 2 * 根据ImageView获适当的压缩的宽和高 3 * 4 * @param view 5 * @return 6 */ 7 public static ImageSize getImageViewSize(View view) { 8 9 ImageSize imageSize = new ImageSize(); 10 11 imageSize.width = getExpectWidth(view); 12 imageSize.height = getExpectHeight(view); 13 14 return imageSize; 15 } 16 17 /** 18 * 根据view获得期望的高度 19 * 20 * @param view 21 * @return 22 */ 23 private static int getExpectHeight(View view) { 24 25 int height = 0; 26 if (view == null) return 0; 27 28 final ViewGroup.LayoutParams params = view.getLayoutParams(); 29 30 //如果是WRAP_CONTENT,此时图片还没加载,getWidth根本无效 31 if (params != null && params.height != ViewGroup.LayoutParams.WRAP_CONTENT) { 32 height = view.getWidth(); // 获得实际的宽度 33 } 34 35 if (height <= 0 && params != null) { 36 height = params.height; // 获得布局文件中的声明的高度 37 } 38 39 if (height <= 0) { 40 height = getImageViewFieldValue(view, "mMaxHeight");// 获得设置的最大的宽度 41 } 42 43 //如果宽度还是没有获取到,使用屏幕的高度 44 if (height <= 0) { 45 DisplayMetrics displayMetrics = view.getContext().getResources() 46 .getDisplayMetrics(); 47 height = displayMetrics.heightPixels; 48 } 49 50 return height; 51 } 52 53 /** 54 * 根据view获得期望的宽度 55 * 56 * @param view 57 * @return 58 */ 59 private static int getExpectWidth(View view) { 60 int width = 0; 61 if (view == null) return 0; 62 63 final ViewGroup.LayoutParams params = view.getLayoutParams(); 64 //如果是WRAP_CONTENT,此时图片还没加载,getWidth根本无效 65 if (params != null && params.width != ViewGroup.LayoutParams.WRAP_CONTENT) { 66 width = view.getWidth(); // 获得实际的宽度 67 } 68 if (width <= 0 && params != null) { 69 width = params.width; // 获得布局文件中的声明的宽度 70 } 71 72 if (width <= 0){ 75 width = getImageViewFieldValue(view, "mMaxWidth");// 获得设置的最大的宽度 76 } 77 //如果宽度还是没有获取到,使用屏幕的宽度 78 if (width <= 0){ 81 DisplayMetrics displayMetrics = view.getContext().getResources() 82 .getDisplayMetrics(); 83 width = displayMetrics.widthPixels; 84 } 85 86 return width; 87 }
浙公网安备 33010602011771号