如何应对 Android 面试官 -> 玩转 RxJava (基础使用)

前言


本章开始 RxJava 的讲解,主要分为三个章节;

  • RxJava 应用场景
  • RxJava 模式与原理
  • RxJava 原理与自定义操作符

首先,开始我们的第一章节:RxJava 应用场景

RxJava 应用场景


第一章节主要分为五部分

  1. 核心思想
  2. RxJava 配合 Retrofit
  3. 防抖
  4. 网络嵌套
  5. doOnNext 运用

核心思想

Rx 思维:以下载图片为例,会有一个起点,一个终点,起点获取数据(图片 api 地址)将 Bitmap 流线下来传递给终点,从起点到终点没有断掉,思维链式调用下来,将结构铺平,思维链式中间可以添加任何拦截卡片(所以 RxJava 的响应式编程又叫卡片式编程,起点到终点之间可以添加任何其他业务需求,(例如:给图片加水印,给图片的下载加入日志系统等等));

有一个起点和一个终点,起点开始流向我们的「事件」把事件流向到终点,只不过在流向到过程中,可以增加拦截,拦截时可以对事件进行改变,终点只关心它上一个拦截;

我们来用一个 『下载』的能力是演示 RxJava 的核心思想;

首先,我们来看下传统的下载方式:

ini 复制代码
public void downloadImageAction(View view) {
    progressDialog = new ProgressDialog(this);
    progressDialog.setTitle("下载图片中...");
    progressDialog.show();

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                URL url = new URL(PATH);
                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                httpURLConnection.setConnectTimeout(5000);
                int responseCode = httpURLConnection.getResponseCode(); // 才开始 request
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    InputStream inputStream = httpURLConnection.getInputStream();
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                    Message message = handler.obtainMessage();
                    message.obj = bitmap;
                    handler.sendMessage(message);
                }
            } catch (Exception e) {
                    e.printStackTrace();
            }
        }
    }).start();
}

private final Handler handler = new Handler(new Handler.Callback() {

    @Override
    public boolean handleMessage(@NonNull Message msg) {
        Bitmap bitmap = (Bitmap) msg.obj;
        image.setImageBitmap(bitmap);

        if (progressDialog != null) progressDialog.dismiss();
        return false;
    }
});

传统方式,使用 new Thread 的方式,进行图片的下载,以及通过 Handler 将下载结果发送到主线程,然后进行展示;

整体来看,零零散散,比较麻烦,那么,我们怎么来规范下它呢,我们可以采用 RX 的思维方式;

java 复制代码
public void rxJavaDownloadImageAction(View view) {
    // 起点
    Observable.just(PATH)  // 内部会分发PATH,第二步
         // TODO 第三步
        .map(new Function<String, Bitmap>() {
            @Override
            public Bitmap apply(String s) throws Exception {
                URL url = new URL(s);
                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                httpURLConnection.setConnectTimeout(5000);
                int responseCode = httpURLConnection.getResponseCode(); // 才开始 request
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    InputStream inputStream = httpURLConnection.getInputStream();
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                    return bitmap;
                }
                return null;
            }
        })
        // 给上面分配异步线程
        .subscribeOn(Schedulers.io())
        // 给下面分配主线程
        .observeOn(AndroidSchedulers.mainThread())
        // 订阅 起点 和 终点 订阅起来
        .subscribe(
                // 终点
                new Observer<Bitmap>() {
                
                    // 订阅开始
                    @Override
                    public void onSubscribe(Disposable d) {
                        // 预备开始要分发
                        // 第一步
                        progressDialog = new ProgressDialog(DownloadActivity.this);
                        progressDialog.setTitle("download run");
                        progressDialog.show();
                    }

                    // 第四步
                    // 拿到事件
                    @Override
                    public void onNext(Bitmap bitmap) {
                        image.setImageBitmap(bitmap);
                    }

                    // 错误事件
                    @Override
                    public void onError(Throwable e) {

                    }

                    // 第五步
                    // 完成事件
                    @Override
                    public void onComplete() {
                        if (progressDialog != null) {
                            progressDialog.dismiss();
                        }
                    }
        });

    }

将被观察者 Observable (被观察者)与 Observer (观察者)通过 subscribe() 方法进行关联起来;

起点分发一个 String 数据,重点观察到的是一个 Bitmap 数据,中间通过 map 等进行变换;

当我们调用 subscribe 方法之后,整体采用 U 型结构来执行订阅和分发;

第一步:先执行 onSubscribe 方法,订阅开始,我们通常在这里展示 loading;

第二步:执行 Observable.just(PATH) 逻辑,将 PATH 分发出去;

第三步:执行 map() 操作,卡片式拦截,拦截将 String 转换成目标数据 Bitmap;

第四步:执行 onNext() 拿到 Bitmap 事件,执行 UI 的展示;

第五步:执行 onComplete() 完成事件,我们通常在这里隐藏 loading;

这种 RX 思维,不会让我们整体流程中断,从起点到终点的任何一个过程中,我们都可以添加变化的卡片,例如:我们需要给下载的图片添加一个水印,那么我们只需要再添加一个 map 操作,来将下载下来的 bitmap 转换成带有水印的 bitmap

java 复制代码
// 添加水印
.map(new Function<Bitmap, Bitmap>() {
    @Override
    public Bitmap apply(Bitmap bitmap) throws Exception {
        Paint paint = new Paint();
        paint.setTextSize(88);
        paint.setColor(Color.RED);
        return drawTextToBitmap(bitmap, "添加水印...",paint, 88 , 88);
    }
})

drawTextToBitmap 是一个具体加水印的方法,这里不是重点

ini 复制代码
private final Bitmap drawTextToBitmap(Bitmap bitmap, String text, Paint paint, int paddingLeft, int paddingTop) {
    Bitmap.Config bitmapConfig = bitmap.getConfig();
    paint.setDither(true); // 获取跟清晰的图像采样
    paint.setFilterBitmap(true);// 过滤一些
    if (bitmapConfig == null) {
        bitmapConfig = Bitmap.Config.ARGB_8888;
    }
    bitmap = bitmap.copy(bitmapConfig, true);
    Canvas canvas = new Canvas(bitmap);
    canvas.drawText(text, paddingLeft, paddingTop, paint);
    return bitmap;
}

变换封装

java 复制代码
public final static <UD> ObservableTransformer<UD, UD> transformer() {
    return new ObservableTransformer<UD, UD>() {
        @Override
        public ObservableSource<UD> apply(Observable<UD> upstream) {
            return upstream.subscribeOn(Schedulers.io())     // 给上面代码分配异步线程
                    .observeOn(AndroidSchedulers.mainThread()) // 给下面代码分配主线程;
                    .map(new Function<UD, UD>() {
                        @Override
                        public UD apply(UD ud) throws Exception {
                            return ud;
                        }
                    });
        }
    };
}

RxJava 配合 Retofit

Retrofit 的基础使用这里就不做介绍了, Retrofit 本质上就是 OKHttp 的封装,并且它也支持了 RxJava,我们具体来看下是如何支持的,我们就以 玩Android 为例子吧;

less 复制代码
public interface WanAndroidApi {

    // 总数据
    @GET("project/tree/json")
    Observable<ProjectBean> getProject();

    // Item 数据
    @GET("project/list/{pageIndex}/json")
    Observable<ProjectItem> getProjectItem(@Path("pageIndex") int pageIndex, @Query("cid") int cid);
    
}

接下来我们需要一个 Retrofit 管理类,RetrofitUtils

arduino 复制代码
public class RetrofitUtils {

    private static final String TAG = "HttpUtils";

    /**
     * 默认环境
     */
    public static String BASE_URL = "https://www.wanandroid.com/";

    public static void setBaseUrl(String baseUrl) {
        BASE_URL = baseUrl;
    }

    /**
     * 根据各种配置创建出 Retrofit
     *
     * @return 返回创建好的 Retrofit
     */
    public static Retrofit getRetrofit() {
        // OKHttp客户端
        OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
        // 各种参数配置
        OkHttpClient okHttpClient = httpBuilder
                .addNetworkInterceptor(new StethoInterceptor())
                .readTimeout(10000, TimeUnit.SECONDS)
                .connectTimeout(10000, TimeUnit.SECONDS)
                .writeTimeout(10000, TimeUnit.SECONDS)
                .build();
        return new Retrofit.Builder().baseUrl(BASE_URL)
                // 请求用 OKhttp
                .client(okHttpClient)
                // 响应RxJava
                // 添加一个 json 解析的工具
                .addConverterFactory(GsonConverterFactory.create(new Gson()))
                // 添加 Rxjava 处理工具
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }
}

然后我们就可以在 Activity 中调用这个 API 来完成网络请求;

scala 复制代码
public class UseActivity extends AppCompatActivity {
    private WanAndroidApi api;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_use);
    
        api = RetrofitUtils.getRetrofit().create(WanAndroidApi.class)
    }
    
     public void getProjectAction(View view) {
        // 获取网络API
        api.getProject()
            .subscribeOn(Schedulers.io()) // 上面异步
            .observeOn(AndroidSchedulers.mainThread()) // 下面主线程
            .subscribe(new Consumer<ProjectBean>() {
                @Override
                public void accept(ProjectBean projectBean) throws Exception {
                    Log.d(TAG, "accept: " + projectBean);
                }
            });
    }
}

点击事件中,触发网络请求;

RxJava 配合 Retrofit 的时候,通常用来处理返回结果,也就是对 response 进行 gson,xml 等格式化处理,并将格式化之后的结果返回给主线程进行业务处理;

防抖

抖动说的是:短时间内多次请求,就会造成抖动;

arduino 复制代码
implementation 'com.jakewharton.rxbinding2:rxbinding:xxxx' // 操作功能防抖

那么如何处理这种抖动呢?我们可以借助于 RxBinding,对于短时间内的多次点击,只响应一次;

scss 复制代码
RxView.clicks(view)
    .throttleFirst(2000, TimeUnit.MILLISECONDS)
    .subscribe{...}

通过 throttleFirst 来解决多次点击的问题,2s 内点击多次,只响应一次;

网络嵌套

我们接下来实现联动查询的功能;

java 复制代码
private void antiShakeActon() {
        // 注意:(项目分类)查询的id,通过此id再去查询(项目列表数据)
        Button bt_anti_shake = findViewById(R.id.bt_anti_shake);

        RxView.clicks(bt_anti_shake)
                .throttleFirst(2000, TimeUnit.MILLISECONDS) // 2秒钟之内 响应你一次
                .subscribe(new Consumer<Object>() {
                    @Override
                    public void accept(Object o) throws Exception {
                        api.getProject() // 查询主数据
                        .compose(DownloadActivity.rxud())
                        .subscribe(new Consumer<ProjectBean>() {
                            @Override
                            public void accept(ProjectBean projectBean) throws Exception {
                                for (ProjectBean.DataBean dataBean : projectBean.getData()) {
                                    // 查询item数据
                                    api.getProjectItem(1, dataBean.getId())
                                    .compose(DownloadActivity.rxud())
                                    .subscribe(new Consumer<ProjectItem>() {
                                        @Override
                                        public void accept(ProjectItem projectItem) throws Exception {
                                            Log.d(TAG, "accept: " + projectItem); // 可以UI操作
                                        }
                                    });
                                }
                            }
                        });
                    }
                });
    }

可以看到,我们每次都是在 accept 的回调方法中进行下一步的操作,这样的写法看起来太嵌套了,这才两次网络请求,如果是多个的网络请求,那么这样的回调地狱式的处理就要被打了;

RxJava 为了解决回调地狱,保证链式结果的铺平,提供了 flatMap 操作符;

我们使用 flatMap 来看下如何将结构铺平,并解决这个回调地狱的问题;

java 复制代码
//
private void antiShakeActonUpdate() {
        // 注意:项目分类查询的id,通过此id再去查询(项目列表数据)

        // 对那个控件防抖动?
        Button bt_anti_shake = findViewById(R.id.bt_anti_shake);

        RxView.clicks(bt_anti_shake)
                .throttleFirst(2000, TimeUnit.MILLISECONDS) // 2秒钟之内 响应你一次
                // 我只给下面 切换 异步
                .observeOn(Schedulers.io())
                .flatMap(new Function<Object, ObservableSource<ProjectBean>>() {
                    @Override
                    public ObservableSource<ProjectBean> apply(Object o) throws Exception {
                        return api.getProject(); // 主数据
                    }
                })
                .flatMap(new Function<ProjectBean, ObservableSource<ProjectBean.DataBean>>() {
                    @Override
                    public ObservableSource<ProjectBean.DataBean> apply(ProjectBean projectBean) throws Exception {
                        return Observable.fromIterable(projectBean.getData());
                    }
                })
                .flatMap(new Function<ProjectBean.DataBean, ObservableSource<ProjectItem>>() {
                    @Override
                    public ObservableSource<ProjectItem> apply(ProjectBean.DataBean dataBean) throws Exception {
                        return api.getProjectItem(1, dataBean.getId());
                    }
                })
                .observeOn(AndroidSchedulers.mainThread()) // 给下面切换主线程
                .subscribe(new Consumer<ProjectItem>() {
                    @Override
                    public void accept(ProjectItem projectItem) throws Exception {
                        Log.d(TAG, "accept: " + projectItem);
                    }
                });
    }

变换之后,清爽了很多了;

所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列;

flatMap() 中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中;

flatMap 的原理:

  1. 使用传入的事件对象创建一个 Observable 对象;
  2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件;
  3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法;

总结:把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat;

doOnNext 运用

假设我们有这样一个需要频繁切换 UI 和非 UI 的场景,例如:新用户注册,注册成功之后更新注册成功的 UI,并立马执行登录,并在登录成功之后更新登录成功的 UI;

我们可以借助 doOnNext() 来实现,本质上就是:在最终的 onNext 之前,做一些其他主线程的更新 UI 处理;

也就是在登录成功的 onNext 之前处理注册成功的 UI;

typescript 复制代码
public void request2(View view) {

        /**
         * 一行代码 实现需求
         * 需求:
         *   还有弹出加载
         *  * 1.请求服务器注册操作
         *  * 2.注册完成之后,更新注册UI
         *  * 3.马上去登录服务器操作
         *  * 4.登录完成之后,更新登录的UI
         */
        RetrofitUtils.getRetrofit().create(IReqeustNetwor.class)
                .registerAction(new RegisterRequest()) // 1.请求服务器注册操作
                .subscribeOn(Schedulers.io()) // 给上面异步线程
                .observeOn(AndroidSchedulers.mainThread()) // 给下面分配主线程
                .doOnNext(new Consumer<RegisterResponse>() {
                    @Override
                    public void accept(RegisterResponse registerResponse) throws Exception {
                        // 2.注册完成之后,更新注册UI
                        Log.d(TAG, "注册完成,更新注册 UI")
                    }
                })
                // 3.马上去登录服务器操作
                .observeOn(Schedulers.io()) // 给下面分配了异步线程
                .flatMap(new Function<RegisterResponse, ObservableSource<LoginResponse>>() { // 4.执行登录的逻辑
                    @Override
                    public ObservableSource<LoginResponse> apply(RegisterResponse registerResponse) throws Exception {
                        Observable<LoginResponse> loginResponseObservable = RetrofitUtils.getRetrofit().create(IReqeustNetwor.class)
                                .loginAction(new LoginReqeust());
                        return loginResponseObservable;
                    }
                })
                .observeOn(AndroidSchedulers.mainThread()) // 给下面执行主线程
                .subscribe(new Observer<LoginResponse>() {
                    // 一定是主线程,为什么,因为 subscribe 马上调用 onSubscribe
                    @Override
                    public void onSubscribe(Disposable d) {
                        progressDialog = new ProgressDialog(RequestActivity.this);
                        progressDialog.show();
                        // UI 操作
                        disposable = d;
                    }

                    @Override
                    public void onNext(LoginResponse loginResponse) {
                        // 5.登录完成之后,更新登录的UI
                        Log.d(TAG, "登录成功,更新登录 UI")
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    // 6.最终的结束回调
                    @Override
                    public void onComplete() {
                        // 最终章
                        if (progressDialog != null) {
                            progressDialog.dismiss();
                        }
                    }
                });

    }

背压

当数据流通过异步的步骤执行时,这些步骤的执行速度可能不一致。也就是说上流数据发送太快,下流没有足够的能力去处理。为了避免这种情况,一般要么缓存上流的数据,要么抛弃数据。但这种处理方式,有时会带来很大的问题;

为此,RxJava带来了backpressure的概念。背压是一种流量的控制步骤,在不知道上流还有多少数据的情形下控制内存的使用,表示它们还能处理多少数据;

支持背压的有 Flowable 类,不支持背压的有 ObservableSingle , Maybe and Completable 类;

相关推荐
介一安全33 分钟前
【Frida Android】基础篇15(完):Frida-Trace 基础应用——JNI 函数 Hook
android·网络安全·ida·逆向·frida
吞掉星星的鲸鱼37 分钟前
android studio创建使用开发打包教程
android·ide·android studio
陈老师还在写代码40 分钟前
android studio 签名打包教程
android·ide·android studio
csj5040 分钟前
android studio设置
android
hifhf43 分钟前
Android Studio gradle下载失败报错
android·ide·android studio
陈老师还在写代码44 分钟前
android studio,java 语言。新建了项目,在哪儿设置 app 的名字和 logo。
android·java·android studio
2501_916007473 小时前
Fastlane 结合 开心上架(Appuploader)命令行实现跨平台上传发布 iOS App 的完整方案
android·ios·小程序·https·uni-app·iphone·webview
listhi5205 小时前
Vue.js 3的组合式API
android·vue.js·flutter
用户69371750013845 小时前
🚀 Jetpack MVI 实战全解析:一次彻底搞懂 MVI 架构,让状态管理像点奶茶一样丝滑!
android·android jetpack
2501_915918416 小时前
iOS 上架应用市场全流程指南,App Store 审核机制、证书管理与跨平台免 Mac 上传发布方案(含开心上架实战)
android·macos·ios·小程序·uni-app·cocoa·iphone