如果你未阅读第一篇,请转至RxJava本质(一)
第三章 你好,响应式世界
在前面的章节里,我们理论性的回顾了观察者模式,我们粗略学习了从list或者从一个已经存在的函数里创建Observables。在这章,我们将要去使用我们所学到的去创建我们第一个响应式安卓App。首先,我们将要去创建环境,引入所要求的库和一些很有用的库。然后我们将要使用一些不同凡响的方式并集中使用RxJava来创建一个简单的包含少许RecyclerView项目的App。
开启引擎
我们将要使用IntelliJ IDEA/Android Studio来学习这个项目,因此下面这个截图你应当熟悉。(我就不截了)
让我们深入并并创建一个新的安卓项目。你可以自己创建的项目或者引入一个本书提供的项目。这取决于你的首选项。
如果你想用Android Studio创建一个新的项目,就如往常一样,你可以参考官方文档[Android Developer](http://developer.android.com/training/basics/firstapp/creating-project.html)
依赖
显而易见,我们将要使用Gradle去管理依赖列表,我们的Build.Gradle文件将会像下面这样:
正如你所见,我们要引入RxAndroid。RxAndroid是RxJava的升级版,特别为安卓设计。
RxAndroid
RxAndroid是RxJava家族的一部分,它基于RxJava 1.0.x,添加了一些有用的类。最重要的是它为安卓添加了具体的Schedulers(UI线程)。我们将在第七章与Schedulers打交道。
实用工具
务实地,我们也引入了Lombok和Butter Knife。它们两者将帮助我们在安卓App编程中避开很多冗余代码。
Lombok
Lombok使用注释来为你产生代码。我们将使用它来产生getter和setter,toString(),equals()和hashCode()。它伴随着一个Gradle依赖和一个Android Studio插件。
Butter Knife
Butter Knife使用注释来帮我们摆脱findViewById()和设置点击监听的痛苦(Butter Knife只能注释与View相关的,比如View的点击、长按)。至于Lombok(估计是印刷错误,应该是Butter Knife),我们可以引入依赖并且安装插件来获取更好的体验(插件名:Android ButterKnife Zeleny)。
RetroLambda
我们最后引入RetroLambda,因为即使我们使用Android Studio来学并且它的Java 1.6支持,我们想要使用Java8 Lambda函数来减少冗余代码。
我们的第一个Observable
在我们的第一个例子中,我们将要去获取安装app的列表并使用RecyclerView来展示它们。我们也有花哨的Pull_To_Refresh功能和一个进度条来通知用户任务正在进行。
首先,我们创建我们的Observable,我们需要一个函数去获取安装app的列表并把它提供给Observable。我们一个一个发射项目(item),然后把他们合并到一个列表里,以此来展示响应式方法的灵活性。
import com.packtpub.apps.rxjava_essentials.apps.AppInfo;
private Observable<AppInfo> getApps() {
return Observable.create(subscriber -> {
List<AppInfoRich> apps = new ArrayList<>();
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> infos = getActivity()
.getPackageManager().queryIntentActivities(mainIntent, 0);
for (ResolveInfo info : infos) {
apps.add(new AppInfoRich(getActivity(), info));
}
for (AppInfoRich appInfo : apps) {
Bitmap icon =
Utils.drawableToBitmap(appInfo.getIcon());
String name = appInfo.getName();
String iconPath = mFilesDir + "/" + name;
Utils.storeBitmap(App.instance, icon, name);
if (subscriber.isUnsubscribed()) {
return;
}
subscriber.onNext(new AppInfo(name, iconPath,
appInfo.getLastUpdateTime()));
}
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
});
}
AppInfo对象像以下这样:
@Data
@Accessors(prefix = "m")
public class AppInfo implements Comparable<Object> {
long mLastUpdateTime;
String mName;
String mIcon;
public AppInfo(String name, String icon, long
lastUpdateTime) {
mName = name;
mIcon = icon;
mLastUpdateTime = lastUpdateTime;
}
@Override
public int compareTo(Object another) {
AppInfo f = (AppInfo) another;
return getName().compareTo(f.getName());
}
}
在发射项目或者完成序列(sequence)前核对Observer的订阅内容是很重要的。这使得代码更有效率,因为没有任何人在等待他们时我们不会产生不必要的项目。
此刻,我们可以订阅这个Observable并观察它。订阅一个Observable意味着当我们所需的资料来时必须提供动作(action)去执行。
我们此刻是什么场景?喔,我们正在展示一个旋转的进度条,等待资料。当资料进来时,我们必须隐藏进度条,填充列表并最终显示。现在我们知道当所有事完成时做什么。那出错的场景呢?在出错时,我们只需用Toast来展示一个错误信息。
使用Butter Knife,我们获得了list和pull_to_refresh元素的参考。
@InjectView(R.id.fragment_first_example_list)
RecyclerView mRecyclerView;
@InjectView(R.id.fragment_first_example_swipe_container)
SwipeRefreshLayout mSwipeRefreshLayout;
我们正在使用安卓5.x的标准部件:RecyclerView和SwipeToRefreshLayout。这个截屏展示了我们这个简单的App fragment布局文件
我们在使用一个pull-to-refresh的方法。因此列表可以来自初始化或者用户的刷新动作触发。在两个场景里我们有两个相同的行为,因此我们将我们的Observer放在同一个函数里使得易于复用。这是我们的带有成功、出错、完成行为的Observable:
private void refreshTheList() {
getApps().toSortedList()
.subscribe(new Observer<List<AppInfo>>() {
@Override
public void onCompleted() {
Toast.makeText(getActivity(), "Here is the
list!", Toast.LENGTH_LONG).show();
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something
went wrong!", Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNext(List<AppInfo> appInfos) {
mRecyclerView.setVisibility(View.VISIBLE);
mAdapter.addApplications(appInfos);
mSwipeRefreshLayout.setRefreshing(false);
}
});
}
一个函数给了我们在两个场景里使用相同块的可能。当fragment载入或者当用户使用pull-to-refresh触发设置refreshTheList()时我们只需调用refreshTheList()
mSwipeRefreshLayout.setOnRefreshListener(this::refreshTheList);
我们的第一个例子现在完成了,运行吧!
从一个list创建一个Observable
在这个例子里,我们将要引入from()函数。使用这个特别的”创建“函数我们能从一个list创建一个Observable。Observable将会从list里发射每个元素,我们可以订阅来对这些发射的元素做出反应。
为了实现和第一个例子一样的结果,我们将要升级每个onNext()的适配器,添加元素并通知插入。
我们将使用与第一个例子一样的架构。主要的差别在于我们将要获取安装应用的列表:将会有外部实体提供。
mApps = ApplicationsList.getInstance().getList();
在获取列表后,我们仅需使得它响应起来并填充RecyclerView项目。
private void loadList(List<AppInfo> apps) {
mRecyclerView.setVisibility(View.VISIBLE);
Observable.from(apps)
.subscribe(new Observer<AppInfo>() {
@Override
public void onCompleted() {
mSwipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), "Here is the
list!", Toast.LENGTH_LONG).show();
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something
went wrong!", Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNext(AppInfo appInfo) {
mAddedApps.add(appInfo);
mAdapter.addApplication(mAddedApps.size()
- 1, appInfo);
}
});
}
正如你所见,我们我们将安装应用的列表作为参数传递给了from()函数,然后我们订阅来生成Observable。这里的Observer与第一个例子的Observer很相似。一个主要的不同在于我们在onCompleted()函数里停止旋转进度条,因为我们当独发射每个项目,第一个例子是发射整个列表,因而在OnNext()函数里停止旋转进度条是安全的。
更多例子
在这部分里,我们将会展示一些基于RxJava的just(),repeat(),defer(),range(),interval()和timer()方法的例子。
Just()
假设我们只有三个单独的APPInfo对象并想转换到一个Observable中去并填充到RecyclerView项目里:
List<AppInfo> apps = ApplicationsList.getInstance().getList();
AppInfo appOne = apps.get(0);
AppInfo appTwo = apps.get(10);
AppInfo appThree = apps.get(24);
loadApps(appOne, appTwo, appThree);
我们可以获取像前面的那个例子一样获取列表并提取仅有的三个元素。然后传递给loadApps()函数:
private void loadApps(AppInfo appOne, AppInfo appTwo, AppInfo
appThree) {
mRecyclerView.setVisibility(View.VISIBLE);
Observable.just(appOne, appTwo, appThree)
.subscribe(new Observer<AppInfo>() {
@Override
public void onCompleted() {
mSwipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), "Here is the
list!", Toast.LENGTH_LONG).show();
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something
went wrong!", Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNext(AppInfo appInfo) {
mAddedApps.add(appInfo);
mAdapter.addApplication(mAddedApps.size()
- 1, appInfo);
}
});
}
正如你所见,代码与前面的很相似。这个方法给了你思考代码重用的机会。
你甚至可以以一个函数作为参数传递给just()方法,你将会获得·一个raw的代码Observable。从一个existing code base迁移到一个新的响应式架构,这种方式是一个有用的开端。
repeat()
假设你想重复发射一个Observable三次。如下,我们将使用与just()中的那个Observable作为例子:
private void loadApps(AppInfo appOne, AppInfo appTwo, AppInfo
appThree) {
mRecyclerView.setVisibility(View.VISIBLE);
Observable.just(appOne, appTwo, appThree)
.repeat(3)
.subscribe(new Observer<AppInfo>() {
@Override
public void onCompleted() {
mSwipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), "Here is the
list!", Toast.LENGTH_LONG).show();
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something
went wrong!", Toast.LENGTH_SHORT).show();
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNext(AppInfo appInfo) {
mAddedApps.add(appInfo);
mAdapter.addApplication(mAddedApps.size()
- 1, appInfo);
}
});
}
如你所见,在just()方法创建Observable后添加repeat(3)将会创建一个九个项目的序列,单个的发射。
defer()
存在一种场景:你想声明一个Observable但你想推迟到有Observer订阅时创建,假如我们有一个getInt()方法:
private Observable<Integer> getInt() {
return Observable.create(subscriber -> {
if (subscriber.isUnsubscribed()) {
return;
}
App.L.debug("GETINT");
subscriber.onNext(42);
subscriber.onCompleted();
});
}
这个方法相当简单并且它没多大实用,但它能适当表达目的。现在我们可以创建一个新的Observable并使用defer():
Observable<Integer> deferred = Observable.defer(this::getInt);
此时,deferred存在了,但是getInt(),create()方法还未被调用:没有“GETINT”在我们的logcat里:
deferred.subscribe(number -> {
App.L.debug(String.valueOf(number));
});
但当我们订阅时,create()获得了回调,我们在logcat获得了两行:GETINT和42。
range()
你是否需要从N个整数里发射开始几个特定的数字?你可以使用range():
Observable.range(10, 3)
.subscribe(new Observer<Integer>() {
@Override
public void onCompleted() {
Toast.makeText(getActivity(), "Yeaaah!",
Toast.LENGTH_LONG).show();
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something went
wrong!", Toast.LENGTH_SHORT).show();
}
@Override
public void onNext(Integer number) {
Toast.makeText(getActivity(), "I say " +
number, Toast.LENGTH_SHORT).show();
}
});
range()函数需要两个数字作为参数:第一个数字作为开始点,第二个参数是我们想发射数字的数量。
interval()
当你创建投票程序是,interval函数使用非常顺手:
Subscription stopMePlease = Observable.interval(3,
TimeUnit.SECONDS)
.subscribe(new Observer<Integer>() {
@Override
public void onCompleted() {
Toast.makeText(getActivity(), "Yeaaah!",
Toast.LENGTH_LONG).show();
}
@Override
public void onError(Throwable e) {
Toast.makeText(getActivity(), "Something went
wrong!", Toast.LENGTH_SHORT).show();
}
@Override
public void onNext(Integer number) {
Toast.makeText(getActivity(), "I say " +
number, Toast.LENGTH_SHORT).show();
}
});
Interval()函数需要两个参数:第一个明确两个任务的时间间隔,第二个是时间的单位。
timer()
如果你需要一个在一段时间间隔后发射的Observable,你可以像下面的例子一样使用timer():
Observable.timer(3, TimeUnit.SECONDS)
.subscribe(new Observer<Long>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Long number) {
Log.d("RXJAVA", "I say " + number)
}
上述例子在3秒后发射0个,然后就会完成。让我们向下面一样使用三个参数的timer():
Observable.timer(3, 3, TimeUnit.SECONDS)
.subscribe(new Observer<Long>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Long number) {
Log.d("RXJAVA", "I say " + number);
}
});
使用这些代码,你可以创建一个开始时延迟并保持每N秒发射一个新的数字的interval()。
总结
在这章中,我们创建了由RxJava实现的安卓App,我们从存在的列表里、存在的函数里创建Observable。我们学习了如何去创建重复、间隔发射和发射推迟的Observable。
在下一章中,我们将要征服filtering并能够从获取的值序列里去创建我们需要的序列。