轻量级的EventBus简单实现

EventBus 简介

EventBus是一种用于Android的事件发布-订阅总线,由GreenRobot开发,Gihub地址是:EventBus。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。

1.1 三个角色

Event:事件,它可以是任意类型,EventBus会根据事件类型进行全局的通知。

Subscriber:事件订阅者,在EventBus 3.0之前我们必须定义以onEvent开头的那几个方法,分别是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe,并且指定线程模型,默认是POSTING。

Publisher:事件的发布者,可以在任意线程里发布事件。一般情况下,使用EventBus.getDefault()就可以得到一个EventBus对象,然后再调用post(Object)方法即可。

1.2 四种线程模型

EventBus3.0有四种线程模型,分别是:

POSTING:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程。

MAIN:表示事件处理函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作。

BACKGROUND:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程。

ASYNC:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。

先看下以下的一种场景:

现在有在同一个进程的ActivityA和ActivityB,现在的简单需求是点击ActivityB中的按钮,修改ActivityA中TextView中的内容.

public class ActivityA extends Activity implements View.OnClickListener {

private TextView textView;

public static ActivityA activityA;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_a);

textView = findViewById(R.id.tv);

        Button button = findViewById(R.id.btnStartB);

        button.setOnClickListener(this);

activityA = this;

    }

public void setTextView(){

textView.setText("ActivityB set ActivityA");

    }

@Override

public void onClick(View v) {

        Intent intent = new Intent();

        intent.setClass(this,ActivityB.class);

        startActivity(intent);

    }

public void onDestroy(){

super.onDestroy();

activityA = null;//防止内存漏洞

    }

}

public class ActivityB extends Activity implements View.OnClickListener{

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_b);

        Button button = findViewById(R.id.btn);

        button.setOnClickListener(this);

    }

@Override

public void onClick(View v) {

        ActivityA.activityA.setTextView();

    }

}

以上的代码是以一种,这种方式的问题是直接引用在ActivityB引用了ActivityA类,耦合度高。

现在做以下的优化:

新增ActivityGloble

public class ActivityGloble {

private  ActivityA activityA;

public static ActivityGloble activityGloble = new ActivityGloble();

public void setActivityA(ActivityA activityA){

this.activityA = activityA;

    }

public void setTextView(){

activityA.setTextView();

    }

}

public class ActivityA extends Activity implements View.OnClickListener {

private TextView textView;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_a);

textView = findViewById(R.id.tv);

        Button button = findViewById(R.id.btnStartB);

        button.setOnClickListener(this);

        ActivityGloble.activityGloble.setActivityA(this);

//  activityA = this;

    }

public void setTextView(){

textView.setText("ActivityB set ActivityA");

    }

@Override

public void onClick(View v) {

        Intent intent = new Intent();

        intent.setClass(this,ActivityB.class);

        startActivity(intent);

    }

public void onDestroy(){

super.onDestroy();

        ActivityGloble.activityGloble.setActivityA(null);//防止内存漏洞

    }

}

public class ActivityB extends Activity implements View.OnClickListener{

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_b);

        Button button = findViewById(R.id.btn);

        button.setOnClickListener(this);

    }

@Override

public void onClick(View v) {

        ActivityGloble.activityGloble.setTextView();

    }

}

现在看起来已经优雅了很多了,但是还有以下一些问题要:

如果不是ActivityA变成了其他的ActivityC或者其他的类,要怎么处理?

现在是只有一个方法,如果方法多了,ActivityGloble是要手动增加方法的,如何才能做到自动获取方法?

针对以上两点,要做如下的改动:将要引用的类改成Object类型,要被执行的方法通过反射获取。

public class ActivityGloble {

private  Object subscriber;//要订阅的类,

    public static ActivityGloble activityGloble = new ActivityGloble();

private HashMap<String,Method> methodMap = new HashMap<>();//需要被执行方法map

    /*

     * @param subscriber//要订阅的类

     * @param postMethodNames,订阅类要执行的方法列表

     */

    public void register(Object subscriber,String[] postMethodNames){

this.subscriber = subscriber;

if(subscriber == null){

return;

        }

if(postMethodNames != null){

            Class<?> subscriberClass = subscriber.getClass();//获取类

            Method[] methods = subscriberClass.getDeclaredMethods();//获取所有方法

            for(int i = 0;i < methods.length;i++){

for(int j = 0;j < postMethodNames.length;j++){

if(methods[i].getName().equals(postMethodNames[j])){

                        StringBuilder paramTypeBuilde = null;//用于处理函数的重载

                        Class<?>[] paramTypes = methods[i].getParameterTypes();

if(paramTypes != null && paramTypes.length > 0){

                            paramTypeBuilde = new StringBuilder();

for(int k = 0; k < paramTypes.length;k++){

                                paramTypeBuilde.append(paramTypes[k].getName());//取出函数对应的参数

                            }

                        }

if(paramTypeBuilde == null){

methodMap.put(postMethodNames[j],methods[i]);//如果这个函数没有参数

                        }else{

//如果有参数,参数也和方法一起,作为一个key

                            //比如setTextView(String arg1,Integer arg3)会转化为 setTextViewjava.lang.Stringjava.lang.Integer                      methodMap.put(postMethodNames[j]+paramTypeBuilde.toString(),methods[i]);

                        }

                    }

                }

            }

        }

    }

/**

     * @param methodName

     * @param args,要传入的参数

     */

    public void post(String methodName, Object... args) {

if(args != null && args.length > 0){

//如果有参数,对方法进行拼接

            StringBuilder builder = new StringBuilder();

            builder.append(methodName);

for(int i = 0;i < args.length;i++){

                builder.append(args[i].getClass().getName());

            }

            methodName = builder.toString();

        }

        Method method = methodMap.get(methodName);

if(method != null){

try {

if(args == null || args.length == 0){

                    method.invoke(subscriber);

                }else{

                    method.invoke(subscriber,args);

                }

            } catch (IllegalAccessException e) {

                e.printStackTrace();

            } catch (InvocationTargetException e) {

                e.printStackTrace();

            }

        }

    }

}

对于post方法,这里要进行说明下:

因为是Object... args这样的方式传参,所以会导致int,long,float,double,boolean,byte转为Integer,Long,Float,Double,Boolean,Byte

所以为了处理这个问题,所以要声名的函数int,long,float,double,boolean,byte这些类型都要转为Integer,Long,Float,Double,Boolean,Byte。

具体的调用:

public class ActivityA extends Activity implements View.OnClickListener {

private TextView textView;

private String[] methods = {"setTextView","setTextView1"};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_a);

textView = findViewById(R.id.tv);

        Button button = findViewById(R.id.btnStartB);

        button.setOnClickListener(this);

        ActivityGloble.activityGloble.register(this,methods);

//  activityA = this;

    }

public void setTextView(){

textView.setText("ActivityB set ActivityA");

    }

public void setTextView1(String arg1,String arg2){

        TextView textView = findViewById(R.id.tv1);

        textView.setText("ActivityB set ActivityA "+arg1+" "+arg2);

    }

public void setTextView(String arg1,Integer arg3){

        TextView textView = findViewById(R.id.tv2);

        textView.setText("ActivityB set ActivityA "+arg1+" "+" arg3 is "+3);

    }

@Override

public void onClick(View v) {

        Intent intent = new Intent();

        intent.setClass(this,ActivityB.class);

        startActivity(intent);

    }

public void onDestroy(){

super.onDestroy();

        ActivityGloble.activityGloble.register(null,null);//防止内存漏洞

    }

}

public class ActivityB extends Activity implements View.OnClickListener{

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_b);

        Button button = findViewById(R.id.btn);

        button.setOnClickListener(this);

    }

@Override

public void onClick(View v) {

        ActivityGloble.activityGloble.post("setTextView");

        ActivityGloble.activityGloble.post("setTextView1","qaa","bbb");

        ActivityGloble.activityGloble.post("setTextView","wwww",1);

    }

}

这样看来解决之前提出的两个问题。

现在又有新的问题出现了:

1.是否可以注册多个类?

2.这里是手动把要注册的方法定好了,有没有更方式,让我们不需要提前写好方法,就能知道哪个方法要被执行?

3.执行的时候,可不可以只传个参数就能知道调用哪个方法?

分析问题1:

参照了EventBus里的源码,可以用缓存做处理,如下:

HashMap<Object,HashMap<String,SubscribleMethod>> cacheMap = new HashMap<>();

Object来作Key,就是订阅的类,HashMap<String,SubscribleMethod>这个是类对应订阅的方法。

代码如下:

HashMap<String,SubscribleMethod> methodMap = cacheMap.get(object);//先从缓存中查找。

if(methodMap == null){//缓存里没有找到

   methodMap = getSubscribleMethodMap(object);//获取方法map,

   cacheMap.put(object,methodMap);//将方法map放入到缓存中

}

分析下问题2:

在反射Method中有一个方法:

public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {

return super.getAnnotation(annotationClass);

}

这个方法可以获取写在方法上的注解,以此来判断这个方法是否有用到这个注解。所以可以通过注解的方法来查找哪个方法需要被执行。

对于问题3,如果每个函数传入的参数都不一样,这样是可以做到的。但是如果是两个不同的函数,有相同的参数,这时是不参区分的。还有就是如果注册了多个类,每个类都有相同的函数名和参数,这个也是不好区分的。

所以要做到以下三点:

1.定义函数的时候,尽量不要有相同的参数

2.如果同一个类里多个函数有相同的参数,调用的时候还是要把函数名给传递。

3.如果注册了多个类,且类里还要相同的函数,这里还是要传入类名。

现在来看下具体的实现:

先声名一个注解类:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface EventBusSubscribe {

    ThreadMode threadMode() default ThreadMode.POSTING;//判断是运行在哪个线程

}

ThreadMode 是判断函数的运行的类型,具体是参照EventBus源码里的:

public enum ThreadMode {

/**

     * Subscriber will be called directly in the same thread, which is posting the event. This is the default. Event delivery

     * implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for

     * simple tasks that are known to complete in a very short time without requiring the main thread. Event handlers

     * using this mode must return quickly to avoid blocking the posting thread, which may be the main thread.

     */

    POSTING,

    /**

     * On Android, subscriber will be called in Android's main thread (UI thread). If the posting thread is

     * the main thread, subscriber methods will be called directly, blocking the posting thread. Otherwise the event

     * is queued for delivery (non-blocking). Subscribers using this mode must return quickly to avoid blocking the main thread.

     * If not on Android, behaves the same as {@link #POSTING}.

     */

    MAIN,

    /**

     * On Android, subscriber will be called in Android's main thread (UI thread). Different from {@link #MAIN},

     * the event will always be queued for delivery. This ensures that the post call is non-blocking.

     */

    MAIN_ORDERED,

    /**

     * On Android, subscriber will be called in a background thread. If posting thread is not the main thread, subscriber methods

     * will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single

     * background thread, that will deliver all its events sequentially. Subscribers using this mode should try to

     * return quickly to avoid blocking the background thread. If not on Android, always uses a background thread.

     */

    BACKGROUND,

    /**

     * Subscriber will be called in a separate thread. This is always independent from the posting thread and the

     * main thread. Posting events never wait for subscriber methods using this mode. Subscriber methods should

     * use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number

     * of long running asynchronous subscriber methods at the same time to limit the number of concurrent threads. EventBus

     * uses a thread pool to efficiently reuse threads from completed asynchronous subscriber notifications.

     */

    ASYNC}

//对所要订阅的方法进行一些封装。

public class SubscribleMethod {

//注册方法

    private Method method;

//线程类型

    private ThreadMode threadMode;

//参数类型

    private Class<?>[] paramTypes;

public SubscribleMethod(Method method, ThreadMode threadMode, Class<?>[] paramTypes) {

this.method = method;

this.threadMode = threadMode;

this.paramTypes = paramTypes;

    }

/**

     *

     * 获取方法名字

     */

    public String getMethodNameKey(){

        String methodName = method.getName();

if(paramTypes == null || paramTypes.length ==0){

// return method.getName();

        }else{

            StringBuilder builder = new StringBuilder();

            builder.append(methodName);

for(int k = 0; k < paramTypes.length;k++){

                builder.append(paramTypes[k].getName());

            }

            methodName = builder.toString();

        }

return  methodName;

    }

/**

     *

     * 参数拼接的字段

     */

    public String getParamTypesNameKey(){

if(paramTypes == null || paramTypes.length ==0){

// return method.getName();

            return "";

        }else{

            StringBuilder builder = new StringBuilder();

for(int k = 0; k < paramTypes.length;k++){

                builder.append(paramTypes[k].getName());

            }

return builder.toString();

        }

    }

public Method getMethod() {

return method;

    }

public void setMethod(Method method) {

this.method = method;

    }

public ThreadMode getThreadMode() {

return threadMode;

    }

public void setThreadMode(ThreadMode threadMode) {

this.threadMode = threadMode;

    }

public Class<?>[] getParamTypes() {

return paramTypes;

    }

public void setParamTypes(Class<?>[] paramTypes) {

this.paramTypes = paramTypes;

    }

}

public class EventBus {

private static EventBus instance = new EventBus();

private  Object subscriber;//要订阅的类,

    private HashMap<Object,HashMap<String,SubscribleMethod>> cacheMap ;

private Handler handler;//用于处理需要在主线程中执行的事件 

    private ExecutorService executorService;//线程池用于处理异步事件

public static EventBus getDefault() {

if(instance == null){

synchronized (EventBus.class){

if(instance == null){

instance = new EventBus();

                }

            }

        }

return instance;

    }

public EventBus(){

this.cacheMap = new HashMap<>();

handler = new Handler(Looper.getMainLooper());

executorService = Executors.newCachedThreadPool();

    }

public void regisg(Object object){

if(object == null){

return;

        }

        HashMap<String,SubscribleMethod> methodMap = cacheMap.get(object);//先从缓存中查找。

        if(methodMap == null){

            methodMap = getSubscribleMethodMap(object);//获取方法map,

            cacheMap.put(object,methodMap);//将方法map放入到缓存中

        }

    }

private HashMap<String,SubscribleMethod> getSubscribleMethodMap(Object subscriber){

        Class<?> subscriberClass = subscriber.getClass();//获取类

        HashMap<String,SubscribleMethod> methodMap = new HashMap<>();

while (subscriberClass != null) {

//判断分类是在那个包下,(如果是系统的就不需要)

            String name = subscriberClass.getName();

if (name.startsWith("java.") ||

                    name.startsWith("javax.") ||

                    name.startsWith("android.") ||

                    name.startsWith("androidx.")) {

break;

            }

            Method[] declaredMethods = subscriberClass.getDeclaredMethods();

for (Method method : declaredMethods) {

//找相应的注解

                EventBusSubscribe annotation = method.getAnnotation(EventBusSubscribe.class);

if(annotation == null){//注解没有找到

                    continue;

                }

                ThreadMode threadMode = annotation.threadMode();

                Class<?>[] paramTypes = method.getParameterTypes();

                SubscribleMethod subscribleMethod = new SubscribleMethod(method,threadMode,paramTypes);

                methodMap.put(subscribleMethod.getMethodNameKey(),subscribleMethod);

            }

            subscriberClass = subscriberClass.getSuperclass();//遍历父类

        }

return methodMap;

    }

public void post(final Object... event){

        postClasssMethod(null,null,event);

    }

public void postMethod(final String medhodName,final Object... event){

        postClasssMethod(null,medhodName,event);

    }

/**

     *

     * @param className 执行对应的类

     * @param methodName 执行对应的方法

     * @param event 参数

     */

    public void postClasssMethod(final String className,final String methodName,final Object... event){

        Set<Object> set = cacheMap.keySet();

        Iterator<Object> iterator = set.iterator();

        Object invokeObj = null;

        SubscribleMethod subscribleMethod= null;

        String myClassName = "";

if(className == null){

        }else{

            myClassName = className;

        }

boolean isFindClass = false;

        String myMethodName = methodName;

if(methodName == null ){

            myMethodName = "";

        }

while (iterator.hasNext()) {

//拿到注册类

            final Object nextClassObj = iterator.next();

if(nextClassObj.getClass().getName().equals(myClassName)) {//通过类名找到对应的类

                isFindClass = true;

            }

            HashMap<String,SubscribleMethod> methodHashMap = cacheMap.get(nextClassObj);

            String tempMethodName = getMethodName(myMethodName,event);

for (Map.Entry<String, SubscribleMethod> entry : methodHashMap.entrySet()) {

                subscribleMethod = entry.getValue();

                String paramTypesName =subscribleMethod.getParamTypesNameKey();//获取参数的拼接字符

                String methodNameKey = subscribleMethod.getMethodNameKey();

if(methodNameKey.equals(tempMethodName)){//通过类名以及参数找到对应的方法了,执行完方法的,就退出循环了

                    invokeObj = nextClassObj;

                    invoke(subscribleMethod,invokeObj,event);

break;

                }else if(paramTypesName.equals(tempMethodName)){//通过参数找到对应的方法

                    if(subscribleMethod != null){

                        invokeObj = nextClassObj;

                        invoke(subscribleMethod,invokeObj,event);

                    }

                }

            }

if(isFindClass){//如果找到对应的类,执行完对应的类中的方法就退出不再执行了

                break;

            }

        }

    }

private String getMethodName(String methodName,Object... args){

        StringBuilder builder = new StringBuilder();

        builder.append(methodName);

if(args != null && args.length > 0){

//如果有参数,对方法进行拼接

            for(int i = 0;i < args.length;i++){

                builder.append(args[i].getClass().getName());

            }

// methodName = builder.toString();

        }

return builder.toString();

    }

private void invoke(SubscribleMethod subscribleMethod, final Object classObj, final Object ... params) {

final Method method = subscribleMethod.getMethod();

        ThreadMode threadMode = subscribleMethod.getThreadMode();

switch (threadMode){

case MAIN:

if(Looper.myLooper() == Looper.getMainLooper()){

                    invokeMethod(method,classObj,params);

                }else{

handler.post(new Runnable() {

@Override

public void run() {

                            invokeMethod(method,classObj,params);

                        }

                    });

                }

break;

case POSTING:

                invokeMethod(method,classObj,params);

break;

case ASYNC:

//post方法执行在主线程中

                if(Looper.myLooper() == Looper.getMainLooper()){

executorService.execute(new Runnable() {

@Override

public void run() {

                            invokeMethod(method,classObj,params);

                        }

                    });

                } else {

//post方法执行在子线程中

                    invokeMethod(method,classObj,params);

                }

break;

        }

    }

private void invokeMethod(Method method,Object classObj, Object ... params){

try {

            method.invoke(classObj, params);

        } catch (IllegalAccessException e) {

            e.printStackTrace();

        } catch (InvocationTargetException e) {

            e.printStackTrace();

        }

    }

//取消注册

    public void unregister(Object subscriber) {

        Object hashMap = cacheMap.get(subscriber);

//如果获取到

        if (hashMap != null) {

cacheMap.remove(subscriber);

        }

    }

}

这里说明下post,postMethod,postClasssMethod,这三个方法。

post方法会执行注册过类中相同参数的所有方法。

postMethod,如果有传递方法的名称,则会根据方法名称和参数名字查找到对应的方法进行执行。

postClasssMethod,如果有同时传递注册过的类名以及其中的方法,则只会执行类中对应的方法,

如果只传入类名而没有传入方法,则执行类中有相同参数的方法。

最后

这个只是参考了eventBus源码,实现了一些基本的功能很多EvenbBus源码中的功能没有实现,如事件的顺序执行、切换到后台的线程等。EventBus源码中,传递事件的参数只有一个,这里代码的实现可以传递多个参数,也算是一个不同点。

对应代码的地址:https://github.com/fzhFidt/eventBus

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容