使用RxJava对Cursor的操作Sample

作者: YoKey | 来源:发表于2015-12-18 20:38 被阅读2524次

RxCursorSample是我在一个用Rx特性实现的图片多选/单选选择器
特性:
1.使用RxJava操作ContentProvider的Cursor,并且在图片详情页面,使用RxJava操作符实现了一个RecyclerView的简单动画,提升了用户体验
2.使用RxBus实现组件通信
3.适配了Android 6.0的权限请求

其他几个特性不分析了,主要分析下项目中的Rx:(源码在文章结尾)

** RxBus(这里是我的一篇关于RxBus的简书)负责了Fragment和Activity通信**

Activity接收RxBus的事件,然后通过FragmentManager切换Fragment。
下面是在ImagePickerActivity的主要代码:

private void initRxBus() {  
    // 接收 切换相册事件
    rxSubscriptions.add(RxBus.getDefault().toObserverable(AddDetailEvent.class)
            .map(addDetailEvent -> addDetailEvent.getBucketName())
            .subscribe(bucketName -> {
                // add DetailExploreFragment                
                addFragment(DetailExploreFragment.newInstance(bucketName, isMultiplePick));
            }, throwable -> {
                throwable.printStackTrace();
                Toast.makeText(this, R.string.yo_switch_bucket_exception,Toast.LENGTH_SHORT).show();
            }));

    // 接收 切换预览事件
    rxSubscriptions.add(RxBus.getDefault().toObserverable(AddPreviewEvent.class)
            .map(addPreviewEvent -> addPreviewEvent.getImgs())
            .subscribe(imgs -> {
                toolbar.setTitle(R.string.yo_preview);
                // add PreviewFragment
                addFragment(PreviewFragment.newInstance(imgs, isMultiplePick));
            }, throwable -> {
                throwable.printStackTrace();
                Toast.makeText(this, R.string.yo_switch_preview_exception, Toast.LENGTH_SHORT).show();
            }));
}

上面的代码中,比如点击了“预览”,由DetailExploreFragment跳转至PreviewFragment,通过RxBus将事件传递到Activity,Activity再切换PreviewFragment

下面是DetailExploreFragment 中的代码,点击“预览”,发送事件

btnPreview.setOnClickListener((v) -> {
    // addFragment
    RxBus.getDefault().post(new AddPreviewEvent(imgs));
});

** RxJava操作ContentProvider的Cursor**

创建Observable<Cursor>的代码:

private Observable<Cursor> cursorObservable() {
    return Observable.create(new Observable.OnSubscribe<Cursor>() {
        @Override
        public void call(Subscriber<? super Cursor> subscriber) {
            if (!subscriber.isUnsubscribed()) {
                try{
                    Cursor cursor = getCursor();
                    // 判断!subscriber.isUnsubscribed() 是为了在取消订阅时,保证cursor可以及时关闭
                    while (cursor.moveToNext() && !subscriber.isUnsubscribed()) {
                        subscriber.onNext(cursor);
                    }
                    subscriber.onCompleted();
                }catch(Exeception e){
                    subscriber.onError(e);
                }finally {
                     assert cursor != null;
                     cursor.close();
                }
            }
        }
    });
}

private Cursor getCursor() {
    String[] mediaColumns = new String[]{
            MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
            MediaStore.Images.Media.DATA,
            "COUNT(*) AS " + COLUMN_NAME_COUNT
    };
    // SELECT _data, COUNT(*) AS v_count  FROM video WHERE ( GROUP BY bucket_display_name)
    String selection = " 1=1 ) GROUP BY (" + MediaStore.Images.Media.BUCKET_DISPLAY_NAME;
    return _activity.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mediaColumns, selection, null, null);
}

Cursor是个比较特殊的数据库操作类,在该例中,在Cursor的游标移动到结尾之前,会一直发射Observable<Cursor>的数据源。
代码中while里有判断!subscriber.isUnsubscribed(),是为了在取消订阅时而cursor还没有执行完的情况下,保证cursor可以及时关闭。

下面在IO线程中将Observable<Cursor>转换成List<BucketEntity>,最终在主线程中的RecyclerView中绑定数据显示。

 private void initData() {
        subscription = cursorObservable()
                .subscribeOn(Schedulers.io())
                .filter(cursor -> {
                    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    return !path.endsWith(".gif");
                })
                .map(cursor -> {
                    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    String bucket_name = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME));
                    int count = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_COUNT));
                    return new BucketEntity(bucket_name, count, path);
                })
                .toList()
                // 如果不用toList()转成List  一定要调用onBackpressureBuffer()方法,防止数据源发射过快,导致异常MissingBackpressureException
                // .onBackpressureBuffer()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        bucketEntity -> {
                            adapter.setDatas(bucketEntity);
                        }, throwable -> {
                            throwable.printStackTrace();
                            Toast.makeText(_activity, R.string.yo_find_exception, Toast.LENGTH_SHORT).show();
                        }
                );
    }

filter操作符过滤掉的gif图片,map操作符将Observable<Cursor>对象转换成Obser<BucketEntity>,这里有2种绑定数据到RecyclerView的方法:

1. 数据源集合绑定:
使用toList操作符,它将一连串的Observable<T>类型数据源集合转换成一个Observable<List<T>>返回,最后一次性绑定数据到RecyclerView上

2. 单数据源依次绑定:
即通过Adapter.addData(bean),一个一个数据逐个绑定到RecyclerView上,但是如果直接这样操作,则会报预测MissingBackpressureException的异常,该异常是由于数据源发射速度过快导致的。
可以使用onBackpressureBuffer操作符解决,它可以缓冲发射速度过快的数据源,直到所有数据源全部发射出去。

这个例子中,使用的是第一种方法
下面这段代码使用了第二种,同时加入了一个操作符,视觉上形成一个逐条加入的动画

private void initData() {
        subscription = cursorObservable()
                .subscribeOn(Schedulers.io())
                // 延迟60ms发射数据 形成动画, delay默认在computation线程 要主动切换到当前的线程
                .delay(60, TimeUnit.MILLISECONDS, Schedulers.immediate())
                .filter(cursor -> {
                    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    return !path.endsWith(".gif");
                })
                .map(cursor -> {
                    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    return new File(path);
                })
                // onBackpressureBuffer()方法,防止数据源发射过快引起的MissingBackpressureException
                .onBackpressureBuffer()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        file -> {
                            adapter.addData(file);
                        }, throwable -> {
                            throwable.printStackTrace();
                            Toast.makeText(_activity, R.string.yo_find_exception, Toast.LENGTH_SHORT).show();
                        }
                );
    }

delay操作符可以延迟发射数据源。

因为该操作符默认在computation线程中运行,我们需要延迟的时间在数据源处理的IO线程上,所以主动指定在Schedules.immediate()上,即当前的IO线程上。(关于RxJava的线程高效使用,可以参考小鄧子的这篇译文

这样的话,每隔60ms观察者就会收到一个经过加工的File数据源,然后将其绑定到RecyclerView上。
在视觉上,每个RecyclerView的Item都会在上个Item显示后的60ms后显示,仅仅通过一个操作符完成了一个Item显示动画 :)

更多详情可以查看源码,这里是完整的代码

相关文章

  • 使用RxJava对Cursor的操作Sample

    RxCursorSample是我在一个用Rx特性实现的图片多选/单选选择器特性:1.使用RxJava操作Conte...

  • 对rxjava实现思想的个人思考

    这篇文章不是讲解rxjava如何使用,而是对其设计的思考。使用过rxjava的同学们都注意到rxjava的操作符很...

  • Android数据库操作----Cursor

    Cursor 游标 Android使用的数据库是SQLite数据库,对于数据库记录的操作,可以使用Cursor来进...

  • RxJava

    其它文章 RxJava操作符大全 1、RxJava之一——一次性学会使用RxJava RxJava简单的使用和使用...

  • MySQLdb 菜鸟笔记 1

    1 打开数据库连接 只有port字段类型为int 2 使用cursor()方法获取操作游标 cursor = db...

  • Android笔记——关于Cursor类的介绍

    使用过 SQLite数据库的童鞋对 Cursor 应该不陌生,加深自己和大家对Android 中使用 Cursor...

  • RxSwift - 操作符四

    sample 不定期的对 Observable 取样 sample 操作符将不定期的对源 Observable 进...

  • RxJava相关

    Room ? RxJava使用RxJava操作数据库 RxPermissionsRxJava实现的Android运...

  • zip操作符的error处理

    熟悉rxjava的同学肯定对操作符不会陌生,比如我们使用map操作符处理数据,使用zip操作符合并多个请求,这里演...

  • RxJava 操作符第二波

    RxJava操作符第二波啦,上篇RxJava 操作符第一波和本篇都只是简单介绍rxjava操作符的使用,有哪里写的...

网友评论

  • PonyNo1:大哥我觉得写博客,最好还是不要用Lambda表达式,能看懂的还可以看不懂的就懵比了
    YoKey:@Destiny丶伟 :joy: 为了阅读起来简洁,->的表达式也比较容易~ 不过以后注意,不用表达式~
  • 1e592c21f6de:博主推荐下Rx入门和最佳实践的文章吧 :smile: ,刚看完你的两篇关于Fragment的文章,感觉很有力道,希望在Rx上也能学到更多的东西
    1e592c21f6de: @YoKey 谢谢,抛物线的这篇文章写得很通俗,入门再好不过
    YoKey:@imxilife 首推扔物线的那篇文章,这里有篇集合,可以看看 :blush:
    https://github.com/THEONE10211024/RxJavaSamples
  • 小鄧子:我之前也用Rxjava做了个图片选择器,而且我发现用Rx来写,简直不能更爽了。不过我还是有一些建议,在.create()中可以使用 subscriber.add()以便在解除订阅的时候关闭未完成的cursor;在类似头像等图片选择器中“.gif”的图片可以在数据源发射之初过滤掉,也就是说我们可以不发射这类数据,而不是在下游操作符中筛选,这样也能减少Backpressure的压力;另外值得一提的是.tolist()的触发条件是.onCompleted()的调用,所以一定注意不要连续发送一组无法停止的事件,因为我们无法使用.unsubscribe()来停止它。还有一点小提醒,.ofType()操作符内置就是.filter().cast()。这或许对你的RxBus有用。欢迎多交流。
    YoKey:@小鄧子 :smile: 太感谢啦,这些建议太棒了~ RxJava实在太优雅了, 多多交流哈!
  • 耗子点灯:Cursor cursor = getCursor();
    while (cursor.moveToNext()) {
    subscriber.onNext(cursor);
    }
    subscriber.onCompleted();
    cursor.close();

    以上应该把 cursor.close(); 包在try catch finally里面,否则出异常了也没有调用onError
    YoKey:@谢三哥 :smile: 好建议!
    谢三弟:@YoKey 最好统一处理~ 一个工具类写一个关闭方法,这些都是实现了 Java 里的 closeable 接口,而这个接口里就只有一个 close 方法。
    YoKey:@耗子点灯 哦 是的是的 多谢提醒

本文标题:使用RxJava对Cursor的操作Sample

本文链接:https://www.haomeiwen.com/subject/uapphttx.html