Android系统进程优先级策略-ADJ

本篇文章是基于Android9.0来介绍Android系统关于进程的优先级是如何定义和管理的。

概述

1.进程

进程-Process 是程序的一个运行实例。通常会有唯一一个pid与之对应。但pid不是绝对唯一的,当进程死亡后pid会被回收给另外的进程使用。在Android世界里,App开发者很容易认为系统的四大组件就是进程的载体,实际上,它们不能算是完整的进程实例,最多只能算是进程的组成部分。由于Android系统框架中,系统对进程的创建和管理进行了封装,每当我们在启动四大组件Activity, BroadcastReceiver,ContentProvider,Service任意一个的时候,系统就会去检查该组件所在的进程是否已经存在,如果不存在,框架就会自动调用startProcessLocked函数去创建进程。当然一个app可以存在多个进程,多个app也可以运行在同一个进程下,我们可以通过Android:process来设置。

2.优先级

Android系统框架设计理念里是希望对用户很重要的进程尽可能的长期存活,以此来提高用户体验。我们知道Android app在启动的时候会去做很多事情,比如系统会检测该app进程是否存在,如果不存在则需要通知zygote进程去fork app进程,同时完成application信息的初始化工作。此时如果app的进程存在,那么就会省略这个步骤,直接快速唤起app。对于app来说也是一样,通过进程保活可以去实现更多的功能,但是如果所有的app不管有用没用的都存活下来了,系统的资源毕竟是有限的,系统内存很快就会枯竭而亡,这时就需要有合理地进程回收机制了。那么究竟该怎么回收进程呢?其实系统是根据各个app四大组件的状态来决定进程的优先级值adj。系统会根据一定的策略先去回收优先级最低的(同时也是adj值最大的),其次再回收优先级略低的,依次类推,直到回收了足够的系统资源,保证系统正常运转。

3.adj取值范围及含义

ADJ级别 取值 含义
NATIVE_ADJ -1000 native进程
SYSTEM_ADJ -900 仅指system_server进程
PERSISTENT_PROC_ADJ -800 系统persistent进程
PERSISTENT_SERVICE_ADJ -700 关联着系统或persistent进程
FOREGROUND_APP_ADJ 0 前台进程
VISIBLE_APP_ADJ 100 可见进程
PERCEPTIBLE_APP_ADJ 200 可感知进程,比如后台音乐播放
BACKUP_APP_ADJ 300 备份进程
HEAVY_WEIGHT_APP_ADJ 400 重量级进程
SERVICE_ADJ 500 服务进程
HOME_APP_ADJ 600 Home进程
PREVIOUS_APP_ADJ 700 上一个进程
SERVICE_B_ADJ 800 B List中的Service
CACHED_APP_MIN_ADJ 900 不可见进程的adj最小值
CACHED_APP_MAX_ADJ 906 不可见进程的adj最大值

从Android 7.0开始,ADJ采用100、200、300;在这之前的版本ADJ采用数字1、2、3,这样的调整可以更进一步地细化进程的优先级,比如在VISIBLE_APP_ADJ(100)与PERCEPTIBLE_APP_ADJ(200)之间,可以有ADJ=101、102级别的进程。

关于进程的优先级可以通过如下的命令查看

adb shell
cd /proc/pid/
oom_adj   oom_score  oom_score_adj
cat oom_adj
0 表示前台进程FOREGROUND_APP_ADJ

Android 7.0之前的版本:
oom_score -- 是该进程的最终得分,分数越高,越容易被杀死;
oom_score_adj -- 范围:[-1000, 1000],是kernel用来配置进程优先级的。值越低,最终的oom_score越低。
oom_adj -- 范围:[-16, 15],是给用户来配置进程优先级的。为了方便用户配置,提供了范围更小的oom_adj参数,数字越小优先级越高,-17表示该进程被保护,不被OOMKiller杀死。
kernel会转换并更新该进程实际的oom_score_adj值,它们的换算关系是:

#define OOM_DISABLE (-17)
    #define OOM_SCORE_ADJ_MAX 1000

    oom_score_adj = (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE;
    oom_adj = (oom_score_adj * - OOM_DISABLE) / OOM_SCORE_ADJ_MAX;

Android 7.0之前的版本,oom_score_adj= oom_adj * 1000/17; 而Android 7.0开始,oom_score_adj= oom_adj,不用再经过一次转换。

4.LowMemoryKiller

Android的Low Memory Killer基于Linux的OOM(Out Of Memory Killer)机制,在Linux中,内存是以页面为单位分配的,OOM的策略更多的是用于分配内存不足时触发,将得分最高的进程杀掉。而lmk则会每隔一段时间检查一次,当系统剩余可用内存较低时,便会触发杀进程的策略,根据不同的剩余内存档位来选择杀不同优先级的进程,而不是等到OOM时再来杀进程,真正OOM时系统可能已经处于异常状态,系统更希望的是未雨绸缪,在内存很低时来杀掉一些优先级较低的进程来保障后续操作的顺利进行。这里不详细说,接下来会有一篇关于Low Memory Killer的文章来详细介绍。
那么在哪里可以看到各个剩余内存档位呢?可以通过如下方式:

frameworks/base/services/core/java/com/android/server/am/ProcessList.java
// These are the various interesting memory levels that we will give to
// the OOM killer.  Note that the OOM killer only supports 6 slots, so we
// can't give it a different value for every possible kind of process.
private final int[] mOomAdj = new int[] {
        FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
        BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
};
从ProcessList.java中关于mOomAdj的定义及注释中我们可以发现,
系统一共定义了6个档位,
分别对应了上表格中列出的几种进程类型。
adb shell
cat /sys/module/lowmemorykiller/parameters/adj
0,100,200,300,900,906

adj值对应如下:
FOREGROUND_APP_ADJ(0)
VISIBLE_APP_ADJ(100)
PERCEPTIBLE_APP_ADJ(200)
BACKUP_APP_ADJ(300)
CACHED_APP_MIN_ADJ(900)
CACHED_APP_MAX_ADJ(906)

cat /sys/module/lowmemorykiller/parameters/minfree
18432,23040,27648,32256,85296,120640
由于一个page=4k,对应的换算公式是:value*4/1024,换算结果如下:
72M,90M,108M,126M,144M,180M

剩余内存值档位对应如下:
FOREGROUND_APP_ADJ(72M)
VISIBLE_APP_ADJ(90M)
PERCEPTIBLE_APP_ADJ(108M)
BACKUP_APP_ADJ(126M)
CACHED_APP_MIN_ADJ(144M)
CACHED_APP_MAX_ADJ(180M)

进程刚启动时ADJ等于INVALID_ADJ,当执行完attachApplication(),该该进程的curAdj和setAdj不相等,则会触发执行setOomAdj()将该进程的节点/proc/pid/oom_score_adj写入oomadj值。举例:当系统剩余空闲内存低于某阈值(比如140MB),则从ADJ大于或等于相应阈值(比如900)的进程中,选择ADJ值最大的进程,如果存在多个ADJ相同的进程,则选择内存最大的进程。
在updateOomLevels()过程,会根据手机屏幕尺寸或内存大小来调整scale,默认大多数手机内存都大于700MB,则scale等于1。对于64位手机,cached进程的阈值会更大些。

private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
        // Scale buckets from avail memory: at 300MB we use the lowest values to
        // 700MB or more for the top values.
        float scaleMem = ((float)(mTotalMemMb-350))/(700-350);
        ...
        // Scale buckets from screen size.
        int minSize = 480*800;  //  384000
        int maxSize = 1280*800; // 1024000  230400 870400  .264
        float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
        ...
        float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
        if (scale < 0) scale = 0;
        else if (scale > 1) scale = 1;
        ...
        final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;
        for (int i=0; i<mOomAdj.length; i++) {
            int low = mOomMinFreeLow[i];
            int high = mOomMinFreeHigh[i];
            if (is64bit) {
                // Increase the high min-free levels for cached processes for 64-bit
                if (i == 4) high = (high*3)/2;
                else if (i == 5) high = (high*7)/4;
            }
            mOomMinFree[i] = (int)(low + ((high-low)*scale));
        }
}

ADJ详解

1.ADJ优先级小于0的,就是说正常情况下肯定不会被杀死,即使被杀死了也会被重启的进程。
  • NATIVE_ADJ(-1000):是由init进程fork出来的Native进程,并不受system管控
  • SYSTEM_ADJ(-900):是指system_server进程
  • PERSISTENT_PROC_ADJ(-800): 是指在AndroidManifest.xml中申明android:persistent=”true”的系统(即带有FLAG_SYSTEM标记)进程,persistent进程一般情况并不会被杀,即便被杀或者发生Crash系统会立即重新拉起该进程
  • PERSISTENT_SERVICE_ADJ(-700):是由startIsolatedProcess()方式启动的进程,或者是由system_server或者persistent进程所绑定(并且带有BIND_ABOVE_CLIENT或者BIND_IMPORTANT)的服务进程

2.ADJ优先级大于等于0的

  • FOREGROUND_APP_ADJ(0): 正在前台运行的进程,是不应该被干掉的进程
    1.正处于resumed状态的Activity; app.adjType = "top-activity"
    2.正执行一个生命周期回调的Service(比如执行onCreate,onStartCommand,onDestroy等); app.adjType = "exec-service";
    3.正执行onReceive()的BroadcastReceiver; app.adjType = "broadcast";
    4.通过startInstrumentation()启动的进程; app.adjType = "instrumentation"
    5.当客户端进程activity里面调用bindService()方法时flags带有BIND_ADJUST_WITH_ACTIVITY参数,并且该activity处于可见状态,则当前服务进程也属于前台进程; app.adjType = "service";
    6.对于provider进程,还有以下两个条件能成为前台进程:

    • 当Provider的客户端进程ADJ<=FOREGROUND_APP_ADJ时,则Provider进程ADJ等于FOREGROUND_APP_ADJ; adjType = "provider";
    • 当Provider有外部进程依赖,也就是调用了getContentProviderExternal()方法,则ADJ至少等于FOREGROUND_APP_ADJ; app.adjType = "ext-provider";
  • VISIBLE_APP_ADJ(100): 可见进程,比如生命周期还没有执行到onstop的activity,有widget组件在桌面的等还是可以被用户看到的; app.adjType = "vis-activity";
    从Android P开始,进一步细化ADJ级别,增加了VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1=99,是指VISIBLE_APP_ADJ(100)跟PERCEPTIBLE_APP_ADJ(200)之间有99个槽,则可见级别ADJ的取值范围为[100,199]。 算法会根据其所在task的mLayerRank来调整其ADJ,100加上mLayerRank就等于目标ADJ,layer越大,则ADJ越小。
    关于TaskRecord的mLayerRank的计算方式是在updateOomAdjLocked()过程调用ASS的rankTaskLayersIfNeeded()方法

  • PERCEPTIBLE_APP_ADJ(200): 当该进程存在不可见的Activity,但Activity正处于PAUSING、PAUSED、STOPPING状态,则为PERCEPTIBLE_APP_ADJ;app.adjType = "pause-activity" or app.adjType = "stop-activity";
    满足以下任一条件的进程也属于可感知进程:
    1.foregroundServices非空:前台服务进程,执行startForegroundService()方法; app.adjType = "fg-service";
    2.app.forcingToImportant非空:执行setProcessImportant()方法,比如Toast弹出过程; app.adjType = "force-imp";
    3.hasOverlayUi非空:非activity的UI位于屏幕最顶层,比如显示类型TYPE_APPLICATION_OVERLAY的窗口; app.adjType = "has-overlay-ui";

  • BACKUP_APP_ADJ(300):执行bindBackupAgent()过程的进程;
    1.执行bindBackupAgent()过程,设置mBackupTarget值;
    2.执行clearPendingBackup()或unbindBackupAgent()过程,置空mBackupTarget值;

  • HEAVY_WEIGHT_APP_ADJ(400): realStartActivityLocked()过程,当应用的privateFlags标识PRIVATE_FLAG_CANT_SAVE_STATE的进程;
    HEAVY_WEIGHT_APP一般是在后台运行,要避免它被干掉。Value set in system/rootdir/init.rc on startup.

  • SERVICE_ADJ(500):没有启动过Activity,并且30分钟之内活跃过的服务进程。startRequested为true,则代表执行startService()且没有stop的进程; app.adjType = "started-services";

  • HOME_APP_ADJ(600):当类型为ACTIVITY_TYPE_HOME的应用,比如桌面APP

  • PREVIOUS_APP_ADJ(700):
    1.用户上一个使用的包含UI的进程,为了给用户在两个APP之间更好的切换体验,将上一个进程ADJ设置到PREVIOUS_APP_ADJ的档次。 当activityStoppedLocked()过程会更新上一个应用。
    2.当provider进程,上一次使用时间不超过20S的情况下,优先级不低于PREVIOUS_APP_ADJ。provider进程这个是Android 7.0以后新增的逻辑 ,这样做的好处是在内存比较低的情况下避免拥有provider的进程出现颠簸,也就是启动后杀,然后又被拉。

  • SERVICE_B_ADJ(800):进程由SERVICE_ADJ(500)降低到SERVICE_B_ADJ(800),有以下两种情况:
    1.A类Service占比过高:当A类Service个数 > Service总数的1/3时,则加入到B类Service。换句话说,B Service的个数至少是A Service的2倍。
    2.内存紧张&&A类Service占用内存较高:当系统内存紧张级别(mLastMemoryLevel)高于ADJ_MEM_FACTOR_NORMAL,且该应用所占内存lastPss大于或等于CACHED_APP_MAX_ADJ级别所对应的内存阈值的1/3(默认值阈值约等于110MB)
    内存因子ADJ_MEM_FACTOR共有4个级别,当前处于哪个内存因子级别,取决于当前进程中cached进程和空进程的个数

final int numCachedAndEmpty = numCached + numEmpty;
int memFactor;
if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES
        && numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) {
    if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
        memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
    } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
        memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW;
    } else {
        memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE;
    }
} else {
    memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
}
内存因子 取值 先决条件
ADJ_MEM_FACTOR_CRITICAL 3 Cached+Empty<=3
ADJ_MEM_FACTOR_LOW 2 Cached+Empty<=5
ADJ_MEM_FACTOR_MODERATE 1 Cached<=5 && Empty<=8
ADJ_MEM_FACTOR_NORMAL 0 Cached>5或者Empty>8
  • 最大缓存进程个数:CUR_MAX_CACHED_PROCESSES = MAX_CACHED_PROCESSES = 32
  • 最大空进程个数: CUR_MAX_EMPTY_PROCESSES = MAX_CACHED_PROCESSES/2 = 16
  • Trim空进程上限:CUR_TRIM_EMPTY_PROCESSES = MAX_CACHED_PROCESSES/4 = 8
  • Trim缓存进程上限:CUR_TRIM_CACHED_PROCESSES = MAX_CACHED_PROCESSES/6 = 5

注:用于限制empty或cached进程的上限为16个,
并且empty超过8个时会清理掉30分钟没有活跃的进程。 cached和empty主要是区别是否有Activity
系统会有相应的log输出:

//默认cachedProcessLimit=16
if (numCached > cachedProcessLimit) {
    app.kill("cached #" + numCached, true);
}
//默认CUR_TRIM_EMPTY_PROCESSES=8, 且满足30min
if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
        && app.lastActivityTime < oldTime) {
      app.kill("empty for "+ (
      (oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
         / 1000) + "s", true);
}
//默认cachedProcessLimit=16
if (numEmpty > emptyProcessLimit) {
     app.kill("empty #" + numEmpty, true);
}
  • CACHED_APP_MIN_ADJ(900):
    缓存进程优先级从CACHED_APP_MIN_ADJ(900)到 CACHED_APP_MAX_ADJ(906)。
    ADJ的转换算法:
    cached: 900, 901, 903, 905
    empty: 900, 902, 904, 906


    cache process

    1.foregroundActivities代表当前不是前台(FOREGROUND_APP_ADJ)进程,并且存在Activity的进程,当该Activity窗口不可见,并且不处于PAUSING(正在)、PAUSED(onPause个)、STOPPING的任一状态的情况下,则设置该进程为PROCESS_STATE_CACHED_ACTIVITY。
    app.adjType = "cch-act";
    2.当该进程Service的客户端进程存在Activity或者是treatLikeActivity的进程,其进程状态都是cached进程。
    app.adjType = "cch-as-act";
    app.adjType = "cch-client-act";

总结

Android进程优先级ADJ的每一个ADJ级别往往都有多种场景,使用adjType完美地区分相同ADJ下的不同场景; 不同ADJ进程所对应的schedGroup不同,从而分配的CPU资源也不同,schedGroup大体分为TOP(T)、前台(F)、后台(B); ADJ跟AMS中的procState有着紧密的联系。

adj:通过调整oom_score_adj来影响进程寿命(Lowmemorykiller杀进程策略);
schedGroup:影响进程的CPU资源调度与分配;
procState:从进程所包含的四大组件运行状态来评估进程状态,影响framework的内存控制策略。比如控制缓存进程和空进程个数上限依赖于procState,再比如控制APP执行handleLowMemory()的触发时机等。


from gityuan

CPU调度组:

调度级别 缩写 解释
SCHED_GROUP_BACKGROUND(0) B 后台进程组
SCHED_GROUP_DEFAULT(1) F 前台进程组
SCHED_GROUP_TOP_APP(2) T TOP进程组
SCHED_GROUP_TOP_APP_BOUND(3) T TOP进程组
  1. 常说的前台进程与后台进程,其实是从CPU调度角度来划分的前台与后台;为了让用户正在使用的TOP进程能分配到更多的CPU资源,从Android 6.0开始新增了TOP进程组,CPU调度优先分配给当前正在跟用户交互的APP,提升用户体验。
  2. 上图adjType=”broadcast”的CPU调度组的选择取决于广播队列,当receiver接收到的广播来自于前台广播队列则采用前台进程组,当receiver接收到的广播来自于后台广播队列则采用后台进程组。前后台广播队列的CPU资源调度优先级不同,所以前台广播超时10秒就会ANR,而后台广播超时60秒才会ANR。更少的CPU资源分配就需要更长的时间来完成执行,这也就是为何两个广播队列定义了不同的超时阈值。
  3. 上图adjType=”exec-service”的CPU调度组的选择取决于caller, 当发起bindService或者startService的调用者caller属于后台进程组,callerFg=false,则Service的生命周期回调运行在后台进程组,非常少的CPU资源;当caller属于前台或者TOP进程组,则Service的生命周期回调运行在前台进程组,分配较多的CPU资源。
  4. 上图adjType=”service”也有机会选择TOP组, 前提条件是在bindService的时候带有BIND_IMPORTANT的flags,用于标记该服务对于客户端进程很重要。

对于app开发者的建议:

  1. UI进程与Service进程一定要分离,因为对于包含activity的service进程,一旦进入后台就成为”cch-started-ui-services”类型的cache进程(ADJ>=900),随时可能会被系统回收;而分离后的Service进程服务属于SERVICE_ADJ(500),被杀的可能性相对较小。尤其是系统允许自启动的服务进程必须做UI分离,避免消耗系统较大内存。
  2. 只有真正需要用户可感知的应用,才调用startForegroundService()方法来启动前台服务,此时ADJ=PERCEPTIBLE_APP_ADJ(200),常驻内存,并且会在通知栏常驻通知提醒用户,比如音乐播放,地图导航。切勿为了常驻而滥用前台服务,这会严重影响用户体验。
  3. 进程中的Service工作完成后,务必主动调用stopService或stopSelf来停止服务,避免占据内存,浪费系统资源;
  4. 不要长时间绑定其他进程的service或者provider,每次使用完成后应立刻释放,避免其他进程常驻于内存;
  5. APP应该实现接口onTrimMemory()和onLowMemory(),根据TrimLevel适当地将非必须内存在回调方法中加以释放。当系统内存紧张时会回调该接口,减少系统卡顿与杀进程频次。
  6. 减少在保活上花心思,更应该在优化内存上下功夫,因为在相同ADJ级别的情况下,系统会选择优先杀内存占用的进程。

参考:
http://gityuan.com/2018/05/19/android-process-adj/
https://blog.csdn.net/kickxxx/article/details/13996565
https://blog.csdn.net/u012602304/article/details/79066000
https://blog.csdn.net/sinat_34606064/article/details/77932268
http://gityuan.com/2016/09/17/android-lowmemorykiller/

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

推荐阅读更多精彩内容