在我的项目中,使用RxAndroid和RxJava搭建项目。在刚开始是很痛苦的,每个操作符和方法都不熟悉。现在项目完成过半,对RxAndroid开始有些熟悉。就从一个实际功能开发入手,总结我对RxAndroid的理解。
说明:
- 从用户角度:是一张华丽丽图片的放大,然后进入主页,很好的用户体验。(参考自:知乎日报)
- 从App的角度:异步属性动画Animator的播放和后台线程的网络链接,全部完成后同步,activity跳转。
实现的关键点:同步/异步的统一
如果是没用使用Rx响应式,就需要接口通信,各种回调处理。并且分别需要监听属性动画的播放开始和结束还有网络连接的成功,同时还需要做异常处理。想想都头大。
首先看看RxJava是什么
“a library for composing asynchronous and event-based programs using observable sequences for the Java VM”(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。这就是 RxJava ,概括得非常精准。
其实,属性动画的开始和结束,网络连接的成功和失败都是事件流。我们只要把它们串联起来,订阅观察就可以了。
属性动画不是事件流,那我们把它包装起来让它有发送事件的能力,至于网络连接RxJava的好搭档Retrofit早就已经实现好了。
在项目中新建Activity类命名WelcomeActivity。
先看它的布局文件
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/img_welcome"
android:scaleType="centerCrop"
android:src="@drawable/welcome_second" />
就一个ImageView控件已经设置好的图片资源。
<activity
android:name=".Welcome.WelcomeActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
manifest定义成启动项和theme,下面的@style/AppTheme.NoActionBar定义在Style-v21中,因为很多属性只支持到API21+,才能实现全屏。
属性动画的文件:
<set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="together"
>
<objectAnimator android:duration="2000"
android:valueFrom="1f"
android:valueTo="1.2f"
android:valueType="floatType"
android:propertyName="scaleY"
android:interpolator="@android:interpolator/decelerate_cubic"
/>
<objectAnimator android:duration="2000"
android:valueFrom="1f"
android:valueTo="1.2f"
android:valueType="floatType"
android:propertyName="scaleX"
android:interpolator="@android:interpolator/decelerate_cubic"
/>
set>
interpolator差值器使用的是:decelerate类型,实现先快后慢,符合Material Design规范。
刚思路分析的时候说到Animator属性动画不具备发送事件的能力,那我们就给它包装出这个能力
它的功能是:用Observable.create创建出一个可观察的Observable对象,并且实现非空检查
public class MyRxObservable {
public static Observable add(Animator animator){
Utils.checkNotNull(animator,"Animation is null");
return Observable.create(new AnimatorOnSubscribe(animator));
}
}
它的功能是:实现发送事件的能力,从Animator的监听器中得到回调,得到发送和完成的通知
/**
* Created by LiCola on 2016/04/10 15:39
*/
public class AnimatorOnSubscribe implements Observable.OnSubscribe<Void> {
final Animator animator;
public AnimatorOnSubscribe(Animator animator) {
this.animator = animator;
}
@Override
public void call(final Subscriber super Void> subscriber) {
checkUiThread();//检查是否在UI线程调用,否则不能播放动画
AnimatorListenerAdapter adapter=new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
subscriber.onNext(null);
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
subscriber.onCompleted();
}
};
animator.addListener(adapter);
animator.start();//先绑定监听器再开始
}
}
以上实现参考自:JakeWharton大神的RxBinding项目的点击事件 RxView.clicks的包装处理
private Observable getUserToken(String username, String password) {
return RetrofitGsonRx.service.httpsTokenRx(username, password);
}
这是我的实现,涉及到Retrofit的使用,我这里就不详细介绍了。源码在我的项目HttpUtils包中
实现了上面的代码,我们就有了可以观察的事件流,现在就把他们串联起来
//初始化动画文件 绑定控件
Animator animation = AnimatorInflater.loadAnimator(mContext, R.animator.welcome_animator);
animation.setTarget(mImageView);
MyRxObservable.add(animation)
.subscribeOn(AndroidSchedulers.mainThread())//指定订阅的Observable对象的call方法运行在ui线程中
.observeOn(Schedulers.io())
.filter(new Func1() {
@Override
public Boolean call(Void aVoid) {
//filter过滤:判断是否登录过,如果false就会跳过下面的操作
return isLogin;
}
})
.filter(new Func1() {
@Override
public Boolean call(Void aVoid) {
//过滤:判断上次登录是否超过时间差
Long lastTime= (Long) SPUtils.get(getApplicationContext(),Constant.LOGINTIME,0L);
return System.currentTimeMillis()-lastTime >mTimeDifference;
}
})
.flatMap(new Func1>() {
@Override
public Observable call(Void aVoid) {
//flatMap转换:和 map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。但需要注意,和 map()不同的是, flatMap() 中返回的是个 Observable 对象
//上面两步都为true才能到这里,取出用户信息,开始联网。
String userAccount= (String) SPUtils.get(getApplicationContext(),Constant.USERACCOUNT,"");
String userPassword= (String) SPUtils.get(getApplicationContext(),Constant.USERPASSWORD,"");
return getUserToken(userAccount,userPassword);
}
})
.observeOn(AndroidSchedulers.mainThread())//最后统一回到UI线程中处理
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
//所有操作完成,统一回调这里,实现Activity跳转功能
MainActivity.launch(WelcomeActivity.this);
finish();
}
@Override
public void onError(Throwable e) {
//异常处理,根据每个项目而定,这里不具体写
Logger.d(e.toString());
}
@Override
public void onNext(TokenBean tokenBean) {
//只有网络成功才会回调这里,这里可以保存网络数据。
}
});
我在每个代码运行的关键部分都打上Log输出,观察程序运行
这是第一次运行App的日志打印:
可以看到:因为没有登录过,所有只有动画的开始、结束,然后跳转。跳转时间由动画时间决定。
04-10 20:19:55.990 8111-8111/ D/Demo: [BaseActivity:onResume:95]: WelcomeActivity @3eb05556
04-10 20:19:56.049 8111-8670/ D/Demo: [WelcomeActivity:call:85]: isLogin= false
04-10 20:19:56.050 8111-8111/ D/Demo: [AnimatorOnSubscribe:onAnimationStart:32]: onAnimationStart
04-10 20:19:58.054 8111-8111/ D/Demo: [AnimatorOnSubscribe:onAnimationEnd:39]: onAnimationEnd
04-10 20:19:58.055 8111-8111/ D/Demo: [WelcomeActivity:onCompleted:123]:
04-10 20:19:58.098 8111-8111/ D/Demo: [BaseActivity:onPause:124]: WelcomeActivity @3eb05556
这是登录用户的打开App日志:
再次打开App后,因为有登录信息,并且超过了登录时间差,会自动登录联网。跳转时间还是在2000毫秒左右。这是因为一般的网络连接在2秒内已经完成没有影响到完成时间。
04-10 20:26:23.705 12131-12131/ D/Demo: [BaseActivity:onResume:95]: WelcomeActivity @3eb05556
04-10 20:26:23.768 12131-12174/ D/Demo: [WelcomeActivity:call:85]: isLogin= true
04-10 20:26:23.768 12131-12131/ D/Demo: [AnimatorOnSubscribe:onAnimationStart:32]: onAnimationStart
04-10 20:26:23.768 12131-12174/ D/Demo: [WelcomeActivity:call:94]: dTime 133545
04-10 20:26:23.769 12131-12174/ D/Demo: [WelcomeActivity:call:103]: flatMap
04-10 20:26:24.135 12131-12131/ D/Demo: [WelcomeActivity:onNext:135]: https success
04-10 20:26:25.781 12131-12131/ D/Demo: [AnimatorOnSubscribe:onAnimationEnd:39]: onAnimationEnd
04-10 20:26:25.783 12131-12131/ D/Demo: [WelcomeActivity:onCompleted:123]:
04-10 20:26:25.827 12131-12131/ D/Demo: [BaseActivity:onPause:124]: WelcomeActivity @3eb05556
因为网络状态太好,从上面的日志看不出关键信息,我修改了部分代码,强制休眠线程,模拟极端网络条件。
.flatMap(开始网络连接)
.filter(new Func1() {
@Override
public Boolean call(TokenBean tokenBean) {
//强制使线程休眠 因为是在IO线程不会使得程序异常
Logger.d("thread sleep start");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Logger.d(Thread.currentThread().toString());
Logger.d("thread sleep end");
return true;
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(订阅回调之前)
这是日志打印:很高兴的看到,因为网络线程的3秒休眠,动画已经结束,但是在等待网络返回成功后,才跳转,很好的保证了实现逻辑,不会出现动画播放完成,activity跳转得不到网络连接结果。
04-10 20:37:07.482 18732-18732/ D/Demo: [BaseActivity:onResume:95]: WelcomeActivity @3eb05556
04-10 20:37:07.731 18732-18732/ D/Demo: [AnimatorOnSubscribe:onAnimationStart:32]: onAnimationStart
04-10 20:37:07.743 18732-18872/ D/Demo: [WelcomeActivity:call:85]: isLogin= true
04-10 20:37:07.743 18732-18872/ D/Demo: [WelcomeActivity:call:94]: dTime 777520
04-10 20:37:07.744 18732-18872/ D/Demo: [WelcomeActivity:call:103]: flatMap
04-10 20:37:08.516 18732-18872/ D/Demo: [WelcomeActivity:call:111]: thread sleep start
04-10 20:37:09.730 18732-18732/ D/Demo: [AnimatorOnSubscribe:onAnimationEnd:39]: onAnimationEnd
04-10 20:37:11.517 18732-18872/ D/Demo: [WelcomeActivity:call:117]: Thread[RxCachedThreadScheduler-1,5,main]
04-10 20:37:11.517 18732-18872/ D/Demo: [WelcomeActivity:call:118]: thread sleep end
04-10 20:37:11.520 18732-18732/ D/Demo: [WelcomeActivity:onNext:138]: https success
04-10 20:37:11.521 18732-18732/ D/Demo: [WelcomeActivity:onCompleted:126]:
04-10 20:37:12.211 18732-18732/ D/Demo: [BaseActivity:onPause:124]: WelcomeActivity @3eb05556
项目参考资料:
http://blog.csdn.net/guolin_blog/article/details/43536355
https://github.com/JakeWharton/RxBinding
http://rxjava.yuxingxin.com/
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1012/3572.html#toc_22