Service 出現(xiàn)ANR的原因

Android知識總結(jié)

一、ANR出現(xiàn)原因

Android 系統(tǒng)中熏兄, ActivityManagerService( 簡稱 AMS)和WindowManagerService( 簡 稱 WMS)會檢測App的響應時間品洛,如果App在特定時間無法相應屏幕觸摸或鍵盤輸入時間,或者特定事件沒有處理完畢摩桶,就會出現(xiàn) ANR桥状。

1、以下四個條件都可以造成 ANR 發(fā)生:

  • InputDispatching Timeout:5 秒內(nèi)無法響應屏幕觸摸事件或鍵盤輸入事件
  • BroadcastQueue Timeout :在執(zhí)行前臺廣播(BroadcastReceiver)的 onReceive()函數(shù)時 10 秒沒有處理完成硝清,后臺為 60 秒辅斟。
  • Service Timeout :前臺服務 20 秒內(nèi),后臺服務在 200 秒內(nèi)沒有執(zhí)行完畢芦拿。
  • ContentProvider Timeout :ContentProvider 的 publish 在 10s 內(nèi)沒進行完士飒。

二、Service ANR 源碼分析

1蔗崎、Service 造成的 e Service Timeout

Service Timeout 是位于 ActivityManager 線程中的 AMS.MainHandler 收到 SERVICE_TIMEOUT_MSG 消息時觸發(fā)变汪。

2、發(fā)送延時消息

Service 進程 attach 到 system_server 進程的過程中會調(diào)用realStartServiceLocked 蚁趁,緊接著mAm.mHandler.sendMessageAtTime() 來發(fā)送一個延時消息裙盾,延時的時常是定義好的,如前臺 Service 的 20 秒他嫡。ActivityManager 線程中的 AMS.MainHandler 收到 SERVICE_TIMEOUT_MSG 消息時會觸發(fā)番官。

  • Service 是在AMS 中交給 ActiveServices 進行管理的。

調(diào)用 ActiveServices.realStartServiceLocked 方法

IApplicationThread thread; //ActivityThread 的內(nèi)部類

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
    ...
    //發(fā)送 delay 消息(SERVICE_TIMEOUT_MSG
    bumpServiceExecutingLocked(r, execInFg, "create");
    
    try {
        if (LOG_SERVICE_START_STOP) {
        ...
        //最終執(zhí)行服務的 onCreate()方法
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
                app.getReportedProcState());
        r.postNotification();
        created = true;
    } catch (DeadObjectException e) {
        Slog.w(TAG, "Application dead when creating service " + r);
        mAm.appDiedLocked(app, "Died when creating service");
        throw e;
    } finally {
        ..
    }
    ...
}
  • 發(fā)送延時消息

ActiveServices.java

private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
    ...
    long now = SystemClock.uptimeMillis();
    if (r.executeNesting == 0) {
        r.executeFg = fg;
        ServiceState stracker = r.getTracker();
        if (stracker != null) {
            stracker.setExecuting(true, mAm.mProcessStats.getMemFactorLocked(), now);
        }
        if (r.app != null) {
            r.app.executingServices.add(r);
            r.app.execServicesFg |= fg;
            if (timeoutNeeded && r.app.executingServices.size() == 1) {
                scheduleServiceTimeoutLocked(r.app);
            }
        }
    } else if (r.app != null && fg && !r.app.execServicesFg) {
        r.app.execServicesFg = true;
        if (timeoutNeeded) {
            scheduleServiceTimeoutLocked(r.app);
        }
    }
    r.executeFg |= fg;
    r.executeNesting++;
    r.executingStart = now;
}
static final int SERVICE_TIMEOUT = 20*1000; //前臺服務 20 秒內(nèi)
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10; //后臺服務在 200 秒
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    if (proc.executingServices.size() == 0 || proc.thread == null) {
        return;
    } 
    Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_TIMEOUT_MSG);
    msg.obj = proc;
    //發(fā)送延時消息
    //當超時后仍沒有 remove 該SERVICE_TIMEOUT_MSG 消息钢属,則執(zhí)行 service Timeout 流程
    mAm.mHandler.sendMessageDelayed(msg,
            proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}

進入 ActivityManagerService#MainHandler處理消息

public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    final MainHandler mHandler; 
    final ActiveServices mServices;

    final class MainHandler extends Handler {
        public MainHandler(Looper looper) {
            super(looper, null, true);
        }
        
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case GC_BACKGROUND_PROCESSES_MSG: {
                synchronized (ActivityManagerService.this) {
                    performAppGcsIfAppropriateLocked();
                }
            } break;
            case SERVICE_TIMEOUT_MSG: { //處理 service 的超時時間 ANR
                mServices.serviceTimeout((ProcessRecord)msg.obj);
            } break;
            ...
        
        }
    }
}

進入ActiveServices#serviceTimeout 進行處理

void serviceTimeout(ProcessRecord proc) {
    String anrMessage = null;
    synchronized(mAm) {
        if (proc.isDebugging()) {
            // The app's being debugged, ignore timeout.
            return;
        }
        if (proc.executingServices.size() == 0 || proc.thread == null) {
            return;
        }
        final long now = SystemClock.uptimeMillis();
        final long maxTime =  now -
                (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
        ServiceRecord timeout = null;
        long nextTime = 0;
        for (int i=proc.executingServices.size()-1; i>=0; i--) {
            ServiceRecord sr = proc.executingServices.valueAt(i);
            if (sr.executingStart < maxTime) {
                timeout = sr;
                break;
            }
            if (sr.executingStart > nextTime) {
                nextTime = sr.executingStart;
            }
        }
        if (timeout != null && mAm.mProcessList.mLruProcesses.contains(proc)) {
            Slog.w(TAG, "Timeout executing service: " + timeout);
            StringWriter sw = new StringWriter();
            PrintWriter pw = new FastPrintWriter(sw, false, 1024);
            pw.println(timeout);
            timeout.dump(pw, "    ");
            pw.close();
            mLastAnrDump = sw.toString();
            mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
            mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
            //異常 消息
            anrMessage = "executing service " + timeout.shortInstanceName;
        } else {
            Message msg = mAm.mHandler.obtainMessage(
                    ActivityManagerService.SERVICE_TIMEOUT_MSG);
            msg.obj = proc;
            mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg
                    ? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));
        }
    }

    if (anrMessage != null) {
        //處理 NAR
        mAm.mAnrHelper.appNotResponding(proc, anrMessage);
    }
}

無論是四大組件或者進程等只要發(fā)生ANR徘熔,最終都會調(diào)用 AMS.appNotResponding() 方法。

class AnrHelper {
    //保存 ANR 信息
    private final ArrayList<AnrRecord> mAnrRecords = new ArrayList<>();
    private final AtomicBoolean mRunning = new AtomicBoolean(false);

    void appNotResponding(ProcessRecord anrProcess, String annotation) {
        appNotResponding(anrProcess, null /* activityShortComponentName */, null /* aInfo */,
                null /* parentShortComponentName */, null /* parentProcess */,
                false /* aboveSystem */, annotation);
    }

    void appNotResponding(ProcessRecord anrProcess, String activityShortComponentName,
            ApplicationInfo aInfo, String parentShortComponentName,
            WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
        synchronized (mAnrRecords) {
            mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
                    parentShortComponentName, parentProcess, aboveSystem, annotation));
        }
        startAnrConsumerIfNeeded();
    }
    
    private void startAnrConsumerIfNeeded() {
        if (mRunning.compareAndSet(false, true)) {
            new AnrConsumerThread().start();
        }
    }

    //執(zhí)行 ANR 的線程
    private class AnrConsumerThread extends Thread {
        AnrConsumerThread() {
            super("AnrConsumer");
        }

        private AnrRecord next() {
            synchronized (mAnrRecords) {
                return mAnrRecords.isEmpty() ? null : mAnrRecords.remove(0);
            }
        }

        @Override
        public void run() {
            AnrRecord r;
            while ((r = next()) != null) {
                final long startTime = SystemClock.uptimeMillis();
                // If there are many ANR at the same time, the latency may be larger. If the latency
                // is too large, the stack trace might not be meaningful.
                final long reportLatency = startTime - r.mTimestamp;
                final boolean onlyDumpSelf = reportLatency > EXPIRED_REPORT_TIME_MS;
                r.appNotResponding(onlyDumpSelf);
                final long endTime = SystemClock.uptimeMillis();
                Slog.d(TAG, "Completed ANR of " + r.mApp.processName + " in "
                        + (endTime - startTime) + "ms, latency " + reportLatency
                        + (onlyDumpSelf ? "ms (expired, only dump ANR app)" : "ms"));
            }

            mRunning.set(false);
            synchronized (mAnrRecords) {
                // The race should be unlikely to happen. Just to make sure we don't miss.
                if (!mAnrRecords.isEmpty()) {
                    startAnrConsumerIfNeeded();
                }
            }
        }
    }

    //存儲 ANR 信息類
    private static class AnrRecord {
        final ProcessRecord mApp;
        final String mActivityShortComponentName;
        final String mParentShortComponentName;
        final String mAnnotation;
        final ApplicationInfo mAppInfo;
        final WindowProcessController mParentProcess;
        final boolean mAboveSystem;
        final long mTimestamp = SystemClock.uptimeMillis();

        AnrRecord(ProcessRecord anrProcess, String activityShortComponentName,
                ApplicationInfo aInfo, String parentShortComponentName,
                WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
            mApp = anrProcess;
            mActivityShortComponentName = activityShortComponentName;
            mParentShortComponentName = parentShortComponentName;
            mAnnotation = annotation;
            mAppInfo = aInfo;
            mParentProcess = parentProcess;
            mAboveSystem = aboveSystem;
        }

        void appNotResponding(boolean onlyDumpSelf) {
            mApp.appNotResponding(mActivityShortComponentName, mAppInfo,
                    mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,
                    onlyDumpSelf);
        }
    }
}
  • 進入目標進程的主線程創(chuàng)建 Service

進入 ActivityThread#ApplicationThread#scheduleCreateService 的方法淆党。

public final void scheduleCreateService(IBinder token,
        ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
    updateProcessState(processState, false);
    CreateServiceData s = new CreateServiceData();
    s.token = token;
    s.info = info;
    s.compatInfo = compatInfo;
    //發(fā)送 CREATE_SERVICE 消息
    sendMessage(H.CREATE_SERVICE, s);
}

調(diào)用 ActivityThread#H 類發(fā)送消息

class H extends Handler {
    public void handleMessage(Message msg) 
        switch (msg.what) {
            case CREATE_SERVICE:
                if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                            ("serviceCreate: " + String.valueOf(msg.obj)));
                }
                handleCreateService((CreateServiceData)msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
        }
}
private void handleCreateService(CreateServiceData data) {
    unscheduleGcIdler();

    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
        //創(chuàng)建 ContextImpl 對象
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        //創(chuàng)建 Application 對象
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        service = packageInfo.getAppFactory()
                .instantiateService(cl, data.info.name, data.intent);
        // Service resources must be initialized with the same loaders as the application
        // context.
        context.getResources().addLoaders(
                app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

        context.setOuterContext(service);
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManager.getService());
        //調(diào)用服務 onCreate()方法
        service.onCreate();
        mServices.put(data.token, service);
        try {
            //取消 AMS.MainHandler 的延時消息
            ActivityManager.getService().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    } catch (Exception e) {
        if (!mInstrumentation.onException(service, e)) {
            throw new RuntimeException(
                "Unable to create service " + data.info.name
                + ": " + e.toString(), e);
        }
    }
}

這個方法中會創(chuàng)建目標服務對象酷师,以及回調(diào)常用的 Service 的 onCreate() 方法,緊接著通過 serviceDoneExecuting() 回到 system_server 執(zhí)行取消 AMS.MainHandler 的延時消息

3染乌、回到 system_server 進程執(zhí)行取消 AMS.MainHandler 的延時消息山孔。

進入ActivityManagerService#serviceDoneExecuting 方法

final ActiveServices mServices;

public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
    synchronized(this) {
        if (!(token instanceof ServiceRecord)) {
            Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);
            throw new IllegalArgumentException("Invalid service token");
        }
        mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res);
    }
}

調(diào)用 ActiveServices#serviceDoneExecutingLocked 方法

void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
    boolean inDestroying = mDestroyingServices.contains(r);
    if (r != null) {
        if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) {
            r.callStart = true;
            switch (res) {
                case Service.START_STICKY_COMPATIBILITY:
                case Service.START_STICKY: {
                    // We are done with the associated start arguments.
                    r.findDeliveredStart(startId, false, true);
                    // Don't stop if killed.
                    r.stopIfKilled = false;
                    break;
                }
                case Service.START_NOT_STICKY: {
                    // We are done with the associated start arguments.
                    r.findDeliveredStart(startId, false, true);
                    if (r.getLastStartId() == startId) {
                        r.stopIfKilled = true;
                    }
                    break;
                }
                case Service.START_REDELIVER_INTENT: {
                    ServiceRecord.StartItem si = r.findDeliveredStart(startId, false, false);
                    if (si != null) {
                        si.deliveryCount = 0;
                        si.doneExecutingCount++;
                        // Don't stop if killed.
                        r.stopIfKilled = true;
                    }
                    break;
                }
                case Service.START_TASK_REMOVED_COMPLETE: {
                    r.findDeliveredStart(startId, true, true);
                    break;
                }
                default:
                    throw new IllegalArgumentException(
                            "Unknown service start result: " + res);
            }
            if (res == Service.START_STICKY_COMPATIBILITY) {
                r.callStart = false;
            }
        } else if (type == ActivityThread.SERVICE_DONE_EXECUTING_STOP) {
            if (!inDestroying) {
                if (r.app != null) {
                    Slog.w(TAG, "Service done with onDestroy, but not inDestroying: "
                            + r + ", app=" + r.app);
                }
            } else if (r.executeNesting != 1) {
                Slog.w(TAG, "Service done with onDestroy, but executeNesting="
                        + r.executeNesting + ": " + r);
                // Fake it to keep from ANR due to orphaned entry.
                r.executeNesting = 1;
            }
        }
        final long origId = Binder.clearCallingIdentity();
        serviceDoneExecutingLocked(r, inDestroying, inDestroying);
        Binder.restoreCallingIdentity(origId);
    } else {
        Slog.w(TAG, "Done executing unknown service from pid "
                + Binder.getCallingPid());
    }
}
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
        boolean finishing) {
    if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r
            + ": nesting=" + r.executeNesting
            + ", inDestroying=" + inDestroying + ", app=" + r.app);
    else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
            "<<< DONE EXECUTING " + r.shortInstanceName);
    r.executeNesting--;
    if (r.executeNesting <= 0) {
        if (r.app != null) {
            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
                    "Nesting at 0 of " + r.shortInstanceName);
            r.app.execServicesFg = false;
            r.app.executingServices.remove(r);
            if (r.app.executingServices.size() == 0) {
                if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
                        "No more executingServices of " + r.shortInstanceName);
                //移除當前服務所在進程中沒有正在執(zhí)行的 service
                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
            } else if (r.executeFg) {
                // Need to re-evaluate whether the app still needs to be in the foreground.
                for (int i=r.app.executingServices.size()-1; i>=0; i--) {
                    if (r.app.executingServices.valueAt(i).executeFg) {
                        r.app.execServicesFg = true;
                        break;
                    }
                }
            }
            if (inDestroying) {
                if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
                        "doneExecuting remove destroying " + r);
                mDestroyingServices.remove(r);
                r.bindings.clear();
            }
            mAm.updateOomAdjLocked(r.app, true, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
        }
        r.executeFg = false;
        if (r.tracker != null) {
            final int memFactor = mAm.mProcessStats.getMemFactorLocked();
            final long now = SystemClock.uptimeMillis();
            r.tracker.setExecuting(false, memFactor, now);
            r.tracker.setForeground(false, memFactor, now);
            if (finishing) {
                r.tracker.clearCurrentOwner(r, false);
                r.tracker = null;
            }
        }
        if (finishing) {
            if (r.app != null && !r.app.isPersistent()) {
                r.app.stopService(r);
                r.app.updateBoundClientUids();
                if (r.whitelistManager) {
                    updateWhitelistManagerLocked(r.app);
                }
            }
            r.setProcess(null);
        }
    }
}

此方法中 Service 邏輯處理完成則移除之前延時的消息SERVICE_TIMEOUT_MSG 。如果沒有執(zhí)行完畢不調(diào)用這個方法,則超時后會發(fā)出 SERVICE_TIMEOUT_MSG 來告知ANR 發(fā)生。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末歇僧,一起剝皮案震驚了整個濱河市润梯,隨后出現(xiàn)的幾起案子污朽,更是在濱河造成了極大的恐慌氧枣,老刑警劉巖凌简,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卷扮,死亡現(xiàn)場離奇詭異荡碾,居然都是意外死亡谨读,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門坛吁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劳殖,“玉大人,你說我怎么就攤上這事阶冈∶颇颍” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵女坑,是天一觀的道長填具。 經(jīng)常有香客問我,道長匆骗,這世上最難降的妖魔是什么劳景? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮碉就,結(jié)果婚禮上盟广,老公的妹妹穿的比我還像新娘。我一直安慰自己瓮钥,他們只是感情好筋量,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著碉熄,像睡著了一般桨武。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锈津,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天呀酸,我揣著相機與錄音,去河邊找鬼琼梆。 笑死性誉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的茎杂。 我是一名探鬼主播错览,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蛉顽!你這毒婦竟也來了蝗砾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤携冤,失蹤者是張志新(化名)和其女友劉穎悼粮,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體曾棕,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡扣猫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了翘地。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片申尤。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖衙耕,靈堂內(nèi)的尸體忽然破棺而出昧穿,到底是詐尸還是另有隱情,我是刑警寧澤橙喘,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布时鸵,位于F島的核電站,受9級特大地震影響厅瞎,放射性物質(zhì)發(fā)生泄漏饰潜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一和簸、第九天 我趴在偏房一處隱蔽的房頂上張望彭雾。 院中可真熱鬧,春花似錦锁保、人聲如沸薯酝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吴菠。三九已至,卻和暖如春霉赡,著一層夾襖步出監(jiān)牢的瞬間橄务,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工穴亏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蜂挪,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓嗓化,卻偏偏與公主長得像棠涮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子刺覆,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

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