diff --git a/.gitignore b/.gitignore index 39fb081..5e3acf5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,18 @@ /build /captures .externalNativeBuild +.idea/* +.idea/codeStyles/Project.xml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +.idea/misc.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml +/.idea \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 96cc43e..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf3..0000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/dictionaries/nanchen.xml b/.idea/dictionaries/nanchen.xml deleted file mode 100644 index 9e0a254..0000000 --- a/.idea/dictionaries/nanchen.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - butterknife - debounce - flowable - rxjava - usecases - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 97626ba..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 7ac24c7..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 7158618..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.8 - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index deea19e..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 7583c2a..988318f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion rootProject.ext.compileSdkVersion - buildToolsVersion rootProject.ext.buildToolsVersion + compileSdkVersion 29 defaultConfig { applicationId "com.nanchen.rxjava2examples" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode rootProject.ext.versionCode - versionName rootProject.ext.versionName + minSdkVersion 23 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { @@ -17,6 +16,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { @@ -24,13 +27,13 @@ dependencies { androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile "com.android.support:appcompat-v7:$supportLibVersion" - compile "com.android.support:design:$supportLibVersion" + compile "com.android.support:appcompat-v7:+" + compile "com.android.support:design:+" compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.jakewharton:butterknife:8.6.0' compile 'io.reactivex.rxjava2:rxjava:2.1.0' compile 'io.reactivex.rxjava2:rxandroid:2.0.1' - compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.20' + compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30' compile 'com.jaeger.statusbarutil:library:1.4.0' compile 'com.squareup.retrofit2:retrofit:2.3.0' compile 'com.squareup.retrofit2:converter-gson:2.3.0' diff --git a/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/OperatorsFragment.java b/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/OperatorsFragment.java index 17d1d0f..d70d6bf 100644 --- a/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/OperatorsFragment.java +++ b/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/OperatorsFragment.java @@ -84,6 +84,9 @@ protected void fillData() { data.add(new OperatorModel(getString(R.string.rx_BehaviorSubject),"BehaviorSubject 的最后一次 onNext() 操作会被缓存,然后在 subscribe() 后立刻推给新注册的 Observer")); data.add(new OperatorModel(getString(R.string.rx_Completable),"只关心结果,也就是说 Completable 是没有 onNext 的,要么成功要么出错,不关心过程,在 subscribe 后的某个时间点返回结果")); data.add(new OperatorModel(getString(R.string.rx_Flowable),"专用于解决背压问题")); + + startActivity(new Intent(getActivity(), RxConcatMapActivity.class)); + } @Override diff --git a/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxConcatMapActivity.java b/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxConcatMapActivity.java index 69ef7de..ad809a3 100644 --- a/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxConcatMapActivity.java +++ b/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxConcatMapActivity.java @@ -1,29 +1,40 @@ package com.nanchen.rxjava2examples.module.rxjava2.operators.item; +import android.annotation.SuppressLint; +import android.os.Bundle; import android.util.Log; +import android.view.View; +import android.widget.Button; import com.nanchen.rxjava2examples.R; +import com.nanchen.rxjava2examples.net.ProbeResult; +import com.nanchen.rxjava2examples.net.UrlProbe; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import butterknife.BindView; import io.reactivex.Observable; import io.reactivex.ObservableEmitter; import io.reactivex.ObservableOnSubscribe; import io.reactivex.ObservableSource; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import io.reactivex.functions.Function; import io.reactivex.schedulers.Schedulers; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; /** * concatMap *

* concatMap作用和flatMap几乎一模一样,唯一的区别是它能保证事件的顺序 *

- * + *

* Author: nanchen * Email: liushilin520@foxmail.com * Date: 2017-06-20 10:27 @@ -31,20 +42,166 @@ public class RxConcatMapActivity extends RxOperatorBaseActivity { private static final String TAG = "RxConcatMapActivity"; + private Disposable mDisposable; + + @BindView(R.id.btn_all) + public Button btnAll; + + @BindView(R.id.btn_1) + public Button btn1; + + @BindView(R.id.btn_2) + public Button btn2; + + @BindView(R.id.btn_3) + public Button btn3; @Override protected String getSubTitle() { return getString(R.string.rx_concatMap); } + /** + * 探测工具 + */ + private final UrlProbe urlProbe = new UrlProbe(); + + // 从网络请求获取的 URL 列表 (提取出来的URL-list),可能n个 + List urls = new ArrayList<>(); + + + @Override + protected void initView(Bundle savedInstanceState) { + super.initView(savedInstanceState); + + btnAll.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + testAll(urls); + getHttp(); + } + }); + + btn1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + test1(urls); + getHttp(); + } + }); + + btn2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + test2(urls); + getHttp(); + } + }); + btn3.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + test3(urls); + getHttp(); + } + }); + } + + private void getHttp() { + mRxOperatorsText.setText(""); + for (String ss : urls) { + mRxOperatorsText.append(ss); + mRxOperatorsText.append("\n"); + } + + mDisposable = urlProbe.probeUrls(urls) + .subscribe(new Consumer() { + @Override + public void accept(@NonNull ProbeResult probeResult) { + String result = "accept: " + probeResult.url + ", state: " + probeResult.flag; + Log.i(TAG, result); + mRxOperatorsText.append("\n"); + mRxOperatorsText.append(result); + } + }, new Consumer() { + @Override + public void accept(@NonNull Throwable throwable) { + throwable.printStackTrace(); + Log.e(TAG, "accept: fail"); + } + }); + } + + @SuppressLint("CheckResult") @Override protected void doSomething() { + + // 从网络请求获取的 URL 列表 (提取出来的URL-list),可能n个 + List urls = new ArrayList<>(); + // + testAll(urls); + + mRxOperatorsText.append(urls.toString()); + mDisposable = urlProbe.probeUrls(urls) + .subscribe(new Consumer() { + @Override + public void accept(@NonNull ProbeResult probeResult) { + String result = "accept: " + probeResult.url + ", state: " + probeResult.flag; + Log.i(TAG, result); + mRxOperatorsText.append("\n"); + mRxOperatorsText.append(result); + } + }, new Consumer() { + @Override + public void accept(@NonNull Throwable throwable) { + throwable.printStackTrace(); + Log.e(TAG, "accept: fail"); + } + }); + } + + + private void testAll(List urls) { + urls.clear(); + urls.add("https://juejin_bad.cn"); + urls.add("https://www.bilibili_bad.com"); + urls.add("https://www.baidu.com_bad"); + } + + private void test1(List urls) { + urls.clear(); + + urls.add("https://juejin.cn"); + urls.add("https://www.bilibili_bad.com"); + urls.add("https://www.baidu.com_bad"); + } + + private void test2(List urls) { + urls.clear(); + + urls.add("https://juejin_bad.cn"); + urls.add("https://www.bilibili.com"); + urls.add("https://www.baidu.com_bad"); + } + + private void test3(List urls) { + urls.clear(); + + urls.add("https://juejin_bad.cn"); + urls.add("https://www.bilibili_bad.com"); + urls.add("https://www.baidu.com:443"); + } + + /** + * 原来的实现在这里 + */ + @SuppressLint("CheckResult") + private void rawFunc() { Observable.create(new ObservableOnSubscribe() { @Override public void subscribe(@NonNull ObservableEmitter e) throws Exception { e.onNext(1); - e.onNext(2); - e.onNext(3); + // e.onNext(2); + // e.onNext(3); } }).concatMap(new Function>() { @Override @@ -67,4 +224,11 @@ public void accept(@NonNull String s) throws Exception { }); } + @Override + protected void onDestroy() { + super.onDestroy(); + if (mDisposable != null) { + mDisposable.dispose(); + } + } } diff --git a/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxFlatMapActivity.java b/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxFlatMapActivity.java index b10b3af..c297bb9 100644 --- a/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxFlatMapActivity.java +++ b/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxFlatMapActivity.java @@ -1,5 +1,6 @@ package com.nanchen.rxjava2examples.module.rxjava2.operators.item; +import android.annotation.SuppressLint; import android.util.Log; import com.nanchen.rxjava2examples.R; @@ -40,6 +41,7 @@ protected String getSubTitle() { return getString(R.string.rx_flatMap); } + @SuppressLint("CheckResult") @Override protected void doSomething() { Observable.create(new ObservableOnSubscribe() { diff --git a/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxMapActivity.java b/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxMapActivity.java index 864eda2..7810c49 100644 --- a/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxMapActivity.java +++ b/app/src/main/java/com/nanchen/rxjava2examples/module/rxjava2/operators/item/RxMapActivity.java @@ -1,5 +1,6 @@ package com.nanchen.rxjava2examples.module.rxjava2.operators.item; +import android.annotation.SuppressLint; import android.util.Log; import com.nanchen.rxjava2examples.R; @@ -31,6 +32,7 @@ protected String getSubTitle() { return getString(R.string.rx_map); } + @SuppressLint("CheckResult") @Override protected void doSomething() { Observable.create(new ObservableOnSubscribe() { diff --git a/app/src/main/java/com/nanchen/rxjava2examples/net/ProbeResult.java b/app/src/main/java/com/nanchen/rxjava2examples/net/ProbeResult.java new file mode 100644 index 0000000..8218bab --- /dev/null +++ b/app/src/main/java/com/nanchen/rxjava2examples/net/ProbeResult.java @@ -0,0 +1,25 @@ +package com.nanchen.rxjava2examples.net; + +/* + * @description 探测地址的结果 + * @author dr + * @time 1/17/24 2:34 PM + */ +public class ProbeResult { + + public final String url; + public final boolean flag; + + public ProbeResult(String url, boolean flag) { + this.url = url; + this.flag = flag; + } + + public String getUrl() { + return url; + } + + public boolean isFlag() { + return flag; + } +} diff --git a/app/src/main/java/com/nanchen/rxjava2examples/net/UrlProbe.java b/app/src/main/java/com/nanchen/rxjava2examples/net/UrlProbe.java new file mode 100644 index 0000000..f907d9e --- /dev/null +++ b/app/src/main/java/com/nanchen/rxjava2examples/net/UrlProbe.java @@ -0,0 +1,120 @@ +package com.nanchen.rxjava2examples.net; + +import android.util.Log; + +import com.nanchen.rxjava2examples.net.api.UrlProbeService; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import io.reactivex.Observable; +import io.reactivex.Scheduler; +import io.reactivex.schedulers.Schedulers; +import okhttp3.OkHttpClient; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; + +/* + * @description 探测URL是否可用工具,可以配置超时时间 + * @author dr + * @time 1/17/24 2:32 PM + */ +public class UrlProbe { + private static final String TAG = "UrlProbe"; + private final UrlProbeService service; + private final Scheduler scheduler; + + /** + * 超时定制 + */ + private final static int timeOut = 2; + + public UrlProbe() { + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .retryOnConnectionFailure(false) + // 连接超时时间 + .connectTimeout(timeOut, TimeUnit.SECONDS) + // 写入超时时间 + .writeTimeout(timeOut, TimeUnit.SECONDS) + // 读取超时时间 + .readTimeout(timeOut, TimeUnit.SECONDS) + .build(); + + Retrofit retrofit = new Retrofit.Builder() + // 必须设置一个地址,不然会崩溃,后面用的时候会替换地址的 + .baseUrl("https://default.com") + .client(okHttpClient) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); + // 探测的get接口 + service = retrofit.create(UrlProbeService.class); + scheduler = Schedulers.io(); + } + + /** + * 探测多个地址 + * + * @param urls 多个地址,不允许null + * @return 返回探测结果 + */ + public Observable probeUrls(List urls) { + // 在这里可以加empty的限制. + return Observable + .fromIterable(urls) + .concatMap(url -> probeUrlObs(url) + .subscribeOn(scheduler) + .onErrorReturnItem(new ProbeResult(url, false))) + .filter(probeResult -> probeResult.flag) + .take(1) + .switchIfEmpty(Observable.just(new ProbeResult(urls.get(0), false))); + } + + /** + * 探测地址的异步请求 + * + * @param url 请求地址 + * @param callback 异步回调 + */ + public void probeUrl(String url, Callback callback) { + Log.i(TAG, "start probeUrl: " + url); + Call call = service.probeUrl(url); + call.enqueue(callback); + } + + /** + * 探测地址是否可用 + * + * @param url 地址 + * @return 一个可以继续观察的obs + */ + private Observable probeUrlObs(String url) { + return Observable.create(emitter -> { + // 发送 HTTP GET 请求并获取响应码, 具体网络请求的实现 + probeUrl(url, new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Log.i(TAG, "URL is accessible."); + // 发送可以访问的地址到下游 + emitter.onNext(new ProbeResult(url, true)); + emitter.onComplete(); + } else { + Log.e(TAG, "onResponse URL returned error: " + response.code()); + // 发送失败,可以继续下一个 + emitter.onError(new Exception("Failed to probe " + url)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Log.e(TAG, "onFailure Failed to access URL: " + t.getMessage()); + // 发送失败,可以继续下一个 + emitter.onError(new Exception("Failed to probe " + url)); + } + }); + }); + } +} diff --git a/app/src/main/java/com/nanchen/rxjava2examples/net/api/UrlProbeService.java b/app/src/main/java/com/nanchen/rxjava2examples/net/api/UrlProbeService.java new file mode 100644 index 0000000..b5990f9 --- /dev/null +++ b/app/src/main/java/com/nanchen/rxjava2examples/net/api/UrlProbeService.java @@ -0,0 +1,24 @@ +package com.nanchen.rxjava2examples.net.api; + +import com.nanchen.rxjava2examples.net.ProbeResult; + +import io.reactivex.Observable; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Url; + +/* + * @description 探测URL地址是否可用的请求接口 + * @author dr + * @time 1/17/24 2:35 PM + */ +public interface UrlProbeService { + /** + * 需要探测的地址 + * + * @param url 探测的地址 + * @return Call + */ + @GET + Call probeUrl(@Url String url); +} diff --git a/app/src/main/res/layout/activity_rx_operator_base.xml b/app/src/main/res/layout/activity_rx_operator_base.xml index 504e6e5..178e060 100644 --- a/app/src/main/res/layout/activity_rx_operator_base.xml +++ b/app/src/main/res/layout/activity_rx_operator_base.xml @@ -1,16 +1,35 @@ - - +