Android之進(jìn)程 crash監(jiān)聽

前言:
針對(duì)crash業(yè)務(wù)分析
代碼
Android 10.0

詳細(xì):

一侧戴、日志分析

1.異常彈框日志

:54.358  1066  1066 E AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.os.Handler.post(java.lang.Runnable)' on a null object reference
    Line 4065: 11-05 23:17:54.358  1066  1066 E AndroidRuntime:     at com.example.myapplication.AutoCameraTestActivity.onClick(AutoCameraTestActivity.java:106)
    Line 4066: 11-05 23:17:54.358  1066  1066 E AndroidRuntime:     at android.view.View.performClick(View.java:5637)
    Line 4067: 11-05 23:17:54.358  1066  1066 E AndroidRuntime:     at android.view.View$PerformClick.run(View.java:22445)
    Line 4068: 11-05 23:17:54.358  1066  1066 E AndroidRuntime:     at android.os.Handler.handleCallback(Handler.java:755)
    Line 4069: 11-05 23:17:54.358  1066  1066 E AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:95)
    Line 4070: 11-05 23:17:54.358  1066  1066 E AndroidRuntime:     at android.os.Looper.loop(Looper.java:154)
    Line 4071: 11-05 23:17:54.358  1066  1066 E AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:6141)
    Line 4072: 11-05 23:17:54.358  1066  1066 E AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
    Line 4073: 11-05 23:17:54.358  1066  1066 E AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:912)
    Line 4074: 11-05 23:17:54.358  1066  1066 E AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:802)

2.進(jìn)程啟動(dòng)加載的數(shù)據(jù)

1149  1149 W System.err: java.lang.Exception: RuntimeInit
11-05 23:17:47.687  1149  1149 W System.err:    at com.android.internal.os.RuntimeInit.commonInit(RuntimeInit.java:122)
11-05 23:17:47.687  1149  1149 W System.err:    at com.android.internal.os.RuntimeInit.zygoteInit(RuntimeInit.java:288)
11-05 23:17:47.687  1149  1149 W System.err:    at com.android.internal.os.ZygoteConnection.handleChildProc(ZygoteConnection.java:757)
11-05 23:17:47.687  1149  1149 W System.err:    at com.android.internal.os.ZygoteConnection.runOnce(ZygoteConnection.java:243)
11-05 23:17:47.687  1149  1149 W System.err:    at com.android.internal.os.ZygoteInit.runSelectLoop(ZygoteInit.java:876)
11-05 23:17:47.688  1149  1149 W System.err:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:798)

二、代碼分析
1.java層的異常彈框監(jiān)聽

com.android.internal.os.RuntimeInit.java

    private static final void commonInit() {
        //1.設(shè)置預(yù)處理異常業(yè)務(wù),目的輸出異常日志。此接口setUncaughtExceptionPreHandler為hide
        //2.設(shè)置默認(rèn)異常處理業(yè)務(wù)及穗,目的是kill應(yīng)用和定制ams控制業(yè)務(wù)
        //1和2分開,可以避免普通應(yīng)用把異常日志捕獲而系統(tǒng)無法留檔
        LoggingHandler loggingHandler = new LoggingHandler();
        RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
        ···
    }

    //預(yù)處理中的異常信息記錄
    //注意:普通應(yīng)用和system_server應(yīng)用異常日志區(qū)別
    //共同TAG:FATAL EXCEPTION
    //異常日志采用ID為L(zhǎng)OG_ID_CRASH的方式記錄:Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr)
    private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
        public volatile boolean mTriggered = false;

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            mTriggered = true;

            // Don't re-enter if KillApplicationHandler has already run
            if (mCrashing) return;

            if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
                //system_server異常绵载,日志打印
                Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
            } else {
               //普通應(yīng)用異常埂陆,日志打印
                StringBuilder message = new StringBuilder();
                message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
                final String processName = ActivityThread.currentProcessName();
                if (processName != null) {
                    message.append("Process: ").append(processName).append(", ");
                }
                message.append("PID: ").append(Process.myPid());
                Clog_e(TAG, message.toString(), e);
            }
        }
    }
    
    //通知ams處理異常業(yè)務(wù)
    private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        private final LoggingHandler mLoggingHandler;

        public KillApplicationHandler(LoggingHandler loggingHandler) {
            this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                //如果日志在預(yù)處理之前沒有抓取到,則再抓取一次
                ensureLogging(t, e);

                if (mCrashing) return;
                mCrashing = true;

                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }
                //通知ams處理異常業(yè)務(wù),例如通知dropbox記錄異常信息娃豹、彈異常對(duì)話框等等
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
                if (t2 instanceof DeadObjectException) {
                    // System process is dead; ignore
                } else {
                    try {
                        Clog_e(TAG, "Error reporting crash", t2);
                    } catch (Throwable t3) {
                        // Even Clog_e() fails!  Oh well.
                    }
                }
            } finally {
                //通過kill -9殺應(yīng)用
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }

        private void ensureLogging(Thread t, Throwable e) {
            if (!mLoggingHandler.mTriggered) {
                try {
                    mLoggingHandler.uncaughtException(t, e);
                } catch (Throwable loggingThrowable) {
                    // Ignored.
                }
            }
        }
    }

總結(jié):
1)Zygote進(jìn)程fork app進(jìn)程時(shí)焚虱,調(diào)用RuntimeInit.zygoteInit,設(shè)置監(jiān)聽Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler())培愁。這是在啟動(dòng)進(jìn)程時(shí)著摔,進(jìn)程內(nèi)部創(chuàng)建的java層異常監(jiān)聽
2)ActivityManagerService.handleApplicationCrash就是展示對(duì)話框的業(yè)務(wù)入口函數(shù)。
3)異常TAG搜索:FATAL EXCEPTION
4)這種方式無法監(jiān)聽jni異常
5)特別說明定续,
設(shè)置預(yù)處理異常業(yè)務(wù)谍咆,目的輸出異常日志。此接口setUncaughtExceptionPreHandler為hide
設(shè)置默認(rèn)異常處理業(yè)務(wù)私股,目的是kill應(yīng)用和定制ams控制業(yè)務(wù)
將兩者分開摹察,可以避免普通應(yīng)用把異常日志捕獲而系統(tǒng)無法留檔

2.Native層的異常彈框處理

com.android.server.SystemServer
private void startOtherServices() {
   ···
   mActivityManagerService.startObservingNativeCrashes();
   ···
}

com.android.server.am.ActivityManagerService
public void startObservingNativeCrashes() {
    final NativeCrashListener ncl = new NativeCrashListener(this);
    ncl.start();
}


com.android.server.am.NativeCrashListener
final class NativeCrashListener extends Thread {
    ···
    static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket";
    ···
    public void run() {
        final byte[] ackSignal = new byte[1];
        ···
        try {
            //創(chuàng)建socket服務(wù)端
            FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0);
            final UnixSocketAddress sockAddr = UnixSocketAddress.createFileSystem(
                    DEBUGGERD_SOCKET_PATH);
            Os.bind(serverFd, sockAddr);
            Os.listen(serverFd, 1);
            Os.chmod(DEBUGGERD_SOCKET_PATH, 0777);

            while (true) {
                FileDescriptor peerFd = null;
                try {
                    //等待socket客戶端連接
                    peerFd = Os.accept(serverFd, null /* peerAddress */);
                    if (peerFd != null) {
                        StructUcred credentials =
                                Os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED);
                        //socket客戶端的uid為0才能處理異常數(shù)據(jù)
                        if (credentials.uid == 0) {
                            //消化native異常信息
                            consumeNativeCrashData(peerFd);
                        }
                    }
                } catch (Exception e) {
                    Slog.w(TAG, "Error handling connection", e);
                } finally {
                    if (peerFd != null) {
                        try {
                            Os.write(peerFd, ackSignal, 0, 1);
                        } catch (Exception e) {
                            
                        }
                        try {
                            Os.close(peerFd);
                        } catch (ErrnoException e) {
                            
                        }
                    }
                }
            }
        } catch (Exception e) {
            Slog.e(TAG, "Unable to init native debug socket!", e);
        }
    }
    
    void consumeNativeCrashData(FileDescriptor fd) {
        final byte[] buf = new byte[4096];
        final ByteArrayOutputStream os = new ByteArrayOutputStream(4096);

        try {
            StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS);
            Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
            Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);

            //從fd中讀取信息到buf
            int headerBytes = readExactly(fd, buf, 0, 8);
            if (headerBytes != 8) {
                return;
            }

            //從buf中讀取pid和signal
            int pid = unpackInt(buf, 0);
            int signal = unpackInt(buf, 4);

            // now the text of the dump
            if (pid > 0) {
                final ProcessRecord pr;
                synchronized (mAm.mPidsSelfLocked) {
                    pr = mAm.mPidsSelfLocked.get(pid);
                }
                if (pr != null) {
                    //如果是persistent進(jìn)程,則不記錄
                    if (pr.isPersistent()) {
                        return;
                    }

                    //將數(shù)據(jù)讀取到os中
                    int bytes;
                    do {
                        // get some data
                        bytes = Os.read(fd, buf, 0, buf.length);
                        if (bytes > 0) {
                            if (buf[bytes-1] == 0) {
                                os.write(buf, 0, bytes-1);  // exclude the EOD token
                                break;
                            }
                            // no EOD, so collect it and read more
                            os.write(buf, 0, bytes);
                        }
                    } while (bytes > 0);
                    

                    synchronized (mAm) {
                        pr.setCrashing(true);
                        pr.forceCrashReport = true;
                    }
                    //把os數(shù)據(jù)轉(zhuǎn)化為String
                    final String reportString = new String(os.toByteArray(), "UTF-8");
                    //報(bào)告原因
                    (new NativeCrashReporter(pr, signal, reportString)).start();
                } else {
                    Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
                }
            } else {
                Slog.e(TAG, "Bogus pid!");
            }
        } catch (Exception e) {
            Slog.e(TAG, "Exception dealing with report", e);
            // ugh, fail.
        }
    }
    
    class NativeCrashReporter extends Thread {
        ProcessRecord mApp;
        int mSignal;
        String mCrashReport;

        NativeCrashReporter(ProcessRecord app, int signal, String report) {
            super("NativeCrashReport");
            mApp = app;
            mSignal = signal;
            mCrashReport = report;
        }

        @Override
        public void run() {
            try {
                CrashInfo ci = new CrashInfo();
                ci.exceptionClassName = "Native crash";
                ci.exceptionMessage = Os.strsignal(mSignal);
                ci.throwFileName = "unknown";
                ci.throwClassName = "unknown";
                ci.throwMethodName = "unknown";
                ci.stackTrace = mCrashReport;
                //通知ams
                mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);//1
            } catch (Exception e) {
                Slog.e(TAG, "Unable to report native crash", e);
            }
        }
    }
}

總結(jié)
1)native監(jiān)聽實(shí)現(xiàn)是在線程中開啟了一個(gè)while循環(huán)
2)注意倡鲸,對(duì)于persistent進(jìn)程供嚎,不做crash report
3)ams設(shè)置的native監(jiān)聽,是作為socket服務(wù)端峭状,而客戶端來自debuggerd進(jìn)程
4)ams接收異常后克滴,調(diào)用handleApplicationCrashInner

3.Ams.handleApplicationCrashInner分析
不管是java層還是native層的crash,最終都會(huì)通知Ams.handleApplicationCrashInner
1)分析handleApplicationCrashInner

ActivityManagerService

    void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
            ApplicationErrorReport.CrashInfo crashInfo) {
        //events log日志:EventLogTags.AM_CRASH --> am_crash
        EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(),
                UserHandle.getUserId(Binder.getCallingUid()), processName,
                r == null ? -1 : r.info.flags,
                crashInfo.exceptionClassName,
                crashInfo.exceptionMessage,
                crashInfo.throwFileName,
                crashInfo.throwLineNumber);
        ···
        //異常信息注入dropbox
        addErrorToDropBox(
                eventType, r, processName, null, null, null, null, null, null, crashInfo);
        //app異常報(bào)告
        mAppErrors.crashApplication(r, crashInfo);
    }

a)分析crash異常時(shí)优床,也可以關(guān)注events日志tag:am_crash
b)執(zhí)行是否重啟app或者彈對(duì)話框進(jìn)行人為點(diǎn)擊確定
c)dropbox也會(huì)記錄異常信息劝赔,前綴名稱system_server/system_app/data_app。即/data/system/dropbox

    private static String processClass(ProcessRecord process) {
        if (process == null || process.pid == MY_PID) {//system_server進(jìn)程
            return "system_server";
        } else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {//集成在system/app或priv-app的為system_app
            return "system_app";
        } else {//其他安裝的為data_app
            return "data_app";
        }
    }

2)mAppErrors.crashApplication(r, crashInfo);

a)針對(duì)persistent或apexmodule進(jìn)程胆敞,進(jìn)行營(yíng)救記錄
b)過濾不彈對(duì)話框業(yè)務(wù)條件
c)通知handler處理對(duì)話框業(yè)務(wù)
d)等待handler并處理相關(guān)結(jié)果
com.android.server.am.AppErrors
    void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,
            int callingPid, int callingUid) {
        ···
        //針對(duì)persistent或apexmodule進(jìn)程着帽,進(jìn)行營(yíng)救記錄
        if (r != null) {
            ···
            if (r.isPersistent() || isApexModule) {
                RescueParty.noteAppCrash(mContext, r.uid);
            }

            mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode());
        }

        ···
        synchronized (mService) {
            //過濾不彈對(duì)話框業(yè)務(wù)條件
            //這里可以實(shí)現(xiàn)IActivityController接口,從而滿足無須彈框業(yè)務(wù)移层。ActivityTaskManagerService.setActivityController
            ···
            //通知handler處理對(duì)話框業(yè)務(wù)
            final Message msg = Message.obtain();
            msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;

            taskId = data.taskId;
            msg.obj = data;
            mService.mUiHandler.sendMessage(msg);
        }

        //等待handler并處理相關(guān)結(jié)果
        int res = result.get();//阻塞
        ···
        
    }

3)通知handler處理對(duì)話框業(yè)務(wù)
主要處理是否展示對(duì)話框業(yè)務(wù)

com.android.server.am.AppErrors
    void handleShowAppErrorUi(Message msg) {
        AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj;
        //針對(duì)anr業(yè)務(wù)是否需要展示對(duì)話框仍翰,默認(rèn)不展示
        boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;

        AppErrorDialog dialogToShow = null;
        ···
        synchronized (mService) {
            ···
            final boolean showFirstCrashDevOption = Settings.Secure.getIntForUser(
                    mContext.getContentResolver(),
                    Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
                    0,
                    mService.mUserController.getCurrentUserId()) != 0;
            //靜默crash白名單。來自framework-res.apk的config_appsNotReportingCrashes观话,多個(gè)用“,”隔開
            //例如:com.android.settings,com.android.systemui
            final boolean crashSilenced = mAppsNotReportingCrashes != null &&
                    mAppsNotReportingCrashes.contains(proc.info.packageName);
            
            //默認(rèn)展示對(duì)話框予借,可設(shè)置Settings.Global.HIDE_ERROR_DIALOGS為1,來隱藏對(duì)話框
            if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
                    && !crashSilenced
                    && (showFirstCrashDevOption || data.repeating)) {
                proc.crashDialog = dialogToShow = new AppErrorDialog(mContext, mService, data);
                ···
            } else {
                // The device is asleep, so just pretend that the user
                // saw a crash dialog and hit "force quit".
                if (res != null) {
                    res.set(AppErrorDialog.CANT_SHOW);
                }
            }
        }
        
        if (dialogToShow != null) {
            Slog.i(TAG, "Showing crash dialog for package " + packageName + " u" + userId);
            dialogToShow.show();
        }
    }

針對(duì)異常對(duì)話框展示問題
a)局部,加白向framework-res.apk的config_appsNotReportingCrashes中加白處理
b)全局蕾羊,設(shè)置Settings.Global.HIDE_ERROR_DIALOGS為1喧笔,Settings.Secure.ANR_SHOW_BACKGROUND為0

4)怎么實(shí)現(xiàn)等待handler處理?

com.android.server.am.AppErrorResult

final class AppErrorResult {
    //set就是notifyAll機(jī)制
    public void set(int res) {
        synchronized (this) {
            mHasResult = true;
            mResult = res;
            notifyAll();
        }
    }

   //get就是wait機(jī)制
    public int get() {
        synchronized (this) {
            while (!mHasResult) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mResult;
    }

    boolean mHasResult = false;
    int mResult;
}

參考學(xué)習(xí)

https://juejin.cn/post/6844904006041468935
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末龟再,一起剝皮案震驚了整個(gè)濱河市书闸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌利凑,老刑警劉巖浆劲,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異哀澈,居然都是意外死亡牌借,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門割按,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膨报,“玉大人,你說我怎么就攤上這事适荣∠帜” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵弛矛,是天一觀的道長(zhǎng)够吩。 經(jīng)常有香客問我,道長(zhǎng)丈氓,這世上最難降的妖魔是什么周循? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮万俗,結(jié)果婚禮上湾笛,老公的妹妹穿的比我還像新娘。我一直安慰自己闰歪,他們只是感情好嚎研,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著课竣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪置媳。 梳的紋絲不亂的頭發(fā)上于樟,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音拇囊,去河邊找鬼迂曲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛寥袭,可吹牛的內(nèi)容都是我干的路捧。 我是一名探鬼主播关霸,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼杰扫!你這毒婦竟也來了队寇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤章姓,失蹤者是張志新(化名)和其女友劉穎佳遣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凡伊,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡零渐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了系忙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诵盼。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖银还,靈堂內(nèi)的尸體忽然破棺而出风宁,到底是詐尸還是另有隱情,我是刑警寧澤见剩,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布杀糯,位于F島的核電站,受9級(jí)特大地震影響苍苞,放射性物質(zhì)發(fā)生泄漏固翰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一羹呵、第九天 我趴在偏房一處隱蔽的房頂上張望骂际。 院中可真熱鬧,春花似錦冈欢、人聲如沸歉铝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽太示。三九已至,卻和暖如春香浩,著一層夾襖步出監(jiān)牢的瞬間类缤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工邻吭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留餐弱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像膏蚓,于是被迫代替她去往敵國(guó)和親瓢谢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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