Android 响应式编程 RxJava2 完全解析

转载请注明出处:http://blog.csdn.net/smartbetter/article/details/68941859

使用了 RxJava2 有一段时间了,深深感受到了其“牛逼”之处。下面,就从 RxJava2 的基础开始,一步步与大家分享一下这个强大的异步库的用法!RxJava 是 一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库,也就是用于实现异步操作的库。

1.RxJava2 基础

RxJava可以浓缩为异步两个字,其核心的东西不外乎两个, Observables(被观察者) 和 Observable(观察者)。Observables可以发出一系列的 事件(例如网络请求、复杂计算、数据库操作、文件读取等),事件执行结束后交给Observable 的回调处理。

1.RxJava2 的观察者模式

观察者模式是对象的行为模式,也叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

什么是观察者模式?举个栗子,Android中View的点击监听器的实现,View是被观察者,OnClickListener对象是观察者,Activity要如何知道View被点击了?那就是派一个OnClickListener对象,入驻View,与View达成一个订阅关系,一旦View被点击了,就通过OnClickListener对象的OnClick方法传达给Activity。采用观察者模式可以避免去轮询检查,节约有限的cpu资源。

RxJava 作为一个工具库,使用的便是通用形式的观察者模式:

普通事件:onNext(),相当于 onClick()、onEvent();特殊事件:onCompleted() 和 onError()

如图所示,RxJava 的基本概念分别为:Observable(被观察者,事件源),Observer(观察者,订阅者),subscribe (订阅)、事件;不同的是,RxJava 把多个事件看做一个队列,并对每个事件单独处理。在一个队列中 onCompleted() 和 onError(),只有一个会被调用。如果调用了 onCompleted() 就说明队列执行完毕,没有出现异常,否则调用 onError() 方法并终止队列。

2.RxJava2 响应式编程结构

什么是响应式编程?举个栗子,a = b + c; 这句代码将b+c的值赋给a,而之后如果b和c的值改变了不会影响到a,然而,对于响应式编程,之后b和c的值的改变也动态影响着a,意味着a会随着b和c的变化而变化。

响应式编程的组成为Observable/Operator/Subscriber,RxJava在响应式编程中的基本流程如下:

这个流程,可以简单的理解为:Observable -> Operator1 -> Operator2 -> Operator3 -> Subscriber

1. Observable发出一系列事件,他是事件的产生者;
2. Subscriber负责处理事件,他是事件的消费者;
3. Operator是对Observable发出的事件进行修改和变换;
4. 若事件从产生到消费不需要其他处理,则可以省略掉中间的Operator,从而流程变为Obsevable -> Subscriber;
5. Subscriber通常在主线程执行,所以原则上不要去处理太多的事务,而这些复杂的处理则交给Operator;

3.创建一个完整的 RxJava2 调用

首先需要添加 RxJava2 在 Android 中的 Gradle 依赖:

compile ‘io.reactivex.rxjava2:rxandroid:2.0.1‘
compile "io.reactivex.rxjava2:rxjava:2.0.8"

RxJava2 可以通过下面这几种方法创建被观察者:

// 发送对应的方法
Observable.create(new ObservableOnSubscribe<String>() {
    // 默认在主线程里执行该方法
    @Override
    public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {
        e.onNext("Hello");
        e.onNext("World");
        // 结束标识
        e.onComplete();
    }
});
// 发送多个数据
Observable.just("Hello", "World");
// 发送数组
Observable.fromArray("Hello", "World");
// 发送一个数据
Observable.fromCallable(new Callable<String>() {
    @Override
    public String call() throws Exception {
        return "Hello";
    }
});

RxJava2 支持链式编程,下来我们创建被观察者,然后创建观察者并订阅:

// 创建被观察者
Observable.just("Hello", "World")
// 将被观察者切换到子线程
.subscribeOn(Schedulers.io())
// 将观察者切换到主线程
.observeOn(AndroidSchedulers.mainThread())
// 创建观察者并订阅
.subscribe(new Observer<String>() {
    // Disposable 相当于RxJava1.x中的 Subscription,用于解除订阅
    private Disposable disposable;
    @Override
    public void onSubscribe(Disposable d) {
        disposable = d;
    }
    @Override
    public void onNext(String s) {
        Log.i("JAVA", "被观察者向观察者发送的数据:" + s);
        if (s == "-1") {   // "-1" 时为异常数据,解除订阅
            disposable.dispose();
        }
    }
    @Override
    public void onError(Throwable e) {
    }
    @Override
    public void onComplete() {
    }
});

一旦 Observer 订阅了 Observable,Observable 就会调用 Observer 的 onNext()、onCompleted()、onError() 等方法。至此一个完整的 RxJava 调用就完成了。看一下输出的Log:

I/JAVA: 被观察者向观察者发送的数据:Hello
I/JAVA: 被观察者向观察者发送的数据:World

若喜欢简洁、定制服务,那么可以实现的方法跟上面的实现方法是对应起来的,大家看参数就知道哪个对应哪个了,你可以通过new Consumer(不需要实现的方法你可以不写,看上去更简洁),Consumer就是消费者的意思,可以理解为消费了 onNext 等事件:

Observable.just("Hello", "World")
.subscribe(new Consumer<String>() {
    @Override
    public void accept(@NonNull String s) throws Exception {
        Log.i("JAVA", "被观察者向观察者发送的数据:" + s);
    }
}, new Consumer<Throwable>() {
    @Override
    public void accept(@NonNull Throwable throwable) throws Exception {
    }
}, new Action() {
    @Override
    public void run() throws Exception {
    }
}, new Consumer<Disposable>() {
    @Override
    public void accept(@NonNull Disposable disposable) throws Exception {
    }
});

4.RxJava2 的操作符

RxJava中提供了大量不同种类,不同场景的Operators(操作符),RxJava的强大性就来自于它所定义的操作符。主要分类:

RxJava 的操作符 说明 例如
创建操作 用于创建Observable的操作符 create、defer、from、just、start、repeat、range
变换操作 用于对Observable发射的数据进行变换 buffer、window、map、flatMap、groupBy、scan
过滤操作 用于从Observable发射的数据中进行选择 debounce、distinct、filter、sample、skip、take
组合操作 用于将多个Observable组合成一个单一的Observable and、startwith、join、merge、switch、zip
异常处理 用于从错误通知中恢复 catch、retry
辅助操作 用于处理Observable的操作符 delay、do、observeOn、subscribeOn、subscribe
条件和布尔操作 all、amb、contains、skipUntil、takeUntil
算法和聚合操作 average、concat、count、max、min、sum、reduce
异步操作 start、toAsync、startFuture、FromAction、FromCallable、runAsync
连接操作 connect、publish、refcount、replay
转换操作 toFuture、toList、toIterable、toMap、toMultiMap
阻塞操作 forEach、first、last、mostRecent、next、single
字符串操作 byLine、decode、encode、from、join、split、stringConcat

其中有一些高频使用的操作符如下:

常用操作符 说明
interval 延时几秒,每隔几秒开始执行
take 超过多少秒停止执行
map 类型转换
observeOn 在主线程运行
doOnSubscribe 在执行的过程中
subscribe 订阅

5.RxJava2 线程调度器

调度器 Scheduler 用于控制操作符和被观察者事件所执行的线程,不同的调度器对应不同的线程。RxJava提供了5种调度器:

RxJava 线程调度器 说明
Schedulers.immediate() 默认线程,允许立即在当前线程执行所指定的工作。
Schedulers.newThread() 新建线程,总是启用新线程,并在新线程执行操作。
Schedulers.io() 适用于I/O操作,根据需要增长或缩减来自适应的线程池。多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
Schedulers.computation() 适用于计算工作(CPU 密集型计算),即不会被 I/O 等操作限制性能的操作。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
Schedulers.trampoline() 当我们想在当前线程执行一个任务时,并不是立即,我们可以用.trampoline()将它入队。这个调度器将会处理它的队列并且按序运行队列中每一个任务。
AndroidSchedulers.mainThread() RxAndroid 提供的,它指定的操作将在 Android 主线程运行。

可以使用 subscribeOn() 和 ObserveOn() 操作符进行线程调度,让 Observable 在一个特定的调度器上执行。subscribeOn() 指定 subscribe() 所发生的线程,事件产生的线程。ObserveOn() 指定 Observer 所运行在的线程,事件消费的线程。

6.RxJava2 模拟发送验证码倒计时功能

public void onCodeClick() {
    final long count = 60; // 设置60秒
    Observable.interval(0, 1, TimeUnit.SECONDS)
            .take(count + 1)
            .map(new Function<Long, Long>() {
                @Override
                public Long apply(@NonNull Long aLong) throws Exception {
                    return count - aLong; // 由于是倒计时,需要将倒计时的数字反过来
                }
            })
            .observeOn(AndroidSchedulers.mainThread())
            .doOnSubscribe(new Consumer<Disposable>() {
                @Override
                public void accept(@NonNull Disposable disposable) throws Exception {
                    button.setEnabled(false);
                    button.setTextColor(Color.GRAY);
                }
            })
            .subscribe(new Observer<Long>() {
                @Override
                public void onSubscribe(Disposable d) {
                }
                @Override
                public void onNext(Long aLong) {
                    button.setText(aLong + "秒后重发");
                }
                @Override
                public void onError(Throwable e) {
                }
                @Override
                public void onComplete() {
                    button.setEnabled(true);
                    button.setTextColor(Color.RED);
                    button.setText("发送验证码");
                }
            });
}

2.RxJava2 系列框架

RxJava 框架 说明 开源地址
RxAndroid 针对 Android 平台的扩展框架,方便 RxJava 用于 Android 开发,目前 RxAndroid 主要的功能是对 Android 主线程的调度 AndroidSchedulers.mainThread()。 https://github.com/ReactiveX/RxAndroid
DataBinding DataBinding 是基于MVVM思想实现数据和UI绑定的的框架,支持双向绑定。 DataBinding 是一个support库,最低支持到Android 2.1
RxBinding 基于 RxJava 的用于绑定 Android UI 控件的框架,它可以异步获取并处理控件的各类事件(例如点击事件、文字变化、选中状态) https://github.com/JakeWharton/RxBinding
Retrofit 网络请求框架,Retrofit 结合 RxJava 简化请求流程。 https://github.com/square/retrofit
RxPermissions 动态权限管理框架,动态权限内容可参考Android 6.0+ 运行时权限处理 https://github.com/tbruyelle/RxPermissions
RxLifecycle 生命周期绑定,提供了基于 Activity 和 Fragment 生命周期事件的自动完成队列,用于避免不完整回调导致的内存泄漏。 https://github.com/trello/RxLifecycle
RxBus 是一种基于RxJava实现事件总线的一种思想。可以替代EventBus/Otto,因为他们都依赖于观察者模式。 https://github.com/AndroidKnife/RxBus

3.RxJava2 与 Retrofit 的使用

RxJava 与 Retrofit 的使用,更像我们的 AsyncTask,通过网络获取数据然后通过 Handler 更新UI。首先需要导入依赖:

compile ‘com.squareup.retrofit2:retrofit:2.2.0‘
compile ‘com.squareup.retrofit2:converter-gson:2.2.0‘
compile ‘com.squareup.retrofit2:adapter-rxjava2:2.2.0‘

1.模拟用户登陆获取用户数据

1.Bean对象:

public class UserParam {
    private String param1;
    private String param2;
    public UserParam(String param1, String param2) {
        this.param1 = param1;
        this.param2 = param2;
    }
    // 省略了 getter setter
}
public class NetBean {
    private FormBean form;
    // 省略了 getter setter
    public static class FormBean {
        private String username;
        private String password;
        // 省略了 getter setter
    }
}
public class UserBean {
    private String username;
    private String password;
    public UserBean(String username, String password) {
        this.username = username;
        this.password = password;
    }
    // 省略了 getter setter
}

2.ApiService,这里返回Observable对象,也就是我们RxJava的被观察者

public interface ApiService {
    @FormUrlEncoded
    @POST("/post")
    Observable<NetBean> getUserInfo(@Field("username")String username,
                                    @Field("password")String password);
}

3.RxJava + Retrofit 的实现

// 构建Retrofit
ApiService apiService = new Retrofit.Builder()
        .baseUrl("http://httpbin.org/")
        .addConverterFactory(GsonConverterFactory.create()) // RxJava2与Gson混用
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // RxJava2与Retrofit混用
        .build()
        .create(ApiService.class);

// 构建RxJava
UserParam param = new UserParam("zhangsan", "123");
// 发送param参数
Observable.just(param)
        // flatMap方法是用于数据格式转换的方法,参数一表示原数据,
        // 参数二表示转换的数据,那么就是通过发送网络参数,转换成网络返回的数据,调用Retrofit
        .flatMap(new Function<UserParam, ObservableSource<NetBean>>() {
            @Override
            public ObservableSource<NetBean> apply(@NonNull UserParam userParam)
                    throws Exception {
                // 1.发送网络请求,获取NetBean
                return apiService.getUserInfo(userParam.getParam1(), userParam.getParam2());
            }
        })
        .flatMap(new Function<NetBean, ObservableSource<UserBean>>() {
            @Override
            public ObservableSource<UserBean> apply(@NonNull NetBean netBean)
                    throws Exception {
                UserBean user = new UserBean(netBean.getForm().getUsername(),
                        netBean.getForm().getPassword());
                // 2.转换NetBean数据为我们需要的UserBean数据
                return Observable.just(user);
            }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<UserBean>() {
            @Override
            public void accept(@NonNull UserBean userBean) throws Exception {
                Log.i("JAVA", "" + "用户名:" + userBean.getUsername()
                        + ", 密码:" + userBean.getPassword());
            }
        });

2.模拟合并本地与服务器购物车列表

这个案例其实就是用户添加购物车的时候,首先会在本地存储一份,然后发现如果没有网络,那么没办法提交到服务器上,只能等下一次有网络的时候采用本地数据库和服务器数据的合并来实现上传到服务器。

首先需要准备 Retrofit 对象和获取本地数据、网络数据的方法:

private ApiService apiService;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 省略
    // 构建Retrofit
    apiService = new Retrofit.Builder()
            .baseUrl("http://httpbin.org/")
            .addConverterFactory(GsonConverterFactory.create()) // RxJava2与Gson混用
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // RxJava2与Retrofit混用
            .build()
            .create(ApiService.class);
}
/**
 * 获取本地数据
 */
private Observable<List<String>> getDataForLocal() {
    List<String> list = new ArrayList<>();
    list.add("购物车的商品1");
    list.add("购物车的商品2");
    return Observable.just(list);
}
/**
 * 获取网络数据
 */
private Observable<List<String>> getDataForNet() {
    return Observable.just("shopName")
        // flatMap方法是用于数据格式转换的方法,参数一表示原数据,
        // 参数二表示转换的数据,那么就是通过发送网络参数,转换成网络返回的数据,调用Retrofit
        .flatMap(new Function<String, ObservableSource<NetBean>>() {
            @Override
            public ObservableSource<NetBean> apply(@NonNull String s) throws Exception {
                // 1.发送网络请求,获取数据
                return apiService.getCartList(s);
            }
        }).flatMap(new Function<NetBean, ObservableSource<List<String>>>() {
            @Override
            public ObservableSource<List<String>> apply(@NonNull NetBean netBean) throws Exception {
                // String shop = netBean.get_$Args257().getShopName();
                String shop = "购物车的商品3";
                List<String> list = new ArrayList<>();
                list.add(shop);
                // 2.转换NetBean数据为我们需要的List<String>数据
                return Observable.just(list);
            }
        }).subscribeOn(Schedulers.io());
}

然后我们就可以创建被观察者并订阅了,来完成合并本地与服务器购物车列表操作:

// merge操作符: 将两个ObservableSource合并为一个ObservableSource
Observable.merge(getDataForLocal(), getDataForNet())
        .subscribe(new Observer<List<String>>() {
            @Override
            public void onSubscribe(Disposable d) {
            }
            @Override
            public void onNext(List<String> strings) {
                for (String str: strings) { Log.i("JAVA", str); }
            }
            @Override
            public void onError(Throwable e) {
            }
            @Override
            public void onComplete() {
                Log.i("JAVA", "onComplete");
            }
        });

最后的打印结果是:

I/JAVA: 购物车的商品1
I/JAVA: 购物车的商品2
I/JAVA: 购物车的商品3
I/JAVA: onComplete

4.RxJava2 与 RxBinding 的使用

1.优化搜索请求

当我们在 EditText 打字时搜索的时候,可能用户会打字很会快,那么我们就没有必要一直发送网络请求,请求搜索结果,我们可以通过当用户打字停止后的延时500毫秒再发送搜索请求:

// RxTextView.textChanges(edittext): Rxbinding用法
RxTextView.textChanges(editText)
        // 表示延时多少秒后执行,当你敲完字之后停下来的半秒就会执行下面语句
        .debounce(500, TimeUnit.MILLISECONDS)
        // 数据转换 flatMap: 当同时多个数据请求访问的时候,前面的网络数据会覆盖后面的网络数据
        // 数据转换 switchMap: 当同时多个网络请求访问的时候,会以最后一个发送请求为准,前面网络数据会被最后一个覆盖
        .switchMap(new Function<CharSequence, ObservableSource<List<String>>>() {
            @Override
            public ObservableSource<List<String>> apply(
                    @NonNull CharSequence charSequence) throws Exception {
                // 网络请求操作,获取我们需要的数据
                List<String> list = new ArrayList<String>();
                list.add("2017");
                list.add("2018");
                return Observable.just(list);
            }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<List<String>>() {
            @Override
            public void accept(@NonNull List<String> strings) throws Exception {
                // 更新UI
                Log.i("JAVA", strings.toString());
            }
        });

2.优化点击请求

当用户一直点击一个按钮的时候,我们不应该一直调用访问网络请求,而是 1秒内,只执行一次网络请求。

RxView.clicks(button).throttleFirst(1, TimeUnit.SECONDS)
        .subscribe(new Observer<Object>() {
            @Override
            public void onSubscribe(Disposable d) {
            }
            @Override
            public void onNext(Object o) {
                Log.i("JAVA", "onClick");
            }
            @Override
            public void onError(Throwable e) {
            }
            @Override
            public void onComplete() {
            }
        });

5.RxJava2 踩过的一些坑

1.未解除订阅而引起的内存泄漏

举个例子,对于前面常用操作符 interval 做周期性操作的例子,并没有使之停下来的,没有去控制订阅的生命周期,这样,就有可能引发内存泄漏。所以,在 Activity 的 onDestroy() 方法执行的时候或者不需要继续执行的时候应该解除订阅。

更多 RxJava 示例代码请访问:https://github.com/smartbetter/RxJavaDemo

时间: 2024-10-20 23:23:57

Android 响应式编程 RxJava2 完全解析的相关文章

什么是函数响应式编程(Java&amp;Android版本)

什么是函数响应式编程(Java&Android版本) 原文链接:http://www.bignerdranch.com/blog/what-is-functional-reactive-programming/ 函数响应式编程(FRP)为解决现代编程问题提供了全新的视角.一旦理解它,可以极大地简化你的项目,特别是处理嵌套回调的异步事件,复杂的列表过滤和变换,或者时间相关问题. 我将尽量跳过对函数响应式编程学院式的解释(网络上已经有很多),并重点从实用的角度帮你理解什么是函数响应式编程,以及工作中

RxJava入门系列四,Android中的响应式编程

RxJava入门系列四,Android中的响应式编程 在入门系列1,2,3中,我基本介绍了RxJava是如何使用的.但是作为一名Android开发人员,你怎么让RxJava能为你所用呢?这篇博客我将针对Android开发来介绍一下RxJava的使用场景. RxAndroid RxAndroid是为Android打造的RxJava扩展.通过RxAndroid可以让你的Android开发变得更轻松. 首先,RxAndroid中提供了AndroidSchedulers,你可以用它来切换Android线

Java的HTTP服务端响应式编程

为什么要响应式编程? 传统的Servlet模型走到了尽头 传统的Java服务器编程遵循的是J2EE的Servlet规范,是一种基于线程的模型:每一次http请求都由一个线程来处理. 线程模型的缺陷在于,每一条线程都要自行处理套接字的读写操作.对于大部分请求来讲,本地处理请求的速度很快,请求的读取和返回是最耗时间的.也就是说大量的线程浪费在了远程连接上,而没有发挥出计算能力.但是需要注意一点,线程的创建是有开销的,每一条线程都需要独立的内存资源.JVM里的-Xss参数就是用来调整线程堆栈大小的.而

Rx系列---响应式编程

Rx是ReactiveX的简称,翻译过来就是响应式编程 首先要先理清这么一个问题:Rxjava和我们平时写的程序有什么不同.相信稍微对Rxjava有点认知的朋友都会深深感受到用这种方式写的程序和我们一般写的程序有很明显的不同.我们一般写的程序 统称为命令式程序,是以流程为核心的,每一行代码实际上都是机器实际上要执行的指令.而Rxjava这样的编程风格,称为函数响应式编程.函数响应式编程是以数据流为核心,处理数据的输入,处理以及输出的.这种思路写出来的代码就会跟机器实际执行的指令大相径庭.所以对于

响应式页面导航设计解析

有人说,2013将是响应式网页设计之年.自用户体验设计师Ethan Marcotte在2010年提出Responsive Web Design(RWD)的名词,即响应式网页设计,这个概念从Responsive Architecture延伸到web设计领域,让所有的交互设计.视觉.前端开发都开始投入到这个趋势,或者说新的设计解决方案中. 当自己和身边的朋友越来越多的用上了iPhone.iPad.android手机或平板,当越来越多的人都在谈论这个老意识新概念的话题,当我们一直秉承的用户体验第一与网

RxJava响应式编程之初级了解

据说现在流行的开发模式是 Retrofit+RxJava+MVP+ButterKnife 如果想要简单学习ButterKnife.MVP模式,可以参考我以前的例子 使用butterknife注解框架 Android-MVP设计模式高级(三) 今天我就简单来学习下RxJava的相关知识 以前我也只是听说过RxJava,RxJava这个到底是什么东西呢? 呵呵,它其实是一个库,所以我们使用里面的方法,得需要下载库,所以我们需要在AS中进行配置 1.RxJava 地址以及添加 github地址: ht

RxJava入门系列三,响应式编程

RxJava入门系列三,响应式编程 在RxJava入门系列一,我向你介绍了RxJava的基础架构.RxJava入门系列二,我向你展示了RxJava提供的多种牛逼操作符.但是你可能仍然没能劝服自己使用RxJava,这一篇博客里我将向你展示RxJava提供的其他优势,没准了解了这些优势,你就真的想去使用RxJava了. 异常处理 直到目前为止,我都没有去介绍onComplete()和onError()方法.这两个方法是用来停止Observable继续发出事件并告知观察者为什么停止(是正常的停止还是因

响应式编程(Reactive programming)

响应式编程是指确保程序对事件或输入做出响应的做法.在这一节,我们将专注于图形界面方面的响应式编程,图形界面总量响应式的.然而,其他网格的编程也需要考虑响应式编程,例如,运行在服务器上的程序总是需要保持对输入作出响应,即使是在它处理其他需要长时间运行的任务期间.我们将在第十一章实现聊天服务器时,会看到在服务器编程方面也要用到这里讨论的一些方法. 大多数图形界面库使用事件循环去处理绘制图形界面,以及与用户之间的交互,就是说,一个线程既要负责绘制图形界面,也要处理其上的所有事件,我们称这种线程为图形界

Unity基于响应式编程(Reactive programming)入门

系列目录 [Unity3D基础]让物体动起来①--基于UGUI的鼠标点击移动 [Unity3D基础]让物体动起来②--UGUI鼠标点击逐帧移动 时光煮雨 Unity3D让物体动起来③—UGUI DoTween&Unity Native2D实现 时光煮雨 Unity3D实现2D人物动画① UGUI&Native2D序列帧动画 时光煮雨 Unity3D实现2D人物动画② Unity2D 动画系统&资源效率 背景 前有慕容小匹夫的一篇<解构C#游戏框架uFrame兼谈游戏架构设计&