使用RxJava对Cursor的操作Sample

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显示动画 :)

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容