流行框架(一)EventBus(组件通信)、ARouter(页面跳转)

news2024/11/25 22:57:25

文章目录

  • 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使用了一个线程池来有效地重用已经完成调用订阅者方法的线程。

使用步骤

  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 '公用组件'
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/586066.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C919商业首航 背后功臣风洞实验室有多牛

5月28日&#xff0c;国产大型客机C919&#xff0c;顺利完成商业首航。 首航背后意味着该机型从研发、制造、取证到投运全面贯通&#xff0c;广大旅客终于有机会坐国产大飞机出行了。 很多人不知道C919其实是依托我国独立自主设计制造的世界级风洞群和风洞实验室反复测试“百炼…

Linux部署jumpserver堡垒机及问题汇总

部署过程相对复杂&#xff01;请耐心浏览&#xff01; 目录 一、jumpserver堡垒机简介 1.1 为什么需要使用堡垒机? 1.2 堡垒机主要功能 二、准备工作 2.1 关闭防火墙以及SElinux 1.2 时间同步 1.3 更改主机名 1.4 yum源备份及准备 1.5 安装初始工具 1.6 修改系统字…

基于PS-InSAR技术的形变监测分析流程

基于PS-InSAR技术的形变监测分析流程 文章目录 基于PS-InSAR技术的形变监测分析流程1. 背景知识1.1 PS-InSAR技术1.1.1 雷达干涉测量1.1.2 InSAR技术1.1.3 技术原理1.1.4 技术特征1.1.5 技术优化1.1.6 应用 1.2 Sentinel-1数据1.2.1 Sentinel-1简介1.2.2 Sentinel-1扫描模式1.2…

一分钟学会怎么让chatGPT帮你写python代码(含使用地址)

一分钟学会怎么让chatGPT帮你写python代码&#xff08;含使用地址&#xff09; 我们用chatGPT做一个python的计算器脚本为例 提出需求 1、给定角色定位 2、提出要求 3、提出要求的细节 标题等待片刻&#xff0c;等待chatGPT生成脚本即可 import tkinter as tkclass Calc…

去公司面试,10:00刚进去,10:08就出来了 ,问的实在是太...

从外包出来&#xff0c;没想到算法死在另一家厂子 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以也就忍了。没想到8月一纸通知&#xff0c;所有人不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个兄弟内…

33 KVM管理设备-配置虚拟机PCIe控制器

文章目录 33 KVM管理设备-配置虚拟机PCIe控制器33.1 概述33.2 配置PCIe Root、PCIe Root Port和PCIe-PCI-Bridge33.2.1 简化配置方法33.2.1完整配制方法 33 KVM管理设备-配置虚拟机PCIe控制器 33.1 概述 虚拟机内部的网卡、磁盘控制器、PCIe直通设备都需要挂接到PCIe Root Po…

IOC源码解析

目录 主要从3方面进行解析 Bean与BeanDefinition 容器初始化主要做的事情(主要脉络) BeanFactory ApplicationContext 模板方法模式 Resource、ResourceLoader、容器之间的关系 BeanDefinitionReader BeanDefinition的注册 小结 主要从3方面进行解析 解析配置定位与注…

EMNLP -- Call for Main Conference Papers

以下内容链接&#xff1a;Call for Main Conference Papers - EMNLP 2023 目录 审核流程&#xff1a; 与 ARR 的交叉提交政策 注意&#xff1a; 注意&#xff1a; 重要日期 强制性摘要提交 提交方向 论文提交信息 论文提交和模板 确认 长论文 短文 贡献 演示模式 著作权 引用与…

Vue设计记事本

项目描述 项目实现功能有&#xff1a;记录今天要完成的任务&#xff0c;勾选已经完成的任务&#xff0c;删除已经完成的全部任务。 界面展示&#xff1a; 代码展示 创建一个Myitem.vue文件夹 <template><li><label ><input type"checkbox"…

机器学习 监督学习 Week2

Lib01 多变量线性回归 依旧是房价预测&#xff0c;但这次引入了多个变量&#xff0c;不仅仅只有房屋面积影响着房价&#xff0c;依旧尝试使用梯度下降算法找到最优的【w,b】&#xff0c;并且习惯使用向量点乘运算提高效率 import copy, math import numpy as np import matplot…

微内核和大内核

微内核和大内核是操作系统内核的两种不同设计思路。 图片来源 微内核 微内核是指将操作系统内核中的核心功能&#xff08;如进程管理、内存管理、设备驱动等&#xff09;作为独立进程运行&#xff0c;各进程间通过IPC(进程间通信)进行通讯。其中微内核相当于一个消息中转站&…

华为OD机试真题B卷 Java 实现【数据最节约的备份方法】,附详细解题思路

一、题目描述 有若干个文件&#xff0c;使用刻录光盘的方式进行备份&#xff0c;假设每张光盘的容量是500MB。 求使用光盘最少的文件分布方式&#xff0c;所有文件的大小都是整数的MB&#xff0c;且不超过500MB&#xff0c;文件不能分隔、分卷打包。 二、输入描述 每组文件…

AD PCB元器件封装设计方法

元器件封装界面 1.元器件可以新建PCB元件库&#xff0c;然后在新建的库中添加 2.也可以采用随便右键某个库中的元器件&#xff0c;选择“Edit…”&#xff0c;进入到元器件封装绘制界面。 元器件封装设计步骤 1.点击菜单栏工具——新的空元件&#xff1b;或者直接点击 Add&a…

认识.Net MAUI跨平台框架

.NET MAUI概念: 全称: .NET 多平台应用 UI (.NET MAUI) 是一个开源的跨平台框架&#xff0c;前身是Xamarin.Forms ! 用于使用 C# 和 XAML 创建本机移动和桌面应用。 NET MAUI&#xff0c;共享代码库,可在 Android、iOS、macOS 和 Windows 上运行的应用 应用架构: github 地址…

MySQL主从复制(概念和作用、实战、常见问题和解决办法、扩展、GTID同步集群、集群扩容、半同步复制)

文章目录 1. 主从复制1.1 概念和作用1.2 主从复制的步骤1.3 搭建主从同步&#xff08;配置步骤&#xff09;1.3.1 配置master主库1.3.2 配置slave从库1.3.3 主从复制的问题和解决方法1.3.4 MySQL主从复制监控和管理、测试 1.4 主从同步扩展1.4.1 主库同步与部分同步&#xff08…

【面试】操作系统面试题

操作系统面试题一 什么是操作系统&#xff1f;请简要概述一下 操作系统是管理计算机硬件和软件资源的计算机程序&#xff0c;提供一个计算机用户与计算机硬件系统之间的接口。 向上对用户程序提供接口&#xff0c;向下接管硬件资源。 操作系统本质上也是一个软件&#xff0…

Clion开发STM32之OTA升级模块(最新完整版)

前言 程序分为上位机部分、BootLoader、App程序上位机程序使用的是C#进行开发&#xff0c;目前只做成控制台部分开发环境依然选择Clion芯片采用的是stm32f103vet6升级模块已和驱动层逻辑进行分离 BootLoader程序 Flash分区定义 头文件 #ifndef STM32F103VET6_PROJECT_APP_FL…

图论-图的基本概念与数据结构

图的基本概念 无向图 边是没有方向的&#xff0c;也就是双向的 结点 V { v 1 , v 2 , . . . , v 7 } \mathcal{V} \{ v_1,v_2,...,v_7\} V{v1​,v2​,...,v7​} 边 ε { e 1 , 2 , e 1 , 3 , . . . , e 6 , 7 } \varepsilon \{e_{1,2},e_{1,3},...,e_{6,7}\} ε{e1,2​…

【面试】计算机网络面试题

计算机网络面试题一 简述OSI七层协议 OSI七层协议包括&#xff1a;物理层&#xff0c;数据链路层&#xff0c;网络层&#xff0c;运输层&#xff0c;会话层&#xff0c;表示层&#xff0c; 应用层 简述TCP/IP五层协议 TCP/IP五层协议包括&#xff1a;物理层&#xff0c;数据…

IntelliJ IDEA使用Alibaba Java Coding Guidelines编码规约扫描插件

代码规范和编码规约扫描插件使用 为什么要有代码规范&#xff1f;1.代码规范插件2.idea插件安装3.插件使用介绍编码规约扫描使用编码规约扫描结果 4.扫描结果严重级别BlockerCriticalMajor 5.《阿里巴巴Java开发手册&#xff08;终极版&#xff09;》 为什么要有代码规范&#…