手记

RxAndroid项目实践-使用RxJava响应式编码实现知乎日报的欢迎界面

介绍

在我的项目中,使用RxAndroid和RxJava搭建项目。在刚开始是很痛苦的,每个操作符和方法都不熟悉。现在项目完成过半,对RxAndroid开始有些熟悉。就从一个实际功能开发入手,总结我对RxAndroid的理解。

效果演示

说明: 
- 从用户角度:是一张华丽丽图片的放大,然后进入主页,很好的用户体验。(参考自:知乎日报) 
- 从App的角度:异步属性动画Animator的播放和后台线程的网络链接,全部完成后同步,activity跳转。

思路分析

基本的思路开始分析

·         图片的缩放:需要属性动画,设置需要控制的View控件属性,属性值和差值器。开始播放

·         逻辑处理:逻辑判断的情况是,首先判断用户是否登录过,如果登录过并且上次登录时间已经超过某个设定好的差值,需要重新联网获取token值(token值是整个App的Hpps联网的关键参数)。如果没有登录属于第一次打开App就没有网络操作。最后统一finish跳转到主页。

·         属性动画和逻辑处理的同步:属性动画的播放时间可以预先设置,但是网络返回结果时间未知,需要同步判定返回结果。

实现的关键点:同步/异步的统一

如果是没用使用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。 
先看它的布局文件

[代码]xml代码:

?

1

2

<!--?xml version="1.0"   encoding="utf-8"?-->

<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" data-filtered="filtered"></imageview>

就一个ImageView控件已经设置好的图片资源。

[代码]xml代码:

?

1

2

3

4

5

6

7

<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">

            </category></action></intent-filter>

        </activity>

manifest定义成启动项和theme,下面的@style/AppTheme.NoActionBar定义在Style-v21中,因为很多属性只支持到API21+,才能实现全屏。

[代码]xml代码:

?

1


属性动画的文件:

[代码]xml代码:

?

1

2

3

4

5

<!--?xml version="1.0"   encoding="utf-8"?-->

<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">

</objectanimator></objectanimator></set>

interpolator差值器使用的是:decelerate类型,实现先快后慢,符合Material Design规范。

前期准备

刚思路分析的时候说到Animator属性动画不具备发送事件的能力,那我们就给它包装出这个能力

MyRxObservable类

它的功能是:用Observable.create创建出一个可观察的Observable对象,并且实现非空检查

[代码]java代码:

?

1

2

3

4

5

6

public class MyRxObservable   {

    public static Observable<void> add(Animator animator){

        Utils.checkNotNull(animator,"Animation   is null");

        return Observable.create(new AnimatorOnSubscribe(animator));

    }

}</void>

AnimatorOnSubscribe类

它的功能是:实现发送事件的能力,从Animator的监听器中得到回调,得到发送和完成的通知

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

/**

 * 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();//先绑定监听器再开始

    }

}</void>

以上实现参考自:JakeWharton大神的RxBinding项目的点击事件 RxView.clicks的包装处理

网络操作

[代码]java代码:

?

1

2

3

private Observable<tokenbean> getUserToken(String username, String   password) {

        return RetrofitGsonRx.service.httpsTokenRx(username,   password);

    }</tokenbean>

这是我的实现,涉及到Retrofit的使用,我这里就不详细介绍了。源码在我的项目HttpUtils包中

RxJava实现

实现了上面的代码,我们就有了可以观察的事件流,现在就把他们串联起来

[代码]java代码:

?

1

2

3

//初始化动画文件 绑定控件

  Animator animation =   AnimatorInflater.loadAnimator(mContext, R.animator.welcome_animator);

        animation.setTarget(mImageView);

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

MyRxObservable.add(animation)

               .subscribeOn(AndroidSchedulers.mainThread())//指定订阅的Observable对象的call方法运行在ui线程中

               .observeOn(Schedulers.io())

               .filter(new Func1<void, boolean="">()   {

                   @Override

                   public Boolean call(Void aVoid) {

                   //filter过滤:判断是否登录过,如果false就会跳过下面的操作

                       return isLogin;

                   }

               })

               .filter(new Func1<void, boolean="">()   {

                   @Override

                   public Boolean call(Void aVoid) {

                   //过滤:判断上次登录是否超过时间差

                       Long   lastTime= (Long) SPUtils.get(getApplicationContext(),Constant.LOGINTIME,0L);

                       return System.currentTimeMillis()-lastTime   >mTimeDifference;            

                   }

               })

               .flatMap(new Func1<void, observable<tokenbean="">>()   {

                   @Override

                   public Observable<tokenbean> 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<tokenbean>() {

                   @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) {

                      //只有网络成功才会回调这里,这里可以保存网络数据。

                   }

               });</tokenbean></tokenbean></void,></void,></void,>

运行代码

我在每个代码运行的关键部分都打上Log输出,观察程序运行

第一次打开App

这是第一次运行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日志: 
再次打开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

模拟极端条件

因为网络状态太好,从上面的日志看不出关键信息,我修改了部分代码,强制休眠线程,模拟极端网络条件。

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

.flatMap(开始网络连接)

 .filter(new Func1<tokenbean, boolean="">()   {

                    @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(订阅回调之前)</tokenbean,>

这是日志打印:很高兴的看到,因为网络线程的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

总结

1.       这我是在使用RxAndroid一段时间后,对它的完整使用,并且使用RxAndroid很好的实现了复杂功能。避免了各种接口回调的迷之代码,链式调用代码逻辑清晰。如果Java 8 Lambda函数会减少更多模板代码。

2.       完整的日志打印分析,看到了各种情况下的App运行状态

原文链接:http://www.apkbus.com/blog-705730-60691.html

0人推荐
随时随地看视频
慕课网APP