week.ioRxJava系列专题(Android方向)今日看点

使用RxJava对Cursor的操作Sample

2015-12-18  本文已影响2524人  YoKey

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

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

上一篇 下一篇

猜你喜欢

热点阅读