diff --git a/EasyHttp.apk b/EasyHttp.apk index 4db5584..7b33c88 100644 Binary files a/EasyHttp.apk and b/EasyHttp.apk differ diff --git a/HelpDoc.md b/HelpDoc.md index 84acea2..24848e5 100644 --- a/HelpDoc.md +++ b/HelpDoc.md @@ -26,6 +26,8 @@ * [请求缓存](#请求缓存) + * [分区存储适配](#分区存储适配) + * [疑难解答](#疑难解答) * [如何添加全局参数](#如何添加全局参数) @@ -76,14 +78,14 @@ * [我想取消请求时显示的加载对话框该怎么办](#我想取消请求时显示的加载对话框该怎么办) + * [如何在 ViewModel 中使用 EasyHttp 请求网络](如何在-viewmodel-中使用-easyhttp-请求网络) + * [搭配 RxJava](#搭配-rxjava) * [准备工作](#准备工作) * [多个请求串行](#多个请求串行) - * [多个请求并行](#多个请求并行) - * [发起轮询请求](#发起轮询请求) * [对返回的数据进行包装](#对返回的数据进行包装) @@ -230,7 +232,7 @@ public final class LoginApi implements IRequestApi { * @HttpIgnore:标记这个字段不会被发送给后台 - * @HttpRename:重新定义这个字段发送给后台的参数名称 + * @HttpRename:重新定义这个字段发送给后台的参数或者请求头名称 * 可在这个类实现一些接口 @@ -544,6 +546,37 @@ public class XxxServer implements IRequestServer { } ``` +#### 分区存储适配 + +* 在 Android 10 之前,我们在读写外部存储的时候,可以直接使用 File 对象来上传或者下载文件,但是在 Android 10 之后,如果你的项目需要 Android 10 分区存储的特性,那么在读写外部存储文件的时候,就不能直接使用 File 对象,因为 `ContentResolver.insert` 返回是一个 `Uri` 对象,这个时候就需要使用到 `FileContentResolver` 对象了(这个对象是 File 的子类),具体使用案例如下: + +```java +File outputFile; +if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ContentValues values = new ContentValues(); + ......... + // 生成一个新的 uri 路径 + Uri outputUri = getContentResolver().insert(MediaStore.Xxx.Media.EXTERNAL_CONTENT_URI, values); + // 适配 Android 10 分区存储特性 + outputFile = new FileContentResolver(context, outputUri); +} else { + outputFile = new File(xxxx); +} + +EasyHttp.post(this) + .api(new XxxApi() + .setImage(outputFile)) + .request(new HttpCallback>(this) { + + @Override + public void onSucceed(Xxx data) { + + } + }); +``` + +* 这是上传的案例,下载也同理,这里不再赘述。 + # 疑难解答 #### 如何添加全局参数 @@ -1224,6 +1257,58 @@ EasyHttp.post(this) }); ``` +#### 如何在 ViewModel 中使用 EasyHttp 请求网络 + +* 第一步:封装一个 BaseViewModel,并将 LifecycleOwner 特性植入进去 + +```java +public class BaseViewModel extends ViewModel implements LifecycleOwner { + + private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); + + public BaseViewModel() { + mLifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); + mLifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME); + } + + @Override + protected void onCleared() { + super.onCleared(); + mLifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY); + } + + @NonNull + @Override + public Lifecycle getLifecycle() { + return mLifecycle; + } +} +``` + +* 第二步:让业务 ViewModel 类继承至 BaseViewModel,具体案例如下 + +```java +public class XxxViewModel extends BaseViewModel { + + public void xxxx() { + EasyHttp.post(this) + .api(new XxxApi()) + .request(new OnHttpListener>() { + + @Override + public void onSucceed(HttpData result) { + + } + + @Override + public void onFail(Exception e) { + + } + }); + } +} +``` + # 搭配 RxJava #### 准备工作 @@ -1288,53 +1373,10 @@ Observable.create(new ObservableOnSubscribe>() { }); ``` -#### 多个请求并行 - -```java -Observable.create(new ObservableOnSubscribe() { - - @Override - public void subscribe(ObservableEmitter emitter) throws Exception { - SearchBlogsApi api1 = new SearchBlogsApi() - .setKeyword("1"); - SearchBlogsApi api2 = new SearchBlogsApi() - .setKeyword("2"); - - emitter.onNext(api1); - emitter.onNext(api2); - emitter.onComplete(); - } -}) -.map(new Function>() { - - @Override - public HttpData apply(IRequestApi api) throws Exception { - try { - return EasyHttp.post(MainActivity.this) - .api(api) - .execute(new ResponseClass>() {}); - } catch (Exception e) { - e.printStackTrace(); - ToastUtils.show(e.getMessage()); - throw e; - } - } -}) -// 让被观察者执行在 IO 线程 -.subscribeOn(Schedulers.io()) -// 让观察者执行在主线程 -.observeOn(AndroidSchedulers.mainThread()) -.subscribe(new Consumer>() { - - @Override - public void accept(HttpData data) throws Exception { - Log.d("EasyHttp", "最终结果为:" + data.getMessage()); - } -}); -``` - #### 发起轮询请求 +* 如果轮询的次数是有限,可以考虑使用 Http 请求来实现,但是如果轮询的次数是无限的,那么不推荐使用 Http 请求来实现,应当使用 WebSocket 来做,又或者其他长链接协议来做。 + ```java // 发起轮询请求,共发起三次请求,第一次请求在 5 秒后触发,剩下两次在 1 秒 和 2 秒后触发 Observable.intervalRange(1, 3, 5000, 1000, TimeUnit.MILLISECONDS) diff --git a/README.md b/README.md index dcf487f..84c9a3a 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ android { dependencies { // 网络请求框架:https://github.com/getActivity/EasyHttp - implementation 'com.github.getActivity:EasyHttp:10.0' + implementation 'com.github.getActivity:EasyHttp:10.2' // OkHttp 框架:https://github.com/square/okhttp // noinspection GradleDependency implementation 'com.squareup.okhttp3:okhttp:3.12.13' @@ -65,9 +65,9 @@ dependencies { | 功能或细节 | [EasyHttp](https://github.com/getActivity/EasyHttp) | [Retrofit](https://github.com/square/retrofit) | [OkGo](https://github.com/jeasonlzy/okhttp-OkGo) | | :----: | :------: | :-----: | :-----: | -| 对应版本 | 10.0 | 2.9.0 | 3.0.4 | +| 对应版本 | 10.2 | 2.9.0 | 3.0.4 | | issues 数 | [![](https://img.shields.io/github/issues/getActivity/EasyHttp.svg)](https://github.com/getActivity/EasyHttp/issues) | [![](https://img.shields.io/github/issues/square/retrofit.svg)](https://github.com/square/retrofit/issues) | [![](https://img.shields.io/github/issues/jeasonlzy/okhttp-OkGo.svg)](https://github.com/jeasonlzy/okhttp-OkGo/issues) | -| **aar 包大小** | 74 KB | 123 KB | 131 KB | +| **aar 包大小** | 76 KB | 123 KB | 131 KB | | minSdk 要求 | API 14+ | API 21+ | API 14+ | | 配置多域名 | ✅ | ❌ | ✅ | | **动态 Host** | ✅ | ❌ | ❌ | @@ -82,7 +82,8 @@ dependencies { | Json 参数提交 | ✅ | ❌ | ✅ | | **请求代码定位** | ✅ | ❌ | ❌ | | **延迟发起请求** | ✅ | ❌ | ❌ | -| 上传文件类型 | File / InputStream / RequestBody | RequestBody | File | +| **分区存储适配** | ✅ | ❌ | ❌ | +| 上传文件类型 | File / FileContentResolver
InputStream / RequestBody | RequestBody | File | | **请求生命周期** | 自动管控 | 需要封装 | 需要封装 | | 参数传值方式 | 字段名 + 字段值 | 参数名 + 参数值 | 定义 Key + Value | | 框架灵活性 | 高 | 低 | 中 | @@ -105,9 +106,39 @@ dependencies { * EasyHttp 采用了 OOP 思想,一个请求代表一个对象,通过类继承和实现的特性来对接口进行动态化配置,几乎涵盖接口开发中所有的功能,使用起来非常简单灵活。而 Retrofit 采用的是注解方式,缺点是灵活性极低,因为注解上面只能放常量,也就会限定你在注解上面的一切参数只能是事先定义好的,这对接口的动态化配置极不利的。 -* 有很多人觉得写一个接口类很麻烦,这个点确实有点麻烦,但是这块的付出是有收获的,从前期开发的效率考虑:OkGo > EasyHttp > Retrofit,但是从后期维护的效率考虑:EasyHttp > Retrofit > OkGo,之所以比较这三个框架,是因为框架的设计思想不同,但是我始终认为 EasyHttp 才是最好的设计,所以我创造了它。 +* 有很多人觉得写一个接口类很麻烦,关于这个问题我后面已经想到一个好方案了,大家可以将 Api 类和 Bean 类写在一起,这样大家就不需要多写一个类了,具体写法示例如下: -* 前期开发和后期维护哪个更重要?我觉得都重要,但是如果两者之间存在利益冲突,我会毫不犹豫地选择后期维护,因为前期开发占据的是小头,后期的持续维护才是大头。 +```java +public final class XxxApi implements IRequestApi { + + @Override + public String getApi() { + return "xxx/xxx"; + } + + private int xxx; + + public XxxApi setXxx(int xxx) { + this.xxx = xxx; + return this; + } + + ...... + + public final static class Bean { + + private int xyz; + + public int getXyz() { + return xyz; + } + + ...... + } +} +``` + +* 是不是很机智?这样不仅很好地解决了这一问题,还能将一个接口所有的信息都包裹在这个类中,非常直观,一览如云,妥妥的一箭双雕。 #### 生命周期自动管控介绍 diff --git a/app/build.gradle b/app/build.gradle index 1bff2f2..4ca4e28 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId 'com.hjq.easy.demo' minSdkVersion 16 targetSdkVersion 30 - versionCode 1000 - versionName '10.0' + versionCode 1020 + versionName '10.2' } // 支持 JDK 1.8 @@ -60,8 +60,8 @@ dependencies { implementation project(':library') - // AppCompat 库:https://developer.android.google.cn/jetpack/androidx/releases/appcompat?hl=zh-cn - implementation 'androidx.appcompat:appcompat:1.3.0' + // AndroidX 库:https://github.com/androidx/androidx + implementation 'androidx.appcompat:appcompat:1.3.1' // OkHttp 框架:https://github.com/square/okhttp // 升级注意事项:https://www.jianshu.com/p/d12d0f536f55 @@ -72,10 +72,10 @@ dependencies { implementation 'com.github.getActivity:ToastUtils:9.5' // 权限请求框架:https://github.com/getActivity/XXPermissions - implementation 'com.github.getActivity:XXPermissions:12.0' + implementation 'com.github.getActivity:XXPermissions:12.3' // 标题栏框架:https://github.com/getActivity/TitleBar - implementation 'com.github.getActivity:TitleBar:8.6' + implementation 'com.github.getActivity:TitleBar:9.2' // Json 解析框架:https://github.com/google/gson implementation 'com.google.code.gson:gson:2.8.8' @@ -83,8 +83,11 @@ dependencies { implementation 'com.github.getActivity:GsonFactory:5.2' // 日志调试框架:https://github.com/getActivity/Logcat - debugImplementation 'com.github.getActivity:Logcat:9.8' + debugImplementation 'com.github.getActivity:Logcat:9.9' // 腾讯 MMKV:https://github.com/Tencent/MMKV implementation 'com.tencent:mmkv-static:1.2.10' + + // 内存泄漏监测框架:https://github.com/square/leakcanary + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3ddc539..125ce65 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -12,7 +12,7 @@ 0; + return mContentResolver.delete(mContentUri, null, null) > 0; } @Override @@ -57,6 +125,19 @@ public boolean isHidden() { @Override public long length() { + InputStream inputStream = null; + try { + inputStream = openInputStream(); + if (inputStream != null) { + return inputStream.available(); + } + } catch (FileNotFoundException e) { + EasyLog.print(e); + } catch (IOException e) { + EasyLog.print(e); + } finally { + EasyUtils.closeStream(inputStream); + } return 0; } diff --git a/library/src/main/java/com/hjq/http/model/FileWrapper.java b/library/src/main/java/com/hjq/http/model/FileWrapper.java index d1f0d83..3c76cae 100644 --- a/library/src/main/java/com/hjq/http/model/FileWrapper.java +++ b/library/src/main/java/com/hjq/http/model/FileWrapper.java @@ -29,17 +29,17 @@ public FileWrapper(@NonNull File file) { } /** - * 获取文件的输出流 + * 打开文件的输入流 */ - public OutputStream getOutputStream() throws FileNotFoundException { - return new FileOutputStream(this); + public InputStream openInputStream() throws FileNotFoundException { + return new FileInputStream(this); } /** - * 获取文件的输入流 + * 打开文件的输出流 */ - public InputStream getInputStream() throws FileNotFoundException { - return new FileInputStream(this); + public OutputStream openOutputStream() throws FileNotFoundException { + return new FileOutputStream(this); } /** @@ -83,6 +83,7 @@ public static String getFileMd5(InputStream inputStream) { } catch (NoSuchAlgorithmException | IOException e) { EasyLog.print(e); } finally { + EasyUtils.closeStream(inputStream); EasyUtils.closeStream(digestInputStream); } return null; diff --git a/library/src/main/java/com/hjq/http/request/BaseRequest.java b/library/src/main/java/com/hjq/http/request/BaseRequest.java index 5d1b29a..54ff32d 100644 --- a/library/src/main/java/com/hjq/http/request/BaseRequest.java +++ b/library/src/main/java/com/hjq/http/request/BaseRequest.java @@ -313,7 +313,12 @@ protected Call createCall() { if (interceptor != null) { interceptor.interceptArguments(mRequestApi, params, headers); } - return mRequestClient.getClient().newCall(createRequest(url, mTag, params, headers, type)); + + Request request = createRequest(url, mTag, params, headers, type); + if (request == null) { + throw new NullPointerException("The request object cannot be empty"); + } + return mRequestClient.getClient().newCall(request); } /** diff --git a/library/src/main/java/com/hjq/http/request/BodyRequest.java b/library/src/main/java/com/hjq/http/request/BodyRequest.java index cc516d2..84ca7f6 100644 --- a/library/src/main/java/com/hjq/http/request/BodyRequest.java +++ b/library/src/main/java/com/hjq/http/request/BodyRequest.java @@ -153,7 +153,7 @@ protected Request createRequest(String url, String tag, HttpParams params, HttpH } } - return getRequestHandler().requestStart(getLifecycleOwner(), getRequestApi(), requestBuilder.build()); + return getRequestHandler().requestStart(getLifecycleOwner(), getRequestApi(), requestBuilder); } /** @@ -206,7 +206,7 @@ private RequestBody createBody(HttpParams params, BodyType type) { // 如果这是一个自定义的 RequestBody 对象 if (object instanceof RequestBody) { if (object instanceof UpdateBody) { - bodyBuilder.addFormDataPart(key, EasyUtils.encodeString(((UpdateBody) object).getName()), (RequestBody) object); + bodyBuilder.addFormDataPart(key, EasyUtils.encodeString(((UpdateBody) object).getKeyName()), (RequestBody) object); } else { bodyBuilder.addFormDataPart(key, null, (RequestBody) object); } diff --git a/library/src/main/java/com/hjq/http/request/UrlRequest.java b/library/src/main/java/com/hjq/http/request/UrlRequest.java index e5bfaba..9b85460 100644 --- a/library/src/main/java/com/hjq/http/request/UrlRequest.java +++ b/library/src/main/java/com/hjq/http/request/UrlRequest.java @@ -83,6 +83,6 @@ protected Request createRequest(String url, String tag, HttpParams params, HttpH } } - return getRequestHandler().requestStart(getLifecycleOwner(), getRequestApi(), requestBuilder.build()); + return getRequestHandler().requestStart(getLifecycleOwner(), getRequestApi(), requestBuilder); } } \ No newline at end of file