Android知识iOS开发Android开发

安卓框架(一) EventBus

2018-04-03  本文已影响282人  MrHorse1992

前言

安卓开发过程中各个组件以及线程之间通信频繁。安卓组件通信采用广播方式时效率较低,而采用回调方式则使代码耦合严重代码显得臃肿,尤其是仅为了更新某个控件就要单独创建一个回调函数,使代码变得十分复杂。
EventBus是greenrobot在Android平台发布的一款以订阅——发布模式为核心的开源库。EventBus采用观察者模式,简化组件以及线程直接的通信方式,降低代码耦合度。尤其是GUI系统中,保证UI层与业务逻辑层的解耦。
观察者模式: 定义对象之间 的一种一对多的关系,使得一个对象改变状态,通知所有依赖于它的对象状态改变。
类比介绍:EventBus 中应用的观察者模式效果类比于实际生活中的用户向出版社订阅报刊行为。也许这种类比方式并不准确,但是可以帮助理解。后续介绍中会一步步详加比较,以助读者理解。

(一)EventBus使用方法

1:创建事件实体类,即需要传递的事件信息
public class MessageEvent {
private String message;
public MessageEvent(String message){
    this.message = message;
  }
public String getMessage(){
    return message;
  }
}

EventBus事件实体类不唯一,用户可以根据自身需要创建不同事件实体类型。
类比:创建事件实体类相似于出版社制造的不同出版物,例如:杂志、报纸等。

2 :订阅注册与解除注册
//订阅注册
EventBus.getDefault().register(Object subscriber);

例如:安卓开发中注册于Activity中,一般可以在onCreate()或者onResume()中调用,实现注册。
类比: 用户向出版社订阅杂志或者报刊信息。

//解除注册 
EventBus.getDefault().unRegister(Object subscriber);

对于Activity而言解除注册常在onPause()或者onDestroy()中调用

3: 实现订阅方法
@Subscribe (threadMode = ThreadMode.POSTING)  
public void onMessageEvent(AnyEventType event) {
    //事件处理函数
    /*函数体*/
}

方法名必须加上注解@Subscribe (threadMode = ThreadMode.POSTING)为设置处理事件的线程。默认不写的情况下为POSTING,ThreadMode共有四种处理事件的线程方式:

ThreadMode.POSTING:执行事件处理函数的线程与发布事件的线程是同一线程,也就是说由哪个线程发出的事件,就由哪个线程处理事件。
ThreadMode.MAIN:事件处理函数在UI线程中执行,注:事件处理函数执行时间不能过长,否则引起ANR。
ThreadMode.BACKGROUND:如果事件由UI线程发出,事件处理函数会在新创建的线程中执行,如果事件由子线程发出,则处理函数在该子线程中执行 ,此种方式不可更新UI。
ThreadMode.ASYNC:无论事件由哪种线程发出,事件处理函数都会在子线程执行。同样禁止更新UI。

类比: 用户向出版社订阅订阅刊物,需要向出版社留下个人地址信息或者电子邮箱信息,以便出版社发刊物时能够送到用户手上。

4: 发送事件
EventBus.getDefault().post(messageEvent);

发送事件可以在任意线程中执行。
类比:出版社发刊物。

通过以上四步可以完成EventBus的完整使用过程,使用方式十分简单。那么关于EventBus是如何具体实现如此强大的功能呢,以下将做出源码分析:

(二)源码分析

对于源码分析最好是带着问题去研究,一步步弄清EventBus的处理流程:

问题1 事件发送过程中是如何寻找到订阅者的?
问题2 事件是如何发送出来的?
问题3 事件的处理线程是如何区分的?
先从注册谈起
EventBus.getDefault().register(Object subscriber);  
EventBus.getDefault()方法;    
public static EventBus getDefault() {
    EventBus instance = defaultInstance;
    if (instance == null) {
        synchronized (EventBus.class) {
            instance = EventBus.defaultInstance;
            if (instance == null) {
                instance = EventBus.defaultInstance = new EventBus();
            }
        }
    }
    return instance;
EventBus的构造函数

关于构造函数中变量的意义已在代码注释中说明。

public EventBus() {
    this(DEFAULT_BUILDER);
}
//创建的全局变量,一个Builder对象,用于创建EventBus对象
//private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
    
// 构造函数建采用建造者模式
EventBus(EventBusBuilder builder) {

    // 获取日志工具类
    logger = builder.getLogger();
        
    /** 全局变量, 事件类型Class<?>与订阅描述Subscription列表的map集合 */
    //  private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    
    subscriptionsByEventType = new HashMap<>();
    
    /** 全局变量, 订阅者Object 和 事件类型列表List<Class<?>>的map集合 */
    //  private final Map<Object, List<Class<?>>> typesBySubscriber;
    
    typesBySubscriber = new HashMap<>();
    
    
    // 粘性事件集合
    stickyEvents = new ConcurrentHashMap<>();

    mainThreadSupport = builder.getMainThreadSupport();
    
    //创建主线程的发送事件对象
    mainThreadPoster = mainThreadSupport != null ? mainThreadSupport
            .createPoster(this) : null;
            
    //后台线程发送事件对象
    backgroundPoster = new BackgroundPoster(this);
    
    //异步发送事件对象
    asyncPoster = new AsyncPoster(this);
        
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes
            .size() : 0;
            
    //辅助对象,用于寻找订阅者中的订阅方法        
    subscriberMethodFinder = new SubscriberMethodFinder(
            builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    eventInheritance = builder.eventInheritance;
    
    /** 线程池 */
    // private final ExecutorService executorService;
    executorService = builder.executorService;
}

EventBus的构造采用Builder模式,构造函数中实现变量的初始化操作。

实现注册订阅对象的方法
//注册方法
//参数 Object subscriber订阅者对象
public void register(Object subscriber) {

    // 获取对象所属的类类型
    Class<?> subscriberClass = subscriber.getClass();

    /** EventBus声明的辅助类,用于 查找subscriberClass类中订阅事件的方法*/
    // 全局对象声明
    // private final SubscriberMethodFinder subscriberMethodFinder;

    // 寻找订阅事件的方法
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder
            .findSubscriberMethods(subscriberClass);

    // 同步代码块,订阅事件
    synchronized (this) {
        // 遍历订阅方法,实现订阅
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

获取订阅方法的函数:

List<SubscriberMethod> subscriberMethods = subscriberMethodFinder
            .findSubscriberMethods(subscriberClass);

获取subscriber所属类中的订阅方法SubscriberMethod列表集合,订阅方法SubscriberMethod就是在注册订阅的类中实现的订阅方法。例如:Activity中实现的onMessageEvent()方法。

类比:该函数的作用就是相当于出版社读取用户个人信息,获取到用户接收刊物的联系方式。比如:获取到用于的住址信息,公司地址信息或者邮箱信息等。

获取到订阅方法信息,就等于找到了事件的发送目标,也就明确了事件要发给谁的问题。该方法的具体实现:

// 获取@Subscribe注解的方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {

    // 从缓存中获取订阅事件的方法
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE
            .get(subscriberClass);
    // 返回不为空,说明存在方法,直接返回
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    // 如果缓存中没有找到,在类中寻找订阅方法 , ignoreGeneratedIndex默认为false,一般不会设置此标志位
    if (ignoreGeneratedIndex) {
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    // 如果没有找到订阅方法
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException(
                "Subscriber "
                        + subscriberClass
                        + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        // 添加订阅方法
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

ignoreGeneratedIndex默认为false,一般不会设置此标志位,因此订阅方法的获取在findUsingInfo(subscriberClass)中获取。
findUsingInfo()方法如下:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    
    //保存订阅方法订阅在等相关对象的类
    FindState findState = prepareFindState();
    
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        findState.subscriberInfo = getSubscriberInfo(findState);
        if (findState.subscriberInfo != null) {
            SubscriberMethod[] array = findState.subscriberInfo
                    .getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method,
                        subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            findUsingReflectionInSingleClass(findState);
        }
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}

FindState类为存储订阅方法信息类,用于辅助查找类文件中的订阅方法。实际用于查找订阅方法的方法为findUsingReflectionInSingleClass();

/**
 * 通过反射遍历类中的所有方法,根据方法类型,参数类型以及注解信息来寻找订阅事件方法
 * 
 * @param findState
 */
private void findUsingReflectionInSingleClass(FindState findState) {
    // 方法数组
    Method[] methods;
    try {
        // This is faster than getMethods, especially when subscribers are
        // fat classes like Activities
        // 获取某个类中所有声明方法
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        
        methods = findState.clazz.getMethods();
        findState.skipSuperClasses = true;
    }
    // 遍历方法列表,获取符合订阅方法格式的方法
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        if ((modifiers & Modifier.PUBLIC) != 0
                && (modifiers & MODIFIERS_IGNORE) == 0) {
            
            //首先public方法

            // 获取一个方法的参数列表数组信息
            Class<?>[] parameterTypes = method.getParameterTypes();

            if (parameterTypes.length == 1) {// 方法只有一个参数
                // 获取注解信息
                Subscribe subscribeAnnotation = method
                        .getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    Class<?> eventType = parameterTypes[0];
                    if (findState.checkAdd(method, eventType)) {
                        // 获取注解信息中的执行线程信息
                        ThreadMode threadMode = subscribeAnnotation
                                .threadMode();

                        // 将订阅方法信息保存至FindState对象中
                        findState.subscriberMethods
                                .add(new SubscriberMethod(method,
                                        eventType, threadMode,
                                        subscribeAnnotation.priority(),
                                        subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification
                    && method.isAnnotationPresent(Subscribe.class)) {

                // 存在Subscribe的注解,但是参数列表不正确
                String methodName = method.getDeclaringClass().getName()
                        + "." + method.getName();

                // 抛出异常
                throw new EventBusException("@Subscribe method "
                        + methodName
                        + "must have exactly 1 parameter but has "
                        + parameterTypes.length);
            }
        } else if (strictMethodVerification
                && method.isAnnotationPresent(Subscribe.class)) {
            
            //存在Subscribe注解,但方法的类型不正确
            String methodName = method.getDeclaringClass().getName() + "."
                    + method.getName();
            throw new EventBusException(
                    methodName
                            + " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

通过以上分析过程可以得到订阅者信息以及订阅者中存在的订阅方法。

类比过程:出版社获取到了用户的订阅信息,以及用户的联系方式,接下来出版社将用户添加到系统中,以便后续发货给用户。再看注册方法:

public void register(Object subscriber) {

    // 获取对象所属的类类型
    Class<?> subscriberClass = subscriber.getClass();

    /** EventBus声明的辅助类,用于 查找subscriberClass类中订阅事件的方法*/
    // 全局对象声明
    // private final SubscriberMethodFinder subscriberMethodFinder;

    // 寻找订阅事件的方法
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder
            .findSubscriberMethods(subscriberClass);

    // 同步代码块,订阅事件
    synchronized (this) {
        // 遍历订阅的方法,实现订阅
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

获取订阅者订阅方法后,调用subscribe(subscriber, subscriberMethod)方法实现订阅过程。

/***
 * 订阅
 * 
 * @param subscriber
 *            订阅者
 * @param subscriberMethod
 *            订阅方法
 */
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {

    // 获取订阅事件类型
    Class<?> eventType = subscriberMethod.eventType;
    
    //Subscription类一个订阅事件描述
    //两个主要属性
    //final Object subscriber;订阅者
    //final SubscriberMethod subscriberMethod;订阅方法
    
    Subscription newSubscription = new Subscription(subscriber,
            subscriberMethod);
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType
            .get(eventType);
    if (subscriptions == null) {
        //将事件类型与订阅事件描述关联
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
    
        //判断订阅者是否已经注册过
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber "
                    + subscriber.getClass()
                    + " already registered to event " + eventType);
        }
    }

    //根据优先级添加订阅事件描述
    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size
                || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        //将订阅者与订阅事件类型关联
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    // 粘性事件的处理,暂不讨论
    if (subscriberMethod.sticky) {
        if (eventInheritance) {
            
            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();
                    checkPostStickyEventToSubscription(newSubscription,
                            stickyEvent);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

通过订阅过程,在Map集合中可以得出以下关键信息:

1: 订阅者订阅了哪些事件类型;例如:Activity可以接收哪些事件
类比: 用户订阅了哪些刊物。
2 :某个事件类型有哪些订阅者,以及订阅者中的订阅方法;
类比: 某一种刊物,比如报纸,该种报纸有哪些用户订阅,并且订阅该报纸的用户联系方式是什么。

发送事件:
/**
 * 发送事件
 * 
 * @param event
 */
public void post(Object event) {

    // 获取当前待发送事件以及线程的状态
    PostingThreadState postingState = currentPostingThreadState.get();

    //发送事件的队列
    List<Object> eventQueue = postingState.eventQueue;
    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 {
            // 连续发送事件
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

发送事件的处理交给了postSingleEvent(eventQueue.remove(0), postingState)方法。

/**
 * 发送单一事件
 * 
 * @param event
 *            事件对象
 * @param postingState
 *            发送线程的状态
 * @throws Error
 */
private void postSingleEvent(Object event, PostingThreadState postingState)
        throws Error {

    // 获取事件类型
    Class<?> eventClass = event.getClass();
    // 判断是否找到订阅信息
    boolean subscriptionFound = false;

    // 是否向上查找事件的父类,默认true
    if (eventInheritance) {

        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            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 boolean postSingleEventForEventType(Object event,
        PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        //根据事件类型获取订阅描述信息列表集合。
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {

        // 遍历订阅描述信息列表集合
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                // 根据描述信息发送事件
                postToSubscription(subscription, event,
                        postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}
/**
 * 发送事件
 * 
 * @param subscription
 * @param event
 * @param isMainThread
 */
private void postToSubscription(Subscription subscription, Object event,
        boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
    case POSTING:// 接受线程类型为POSTING
        invokeSubscriber(subscription, event);
        break;
    case MAIN:// 接受线程MAIN
        if (isMainThread) {
            invokeSubscriber(subscription, event);
        } else {
            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:接收线程BACKGROUND
        if (isMainThread) {
            backgroundPoster.enqueue(subscription, event);
        } else {
            invokeSubscriber(subscription, event);
        }
        break;
    case ASYNC:接收线程ASYNC
        asyncPoster.enqueue(subscription, event);
        break;
    default:
        throw new IllegalStateException("Unknown thread mode: "
                + subscription.subscriberMethod.threadMode);
    }
}

// 利用反射的方式调用订阅者的事件处理方法
void invokeSubscriber(Subscription subscription, Object event) {

    try {
        // 使用反射的方式动态调用订阅方法Method
        // subscription.subscriber拥有该方法的对象,
        // event 为被调用方法的参数
        subscription.subscriberMethod.method.invoke(
                subscription.subscriber, event);
    } catch (InvocationTargetException e) {
        handleSubscriberException(subscription, event, e.getCause());
    } catch (IllegalAccessException e) {
        throw new IllegalStateException("Unexpected exception", e);
    }
}

事件的发送过程的分析大部分已在代码注释中阐述,主要是通过反射的方式动态的调用订阅者中的订阅方法,就实现了将event事件发送给了subscriber订阅者。到此,可以回答问题1,问题2。

问题1 事件发送过程中是如何寻找到订阅者的?

在订阅者注册订阅时,EventBus保存了事件与订阅描述的映射关系。订阅描述中包含了订阅者以及该订阅者的订阅方法。

问题2 事件是如何发送出来的?

事件的发送是通过反射的方式调用订阅该事件的订阅者中的订阅方法实现的。通过反射机制动态调用某个对象的具体方法,实现了一种"发送事件"和"接收事件的效果"。

关于问题3的分析:

case POSTING:// 接受线程类型为POSTING
        invokeSubscriber(subscription, event);
        break;

//POSTING方式直接调用invokeSubscriber()方法,保证发送和接收是同一线程。

case MAIN:// 接受线程MAIN
        if (isMainThread) {
            invokeSubscriber(subscription, event);
        } else {
            mainThreadPoster.enqueue(subscription, event);
        }
        break;
//先判断是否为主线程,是的话直接调用invokeSubscriber(),在主线程中调用订阅方法,如果不为主线程,则将事件加入到主线程的事件队列中,然后由主线程发送事件并调用invokeSubscriber()方法。

case BACKGROUND:接收线程BACKGROUND
        if (isMainThread) {
            backgroundPoster.enqueue(subscription, event);
        } else {
            invokeSubscriber(subscription, event);
        }
        break;
//判断事件是否由主线程发送的,如果是则加入到后台线程发送事件的队列中,否则直接在发送线程中执行。

case ASYNC:接收线程ASYNC
        asyncPoster.enqueue(subscription, event);
        break;
//无论事件由哪个线程发出,一律加入到子线程的事件队列中发送。

通过将事件加入到不同线程的事件队列中,实现事件的发送与接收的线程切换。

解除注册。
public synchronized void unregister(Object subscriber) {

    //根据订阅者获取其订阅事件列表信息
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
    
        //遍历事件列表
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING,
                "Subscriber to unregister was not registered before: "
                        + subscriber.getClass());
    }
}

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {

    //根据事件类型信息找到订阅描述列表
    List<Subscription> subscriptions = subscriptionsByEventType
            .get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        //遍历列表,找到待解除注册的订阅者,移除其订阅信息
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            
            
            if (subscription.subscriber == subscriber) {
            
                //移除订阅描述信息
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

通过上述流程,大致完成了EventBus源码分析过程。个人觉得EventBus的关键步骤主要有两部分,一是对于订阅者中方法的扫描寻找到订阅方法,解决了事件发给谁的问题,二是反射机制调用订阅者的订阅方法实现了事件怎么发的问题。

结语

EventBus是实现观察者模式最佳实践,事件是被观察者,订阅者是观察者,当事件出现或者发送变更的时候,会通过EventBus通知观察者,使得观察者的订阅方法能够被自动调用。出版社和用户的例子只是为了帮助大家理解EventBus,某些方面与观察者模式还是存在一定差异。由于本人能力有限,希望读者针对文中问题多多提出建议,谢谢。

上一篇 下一篇

猜你喜欢

热点阅读