流行框架(一)EventBus(组件通信)、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使用了一个线程池来有效地重用已经完成调用订阅者方法的线程。

使用步骤

  1. 在build.gradle中添加依赖:

implementation ‘org.greenrobot:eventbus:3.3.1’

  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;
    }
}
  1. 在需要接收消息的activity中,注册订阅,注册的同时记住解注册。一般在onCreate()注册,在onDestroy()解注册。

在这里插入图片描述
在这里插入图片描述
4. 在需要接收消息的activity中,使用@Subscribe注解,编写接收到消息的回调函数:

该函数入参为传递过来的对象:

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(Fruit f) {
        Log.d("shoudao", "onMessageEvent: "+ f.getName() + f.getPrice().toString());
    }
  1. 在发布消息的activity中使用EventBus,post对象:

在这里插入图片描述

EventBus黏性事件(sticky event)

普通事件在发布者post前接收者所在组件需要提前注册好,当post发出后,接收者所在组件即调用回调函数。黏性事件允许发布者post的事件在接收者的组件中持久存在,直至接收者注册了黏性事件才调用回调函数。

  1. 发布者:postSticky()

在这里插入图片描述

  1. 接收者:
    回调函数 sticky = true
    在这里插入图片描述
    使用按钮注册,注意 event bus 不可重复注册,因此,在注册前判断是否已经注册。
    在这里插入图片描述

工作原理

中介者模式

  • 定义:中介者模式(Mediaor Pattern)包装了一些列对象相互作用的方式,使得这些对象不必相互明显作用。从而使它们可以松散耦合。

  • 中介者模式是用来解决紧耦合问题,该模式将对象之间“多”对“多”的关系转变成“多”对“一”对“多”的关系,其中“一”就是中介者。中介者对象将系统从网状结构变成了以中介者为中心的星形结构。

很明显可以看出EventBus作为Publisher(发布者)和Subscriber(订阅者)之间的中介者,用于传输Event。
在这里插入图片描述

源码解读

Event Bus中介者对象

EventBus 对象中定义了两个哈希表。

  • 一个HashMap,以事件类型eventTypekey,以存储了订阅者subscriber和订阅方法subscriberMethod的集合CopyOnWriteArrayList<Subscription>value.
  • 一个HashMap,以订阅者subscriberkey,以订阅者所订阅的事件类型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,是上层组件AModuleBModule的公共依赖。

假如上层组件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 个步骤:

  1. 获取路由元素(Element)
    RouteProcessor 在处理注解时首先会获取元素Element,Element 表示 Java 语言元素,比如字段、包、方法、类以及接口。
  2. 创建路由元信息(RouteMate)
    RouteProcessor 会把声明了 @Route 注解的的 Activity、Provider、Service 或 Fragment 和一个 RouteMeta 关联起来。
  3. 把路由元信息进行分组(RouteMate group )
    在 RouteProcessor 中有一个 groupMap,在 RouteMeta 创建好后,RouteProcessor 会把不同的 RouteMeta 进行分组,放入到 groupMap 中。拿路径 /goods/details 来说,如果我们在 @Route 中没有设置 group 的值,那么 RouteProcessor 就会把第一级路径参数goods 作为 RouteMeta 的 group
  4. 生成路由表
    当 RouteProcessor 把 RouteMeta 分组好后,就会用 JavaPoet 生成 Group、Provider 和 Root 路由文件,路由表就是由这些文件组成的。Group表示模块或组件的分组,用于归类管理路由信息;Provider表示提供服务的组件,可以被其他模块调用;Root是整个路由表的根节点,提供路由查找和跳转的功能。

ARouter跳转原理

  1. 预处理服务:预处理是指在进行路由跳转之前对路由进行一些操作或检查(类似表单验证)。 _ARouter 的 navigation() 首先会根据我们实现的预处理服务,判断是否继续往下处理,不往下处理则中断跳转流程。
  2. 完善PostCard:把 RouteMeta 的信息填充到 Postcard 中,比如终点 destination 等信息,这个操作就类似在邮局(物流中心),根据信息(RouteMate)写好信件(Postcard)。
  3. 是否执行降级策略(找不到目标时的提示) 如果在完善明信片的过程中遇到了异常,比如找不到路径对应的目标,那么就会调用降级策略我们可以在降级策略中显示错误提示等信息
  4. 是否为绿色通道。PostCard有一个是否为绿色通道的属性参数,标记为绿色通道的PostCard不会被拦截处理。
  5. 拦截器链。实现拦截器时,我们要调用 onContinue() (表示处理完成)或 onInterrupt()(表示被拦截,抛出异常中断) 方法,至少需要调用其中一种方法,否则不会继续路由。
  6. 按类型跳转。在这个方法中,会根据 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 '公用组件'
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值