Android 騰訊 Matrix 原理分析(一):Matrix 概覽

寫在前面

近期開始 Android Framework 層的學(xué)習(xí)烈涮,然而較為龐大的 Framework 讓人感覺無從下手揉阎。碰巧看到一篇文章說到騰訊的 性能監(jiān)控框架 Matrix 用到了大量 Framework 相關(guān)的知識,所以試著分析下該框架的源碼實(shí)現(xiàn)。

在學(xué)習(xí)大佬們代碼的同時(shí)主要關(guān)注該框架用到了哪些、是怎么使用的 Framework 的內(nèi)容床玻。

一、Matrix 簡介

官方說明

Matrix 是一款微信研發(fā)并日常使用的應(yīng)用性能接入框架后添,支持iOS, macOS和Android笨枯。 Matrix 通過接入各種性能監(jiān)控方案薪丁,對性能監(jiān)控項(xiàng)的異常數(shù)據(jù)進(jìn)行采集和分析,輸出相應(yīng)的問題分析馅精、定位與優(yōu)化建議严嗜,從而幫助開發(fā)者開發(fā)出更高質(zhì)量的應(yīng)用。

大公司就是大氣洲敢,直接雙端都給你整一套漫玄。

Matrix 地址

Matrix for Android

Matrix-android 當(dāng)前監(jiān)控范圍包括:應(yīng)用安裝包大小,幀率變化压彭,啟動耗時(shí)睦优,卡頓,慢方法壮不,SQLite 操作優(yōu)化汗盘,文件讀寫,內(nèi)存泄漏等等询一。

  • APK Checker: 針對 APK 安裝包的分析檢測工具隐孽,根據(jù)一系列設(shè)定好的規(guī)則,檢測 APK 是否存在特定的問題健蕊,并輸出較為詳細(xì)的檢測結(jié)果報(bào)告菱阵,用于分析排查問題以及版本追蹤
  • Resource Canary: 基于 WeakReference 的特性和 Square Haha 庫開發(fā)的 Activity 泄漏和 Bitmap 重復(fù)創(chuàng)建檢測工具
  • Trace Canary: 監(jiān)控界面流暢性、啟動耗時(shí)缩功、頁面切換耗時(shí)晴及、慢函數(shù)及卡頓等問題
  • SQLite Lint: 按官方最佳實(shí)踐自動化檢測 SQLite 語句的使用質(zhì)量
  • IO Canary: 檢測文件 IO 問題,包括:文件 IO 監(jiān)控和 Closeable Leak 監(jiān)控

好家伙嫡锌,功能還真不少虑稼。看樣子是個大工程世舰,排個計(jì)劃吧:

  1. 首先是對框架的大致了解动雹,對框架中用到的需要用到的類槽卫、函數(shù)進(jìn)行預(yù)習(xí)跟压;
  2. 從某一模塊入手,分析功能實(shí)現(xiàn)的同時(shí)注重 Framework 的內(nèi)容歼培;
  3. 最后進(jìn)行總結(jié)震蒋,思考為什么這樣做,有沒有更好的做法躲庄。

那么本文先大概了解一下框架查剖,遇到 Framework 中的知識進(jìn)行簡單的了解和預(yù)習(xí)。

二噪窘、使用 Matrix

有關(guān) Matrix 的接入和使用官方文檔已經(jīng)寫得很清楚了笋庄,本文簡單總結(jié)下:

  1. 引入 Matrix 庫,添加相關(guān)依賴;
  2. 創(chuàng)建插件監(jiān)聽直砂,可以接收到插件的啟動和工作通知菌仁。
    Matrix 的功能基本都是由這些 插件 Plugin 實(shí)現(xiàn)的,這樣做的好處一方面是解耦静暂,另一方面是用戶可以根據(jù)需要選擇使用的功能济丘。
  3. 在 Application 中初始化 Matrix,添加插件并開啟插件功能洽蛀。

三摹迷、Matrix 結(jié)構(gòu)

接下來根據(jù) Matrix 的創(chuàng)建和使用來確定它的結(jié)構(gòu)。

初始化

Matrix 需要在 Applicaton 中初始化郊供,對象的構(gòu)建方式是熟悉的建造者模式:

public class MatrixApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // 創(chuàng)建 Matrix峡碉,傳入 application
        Matrix.Builder builder = new Matrix.Builder(this);
        // 設(shè)置插件監(jiān)聽
        builder.patchListener(new TestPluginListener(this));
        // 創(chuàng)建插件
        TracePlugin tracePlugin = new TracePlugin(new TraceConfig.Builder()
                .build());
        // 添加插件
        builder.plugin(tracePlugin);
        // 初始化
        Matrix.init(builder.build());
        // 插件開始工作
        tracePlugin.start();
    }
}

維護(hù)的變量也比較簡單:

public static class Builder {
    // 持有 Application 
    private final Application application;
    // 插件工作回調(diào)
    private PluginListener pluginListener;
    // 維護(hù)插件列表
    private HashSet<Plugin> plugins = new HashSet<>();

    public Builder(Application app) {
        if (app == null) {
            throw new RuntimeException("matrix init, application is null");
        }
        this.application = app;
    }
}
  • PluginListener:是一個接口,定義了插件的生命周期驮审。官方提供了默認(rèn)實(shí)現(xiàn) DefaultPluginListener异赫,我們只需要繼承該類并設(shè)置給 Matrix 就可以接收到插件的生命周期。
public interface PluginListener {
    void onInit(Plugin plugin);// 初始化

    void onStart(Plugin plugin);// 開始

    void onStop(Plugin plugin);// 結(jié)束

    void onDestroy(Plugin plugin);// 銷毀

    void onReportIssue(Issue issue);// 提交報(bào)告
}
public class TestPluginListener extends DefaultPluginListener {
    public static final String TAG = "Matrix.TestPluginListener";

    public TestPluginListener(Context context) {
        super(context);
    }

    @Override
    public void onReportIssue(Issue issue) {
        super.onReportIssue(issue);
        MatrixLog.e(TAG, issue.toString());
        //add your code to process data
    }
}
  • plugins:插件列表头岔,使用 HashSet 維護(hù)塔拳,保證插件不會重復(fù)添加。

插件 Plugin

插件是 Matrix 的重要組成結(jié)構(gòu)峡竣,通過繼承抽象類 Plugin 來創(chuàng)建一個插件靠抑,Plugin 是接口 IPlugin 的實(shí)現(xiàn)。IPlugin 接口定義了插件所實(shí)現(xiàn)的主要功能:

public interface IPlugin {

    Application getApplication();

    void init(Application application, PluginListener pluginListener);

    void start();

    void stop();

    void destroy();

    String getTag();

    void onForeground(boolean isForeground);
}

比如分析卡頓的 TracePlugin适掰,它就是一個繼承了 Plugin 的插件實(shí)現(xiàn)颂碧,在工作的過程中也會調(diào)用這些方法。

Matrix 構(gòu)造器

Matrix.Builder builder = new Matrix.Builder(this);
Matrix.init(builder.build());

調(diào)用 builder.build() 之后會創(chuàng)建一個 Matrix 對象类浪,然后創(chuàng)建一個用于監(jiān)聽 App 生命周期的 AppActiveMatrixDelegate载城。之后遍歷所有的插件列表,并調(diào)用它們的 init() 方法初始化插件费就。

private Matrix(Application app, PluginListener listener, HashSet<Plugin> plugins) {
    this.application = app;
    this.pluginListener = listener;
    this.plugins = plugins;
    // 初始化
    AppActiveMatrixDelegate.INSTANCE.init(application);
    for (Plugin plugin : plugins) {
        plugin.init(application, pluginListener);
        pluginListener.onInit(plugin);
    }
}

AppActiveMatrixDelegate

這個類是個枚舉單例诉瓦,并且監(jiān)聽?wèi)?yīng)用 Activity 生命周期以及內(nèi)存狀態(tài)。

public enum AppActiveMatrixDelegate {
    // 1. 利用枚舉創(chuàng)建單例
    INSTANCE;

    private static final String TAG = "Matrix.AppActiveDelegate";
    private final Set<IAppForeground> listeners = new HashSet();
    private boolean isAppForeground = false;
    private String visibleScene = "default";
    private Controller controller = new Controller();
    private boolean isInit = false;
    private String currentFragmentName;
    private Handler handler;

    public void init(Application application) {
        if (isInit) {
            MatrixLog.e(TAG, "has inited!");
            return;
        }
        this.isInit = true;
        // 2. HandlerTherad:一個封裝了 Handler 的線程
        if (null != MatrixHandlerThread.getDefaultHandlerThread()) {
            this.handler = new Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper());
        }
        // 3. 注冊應(yīng)用內(nèi)存狀態(tài)回調(diào)
        application.registerComponentCallbacks(controller);
        // 4. 注冊監(jiān)聽 Activity 生命周期
        application.registerActivityLifecycleCallbacks(controller);
    }
}
  1. 枚舉實(shí)現(xiàn)單例的原理是利用枚舉的特點(diǎn)來實(shí)現(xiàn)的:枚舉類型是線程安全的力细,并且只會裝載一次睬澡。
  2. HandlerTherad 繼承了 Thread,可以看作是一個線程眠蚂。而其內(nèi)部維護(hù)了一個 Handler煞聪,在調(diào)用 start() 方法后初始化 Looper,可以很方便地執(zhí)行異步任務(wù)逝慧。其它類可以通過 getThreadHandler 方法獲取 HandlerTherad 的 Handler昔脯,然后 post 任務(wù)由 HandlerTherad 內(nèi)部的 Looper 取出并執(zhí)行啄糙。
  3. registerComponentCallbacks:Application 的方法,作用是監(jiān)聽?wèi)?yīng)用的內(nèi)存狀態(tài)云稚。
    在系統(tǒng)內(nèi)存不足迈套,所有后臺程序(優(yōu)先級為background的進(jìn)程,不是指后臺運(yùn)行的進(jìn)程)都被殺死時(shí)碱鳞,系統(tǒng)會調(diào)用 onLowMemory桑李。
    OnTrimMemory 是 Android 4.0 之后提供的 API。比起 onLowMemory窿给,這個回調(diào)新增返回了一個 int 值表示當(dāng)前內(nèi)存狀態(tài)贵白,開發(fā)者可以根據(jù)返回的狀態(tài)來適當(dāng)回收資源避免 app 被殺死的風(fēng)險(xiǎn)。
    想要監(jiān)聽內(nèi)存狀態(tài)回調(diào)需要實(shí)現(xiàn) ComponentCallbacks2 接口崩泡,該接口是 ComponentCallbacks 的升級版禁荒。

應(yīng)用內(nèi)存優(yōu)化之OnLowMemory&OnTrimMemory

  1. registerActivityLifecycleCallbacks:注冊監(jiān)聽 Activity 狀態(tài)回調(diào),實(shí)現(xiàn) Application.ActivityLifecycleCallbacks 接口以監(jiān)聽 Activity 生命周期回調(diào)角撞。

Controller

Controller 實(shí)現(xiàn) ComponentCallbacks2 監(jiān)聽內(nèi)存狀態(tài)呛伴、ActivityLifecycleCallbacks 監(jiān)聽 Activity 生命周期。

private final class Controller implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {

    @Override
    public void onActivityStarted(Activity activity) {
        // 1. 記錄啟動的 Activity
        updateScene(activity);
        // 1.1 告知 listeners Activity 在前臺了
        onDispatchForeground(getVisibleScene());
    }


    @Override
    public void onActivityStopped(Activity activity) {
        // 1.2 獲取棧頂活動的 Activity
        if (getTopActivityName() == null) {
            // 1.3 告知 listeners Activity 在后臺了
            onDispatchBackground(getVisibleScene());
        }
    }
    ...

    @Override
    public void onTrimMemory(int level) {
        MatrixLog.i(TAG, "[onTrimMemory] level:%s", level);
        // 2. TRIM_MEMORY_UI_HIDDEN 表示當(dāng)前 app UI 不再可見
        if (level == TRIM_MEMORY_UI_HIDDEN && isAppForeground) { // fallback
            onDispatchBackground(visibleScene);
        }
    }
}

Controller 的邏輯主要為了區(qū)分 App 進(jìn)入前臺或后臺谒所。怎么區(qū)分呢热康?

  • 有 Activity 回調(diào)了 onStart,說明 App 進(jìn)入了前臺劣领,記錄并返回給監(jiān)聽就行姐军;
  • 有 Activity 回調(diào)了 onStop,且棧頂沒有 Resume 狀態(tài)的 Activity尖淘,說明 App 進(jìn)入了后臺奕锌;
    用戶點(diǎn)擊了 Home 或者 Back 鍵,系統(tǒng)會通過 onTrimMemory 回調(diào)一個 TRIM_MEMORY_UI_HIDDEN 狀態(tài)村生,告知這是 App 進(jìn)入后臺惊暴,是回收資源的大好時(shí)機(jī)。

回調(diào) onStart 之后用一個字符串記錄當(dāng)前 Activity

private void updateScene(Activity activity) {
    visibleScene = activity.getClass().getName();
}

public String getVisibleScene() {
    return visibleScene;
}

我們主要關(guān)注 onActivityStopped() 回調(diào)中的 getTopActivityName() 方法趁桃,該方法用于獲取棧頂活動狀態(tài)的 Activity辽话。

getTopActivityName()

public static String getTopActivityName() {
    long start = System.currentTimeMillis();
    try {
        // 獲取 ActivityThread Class 對象
        Class activityThreadClass = Class.forName("android.app.ActivityThread");
        // 調(diào)用這個類的 currentActivityThread 方法,返回一個靜態(tài)的 ActivityThread 實(shí)例 sCurrentActivityThread
        // 這個靜態(tài)實(shí)例是在 main 函數(shù)中賦值的
        Object activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null);
        // 獲取 ActivityThread 的 mActivities 列表
        // 在 Activity onCreate 之后镇辉,往列表添加 Activity 記錄
        Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
        activitiesField.setAccessible(true);

        Map<Object, Object> activities; // 獲取 activityThread 類的 mActivities 對象
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            activities = (HashMap<Object, Object>) activitiesField.get(activityThread);
        } else {
            activities = (ArrayMap<Object, Object>) activitiesField.get(activityThread);
        }
        if (activities.size() < 1) {
            return null;
        }
        for (Object activityRecord : activities.values()) {
            Class activityRecordClass = activityRecord.getClass();
            Field pausedField = activityRecordClass.getDeclaredField("paused");
            pausedField.setAccessible(true);
            if (!pausedField.getBoolean(activityRecord)) {// onResume 的 Activity paused 為 false
                Field activityField = activityRecordClass.getDeclaredField("activity");
                activityField.setAccessible(true);
                Activity activity = (Activity) activityField.get(activityRecord);
                return activity.getClass().getName();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        long cost = System.currentTimeMillis() - start;
        MatrixLog.d(TAG, "[getTopActivityName] Cost:%s", cost);
    }
    return null;
}

整個過程就是利用反射操作 Framework ActivityThread 的參數(shù)和函數(shù)屡穗,獲取棧頂?shù)姆?paused 狀態(tài)的 Activity。這段代碼需要看著 ActivityThread 類慢慢消化忽肛,在你的 IDE 查看或者在線查看

綜上烂斋,AppActiveMatrixDelegate 是利用內(nèi)部的 Controller 監(jiān)聽 App 發(fā)來的信號屹逛,用來確定應(yīng)用程序的前后臺狀態(tài)础废。

外部可以設(shè)置監(jiān)聽,等應(yīng)用程序前后臺轉(zhuǎn)換的時(shí)候再遍歷監(jiān)聽者回調(diào)告知罕模。

Issue

當(dāng)插件監(jiān)控到 App 運(yùn)行出現(xiàn)問題時(shí)评腺,會把問題信息封裝為一個 Issue 類進(jìn)行報(bào)告。

public class Issue {
    private int        type;
    private String     tag;
    private String     key;
    private JSONObject content;
    private Plugin     plugin;

    public static final String ISSUE_REPORT_TYPE    = "type";
    public static final String ISSUE_REPORT_TAG     = "tag";
    public static final String ISSUE_REPORT_PROCESS = "process";
    public static final String ISSUE_REPORT_TIME = "time";
}

可以看到該類詳細(xì)記錄了問題的類型淑掌、信息蒿讥、插件信息等,發(fā)現(xiàn)問題是怎么報(bào)告呢抛腕?我們拿性能監(jiān)控插件 TracePlugin 中的 FrameTracer 舉例:

FrameTracer

void report() {
    float fps = Math.min(60.f, 1000.f * sumFrame / sumFrameCost);
    MatrixLog.i(TAG, "[report] FPS:%s %s", fps, toString());

    try {
        // 根據(jù)插件名稱遍歷查找
        TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class);
        if (null == plugin) {
            return;
        }
        // ... 省略部分代碼
        
        JSONObject resultObject = new JSONObject();
        resultObject = DeviceUtil.getDeviceInfo(resultObject, plugin.getApplication());
        // 組裝內(nèi)容
        resultObject.put(SharePluginInfo.ISSUE_SCENE, visibleScene);
        resultObject.put(SharePluginInfo.ISSUE_DROP_LEVEL, dropLevelObject);
        resultObject.put(SharePluginInfo.ISSUE_DROP_SUM, dropSumObject);
        resultObject.put(SharePluginInfo.ISSUE_FPS, fps);

        Issue issue = new Issue();
        issue.setTag(SharePluginInfo.TAG_PLUGIN_FPS);
        issue.setContent(resultObject);
        // 調(diào)用插件方法
        plugin.onDetectIssue(issue);

    } catch (JSONException e) {
        MatrixLog.e(TAG, "json error", e);
    } finally {
        sumFrame = 0;
        sumDroppedFrames = 0;
        sumFrameCost = 0;
    }
}

最后會調(diào)用 Plugin 的 onDetectIssue (Detect:發(fā)現(xiàn)芋绸、偵查出)方法傳遞 Issue 信息。

Plugin # onDetectIssue

@Override
public void onDetectIssue(Issue issue) {
    if (issue.getTag() == null) {
        // 設(shè)置默認(rèn) tag
        issue.setTag(getTag());
    }
    issue.setPlugin(this);
    JSONObject content = issue.getContent();
    // add tag and type for default
    try {
        if (issue.getTag() != null) {
            content.put(Issue.ISSUE_REPORT_TAG, issue.getTag());
        }
        if (issue.getType() != 0) {
            content.put(Issue.ISSUE_REPORT_TYPE, issue.getType());
        }
        content.put(Issue.ISSUE_REPORT_PROCESS, MatrixUtil.getProcessName(application));
        content.put(Issue.ISSUE_REPORT_TIME, System.currentTimeMillis());

    } catch (JSONException e) {
        MatrixLog.e(TAG, "json error", e);
    }

    // 報(bào)告 Issue
    pluginListener.onReportIssue(issue);
}

這個 pluginListener 對象其實(shí)就是在 初始化 的時(shí)候創(chuàng)建并設(shè)置的 TestPluginListener担敌,現(xiàn)在發(fā)現(xiàn)問題了就通過這個 Listener 報(bào)告問題摔敛。

public class TestPluginListener extends DefaultPluginListener {
    public static final String TAG = "Matrix.TestPluginListener";

    public TestPluginListener(Context context) {
        super(context);
    }

    @Override
    public void onReportIssue(Issue issue) {
        super.onReportIssue(issue);
        MatrixLog.e(TAG, issue.toString());
        // 收到 Issue,做后續(xù)工作
    }
}

總結(jié)

畫個簡單的流程圖:

流程

到這里全封,Matrix 大致的工作流程已經(jīng)搞清楚了马昙。但是到現(xiàn)在基本沒有接觸核心功能,Matrix 是怎么分析卡頓的刹悴?怎么分析 ANR 的行楞?... 后面會發(fā)文繼續(xù)分析,敬請期待土匀。

系列文章

Android 騰訊 Matrix 原理分析(二):TracePlugin 卡頓分析之主線程監(jiān)聽
Android 騰訊 Matrix 原理分析(三):TracePlugin 卡頓分析之幀率監(jiān)聽
Android 騰訊 Matrix 原理分析(四):TracePlugin 卡頓分析之丟幀展現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末律姨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子卖毁,更是在濱河造成了極大的恐慌怀薛,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钓丰,死亡現(xiàn)場離奇詭異躯砰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)携丁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門琢歇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人梦鉴,你說我怎么就攤上這事李茫。” “怎么了肥橙?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵魄宏,是天一觀的道長。 經(jīng)常有香客問我存筏,道長宠互,這世上最難降的妖魔是什么味榛? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮予跌,結(jié)果婚禮上搏色,老公的妹妹穿的比我還像新娘。我一直安慰自己券册,他們只是感情好频轿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著烁焙,像睡著了一般航邢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上考阱,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天翠忠,我揣著相機(jī)與錄音,去河邊找鬼乞榨。 笑死秽之,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吃既。 我是一名探鬼主播考榨,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鹦倚!你這毒婦竟也來了河质?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤震叙,失蹤者是張志新(化名)和其女友劉穎掀鹅,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體媒楼,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乐尊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了划址。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扔嵌。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖夺颤,靈堂內(nèi)的尸體忽然破棺而出痢缎,到底是詐尸還是另有隱情,我是刑警寧澤世澜,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布独旷,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏势告。R本人自食惡果不足惜蛇捌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一抚恒、第九天 我趴在偏房一處隱蔽的房頂上張望咱台。 院中可真熱鬧,春花似錦俭驮、人聲如沸回溺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遗遵。三九已至,卻和暖如春逸嘀,著一層夾襖步出監(jiān)牢的瞬間车要,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工崭倘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翼岁,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓司光,卻偏偏與公主長得像琅坡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子残家,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容