如何应对 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 类;

相关推荐
alexhilton1 小时前
Compose Unstyled:Compose UI中失传的设计系统层
android·kotlin·android jetpack
柿蒂4 小时前
从动态缩放自定义View,聊聊为什么不要把问题复杂化
android·ai编程·android jetpack
kerli4 小时前
kotlin协程系列:callbackFlow
android·kotlin
没有了遇见5 小时前
Android 原生定位实现(替代融合定位收费,获取经纬度方案)
android·kotlin
一枚小小程序员哈5 小时前
基于Android的车位预售预租APP/基于Android的车位租赁系统APP/基于Android的车位管理系统APP
android·spring boot·后端·struts·spring·java-ee·maven
诸神黄昏EX5 小时前
Android SystemServer 系列专题【篇四:SystemServerInitThreadPool线程池管理】
android
用户2018792831676 小时前
pm path 和 dumpsys package 的区别
android
是店小二呀6 小时前
【C++】智能指针底层原理:引用计数与资源管理机制
android·java·c++
DoubleYellowIce7 小时前
一次混淆XLog导致的crash分析记录
android