多线程优化

1.线程通信基础

  • 生成者消费者

2.AsyncTask

  • FutureTask
  • 线程池
  • 问题和缺点

3.HandlerThread

  • 优点
  • 例子
  1. IntentService
  • 原理和使用
  • 优点

5.Loader

  • 优点
  • 例子

1.线程通信基础

1.1.普通的生产者消费者模式

public class ThreadTest1 {
    
    //产品
    static class ProductObject{
        //线程操作变量可见
        public volatile static String value;
    }
    
    //生产者线程
    static class Producer extends Thread{
        
        @Override
        public void run() {
            //不断生产产品
            while(true){
                if(ProductObject.value == null){
                    ProductObject.value = "NO:"+System.currentTimeMillis();
                    System.out.println("生产产品:"+ProductObject.value);
                }       
            }
        }
    }
    
    //消费者线程
    static class Consumer extends Thread{
        
        @Override
        public void run() {
            while(true){
                if(ProductObject.value != null){
                       System.out.println("消费产品:"+ProductObject.value);
                       ProductObject.value = null;
                }
            }
        }
    }

    
    public static void main(String[] args) {
        new Producer().start();
        new Consumer().start();
    }
    
}

当两个线程对同一个值value操作的时候,在每个线程中都会有一个私有空间保存这个值,即每个线程分别有一个value,假如A线程修改了value,B是不知道A修改了。

1.boolean value=true
生成者线程中vaule修改为false,消费者中的value任然为true。

如何修改:给修改值加上volatile,就能保证同步。

volatile boolean  value=true;
image.png

1.2.优化

但是volatile的这种操作也会带来一个问题,就是消费者和生产者线程需要不断的去判断值是否消费,这样也会带来性能消耗,这里引入了锁的概念。


image.png
public class ThreadTest1 {
    
    //产品
    static class ProductObject{
        //线程操作变量可见
        public volatile static String value;
    }
    
    //生产者线程
    static class Producer extends Thread{
        Object lock;
        
        public Producer(Object lock) {
            this.lock = lock;
        }
        
        @Override
        public void run() {
            //不断生产产品
            while(true){
                synchronized (lock) { //互斥锁
                    //产品还没有被消费,等待
                    if(ProductObject.value != null){
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //产品已经消费完成,生产新的产品
                    ProductObject.value = "NO:"+System.currentTimeMillis();
                    System.out.println("生产产品:"+ProductObject.value);
                    lock.notify(); //生产完成,通知消费者消费
                }
            }
    
        }
    }
    
    //消费者线程
    static class Consumer extends Thread{
        Object lock;
        public Consumer(Object lock) {
            this.lock = lock;
        }
        
        @Override
        public void run() {
            while(true){
                synchronized (lock) {
                    //没有产品可以消费
                    if(ProductObject.value == null){
                        //等待,阻塞
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("消费产品:"+ProductObject.value);
                    ProductObject.value = null;
                    lock.notify(); //消费完成,通知生产者,继续生产
                }
            }
        }
    }

    
    public static void main(String[] args) {
        Object lock = new Object();
        new Producer(lock).start();
        new Consumer(lock).start();
    }
    
}

2.AsyncTask

Android的刷新频率是60fps,如果低于25fps,就会感觉有卡顿的现象。
优化点:减少主线程的负担,创建子线程进行处理。那么就涉及到子线程和主线程的通信。

子线程和主线程的通信方式:

  • AsyncTask
  • Handler

2.1.FutureTask

Callable:可以返回结果,Runable是无法获取结果的
Future
在普通的线程中(比如上面的例子),异步任务执行的结果,主线程是无法轻易获取。
FutureTask是可以获取到异步线程中的结果。

Java FutureTask 异步任务操作提供了便利性:

  • 1.获取异步任务的返回值
  • 2.监听异步任务的执行完毕
  • 3.取消异步任务
   public AsyncTask() {
       // 实现了Callable
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);   //子线程
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };
        //实现了RunnableFuture
        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());   //主线程
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

FutureTask模式实现:

public class FutureTest1 {

    
    
    public static void main(String[] args) {
        Task work = new Task();
        FutureTask<Integer> future = new FutureTask<Integer>(work){
            //异步任务执行完成,回调
            @Override
            protected void done() {
                try {
                    System.out.println("done:"+get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        };
        //线程池(使用了预定义的配置)
        ExecutorService executor = Executors.newCachedThreadPool();
        executor.execute(future);
        
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        //取消异步任务
        future.cancel(true);
        
        try {
            //阻塞,等待异步任务执行完毕
            System.out.println(future.get()); //获取异步任务的返回值
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    
    //异步任务
    static class Task implements Callable<Integer>{

        //返回异步任务的执行结果
        @Override
        public Integer call() throws Exception {
            int i = 0;
            for (; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + "_"+i);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            return i;
        }
    }
}

doBackground()在call方法中执行
call的返回值在Future的done方法中获取

->onPostExecute

new MyTask().execute();

2.2.执行流程:

->onPostExecute

new MyTask().execute();

实例化:
new AsyncTask() -> new FutureTask()

执行:
Executor.execute(mFuture) -> SerialExecutor.myTasks(队列)
-> (线程池)THREAD_POOL_EXECUTOR.execute

线程池中的所有线程,为了执行异步任务

2.3.线程池:

线程池中的所有线程,为了执行异步任务

public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 
KEEP_ALIVE,TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
参数 意义
CORE_POOL_SIZE 核心线程数
MAXIMUM_POOL_SIZE 最大线程数量
KEEP_ALIVE 1s闲置回收
TimeUnit.SECONDS 时间单位
sPoolWorkQueue 异步任务队列
sThreadFactory 线程工厂
  • 如果当前线程池中的数量小于corePoolSize,创建并添加的任务。
  • 如果当前线程池中的数量等于corePoolSize,缓冲队列 workQueue未满,那么任务被放入缓冲队列、等待任务调度执行。
  • 如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量小于maximumPoolSize,新提交任务会创建新线程执行任务。
  • 如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量等于maximumPoolSize,新提交任务由Handler处理。
  • 当线程池中的线程大于corePoolSize时,多余线程空闲时间超过keepAliveTime时,会关闭这部分线程。
image.png

最终AsyncTask执行的任务是在线程池中执行的,如果创建大量的线程,会出现线程堵塞的现象(FC的风险)。

2.4.问题和缺点:参考

  • 1.生命周期;
  • 2.内存泄漏;
  • 3.结果丢失;
  • 4.并行还是串行;
  • 5.线程池不够导致抛出异常:线程池中已经有128个线程,缓冲队列已满,如果此时向线程提交任务,将会抛出RejectedExecutionException。过多的线程会引起大量消耗系统资源和导致应用FC的风险;
  • 6.异步任务中只能干一件事情,一个线程只能干一件事情。

添加任务到线程池的过程是串行,在线程池中执行时是并行。

public class AsyncTaskTest {

    public static void main(String[] args) {
        int CPU_COUNT = Runtime.getRuntime().availableProcessors();  //可用的CPU个数
        int CORE_POOL_SIZE = CPU_COUNT + 1; //5
        int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; //9
        int KEEP_ALIVE = 1;
        
        //任务队列(128)
        final BlockingQueue<Runnable> sPoolWorkQueue =
                new LinkedBlockingQueue<Runnable>(128);
        
        //线程工厂
        ThreadFactory sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);

            public Thread newThread(Runnable r) {
                String name = "Thread #" + mCount.getAndIncrement();
                System.out.println(name);
                return new Thread(r, name);
            }
        };
        
        //线程池
        Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
        
        //执行异步任务
        //如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,
        //并且线程池中的数量等于maximumPoolSize,新提交任务由Handler处理。
        //RejectedExecutionException
        for (int i = 0; i < 200; i++) {
            //相当于new AsyncTask().execute();
            THREAD_POOL_EXECUTOR.execute(new MyTask());
        }
    }
    
    static class MyTask implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
            /*while(true){
                try {
                    System.out.println(Thread.currentThread().getName());
                    //Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }*/
        }
        
    }

}

AsyncTask可以自定义线程池,风险是:太多线程可能导致内存消耗太多。

//1.使用的默认线程池
task = new MyTask();
task.execute();
//2.线程池扩容,自定义线程池
Executor exec = Executors.newScheduledThreadPool(25);
for (int i = 0; i < 200; i++) {
    new MyTask().executeOnExecutor(exec);
}

在Activity的onDestroy中task.cancel(true),并不能真正取消线程执行。

AsyncTask的handler用到的的Looper是主线程的,如果任务太多,在主线程中进行轮询,会导致UI线程有卡顿的现象。

   private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

3.HandlerThread

那如果对异步任务的轮询放在子线程中处理,会不会好点呢。那么就引出了HandlerThread,他就是一个Thread

public class HandlerThreadActivity1 extends Activity {
    
    
    HandlerThread fetchThread = new HandlerThread("fetching_thread");
    Handler fetchHandler;
    private TextView tv;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);
        tv = (TextView) findViewById(R.id.tv);
        
        //启动线程
        fetchThread.start();
        //通过fetchHandler发送的消息,会被fetchThread线程创建的轮询器拉取到
        fetchHandler = new Handler(fetchThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                //模拟访问网络延迟
                SystemClock.sleep(1000);
                
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tv.setText("泰铢汇率:"+new Random().nextInt(10));
                    }
                });
                
                //循环执行
                fetchHandler.sendEmptyMessage(1);
            }
        };
        
        
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        fetchHandler.sendEmptyMessage(1);
    }
    
    
    @Override
    protected void onStop() {
        super.onStop();
        fetchThread.quit(); //取消
    }
}

3.1.HandlerThread的优点:

  • 1.减轻主线程的压力,提高UI的流畅度(减少主线程的轮询);
  • 2.可以处理多个任务,开启一个线程起到多个线程的作用(原理是:looper共享)

3.2.例子

1.打开相机
2.预览回调(编码)

public class HandlerThreadActivity2 extends Activity implements Callback {

static final String TAG = "jason";
    
    Camera mCamera;
    SurfaceView surfaceView;
    SurfaceHolder surfaceHolder;
    byte[] buffers;
    
    HandlerThread mHandlerThread = new HandlerThread("my_handlerthread");
    Handler subHandler;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread2);
        
        surfaceView = (SurfaceView) findViewById(R.id.surface_view);
        surfaceHolder = surfaceView.getHolder();
        
        surfaceHolder.addCallback(this);
    }
    
    class MyTask implements Runnable, PreviewCallback{

        @Override
        public void run() {
            //打开相机
            //子线程中打开
            Log.d("jason", Thread.currentThread().getName() + "_open");
            mCamera = Camera.open(CameraInfo.CAMERA_FACING_BACK);
            try {
                mCamera.setPreviewDisplay(surfaceHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }
            Camera.Parameters parameters = mCamera.getParameters();
            //设置相机参数
            parameters.setPreviewSize(480, 320); //预览画面宽高
            mCamera.setParameters(parameters);
            //获取预览图像数据
            buffers = new byte[480 * 320 * 4];
            mCamera.addCallbackBuffer(buffers);
            mCamera.setPreviewCallbackWithBuffer(this);
            mCamera.startPreview();
            
            Log.d(TAG, Thread.currentThread().getName()+ "_run");
        }

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            if(mCamera != null){
                mCamera.addCallbackBuffer(buffers);
                //编码
                Log.d(TAG, Thread.currentThread().getName()+ "_onPreviewFrame");
            }
        }
        
    }
    

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mHandlerThread.start();
        subHandler = new Handler(mHandlerThread.getLooper());
        subHandler.post(new MyTask());
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        
    }
}

如果用AsyncTask,onPreviewFrame的执行就到了主线程。为什么呢?
相机中的代码Camera:

异步任务的Looper,使用的MainLooper
Handler.handleMessage的执行,一定在它的Looper线程中
onPreviewFrame的执行,在Camera所持有的Looper线程中执行
new Camera -> looper -> EventHandler.handleMessage -> onPreviewFrame

    private int cameraInitVersion(int cameraId, int halVersion) {
        mShutterCallback = null;
        mRawImageCallback = null;
        mJpegCallback = null;
        mPreviewCallback = null;
        mPostviewCallback = null;
        mUsingPreviewAllocation = false;
        mZoomListener = null;

        Looper looper;
        if ((looper = Looper.myLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else if ((looper = Looper.getMainLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else {
            mEventHandler = null;
        }

        return native_setup(new WeakReference<Camera>(this), cameraId, halVersion,
                ActivityThread.currentOpPackageName());
    }

java中普通的线程其他方法调用执行情况:

public class ThreadTest2 {
    
    static class MyTask extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "_run");
        }
        
        void onPreviewFrame(){
            System.out.println(Thread.currentThread().getName() + "_onPreviewFrame");
        }
    }
    
    public static void main(String[] args) {
        //子线程
        MyTask task = new MyTask();
        task.start();
        //在主线程执行
        task.onPreviewFrame();
    }

}

4.IntentService

4.1.原理和使用

IntentService(本质:Service+HandlerThread+Intent)

要通过startService来启动,bindService没什么用;

public class IntentServiceActivity extends Activity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intent_service);       
    }
    
    //发送意图给IntentService,启动子线程执行任务
    public void mClick(View btn){
        Intent intent = new Intent(this,MyIntentService.class);
        startService(intent);
    }
    

}
public class MyIntentService extends IntentService {

    //至少要有一个空的构造方法
    public MyIntentService() {
        super("MyIntentService");
    }
    
    public MyIntentService(String name) {
        super(name);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        Log.d("jason", Thread.currentThread().getName() + "_onStart");
    }
    
    
    //UI线程发送Intent,会在子线程中执行
    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d("jason", Thread.currentThread().getName() + "_onHandleIntent");
    }

}

至少要有一个空的构造方法

4.2.优点

  • 1.提高子线程的优先级
  • 2.减轻主线程的压力

IntentService内部会创建一个HandlerThread,onHandleIntent在HandlerThread线程中执行

5.Loader

Activity中启动子线程存在的问题:

  • 1.内存泄露
  • 2.无效的更新UI

Loader保证子线程与Activity或者Fragment的生命周期一致
Activity和Fragment自带LoaderManager

5.1.优点:

1.方便
2.Activity或者Fragment的生命周期一致
3.数据缓存与更新通知

5.2.例子:

查询通话记录,是一个比较耗时的操作,应该放在子线程中处理。

优点:
1.数据查询和跟新UI不需要自己去做线程切换和处理;
2.数据更新后,自动更新;
3.Activity销毁后,自动取消查询数据库操作。

使用加载器加载通话记录:

public class MainActivity extends Activity {

    private static final String TAG = "jason";
    // 查询指定的条目
    private static final String[] CALLLOG_PROJECTION = new String[] { CallLog.Calls._ID, CallLog.Calls.NUMBER,
            CallLog.Calls.CACHED_NAME, CallLog.Calls.TYPE, CallLog.Calls.DATE };
    private ListView mListView;
    private MyLoaderCallback mLoaderCallback = new MyLoaderCallback();
    private MyCursorAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListView = (ListView) findViewById(R.id.lv_list);

        mAdapter = new MyCursorAdapter(MainActivity.this, null);
        mListView.setAdapter(mAdapter);

        //执行Loader的回调
        getLoaderManager().initLoader(0, null, mLoaderCallback);
    }

    
    private class MyLoaderCallback implements LoaderManager.LoaderCallbacks<Cursor> {

        //创建Loader
        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            //加载的过程在子线程中进行
            CursorLoader loader = new CursorLoader(MainActivity.this, CallLog.Calls.CONTENT_URI, CALLLOG_PROJECTION,
                    null, null, CallLog.Calls.DEFAULT_SORT_ORDER);
            Log.d(TAG, "onCreateLoader");
            return loader;
        }

        //Loader检测底层数据,当检测到改变时,自动执行新的载入获取最新数据
        //Activity/Fragment所需要做的就是初始化Loader,并且对任何反馈回来的数据进行响应。
        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            if (data == null)
                return;
            mAdapter.swapCursor(data);
            Log.d(TAG, "onLoadFinished data count = " + data.getCount());
        }

        //OnDestroy,自动停止load
        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            Log.d(TAG, "onLoaderReset");
            mAdapter.swapCursor(null);
        }
    }

}

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

推荐阅读更多精彩内容