文章目录
- EventBus
- 基本使用
- EventBus三要素
- 五种线程模式
- 使用步骤
- EventBus黏性事件(sticky event)
- 工作原理
- 中介者模式
- 源码解读
- Event Bus中介者对象
- register() / 注册
- 发布事件 / post
- Arouter
- 组件化开发
- 组件化开发的优点
- 组件分层方案
- 组件化的gradle工程
- ARouter简介
- 基本使用
- 添加依赖
- 使用Gradle实现路由表自动加载
- 初始化
- 简单界面跳转
- Activity界面跳转
- 获取fragment实例
- 携带基本参数跳转
- 携带对象参数跳转
- 携带序列化对象的界面跳转
- 携带集合和数组的界面跳转
- 有回调的界面跳转
- 工作原理
- RouteMate
- Postcard
- ARouter路由表生成原理
- ARouter跳转原理
- 面试题
- ARouter的原理
- ARouter怎么实现页面拦截
- 怎么应用到组件中
EventBus
EventBus是一款针对安卓优化的发布-订阅事件总线。开销小,代码简洁,方便组件与线程、组件间的通信以及发送者与接受者解耦;比如说,Fragment之间的通信,要么需要Activity作为媒介,要么需要Broadcast作为媒介,效率不高,而且难以传递实体类数据。而EventBus就能很好解决这一问题。
EventBus使用了发布者/订阅者模式:
发布者通过EventBus发布事件,订阅者通过EventBus订阅事件。当发布者发布事件时,订阅该事件的订阅者的事件处理方法将被调用。
基本使用
EventBus三要素
- Event:
事件
,事件可以是任意类型的对象
,是通信内容
。 - Subscriber:事件订阅者。在需要接收事件的组件
创建时订阅注册
,同时要在该组件被销毁前解除注册
。在该组件中编写接收到事件的回调函数, 入参为事件对象
,回调函数可以随便取名,但是必须注解@Subscribe,并指定线程模式(默认为POSTING)。 - Publisher:事件发布者,可以在任意线程模式、位置发送事件。直接调用
EventBus.post(Object)
。根据post函数的参数类型,会自动调用订阅相应类型的回调函数。
即:订阅者在订阅中心注册了订阅,并且使用@Subscribe标记了回调函数,指明了事件类型,发布者post事件对象后,注册中心在有@Subscribe注解的函数对象中,寻找与所post的事件对象类型一致的函数,调用。如果有多个@@Subscribe注解的函数对象与post的事件对象类型一致,那么它们都会被调用!!!
五种线程模式
在使用@Subscribe注解时,需要指明线程模式@Subscribe(thread = )
,
- ThreadMode.POSTING 订阅者方法将在发布事件所在的线程中被调用。这是
默认
的线程模式。事件的传递是同步的,一旦发布事件,所有该模式的订阅者方法都将被调用。这种线程模式意味着最少的性能开销,因为它避免了线程的切换。因此,对于不要求是主线程并且耗时很短的简单任务推荐使用该模式。使用该模式的订阅者方法应该快速返回,以避免阻塞发布事件的线程,这可能是主线程。 - ThreadMode.MAIN 订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。如果发布事件的线程是主线程,那么该模式的订阅者方法将被直接调用。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
- ThreadMode.MAIN_ORDERED 订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。事件将先进入队列然后才发送给订阅者,所以发布事件的调用将立即返回。这使得事件的处理保持严格的串行顺序。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
- ThreadMode.BACKGROUND 订阅者方法将在后台线程中被调用。如果发布事件的线程不是主线程,那么订阅者方法将直接在该线程中被调用。如果发布事件的线程是主线程,那么将使用一个单独的后台线程,该线程将按顺序发送所有的事件。使用该模式的订阅者方法应该快速返回,以避免阻塞后台线程。
- ThreadMode.ASYNC 订阅者方法将在一个单独的线程中被调用。因此,发布事件的调用将立即返回。如果订阅者方法的执行需要一些时间,例如网络访问,那么就应该使用该模式。避免触发大量的长时间运行的订阅者方法,以限制并发线程的数量。EventBus使用了一个线程池来有效地重用已经完成调用订阅者方法的线程。
使用步骤
- 在build.gradle中添加依赖:
implementation ‘org.greenrobot:eventbus:3.3.1’
- 构建一个类,作为传递的参数对象:
public class Fruit {
private String name;
private Double price;
public Fruit(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}
- 在需要接收消息的activity中,注册订阅,注册的同时记住解注册。一般在onCreate()注册,在onDestroy()解注册。
4. 在需要接收消息的activity中,使用@Subscribe注解,编写接收到消息的回调函数:
该函数入参为传递过来的对象:
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(Fruit f) {
Log.d("shoudao", "onMessageEvent: "+ f.getName() + f.getPrice().toString());
}
- 在发布消息的activity中使用EventBus,post对象:
EventBus黏性事件(sticky event)
普通事件在发布者post前接收者所在组件需要提前注册好,当post发出后,接收者所在组件即调用回调函数。黏性事件允许发布者post的事件在接收者的组件中持久存在,直至接收者注册了黏性事件才调用回调函数。
- 发布者:postSticky()
- 接收者:
回调函数 sticky = true
使用按钮注册,注意 event bus 不可重复注册
,因此,在注册前判断是否已经注册。
工作原理
中介者模式
-
定义:中介者模式(Mediaor Pattern)包装了一些列对象相互作用的方式,使得这些对象不必相互明显作用。从而使它们可以松散耦合。
-
中介者模式是用来解决紧耦合问题,
该模式将对象之间“多”对“多”的关系转变成“多”对“一”对“多”的关系,其中“一”就是中介者。
中介者对象将系统从网状结构变成了以中介者为中心的星形结构。
很明显可以看出EventBus作为Publisher(发布者)和Subscriber(订阅者)之间的中介者,用于传输Event。
源码解读
Event Bus中介者对象
EventBus 对象中定义了两个哈希表。
- 一个HashMap,以事件类型
eventType
为key
,以存储了订阅者subscriber和订阅方法subscriberMethod的集合CopyOnWriteArrayList<Subscription>
为value
. - 一个HashMap,以
订阅者subscriber
为key
,以订阅者所订阅的事件类型eventType的集合List<Class<?>>
为value
.
1. 根据事件类型找订阅者和订阅方法的哈希表subscriptionsByEventType
很好理解,这有助于在发布者发布事件时,快速根据事件类型,定位到订阅者和订阅方法,执行回调。
2. 而根据订阅者找所订阅事件类型的哈希表typesBySubscriber
,则是一种优化机制,用于快速解除订阅和避免重复订阅。
public class EventBus {
//一个HashMap,以事件类型eventType为key,以存储了订阅者subscriber和订阅方法subscriberMethod的集合CopyOnWriteArrayList<Subscription>为value
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
//一个HashMap,以订阅者subscriber为key,以订阅者所订阅的事件类型eventType的集合List<Class<?>>为value
private final Map<Object, List<Class<?>>> typesBySubscriber;
}
register() / 注册
- 订阅者注册时, register()函数首先反射获取订阅者的class对象。
- 通过反射方式遍历订阅者内部被注解的订阅方法,将订阅方法与订阅者一起构建一个集合元素中。
- 遍历集合,将订阅者与事件类型绑定。即向Event Bus的两个哈希表中push键值对。
- 在绑定后还会判断订阅方法的入参是否为黏性事件,如果是黏性事件,直接调用postToSubscription(),将之前发布的黏性事件发送给订阅者,直接调用。
public void register(Object subscriber) {
// 1、通过反射获取到订阅者的Class对象
Class<?> subscriberClass = subscriber.getClass();
// 2、通过subscriberMethodFinder对象获取订阅者所订阅事件的集合
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
// 3、遍历集合进行注册
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// 4、获取事件类型
Class<?> eventType = subscriberMethod.eventType;
// 5、封装Subscription对象
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// 6、通过事件类型获取该事件的订阅者集合
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
// 7、如果没有订阅者订阅该事件
if (subscriptions == null) {
// 创建集合,存入subscriptionsByEventType集合中
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else { // 8、如果有订阅者已经订阅了该事件
// 判断这些订阅者中是否有重复订阅的现象
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
int size = subscriptions.size();
// 9、遍历该事件的所有订阅者
for (int i = 0; i <= size; i++) {
// 按照优先级高低进行插入,如果优先级最低,插入到集合尾部
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// 10、获取该事件订阅者订阅的所有事件集合
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
// 11、将该事件加入到集合中
subscribedEvents.add(eventType);
// 12、判断该事件是否是粘性事件
if (subscriberMethod.sticky) {
if (eventInheritance) { // 13、判断事件的继承性,默认是不可继承
// 14、获取所有粘性事件并遍历,判断继承关系
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
// 15、调用checkPostStickyEventToSubscription方法
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
// 16、如果粘性事件不为空
postToSubscription(newSubscription, stickyEvent, isMainThread());
}
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
// 17、根据threadMode的类型去选择是直接反射调用方法,还是将事件插入队列
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
// 18、通过反射的方式调用
invokeSubscriber(subscription, event);
} else {
// 19、将粘性事件插入到队列中
// 最后还是会调用EventBus.invokeSubscriber(PendingPost pendingPost)方法。
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
void invokeSubscriber(PendingPost pendingPost) {
Object event = pendingPost.event;
Subscription subscription = pendingPost.subscription;
PendingPost.releasePendingPost(pendingPost);
if (subscription.active) {
invokeSubscriber(subscription, event);
}
}
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
}
public class SubscriberMethod {
final Method method; // 处理事件的Method对象
final ThreadMode threadMode; //线程模型
final Class<?> eventType; //事件类型
final int priority; //事件优先级
final boolean sticky; //是否是粘性事件
String methodString;
}
final class Subscription {
final Object subscriber;
final SubscriberMethod subscriberMethod;
}
发布事件 / post
- 事件发布函数post,首先获取当前线程的事件集合,将要发送的事件加入到集合。
- 通过循环,只要事件集合中还有事件,就一直发送。
- 根据里氏替换原则,
除了订阅该具体事件类型的订阅者外,EventBus还希望能够通知订阅了该事件类型的父类和实现的接口的订阅者
。因此在发布事件时,需要获取事件的class对象,找到它的所有父类或者实现了接口的class集合,遍历这个集合,它们都要向外发布事件。 - 根据事件获取到它的订阅者和定义方法(哈希表:
subscriptionsByEventType
),依据不同的线程模式,完成回调。
public void post(Object event) {
// 1、获取当前线程的PostingThreadState,这是一个ThreadLocal对象
PostingThreadState postingState = currentPostingThreadState.get();
// 2、当前线程的事件集合
List<Object> eventQueue = postingState.eventQueue;
// 3、将要发送的事件加入到集合中
eventQueue.add(event);
// 查看是否正在发送事件
if (!postingState.isPosting) {
// 判断是否是主线程
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
// 4、只要事件集合中还有事件,就一直发送
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
// currentPostingThreadState是包含了PostingThreadState的ThreadLocal对象
// ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据, 并且线程之间的数据是相互独立的。
// 其内部通过创建一个它包裹的泛型对象的数组,不同的线程对应不同的数组索引,每个线程通过get方法获取对应的线程数据。
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
// 每个线程中存储的数据
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<>(); // 线程的事件队列
boolean isPosting; //是否正在发送中
boolean isMainThread; //是否在主线程中发送
Subscription subscription; //事件订阅者和订阅事件的封装
Object event; //事件对象
boolean canceled; //是否被取消发送
}
...
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
// 5、获取事件的Class对象
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) { // eventInheritance一般为true
// 6、 找到当前的event的所有 父类和实现的接口 的class集合
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
// 7、遍历集合发送单个事件
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
synchronized (eventTypesCache) {
// 获取事件集合
List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
if (eventTypes == null) { //如果为空
eventTypes = new ArrayList<>();
Class<?> clazz = eventClass;
while (clazz != null) {
eventTypes.add(clazz); //添加事件
addInterfaces(eventTypes, clazz.getInterfaces()); //添加当前事件的接口class
clazz = clazz.getSuperclass();// 获取当前事件的父类
}
eventTypesCache.put(eventClass, eventTypes);
}
return eventTypes;
}
}
//循环添加当前事件的接口class
static void addInterfaces(List<Class<?>> eventTypes, Class<?>[] interfaces) {
for (Class<?> interfaceClass : interfaces) {
if (!eventTypes.contains(interfaceClass)) {
eventTypes.add(interfaceClass);
addInterfaces(eventTypes, interfaceClass.getInterfaces());
}
}
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// 8、根据事件获取所有订阅它的订阅者
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
// 9、遍历集合
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
// 10、将事件发送给订阅者
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
// 11、重置postingState
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
// 12、根据订阅方法的线程模式调用订阅方法
switch (subscription.subscriberMethod.threadMode) {
case POSTING: //默认类型,表示发送事件操作直接调用订阅者的响应方法,不需要进行线程间的切换
invokeSubscriber(subscription, event);
break;
case MAIN: //主线程,表示订阅者的响应方法在主线程进行接收事件
if (isMainThread) { //如果发送者在主线程
invokeSubscriber(subscription, event);//直接调用订阅者的响应方法
} else { //如果事件的发送者不是主线程
//添加到mainThreadPoster的队列中去,在主线程中调用响应方法
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:// 主线程优先模式
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
//如果不是主线程就在消息发送者的线程中进行调用响应方法
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
// 如果事件发送者在主线程,加入到backgroundPoster的队列中,在线程池中调用响应方法
backgroundPoster.enqueue(subscription, event);
} else {
// 如果不是主线程,在事件发送者所在的线程调用响应方法
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
//这里没有进行线程的判断,也就是说不管是不是在主线程中,都会在子线程中调用响应方法
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
Arouter
组件化开发
组件化开发的优点
组件化开发是基于可重用的目的,将应用拆分为多个独立的组件,以减少耦合。它具有以下优点:
- 将APP分离为多个组件模块,解决业务耦合在一起的问题,并有利于协作分工。
- 开发过程中,这些组件被其他组件依赖,但是在调试时,也可单独成为独立工程并且运行,提高调试效率。
组件分层方案
上图是一种常见的组件分层设计方案,主要包含四层:
- 主(壳)工程(APP):应用入口,除了全局配置和MainActivity外,不包含任何业务代码,因此也称为壳工程。
- 第二层为业务组件层,其中的每个组件之间无直接关联,通过路由进行通信,每个组件负责相对独立的一块业务。
- 第三层为功能组件层,它是对一些公共的业务功能进行封装与实现,比如支付组件、分享组件等。
- 第四层为公共基础组件层,封装用于实现各种业务的公用基础组件,如网络请求库,图片加载库等等。
组件化的gradle工程
ARouter简介
ARouter是阿里开源的一款路由框架,是一个用于帮助Android App进行组件化改造的框架,支持模块间的路由跳转
、通信
、解耦
。
ARouter通常作为公共基础组件BaseModule
,是上层组件AModule
和BModule
的公共依赖。
假如上层组件AModule
中的某个Activity(例如AActivity
)想要跳转到BModule
中的BActivity
,为了实现模块间解耦,又不能直接引用目标Activity。ARouter
的做法是给每个需要跳转的目标Activity设置一个别名,通过映射表的方式Map<String, Calss<>>
维护别名与Activity的关系。当AActivity
发起跳转到BActivity
的请求时,基础模块会从映射表中查找对应的BActivity
实例,然后进行跳转。
基本使用
添加依赖
这里一般习惯的做法是把arouter-api
的依赖放在基础服务的module
里面,因为既然用到了组件化,那么肯定是所有的module都需要依赖arouter-api库的,而arouter-compiler的依赖需要放到每一个module里面。
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
//arouter编译的时候需要的module名字
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
...
implementation 'com.alibaba:arouter-api:1.5.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.5.1'
}
使用Gradle实现路由表自动加载
如果我们想缩短 ARouter 的初始化时间,可以用 ARouter 的 register插件,这个register插件能在编译期
把需要依赖arouter注解的类自动扫描到arouter的map管理器里面,这样 ARouter 初始化的时候就不需要读取类的信息,从而缩短初始化时间。
//app的module的build.gradle
apply plugin: 'com.alibaba.arouter'
//工程的build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.alibaba:arouter-register:1.0.2"
}
}
初始化
debug模式下,开启ARouter的日志记录。
if (isDebug()) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
ARouter.init()方法主要完成以下几个任务:
-
注册路由表:该方法会扫描项目中的所有路由注解,解析路由信息,并将其注册到ARouter的路由表中。这样,ARouter就能够根据路由地址进行正确的路由跳转。
-
初始化全局参数:ARouter库可能需要一些全局参数用于运行时的配置和表现。通过ARouter.init()方法,可以传入一个ARouterOptions对象来设置这些全局参数,例如调试模式、路由降级策略、全局拦截器等。
-
初始化其他依赖:ARouter框架可能依赖其他库或组件进行工作。ARouter.init()方法可以进行这些依赖的初始化,以确保ARouter的正常运行。
-
注入依赖:ARouter支持通过注解的方式进行依赖注入。在ARouter.init()方法中,可以通过调用ARouter.inject()方法,将依赖注入到指定的目标对象中。
简单界面跳转
Activity界面跳转
目标Activity添加注释(跳转语句,路由路径建议写成常量,创建路由表进行统一管理。)
@Route(path = "/app/login")
public class LoginActivity extends AppCompatActivity {
发送Activity实现跳转到:
ARouter.getInstance().build("/app/login").navigation();
获取fragment实例
//目标界面
@Route(path = "/app/fragment")
public class EmptyFragment extends BaseFragment {
}
//启动界面
Fragment fragment= (Fragment) ARouter.getInstance().build("/app/fragment").navigation();
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.fl_fragment_content, fragment);
transaction.commit();
携带基本参数跳转
基本参数包含:
使用案例:
- 发起跳转:
Bundle bundle = new Bundle();
bundle.putString("bundleStringKey", "bundleStringValue");
ARouter.getInstance().build("/app/login")
.withString("stringKey", "stringValue")
.withInt("intKey", 100)
.withBoolean("booleanKey", true)
.withBundle("bundle", bundle)
.navigation();
- 目标界面使用@Autowired注解进行注入:
@Route(path = "/app/login")
public class LoginActivity extends AppCompatActivity {
@Autowired
String stringKey;
@Autowired
int intKey;
@Autowired
boolean booleanKey;
@Autowired
Bundle bundle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//注入ARouter
ARouter.getInstance().inject(this);
Log.e(TAG, stringKey + "..." + intKey + "..." + booleanKey);
Log.e(TAG, bundle.getString("bundleStringKey"));
}
}
注意:
- 注入的属性名要和之前携带的key值完全相同,并且要在需要注入的界面通过
ARouter.getInstance().inject(this)
注入ARouter,否则无法注入成功。 - 建议将
ARouter.getInstance().inject(this)
操作放在BaseActivity的onCreate()
方法中进行。 有注入,就一定有资源的释放
,释放资源在Application
中进行。
@Override
public void onTerminate() {
super.onTerminate();
ARouter.getInstance().destroy();
}
携带对象参数跳转
携带序列化对象的界面跳转
携带 Serializable 和 Parcelable 序列化的对象。
发起界面:
TestSerializableBean serializableBean = new TestSerializableBean();
serializableBean.setName("serializable");
TestParcelableBean parcelableBean = new TestParcelableBean();
parcelableBean.setName("parcelable");
ARouter.getInstance().build("/app/login")
.withParcelable("parcelableBean", parcelableBean)
.withSerializable("serializableBean", serializableBean)
.navigation();
目标界面:
@Autowired
TestParcelableBean parcelableBean;
@Autowired
TestSerializableBean serializableBean;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
Log.e(TAG, parcelableBean + "");
Log.e(TAG, serializableBean + "");
}
我们发现Serializable序列化的对象为null,我们查看withSerializable方法发现其被装进了Bundle:
public Postcard withSerializable(@Nullable String key, @Nullable Serializable value) {
mBundle.putSerializable(key, value);
return this;
}
因此换一种方法来取值,发现打印成功:
TestSerializableBean serializableBean = (TestSerializableBean) getIntent().getExtras().getSerializable("serializableBean");
Log.e(TAG, serializableBean + "");
携带集合和数组的界面跳转
集合和数组的界面跳转统一使用 withObject 方法传递,并且能够支持成员的各种序列化方式。
List<NormalTest> listNormal = new ArrayList<>();
listNormal.add(new NormalTest());
listNormal.add(new NormalTest());
List<TestSerializableBean> listSerializable = new ArrayList<>();
listSerializable.add(new TestSerializableBean());
listSerializable.add(new TestSerializableBean());
List<TestParcelableBean> listParcelable = new ArrayList<>();
listParcelable.add(new TestParcelableBean());
listParcelable.add(new TestParcelableBean());
Map<String, NormalTest> map = new HashMap<>();
map.put("1", new NormalTest());
map.put("2", new NormalTest());
ARouter.getInstance().build("/app/login")
.withObject("listNormal", listNormal)
.withObject("listSerializable",listSerializable)
.withObject("listParcelable",listParcelable)
.withObject("map", map)
.navigation();
//目标界面
@Autowired
List<NormalTest> listNormal;
@Autowired
List<TestSerializableBean> listSerializable;
@Autowired
List<TestParcelableBean> listParcelable;
@Autowired
Map<String, NormalTest> map;
Log.e(TAG, listNormal + "");
Log.e(TAG, listSerializable + "");
Log.e(TAG, listParcelable + "");
Log.e(TAG, map + "");
有回调的界面跳转
相当于startActivityForResult()
//启动界面
ARouter.getInstance().build("/app/login")
.navigation(MainActivity.this, REQUEST_CODE);
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE&& resultCode == RESULT_CODE) {
LogUtils.e(data.getStringExtra("data"));
}
}
//目标界面
Intent intent = new Intent();
intent.putExtra("data", "resultData");
setResult(RESULT_CODE, intent);
finish();
工作原理
RouteMate
RouteMeta 包含了跳转路线的基本信息,是路由信息的容器,主要包含路由起点的Element、路由终点的类型、终点目标的class、路径和路线组、优先级等。,从名字上来看就是路线的元信息,元信息也就是关于信息的信息,RouteMeta 包含了以下字段。
- 路线类型 type:一个枚举类(包含Activity、Service、Provider、Fragment、Broadcast 等。)其中 Service、Broadcast 和 Method 是未实现的。
- 路线原始类型 rawType:路由原始类型 rawType 的类型为 Element,Element 表示 Java 语言元素,比如字段、包、方法、类以及接口。Element 元素包含了跳转目标的 Class 信息,是由路由处理器 RouteProcessor 设定的。
- 终点 destination:终点 destination 就是声明了 @Route 的跳转目标的 Class,该信息也是由RouteProcessor 设定。
- 路径 path
- 路线组 group:如果我们在 @Route 中只设定了路径,比如 path = /goods/details ,那么 goods 就是 group ,details 就是路径 path 。
- 优先级 priority:优先级在 @Route 中无法设定,是给拦截器@Interceptor用的,priority 的值越小,拦截器的优先级就越高。
- 标志 extra
- 参数类型 paramsType:对于我们跳转时设定的参数,ARouter 会根据不同的类型给它们一个枚举值,然后取值时,再根据不同的类型调用 Intent 的 getXXXExtra() 等方法。
- 路线名 name
- 注入配置 injectConfig
Postcard
Postcard
继承了RouteMeta,它更多的作用是完成寻找对象前的准备,以及跳转Activity前的参数准备,PostCard是对RouteMeta的进一步封装,用于配合降级策略、拦截器链等。
- 统一资源标识符 uri
- 标签 tag
- 传参 mBundle
- 标志 flags
- 超时时间 timeout:拦截器链处理跳转事件是放在 CountDownLatch 中执行的,超时时间默认为 300 秒,也就是 5 分钟,所以我们在拦截器中不要进行时间太长的耗时操作。
- 自定义服务 provider
- 是否走绿色通道 greenChannel
ARouter路由表生成原理
RouteProcessor 对于声明了 @Route 注解的类的处理大致可分为下面 4 个步骤:
- 获取路由元素(Element)
RouteProcessor 在处理注解时首先会获取元素Element,Element 表示 Java 语言元素,比如字段、包、方法、类以及接口。 - 创建路由元信息(RouteMate)
RouteProcessor 会把声明了 @Route 注解的的 Activity、Provider、Service 或 Fragment 和一个 RouteMeta 关联起来。 - 把路由元信息进行分组(RouteMate group )
在 RouteProcessor 中有一个 groupMap,在 RouteMeta 创建好后,RouteProcessor 会把不同的 RouteMeta 进行分组,放入到 groupMap 中。拿路径 /goods/details 来说,如果我们在 @Route 中没有设置 group 的值,那么 RouteProcessor 就会把第一级路径参数goods 作为 RouteMeta 的 group。 - 生成路由表
当 RouteProcessor 把 RouteMeta 分组好后,就会用 JavaPoet 生成 Group、Provider 和 Root 路由文件,路由表就是由这些文件组成的。Group表示模块或组件的分组,用于归类管理路由信息;Provider表示提供服务的组件,可以被其他模块调用;Root是整个路由表的根节点,提供路由查找和跳转的功能。
ARouter跳转原理
- 预处理服务:预处理是指在进行路由跳转之前对路由进行一些操作或检查(类似表单验证)。 _ARouter 的 navigation() 首先会根据我们实现的预处理服务,判断是否继续往下处理,不往下处理则中断跳转流程。
- 完善PostCard:把
RouteMeta
的信息填充到Postcard
中,比如终点destination
等信息,这个操作就类似在邮局(物流中心),根据信息(RouteMate)写好信件(Postcard)。 - 是否执行降级策略(找不到目标时的提示) 如果在完善明信片的过程中遇到了异常,比如找不到路径对应的目标,那么就会调用
降级策略
,我们可以在降级策略中显示错误提示等信息。 - 是否为绿色通道。PostCard有一个是否为绿色通道的属性参数,标记为绿色通道的PostCard不会被拦截处理。
- 拦截器链。实现拦截器时,我们要调用 onContinue() (表示处理完成)或 onInterrupt()(表示被拦截,抛出异常中断) 方法,至少需要调用其中一种方法,否则不会继续路由。
- 按类型跳转。在这个方法中,会根据 Postcard 的路由类型 RouteType (本质为目标组件的类型)来判断怎么跳转:
- 当 RouteType 为 Activity 时,启动 Activity 的流程和我们平时启动 Activity 的流程是一样的,创建 Intent、传入 destination、设置 fragment 和重写动画等,最终调用 startActivity() 启动目标页面。
- 当跳转目标为 Fragment、Broadcast 或 ContentProvider 时,会通过 destination 用反射创建实例,如果是 Framgent ARouter 还会为它设置要传递给目标 Fragment 的参数,然后返回实例。
- 如果跳转目标为 Provider,也就是自定义服务的话,就对应了后面讲 ARouter 自定义服务时讲的“通过依赖查找发现服务”。
面试题
ARouter的原理
ARouter是阿里巴巴研发的一个用于解决组件间,模块间界面跳转问题的框架,十分适合用于组件化开发项目中各个页面之间的跳转的结耦合。
不同于平时用到的显式或隐式跳转,只需要在对应的界面上添加注解,就可以实现跳转到指定的界面。
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
//跳转
ARouter.getInstance().build("/test/activity").navigation();
添加注解,@Route + path = 参数。就可以直接使用ARouter进行跳转。
- 代码里加入的@Route注解,会在编译时期通过apt生成一些存储path和activity.class映射关系的类文件。
- app进程启动的时候会加载这些类文件,把保存这些映射关系的数据读到内存里(保存在map里)
- 进行路由跳转的时候,通过build()方法传入要到达页面的路由地址,ARouter会通过它自己存储的路由表找到路由地址对应的Activity.class
ARouter怎么实现页面拦截
拦截器实现IInterceptor接口,使用注解@Interceptor,这个拦截器就会自动被注册了,同样是使用APT技术自动生成映射关系类。这里还有一个优先级参数priority,数值越小,就会越先执行。
怎么应用到组件中
首先在公共基础组件的build.gradle中添加ARouter的依赖:
dependencies {
api 'com.alibaba:arouter-api:1.4.0'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'
}
其次,必须在每个业务组件,也就是用到了arouter的组件中都声明annotationProcessorOptions
,否则会无法通过apt生成索引文件,也就无法正常跳转了:
//业务组件的build.gradle
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'
implementation '公用组件'
}