android事件总线EventBus解析

来源:互联网 发布:cocos2dx 3.10 mac 编辑:程序博客网 时间:2024/04/27 15:06

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:【张鸿洋的博客】

上一篇带大家初步了解了EventBus的使用方式,详见:Android EventBus实战 没听过你就out了,本篇博客将解析EventBus的源码,相信能够让大家深入理解该框架的实现,也能解决很多在使用中的疑问:为什么可以这么做?为什么这么做不好呢?

1、概述

一般使用EventBus的组件类,类似下面这种方式:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class SampleComponent extends Fragment  
  2. {  
  3.   
  4.     @Override  
  5.     public void onCreate(Bundle savedInstanceState)  
  6.     {  
  7.         super.onCreate(savedInstanceState);  
  8.         EventBus.getDefault().register(this);  
  9.     }  
  10.   
  11.     public void onEventMainThread(param)  
  12.     {  
  13.     }  
  14.       
  15.     public void onEventPostThread(param)  
  16.     {  
  17.           
  18.     }  
  19.       
  20.     public void onEventBackgroundThread(param)  
  21.     {  
  22.           
  23.     }  
  24.       
  25.     public void onEventAsync(param)  
  26.     {  
  27.           
  28.     }  
  29.       
  30.     @Override  
  31.     public void onDestroy()  
  32.     {  
  33.         super.onDestroy();  
  34.         EventBus.getDefault().unregister(this);  
  35.     }  
  36.       
  37. }  

大多情况下,都会在onCreate中进行register,在onDestory中进行unregister ;

看完代码大家或许会有一些疑问:

1、代码中还有一些以onEvent开头的方法,这些方法是干嘛的呢?

在回答这个问题之前,我有一个问题,你咋不问register(this)是干嘛的呢?其实register(this)就是去当前类,遍历所有的方法,找到onEvent开头的然后进行存储。现在知道onEvent开头的方法是干嘛的了吧。

2、那onEvent后面的那些MainThread应该是什么标志吧?

嗯,是的,onEvent后面可以写四种,也就是上面出现的四个方法,决定了当前的方法最终在什么线程运行,怎么运行,可以参考上一篇博客或者细细往下看。


既然register了,那么肯定得说怎么调用是吧。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. EventBus.getDefault().post(param);  

调用很简单,一句话,你也可以叫发布,只要把这个param发布出去,EventBus会在它内部存储的方法中,进行扫描,找到参数匹配的,就使用反射进行调用。

现在有没有觉得,撇开专业术语:其实EventBus就是在内部存储了一堆onEvent开头的方法,然后post的时候,根据post传入的参数,去找到匹配的方法,反射调用之。

那么,我告诉你,它内部使用了Map进行存储,键就是参数的Class类型。知道是这个类型,那么你觉得根据post传入的参数进行查找还是个事么?


下面我们就去看看EventBus的register和post真面目。

2、register

EventBus.getDefault().register(this);

首先:

EventBus.getDefault()其实就是个单例,和我们传统的getInstance一个意思:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** Convenience singleton for apps using a process-wide EventBus instance. */  
  2.    public static EventBus getDefault() {  
  3.        if (defaultInstance == null) {  
  4.            synchronized (EventBus.class) {  
  5.                if (defaultInstance == null) {  
  6.                    defaultInstance = new EventBus();  
  7.                }  
  8.            }  
  9.        }  
  10.        return defaultInstance;  
  11.    }  

使用了双重判断的方式,防止并发的问题,还能极大的提高效率。

然后register应该是一个普通的方法,我们去看看:

register公布给我们使用的有4个:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1.  public void register(Object subscriber) {  
  2.         register(subscriber, DEFAULT_METHOD_NAME, false0);  
  3.     }  
  4.  public void register(Object subscriber, int priority) {  
  5.         register(subscriber, DEFAULT_METHOD_NAME, false, priority);  
  6.     }  
  7. public void registerSticky(Object subscriber) {  
  8.         register(subscriber, DEFAULT_METHOD_NAME, true0);  
  9.     }  
  10. public void registerSticky(Object subscriber, int priority) {  
  11.         register(subscriber, DEFAULT_METHOD_NAME, true, priority);  
  12.     }  

本质上就调用了同一个:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private synchronized void register(Object subscriber, String methodName, boolean sticky, int priority) {  
  2.         List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),  
  3.                 methodName);  
  4.         for (SubscriberMethod subscriberMethod : subscriberMethods) {  
  5.             subscribe(subscriber, subscriberMethod, sticky, priority);  
  6.         }  
  7.     }  

四个参数

subscriber 是我们扫描类的对象,也就是我们代码中常见的this;

methodName 这个是写死的:“onEvent”,用于确定扫描什么开头的方法,可见我们的类中都是以这个开头。

sticky 这个参数,解释源码的时候解释,暂时不用管

priority 优先级,优先级越高,在调用的时候会越先调用。

下面开始看代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),  
  2.                 methodName);  

调用内部类SubscriberMethodFinder的findSubscriberMethods方法,传入了subscriber 的class,以及methodName,返回一个List<SubscriberMethod>。

那么不用说,肯定是去遍历该类内部所有方法,然后根据methodName去匹配,匹配成功的封装成SubscriberMethod,最后返回一个List。下面看代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass, String eventMethodName) {  
  2.         String key = subscriberClass.getName() + '.' + eventMethodName;  
  3.         List<SubscriberMethod> subscriberMethods;  
  4.         synchronized (methodCache) {  
  5.             subscriberMethods = methodCache.get(key);  
  6.         }  
  7.         if (subscriberMethods != null) {  
  8.             return subscriberMethods;  
  9.         }  
  10.         subscriberMethods = new ArrayList<SubscriberMethod>();  
  11.         Class<?> clazz = subscriberClass;  
  12.         HashSet<String> eventTypesFound = new HashSet<String>();  
  13.         StringBuilder methodKeyBuilder = new StringBuilder();  
  14.         while (clazz != null) {  
  15.             String name = clazz.getName();  
  16.             if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {  
  17.                 // Skip system classes, this just degrades performance  
  18.                 break;  
  19.             }  
  20.   
  21.             // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)  
  22.             Method[] methods = clazz.getMethods();  
  23.             for (Method method : methods) {  
  24.                 String methodName = method.getName();  
  25.                 if (methodName.startsWith(eventMethodName)) {  
  26.                     int modifiers = method.getModifiers();  
  27.                     if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {  
  28.                         Class<?>[] parameterTypes = method.getParameterTypes();  
  29.                         if (parameterTypes.length == 1) {  
  30.                             String modifierString = methodName.substring(eventMethodName.length());  
  31.                             ThreadMode threadMode;  
  32.                             if (modifierString.length() == 0) {  
  33.                                 threadMode = ThreadMode.PostThread;  
  34.                             } else if (modifierString.equals("MainThread")) {  
  35.                                 threadMode = ThreadMode.MainThread;  
  36.                             } else if (modifierString.equals("BackgroundThread")) {  
  37.                                 threadMode = ThreadMode.BackgroundThread;  
  38.                             } else if (modifierString.equals("Async")) {  
  39.                                 threadMode = ThreadMode.Async;  
  40.                             } else {  
  41.                                 if (skipMethodVerificationForClasses.containsKey(clazz)) {  
  42.                                     continue;  
  43.                                 } else {  
  44.                                     throw new EventBusException("Illegal onEvent method, check for typos: " + method);  
  45.                                 }  
  46.                             }  
  47.                             Class<?> eventType = parameterTypes[0];  
  48.                             methodKeyBuilder.setLength(0);  
  49.                             methodKeyBuilder.append(methodName);  
  50.                             methodKeyBuilder.append('>').append(eventType.getName());  
  51.                             String methodKey = methodKeyBuilder.toString();  
  52.                             if (eventTypesFound.add(methodKey)) {  
  53.                                 // Only add if not already found in a sub class  
  54.                                 subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));  
  55.                             }  
  56.                         }  
  57.                     } else if (!skipMethodVerificationForClasses.containsKey(clazz)) {  
  58.                         Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "."  
  59.                                 + methodName);  
  60.                     }  
  61.                 }  
  62.             }  
  63.             clazz = clazz.getSuperclass();  
  64.         }  
  65.         if (subscriberMethods.isEmpty()) {  
  66.             throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "  
  67.                     + eventMethodName);  
  68.         } else {  
  69.             synchronized (methodCache) {  
  70.                 methodCache.put(key, subscriberMethods);  
  71.             }  
  72.             return subscriberMethods;  
  73.         }  
  74.     }  
呵,代码还真长;不过我们直接看核心部分:

22行:看到没,clazz.getMethods();去得到所有的方法:

23-62行:就开始遍历每一个方法了,去匹配封装了。

25-29行:分别判断了是否以onEvent开头,是否是public且非static和abstract方法,是否是一个参数。如果都复合,才进入封装的部分。

32-45行:也比较简单,根据方法的后缀,来确定threadMode,threadMode是个枚举类型:就四种情况。

最后在54行:将method, threadMode, eventType传入构造了:new SubscriberMethod(method, threadMode, eventType)。添加到List,最终放回。

注意下63行:clazz = clazz.getSuperclass();可以看到,会扫描所有的父类,不仅仅是当前类。

继续回到register:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. for (SubscriberMethod subscriberMethod : subscriberMethods) {  
  2.             subscribe(subscriber, subscriberMethod, sticky, priority);  
  3.         }  

for循环扫描到的方法,然后去调用suscribe方法。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // Must be called in synchronized block  
  2.    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {  
  3.        subscribed = true;  
  4.        Class<?> eventType = subscriberMethod.eventType;  
  5.        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);  
  6.        Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);  
  7.        if (subscriptions == null) {  
  8.            subscriptions = new CopyOnWriteArrayList<Subscription>();  
  9.            subscriptionsByEventType.put(eventType, subscriptions);  
  10.        } else {  
  11.            for (Subscription subscription : subscriptions) {  
  12.                if (subscription.equals(newSubscription)) {  
  13.                    throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "  
  14.                            + eventType);  
  15.                }  
  16.            }  
  17.        }  
  18.   
  19.        // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)  
  20.        // subscriberMethod.method.setAccessible(true);  
  21.   
  22.        int size = subscriptions.size();  
  23.        for (int i = 0; i <= size; i++) {  
  24.            if (i == size || newSubscription.priority > subscriptions.get(i).priority) {  
  25.                subscriptions.add(i, newSubscription);  
  26.                break;  
  27.            }  
  28.        }  
  29.   
  30.        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);  
  31.        if (subscribedEvents == null) {  
  32.            subscribedEvents = new ArrayList<Class<?>>();  
  33.            typesBySubscriber.put(subscriber, subscribedEvents);  
  34.        }  
  35.        subscribedEvents.add(eventType);  
  36.   
  37.        if (sticky) {  
  38.            Object stickyEvent;  
  39.            synchronized (stickyEvents) {  
  40.                stickyEvent = stickyEvents.get(eventType);  
  41.            }  
  42.            if (stickyEvent != null) {  
  43.                // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)  
  44.                // --> Strange corner case, which we don't take care of here.  
  45.                postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());  
  46.            }  
  47.        }  
  48.    }  
我们的subscriberMethod中保存了method, threadMode, eventType,上面已经说了;

4-17行:根据subscriberMethod.eventType,去subscriptionsByEventType去查找一个CopyOnWriteArrayList<Subscription> ,如果没有则创建。

顺便把我们的传入的参数封装成了一个:Subscription(subscriber, subscriberMethod, priority);

这里的subscriptionsByEventType是个Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> ; 这个Map其实就是EventBus存储方法的地方,一定要记住!

22-28行:实际上,就是添加newSubscription;并且是按照优先级添加的。可以看到,优先级越高,会插到在当前List的前面。

30-35行:根据subscriber存储它所有的eventType ; 依然是map;key:subscriber ,value:List<eventType> ;知道就行,非核心代码,主要用于isRegister的判断。

37-47行:判断sticky;如果为true,从stickyEvents中根据eventType去查找有没有stickyEvent,如果有则立即发布去执行。stickyEvent其实就是我们post时的参数。

postToSubscription这个方法,我们在post的时候会介绍。


到此,我们register就介绍完了。

你只要记得一件事:扫描了所有的方法,把匹配的方法最终保存在subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> )中;

eventType是我们方法参数的Class,Subscription中则保存着subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了执行改方法所需的一切。


3、post

register完毕,知道了EventBus如何存储我们的方法了,下面看看post它又是如何调用我们的方法的。

再看源码之前,我们猜测下:register时,把方法存在subscriptionsByEventType;那么post肯定会去subscriptionsByEventType去取方法,然后调用。

下面看源码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** Posts the given event to the event bus. */  
  2.    public void post(Object event) {  
  3.        PostingThreadState postingState = currentPostingThreadState.get();  
  4.        List<Object> eventQueue = postingState.eventQueue;  
  5.        eventQueue.add(event);  
  6.   
  7.        if (postingState.isPosting) {  
  8.            return;  
  9.        } else {  
  10.            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();  
  11.            postingState.isPosting = true;  
  12.            if (postingState.canceled) {  
  13.                throw new EventBusException("Internal error. Abort state was not reset");  
  14.            }  
  15.            try {  
  16.                while (!eventQueue.isEmpty()) {  
  17.                    postSingleEvent(eventQueue.remove(0), postingState);  
  18.                }  
  19.            } finally {  
  20.                postingState.isPosting = false;  
  21.                postingState.isMainThread = false;  
  22.            }  
  23.        }  
  24.    }  

currentPostingThreadState是一个ThreadLocal类型的,里面存储了PostingThreadState;PostingThreadState包含了一个eventQueue和一些标志位。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {  
  2.        @Override  
  3.        protected PostingThreadState initialValue() {  
  4.            return new PostingThreadState();  
  5.        }  
  6.    }  

把我们传入的event,保存到了当前线程中的一个变量PostingThreadState的eventQueue中。

10行:判断当前是否是UI线程。

16-18行:遍历队列中的所有的event,调用postSingleEvent(eventQueue.remove(0), postingState)方法。

这里大家会不会有疑问,每次post都会去调用整个队列么,那么不会造成方法多次调用么?

可以看到第7-8行,有个判断,就是防止该问题的,isPosting=true了,就不会往下走了。


下面看postSingleEvent

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {  
  2.         Class<? extends Object> eventClass = event.getClass();  
  3.         List<Class<?>> eventTypes = findEventTypes(eventClass);  
  4.         boolean subscriptionFound = false;  
  5.         int countTypes = eventTypes.size();  
  6.         for (int h = 0; h < countTypes; h++) {  
  7.             Class<?> clazz = eventTypes.get(h);  
  8.             CopyOnWriteArrayList<Subscription> subscriptions;  
  9.             synchronized (this) {  
  10.                 subscriptions = subscriptionsByEventType.get(clazz);  
  11.             }  
  12.             if (subscriptions != null && !subscriptions.isEmpty()) {  
  13.                 for (Subscription subscription : subscriptions) {  
  14.                     postingState.event = event;  
  15.                     postingState.subscription = subscription;  
  16.                     boolean aborted = false;  
  17.                     try {  
  18.                         postToSubscription(subscription, event, postingState.isMainThread);  
  19.                         aborted = postingState.canceled;  
  20.                     } finally {  
  21.                         postingState.event = null;  
  22.                         postingState.subscription = null;  
  23.                         postingState.canceled = false;  
  24.                     }  
  25.                     if (aborted) {  
  26.                         break;  
  27.                     }  
  28.                 }  
  29.                 subscriptionFound = true;  
  30.             }  
  31.         }  
  32.         if (!subscriptionFound) {  
  33.             Log.d(TAG, "No subscribers registered for event " + eventClass);  
  34.             if (eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {  
  35.                 post(new NoSubscriberEvent(this, event));  
  36.             }  
  37.         }  
  38.     }  

将我们的event,即post传入的实参;以及postingState传入到postSingleEvent中。

2-3行:根据event的Class,去得到一个List<Class<?>>;其实就是得到event当前对象的Class,以及父类和接口的Class类型;主要用于匹配,比如你传入Dog extends Dog,他会把Animal也装到该List中。

6-31行:遍历所有的Class,到subscriptionsByEventType去查找subscriptions;哈哈,熟不熟悉,还记得我们register里面把方法存哪了不?

是不是就是这个Map;

12-30行:遍历每个subscription,依次去调用postToSubscription(subscription, event, postingState.isMainThread);
这个方法就是去反射执行方法了,大家还记得在register,if(sticky)时,也会去执行这个方法。

下面看它如何反射执行:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {  
  2.         switch (subscription.subscriberMethod.threadMode) {  
  3.         case PostThread:  
  4.             invokeSubscriber(subscription, event);  
  5.             break;  
  6.         case MainThread:  
  7.             if (isMainThread) {  
  8.                 invokeSubscriber(subscription, event);  
  9.             } else {  
  10.                 mainThreadPoster.enqueue(subscription, event);  
  11.             }  
  12.             break;  
  13.         case BackgroundThread:  
  14.             if (isMainThread) {  
  15.                 backgroundPoster.enqueue(subscription, event);  
  16.             } else {  
  17.                 invokeSubscriber(subscription, event);  
  18.             }  
  19.             break;  
  20.         case Async:  
  21.             asyncPoster.enqueue(subscription, event);  
  22.             break;  
  23.         default:  
  24.             throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);  
  25.         }  
  26.     }  
前面已经说过subscription包含了所有执行需要的东西,大致有:subscriber, subscriberMethod(method, threadMode, eventType), priority;

那么这个方法:第一步根据threadMode去判断应该在哪个线程去执行该方法;
case PostThread:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void invokeSubscriber(Subscription subscription, Object event) throws Error {  
  2.           subscription.subscriberMethod.method.invoke(subscription.subscriber, event);  
  3. }  

直接反射调用;也就是说在当前的线程直接调用该方法;

case MainThread:

首先去判断当前如果是UI线程,则直接调用;否则: mainThreadPoster.enqueue(subscription, event);把当前的方法加入到队列,然后直接通过handler去发送一个消息,在handler的handleMessage中,去执行我们的方法。说白了就是通过Handler去发送消息,然后执行的。

 case BackgroundThread:

如果当前非UI线程,则直接调用;如果是UI线程,则将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用

executorService = Executors.newCachedThreadPool();。

 case Async:将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用;线程池与BackgroundThread用的是同一个。

这么说BackgroundThread和Async有什么区别呢?

BackgroundThread中的任务,一个接着一个去调用,中间使用了一个布尔型变量handlerActive进行的控制。

Async则会动态控制并发。


到此,我们完整的源码分析就结束了,总结一下:register会把当前类中匹配的方法,存入一个map,而post会根据实参去map查找进行反射调用。分析这么久,一句话就说完了~~

其实不用发布者,订阅者,事件,总线这几个词或许更好理解,以后大家问了EventBus,可以说,就是在一个单例内部维持着一个map对象存储了一堆的方法;post无非就是根据参数去查找方法,进行反射调用。


4、其余方法

介绍了register和post;大家获取还能想到一个词sticky,在register中,如何sticky为true,会去stickyEvents去查找事件,然后立即去post;

那么这个stickyEvents何时进行保存事件呢?

其实evevntbus中,除了post发布事件,还有一个方法也可以:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void postSticky(Object event) {  
  2.        synchronized (stickyEvents) {  
  3.            stickyEvents.put(event.getClass(), event);  
  4.        }  
  5.        // Should be posted after it is putted, in case the subscriber wants to remove immediately  
  6.        post(event);  
  7.    }  

和post功能类似,但是会把方法存储到stickyEvents中去;

大家再去看看EventBus中所有的public方法,无非都是一些状态判断,获取事件,移除事件的方法;没什么好介绍的,基本见名知意。


好了,到此我们的源码解析就结束了,希望大家不仅能够了解这些优秀框架的内部机理,更能够体会到这些框架的很多细节之处,并发的处理,很多地方,为什么它这么做等等。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 自己的行李忘到高铁安检怎么办 高铁二等座睡觉怎么办 空少岁数大了怎么办 美国留学生办欧洲签证怎么办 苏州小区不让装充电桩怎么办 饿了么运力不足怎么办 书法落款写偏了怎么办 辐射4运行不流畅怎么办 vgs币忘了映射怎么办 货车把我的货物运输中损坏怎么办 道路货物运输从业资格证过期怎么办 高铁喷雾被扣了怎么办 宝宝不好好吸奶怎么办 宝宝不用劲吸奶怎么办 刚出生的宝宝不吃奶怎么办 老婆怀孕想吐怎么办呢 菜把下水道堵了怎么办 一楼厕所堵了怎么办 农村房屋确权有争议怎么办 盲审一个没过怎么办 本科生论文盲审不过怎么办 本科盲审没通过怎么办 一篇论文多次引用著作怎么办 学生毕业后改名学籍怎么办 大学毕业后改名字后学籍怎么办 考科目三下暴雨怎么办 挂科太多拿不到毕业证怎么办 挂科太多不给毕业证怎么办 大专毕业拿不到毕业证怎么办 高考差一分二本怎么办 3个月宝宝大小眼怎么办 华东交大理工学院没有评教怎么办 学校断4g网怎么办 学校移动4g网卡怎么办 没上专科线怎么办福建 联考没过本科线怎么办 拍婚纱拍的脸歪怎么办? 老婆彻底寒心要离婚怎么办 手机被同学偷了怎么办 钱被同学偷了怎么办 上班穿皮鞋脚疼怎么办