Android Timer、CountDownTimer、AlarmManager

一、Timer TimerTask

参考Java中的Timer和TimerTask在Android中的用法

在开发中我们有时会有这样的需求,即在固定的每隔一段时间执行某一个任务。比如UI上的控件需要随着时间改变,我们可以使用Java为我们提供的计时器的工具类,即Timer和TimerTask。

Timer是一个普通的类,其中有几个重要的方法;而TimerTask则是一个抽象类,其中有一个抽象方法run(),类似线程中的run()方法,我们使用Timer创建一个他的对象,然后使用这对象的schedule方法来完成这种间隔的操作。

//true 说明这个timer以daemon方式运行(优先级低,程序结束timer也自动结束) 
java.util.Timer timer = new java.util.Timer(true);

TimerTask task = new TimerTask() {
   public void run() {
   //每次需要执行的代码放到这里面。   
   }   
};

//以下是几种调度task的方法:

//time为Date类型:在指定时间执行一次。
timer.schedule(task, time);

//firstTime为Date类型,period为long,表示从firstTime时刻开始,每隔period毫秒执行一次。
timer.schedule(task, firstTime, period);   

//delay 为long类型:从现在起过delay毫秒执行一次。
timer.schedule(task, delay);

//delay为long,period为long:从现在起过delay毫秒以后,每隔period毫秒执行一次。
timer.schedule(task, delay, period);

//该任务每隔2秒更新主线程的UI(在主线程的TextView显示最新的系统时间System.currentTimeMillis())。
package zhangphil.timertask;

import java.util.Timer;
import java.util.TimerTask;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.widget.TextView;

public class MainActivity extends Activity {

    private Timer timer;
    private TimerTask task;

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

        final TextView tv = (TextView) findViewById(R.id.textView);

        final int WHAT = 102;
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                case WHAT:
                    tv.setText(msg.obj + "");
                    break;
                }
            }
        };

        task = new TimerTask() {
            @Override
            public void run() {
                Message message = new Message();
                message.what = WHAT;
                message.obj = System.currentTimeMillis();
                handler.sendMessage(message);
            }
        };

        timer = new Timer();
        // 参数:
        // 1000,延时1秒后执行。
        // 2000,每隔2秒执行1次task。
        timer.schedule(task, 1000, 2000);
    }

    @Override
    protected void onStop() {
        super.onStop();

        // 暂停
        // timer.cancel();
        // task.cancel();
    }
}

schedule方法有三个参数
第一个参数就是TimerTask类型的对象,我们实现TimerTask的run()方法就是要周期执行的一个任务;
第二个参数有两种类型,第一种是long类型,表示多长时间后开始执行,另一种是Date类型,表示从那个时间后开始执行;
第三个参数就是执行的周期,为long类型。

schedule方法还有一种两个参数的执行重载,第一个参数仍然是TimerTask,第二个表示为long的形式表示多长时间后执行一次,为Date就表示某个时间后执行一次。

Timer就是一个线程,使用schedule方法完成对TimerTask的调度,多个TimerTask可以共用一个Timer,也就是说Timer对象调用一次schedule方法就是创建了一个线程,并且调用一次schedule后TimerTask是无限制的循环下去的,使用Timer的cancel()停止操作。当然同一个Timer执行一次cancel()方法后,所有Timer线程都被终止。

若要在TimerTask中更新主线程UI,鉴于Android编程模型不允许在非主线程中更新主线程UI,因此需要结合Android的Handler实现在Java的TimerTask中更新主线程UI。

二、ScheduledThreadPoolExecutor

public static ExecutorService newScheduledThreadPool(int corePoolSize){
return new ThreadPoolExecutor(corePoolSize,Integer.MAX_VALUE,0L,TimeUnit.SECONDS,new DelayedWorkQueue<Runnable>());
}

ScheduledThreadPoolExecutor核心线程数量固定,非核心线程数没有限制。主要用于执行定时任务和具有固定周期的重复任务。

参考[Java 并发专题 : Timer的缺陷 用ScheduledExecutorService替代](http://blog.csdn.net/lmj623565791/article/details/27109467)
**以前在项目中也经常使用定时器,比如每隔一段时间清理项目中的一些垃圾文件,每个一段时间进行数据清洗;然而Timer是存在一些缺陷的,因为Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,会发生一些缺陷.**

1.定义了两个任务,预计是第一个任务1s后执行,第二个任务3s后执行

package com.zhy.concurrency.timer;

import java.util.Timer;
import java.util.TimerTask;

public class TimerTest
{
private static long start;

public static void main(String[] args) throws Exception
{

    TimerTask task1 = new TimerTask()
    {
        @Override
        public void run()
        {

            System.out.println("task1 invoked ! "
                    + (System.currentTimeMillis() - start));
            try
            {
                Thread.sleep(3000);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }

        }
    };
    TimerTask task2 = new TimerTask()
    {
        @Override
        public void run()
        {
            System.out.println("task2 invoked ! "
                    + (System.currentTimeMillis() - start));
        }
    };
    Timer timer = new Timer();
    start = System.currentTimeMillis();
    timer.schedule(task1, 1000);
    timer.schedule(task2, 3000);

}

}

运行结果:

task1 invoked ! 1000
task2 invoked ! 4000

task2实际上是4后才执行,正因为Timer内部是一个线程,而任务1所需的时间超过了两个任务间的间隔导致。下面使用ScheduledThreadPool解决这个问题:

package com.zhy.concurrency.timer;

import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolExecutorTest
{
private static long start;

public static void main(String[] args)
{
    /**
     * 使用工厂方法初始化一个ScheduledThreadPool
     */
    ScheduledExecutorService newScheduledThreadPool = Executors
            .newScheduledThreadPool(2);
    
    TimerTask task1 = new TimerTask()
    {
        @Override
        public void run()
        {
            try
            {

                System.out.println("task1 invoked ! "
                        + (System.currentTimeMillis() - start));
                Thread.sleep(3000);
            } catch (Exception e)
            {
                e.printStackTrace();
            }

        }
    };

    TimerTask task2 = new TimerTask()
    {
        @Override
        public void run()
        {
            System.out.println("task2 invoked ! "
                    + (System.currentTimeMillis() - start));
        }
    };
    start = System.currentTimeMillis();
    newScheduledThreadPool.schedule(task1, 1000, TimeUnit.MILLISECONDS);
    newScheduledThreadPool.schedule(task2, 3000, TimeUnit.MILLISECONDS);
}

}

2.如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行:

package com.zhy.concurrency.timer;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class ScheduledThreadPoolDemo01
{

public static void main(String[] args) throws InterruptedException
{

    final TimerTask task1 = new TimerTask()
    {

        @Override
        public void run()
        {
            throw new RuntimeException();
        }
    };

    final TimerTask task2 = new TimerTask()
    {

        @Override
        public void run()
        {
            System.out.println("task2 invoked!");
        }
    };
    
    Timer timer = new Timer();
    timer.schedule(task1, 100);
    timer.scheduleAtFixedRate(task2, new Date(), 1000);
    
    

}

}

上面有两个任务,任务1抛出一个运行时的异常,任务2周期性的执行某个操作,输出结果:

task2 invoked!
Exception in thread "Timer-0" java.lang.RuntimeException
at com.zhy.concurrency.timer.ScheduledThreadPoolDemo01$1.run(ScheduledThreadPoolDemo01.java:24)
at java.util.TimerThread.mainLoop(Timer.java:512)
at java.util.TimerThread.run(Timer.java:462)

由于任务1的一次,任务2也停止运行了。。。下面使用ScheduledExecutorService解决这个问题:

package com.zhy.concurrency.timer;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolDemo01
{

public static void main(String[] args) throws InterruptedException
{

    final TimerTask task1 = new TimerTask()
    {

        @Override
        public void run()
        {
            throw new RuntimeException();
        }
    };

    final TimerTask task2 = new TimerTask()
    {

        @Override
        public void run()
        {
            System.out.println("task2 invoked!");
        }
    };
    
    
    
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
    pool.schedule(task1, 100, TimeUnit.MILLISECONDS);
    pool.scheduleAtFixedRate(task2, 0 , 1000, TimeUnit.MILLISECONDS);

}

}

代码基本一致,但是ScheduledExecutorService可以保证,task1出现异常时,不影响task2的运行:

task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!

#####三、AlarmManager
>Java的Timer类可以用来计划需要循环执行的任务。简单的说,一个Timer内部封装装了“一个Thread”和“一个TimerTask队列”,这个队列按照一定的方式将任务排队处理。封装的Thread在Timer的构造方法调用时被启动,这个Thread的run方法按照条件去循环这个TimerTask队列,然后调用TimerTask的run方法。
但是,**如果CPU进入了休眠状态,那么这个thread将会因为失去CPU时间片而阻塞,从而造成我们需要的定时任务失效**。上述定时任务失效的场景分析:假设定时任务的条件是到了时间xx:yy才能执行,但由于cpu休眠造成线程阻塞的关系,当前系统时间超过了这个时间,即便CPU从终端中恢复了,那么由于条件不满足,定时任务在这一次自然就失效了。
解决方案是:它**需要用WakeLock让CPU 保持唤醒状态。那么问题就来了,这样会大量消耗手机电量(CPU唤醒和屏幕唤醒不是同一概念)**,大大减短手机待机时间。这种方式不能满足我们的需求。
注:TimerTask实现Runnable接口,但它的run方法只是简单的在Timer中封装的Thread中被调用,并未将其放在其它线程中执行。也就是说timer是单线程执行的,那么问题来了,为何要这么费劲的封装一个Runnable接口又不进行多线程调用?我的答案是,老外只是想要表示它是可执行的方法。
关于休眠,可以参考
[Android 关于休眠引发的几个血案](http://www.ithtw.com/437.html)
[Android手机休眠后时间不准确的解决方案](http://blog.csdn.net/t12x3456/article/details/7826811)
AlarmManager是Android 系统封装的用于管理RTC的模块,RTC(Real Time Clock) 是一个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒CPU。这意味着,如果我们用 AlarmManager 来定时执行任务,CPU 可以正常的休眠,只有在需要运行任务时醒来一段很短的时间。

以下参考[Android基础入门教程——10.5 AlarmManager(闹钟服务)](http://blog.csdn.net/coder_pig/article/details/49423531)

**核心流程如下:**
* `AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); `
获得系统提供的AlarmManager服务的对象
* Intent设置要启动的组件: 
`Intent intent = new Intent(MainActivity.this, ClockActivity.class);`
* PendingIntent对象设置动作,启动的是Activity还是Service,又或者是广播! 
`PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0, intent, 0);`
* 调用AlarmManager的set( )方法设置单次闹钟的闹钟类型,启动时间以及PendingIntent对象! 
`alarmManager.set(AlarmManager.RTC_WAKEUP,c.getTimeInMillis(), pi);`

//10秒钟后执行一个任务
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);

**相关方法:**
* set(int type,long startTime,PendingIntent pi):一次性闹钟
* setRepeating(int type,long startTime,long intervalTime,PendingIntent pi): 
重复性闹钟,和3有区别,3闹钟间隔时间不固定
* setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi): 
重复性闹钟,时间不固定
* cancel(PendingIntent pi):取消AlarmManager的定时服务
* getNextAlarmClock():得到下一个闹钟,返回值AlarmManager.AlarmClockInfo
* setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) 
和set方法类似,这个闹钟运行在系统处于低电模式时有效
* setExact(int type, long triggerAtMillis, PendingIntent operation): 
在规定的时间精确的执行闹钟,比set方法设置的精度更高
* setTime(long millis):设置系统墙上的时间
* setTimeZone(String timeZone):设置系统持续的默认时区
* setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation): 
设置一个闹钟在给定的时间窗触发。类似于set,该方法允许应用程序精确地控制操作系统调 整闹钟触发时间的程度。

**关键参数讲解:**
* Type(闹钟类型): 
有五个可选值: 
  * AlarmManager.ELAPSED_REALTIME: 
闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3; 
  * AlarmManager.ELAPSED_REALTIME_WAKEUP 
闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2; 
  * AlarmManager.RTC 
闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1; 
  * AlarmManager.RTC_WAKEUP 
表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0; 
  * AlarmManager.POWER_OFF_WAKEUP 
表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持;
* startTime:闹钟的第一次执行时间,以毫秒为单位,可以自定义时间,不过一般使用当前时间。 需要注意的是,本属性与第一个属性(type)密切相关,如果第一个参数对应的闹钟使用的是相对时间 (ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那么本属性就得使用相对时间相对于系统启动时间来说),比如当前时间就表示为:SystemClock.elapsedRealtime(); 如果第一个参数对应的闹钟使用的是绝对时间(RTC、RTC_WAKEUP、POWER_OFF_WAKEUP), 那么本属性就得使用绝对时间,比如当前时间就表示 为:System.currentTimeMillis()。
* intervalTime:表示两次闹钟执行的间隔时间,也是以毫秒为单位.
* PendingIntent:绑定了闹钟的执行动作,比如发送一个广播、给出提示等等。 
PendingIntent是Intent的封装类。需要注意的是,
  * 如果是通过启动服务来实现闹钟提示的话,PendingIntent对象的获取就应该采用Pending.getService (Context c,int i,Intent intent,int j)方法;
  * 如果是通过广播来实现闹钟提示的话,PendingIntent对象的获取就应该采用 PendingIntent.getBroadcast (Context c,int i,Intent intent,int j)方法;
  * 如果是采用Activity的方式来实现闹钟提示的话,PendingIntent对象的获取就应该采用 
PendingIntent.getActivity(Context c,int i,Intent intent,int j)方法。

如果这三种方法错用了的话,虽然不会报错,但是看不到闹钟提示效果。

下面是一个每隔一小时就会在后台执行定时任务的服务。

public class LongRunningService extends Service{
public IBinder onBind(Intent intent){
return null;
}

public int onStartCommand(Intent intent, int flags, int startId){
new Thread(new Runnable(){
public void run(){
Log.d("LongRunningService","executed at"+new Date().toString());
}
}).start();
}
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 60 * 60 * 1000;
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent i = new Intent(this,AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this,0,i,0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi);
return super.onStartCommand(intent,flags,startId);
}

public class AlarmReceiver extends BroadcastReceiver{
public void onReceive(Context context, Intent intent){
Intent i = new Intent(context, LongRunningService.class);
context.startService(i);
}
}

public class MainActivity extends Activity{
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Intent intent = new Intent(this,LongRunningService);
startService(intent);
}
}

#####四、CountDownTimer
参考
[Android实现倒计时之使用CountDownTimer](http://blog.csdn.net/qq_20785431/article/details/51571300)
[[Android] CountDownTimer 简单用法与源码解析](http://blog.qiji.tech/archives/7485)
>在开发中会经常用到倒计时这个功能,包括给手机发送验证码等等,之前我的做法都是使用Handler + Timer +TimerTask来实现,现在发现了这个类,果断抛弃之前的做法,相信还是有很多人和我一样一开始不知道Android已经帮我们封装好了一个叫CountDownTimer的类。`CountDownTimer timer = new CountDownTimer(10000,1000);`以毫秒为单位,第一个参数是指从开始调用start()方法到倒计时完成的时候onFinish()方法被调用这段时间的毫秒数,也就是倒计时总的时间;第二个参数表示间隔多少毫秒调用一次 onTick方法,例如间隔1000毫秒。在调用的时候直接使用`timer.start();`

//共有4个方法,前两个抽象方法需要重写
public abstract void onTick(long millisUntilFinished);//固定间隔调用
public abstract void onFinish();//倒计时完成时被调用
public synchronized final void cancel();//取消倒计时,当再次启动会重新开始倒计时
public synchronized final CountDownTimer start();//启动倒计时
//该类的成员变量与成员函数均较少,功能实现的关键部分在于 mHandler,下面看 mHandler 的源码:
private Handler mHandler = new Handler() {

@Override public void handleMessage(Message msg) {

synchronized (CountDownTimer.this) {
  if (mCancelled) {
    return;
  }

  final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();

  if (millisLeft <= 0) {
    onFinish();
  } else if (millisLeft < mCountdownInterval) {
    // no tick, just delay until done
    sendMessageDelayed(obtainMessage(MSG), millisLeft);
  } else {
    long lastTickStart = SystemClock.elapsedRealtime();
    onTick(millisLeft);

    // take into account user's onTick taking time to execute
    long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();

    // special case: user's onTick took more than interval to
    // complete, skip to next interval
    while (delay < 0) delay += mCountdownInterval;

    sendMessageDelayed(obtainMessage(MSG), delay);
  }
}

}
};


![内部流程](http://upload-images.jianshu.io/upload_images/2354823-46005a2dd429b052.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
看一个使用例子:

package com.per.countdowntimer;

import android.app.Activity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity {
private TextView mTvShow;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mTvShow = (TextView) findViewById(R.id.show);
}

/**
 * 取消倒计时
 * @param v
 */
public void oncancel(View v) {
    timer.cancel();
}

/**
 * 开始倒计时
 * @param v
 */
public void restart(View v) {
    timer.start();
}

private CountDownTimer timer = new CountDownTimer(10000, 1000) {

    @Override
    public void onTick(long millisUntilFinished) {
        mTvShow.setText((millisUntilFinished / 1000) + "秒后可重发");
    }

    @Override
    public void onFinish() {
        mTvShow.setEnabled(true);
        mTvShow.setText("获取验证码");
    }
};

}

#####五、new Handler().postDelayed()
该方法就是利用我们常说的消息处理器。该方法原理就是在主线程中创建一个Handler消息处理器,然后利用其中的一个postDelayed(Runnable r, long delayMillis)方法,该方法第一个参数需要传入一个Runnable接口,并实现run()方法,第二个参数就是延迟多少时间将run()方法中的代码通过一个消息发送给消息队列,然后在主线程中执行这个消息中的代码,即是run方法中的代码,从而实现在主线程中更新界面UI。
贴代码吧:

new Handler().postDelayed(new Runnable() {//在当前线程(也即主线程中)开启一个消息处理器,并在3秒后在主线程中执行,从而来更新UI
@Override
public void run() {
//有关更新UI的代码
}
}, 3000);//3秒后发送

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

推荐阅读更多精彩内容