SystemUI-Plugin源碼分析

0.前情提要

SystemUIApplication.java

1.SystemUIApplication的startServicesIfNeeded函數(shù)

public void startServicesIfNeeded() {
    String[] names =getResources().getStringArray(R.array.config_systemUIServiceComponents);
    startServicesIfNeeded(names);
}

2.在startServicesIfNeeded中先是啟動(dòng)了一堆組件,然后添加了Plugin的監(jiān)聽

private void startServicesIfNeeded(String[] services) {
     //啟動(dòng)了一堆組件
     
     //獲取PluginManagerImpl對(duì)象略贮,并添加插件監(jiān)聽
     Dependency.get(PluginManager.class).addPluginListener(
             new PluginListener<OverlayPlugin>() {
                 private ArraySet<OverlayPlugin> mOverlays;

                 @Override
                 public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
                     //onPluginConnected連接成功以后溶其,拿到對(duì)象
                     StatusBar statusBar = getComponent(StatusBar.class);
                     if (statusBar != null) {
                         //調(diào)用plugin的setup事件
                         plugin.setup(statusBar.getStatusBarWindow(),
                                 statusBar.getNavigationBarView());
                     }
                     // Lazy init.
                     if (mOverlays == null) mOverlays = new ArraySet<>();
                     if (plugin.holdStatusBarOpen()) {
                         mOverlays.add(plugin);
                         Dependency.get(StatusBarWindowManager.class).setStateListener(b ->
                                 mOverlays.forEach(o -> o.setCollapseDesired(b)));
                         Dependency.get(StatusBarWindowManager.class).setForcePluginOpen(
                                 mOverlays.size() != 0);

                     }
                 }

                 @Override
                 public void onPluginDisconnected(OverlayPlugin plugin) {
                     mOverlays.remove(plugin);
                     Dependency.get(StatusBarWindowManager.class).setForcePluginOpen(
                             mOverlays.size() != 0);
                 }
             }, OverlayPlugin.class, true /* Allow multiple plugins */);

     mServicesStarted = true;
 }

PluginManagerIml.java

3.這里通過Dependency拿PluginManager.class對(duì)應(yīng)的類就是PluginManagerIml.java置济,可以看到它調(diào)用的是重載方法2,最終會(huì)調(diào)用的是重載方法4啃洋,其實(shí)對(duì)應(yīng)的action=PluginManager.getAction(PluginManager.class),

public class PluginManagerImpl extends BroadcastReceiver implements PluginManager {

   public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
           boolean allowMultiple) {
       addPluginListener(PluginManager.getAction(cls), listener, cls, allowMultiple);
   }

   public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
           Class cls, boolean allowMultiple) {
       if (!isDebuggable) {
           // Never ever ever allow these on production builds, they are only for prototyping.
           return;
       }
       mPluginPrefs.addAction(action);
       PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
               allowMultiple, mLooper, cls, this);
       p.loadAll();
       mPluginMap.put(listener, p);
       startListening();
   }
}

核心流程

簡化一下也就是相當(dāng)于執(zhí)行以下這段程序坞古,關(guān)鍵的步驟也就是以下的流程

    public <T extends Plugin> void addPluginListener(PluginListener<T> listener) {
        if (!isDebuggable) {
            // Never ever ever allow these on production builds, they are only for prototyping.
            return;
        }
        //getAction,拿到的OverlayPlugin的關(guān)于ProvidesInterface注解里的action值
        //action值是 OverlayPlugin.ACTION="com.android.systemui.action.PLUGIN_OVERLAY"
        String action=PluginManager.getAction(OverlayPlugin.class);
       
        //將拿到的action值添加到SharedPreferences中
        mPluginPrefs.addAction(action);
        
        //第一步,通過PluginInstanceManagerFactory工廠創(chuàng)建一個(gè)PluginInstanceManager的實(shí)例
        PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, 
     listener,true, mLooper, OverlayPlugin.class, this);

        //第二步辞居,加載Plugin實(shí)例
        p.loadAll();
        
        //把Plugin實(shí)例添加到map映射中
        mPluginMap.put(listener, p);
        
        //第三步楷怒,監(jiān)聽Plugin實(shí)例
        startListening();
    }

PluginManager.java

public interface PluginManager {
    static <P> String getAction(Class<P> cls) {
        ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
        if (info == null) {
            throw new RuntimeException(cls + " doesn't provide an interface");
        }
        if (TextUtils.isEmpty(info.action())) {
            throw new RuntimeException(cls + " doesn't provide an action");
        }
        return info.action();
    }
}

OverlayPlugin.java

@ProvidesInterface(action = OverlayPlugin.ACTION, version = OverlayPlugin.VERSION)
public interface OverlayPlugin extends Plugin {

    String ACTION = "com.android.systemui.action.PLUGIN_OVERLAY";
    int VERSION = 2;

    void setup(View statusBar, View navBar);

    default boolean holdStatusBarOpen() {
        return false;
    }

    /**
     * Only called if the plugin has returned true to holdStatusBarOpen().
     */
    default void setCollapseDesired(boolean collapseDesired) {
    }
}

注解 ProvidesInterface.java

@Retention(RetentionPolicy.RUNTIME)
public @interface ProvidesInterface {
    int version();

    String action() default "";

}

1.第一步,創(chuàng)建PluginInstanceManager 對(duì)象

PluginInstanceManagerFactory.java

接下來我們要看下PluginInstanceManagerFactory里面是創(chuàng)建的PluginInstanceManager對(duì)象的方式也比較簡單瓦灶,直接就是new一個(gè)對(duì)象鸠删,其實(shí)的參數(shù)值我們列一下
context:Application對(duì)象(來自于SystemUIApplication)
action:"com.android.systemui.action.PLUGIN_OVERLAY"
listener:PluginListener對(duì)象(SystemUIApplication中的new PluginListener對(duì)象)
allowMultiple:true
looper:new HandlerThread("SysUiBg",Process.THREAD_PRIORITY_BACKGROUND).getLooper();
(來自于SystemUIApplication)
versionInfo:OverlayPlugin.cls,用于保存OverlayPlugin的版本信息
manager:PluginManagerImpl .this

public static class PluginInstanceManagerFactory {
 public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
       String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
              Class<?> cls, PluginManagerImpl manager) {
            return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
                 new VersionInfo().addClass(cls), manager);
        }
    }

PluginInstanceManager.java

public class PluginInstanceManager<T extends Plugin> {
    PluginInstanceManager(Context context, String action, PluginListener<T> listener,
            boolean allowMultiple, Looper looper, VersionInfo version, PluginManagerImpl manager) {
        this(context, context.getPackageManager(), action, listener, allowMultiple, looper,
         version,manager, Build.IS_DEBUGGABLE);
    }

    @VisibleForTesting
    PluginInstanceManager(Context context, PackageManager pm, String action,
            PluginListener<T> listener, boolean allowMultiple, Looper looper, 
            VersionInfo version,PluginManagerImpl manager, boolean debuggable) {
        mMainHandler = new MainHandler(Looper.getMainLooper());
        mPluginHandler = new PluginHandler(looper);
        mManager = manager;
        mContext = context;
        mPm = pm;
        mAction = action;
        mListener = listener;//listener在此就已經(jīng)注冊(cè)進(jìn)來了
        mAllowMultiple = allowMultiple;
        mVersion = version;
        isDebuggable = debuggable;
    }
}

綜上所述,SystemUI一起來的時(shí)候倚搬,就通過PluginManagerIml將其listener注冊(cè)到了PluginInstanceManager中,

2.第二步乾蛤,通過loadAll生成PluginInfo對(duì)象

  private class PluginHandler extends Handler {
        private final ArrayList<PluginInfo<T>> mPlugins = new ArrayList<>();
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case QUERY_ALL:
                    //先將plugins中所有Pluginc對(duì)象onDestroy每界,并clear掉plugins
                    for (int i = mPlugins.size() - 1; i >= 0; i--) {
                        PluginInfo<T> plugin = mPlugins.get(i);
                        mListener.onPluginDisconnected(plugin.mPlugin);
                        if (!(plugin.mPlugin instanceof PluginFragment)) {
                            // Only call onDestroy for plugins that aren't fragments, as fragments
                            // will get the onDestroy as part of the fragment lifecycle.
                            plugin.mPlugin.onDestroy();
                        }
                    }
                    mPlugins.clear();
                    handleQueryPlugins(null);
                    break;
            }
        }
        
         private void handleQueryPlugins(String pkgName) {
            //這實(shí)際上不是一項(xiàng)服務(wù),也不應(yīng)該啟動(dòng)家卖,但它是一種方便的基于PM的插件管理方式眨层。
            Intent intent = new Intent(mAction);
            if (pkgName != null) {
                intent.setPackage(pkgName);
            }
            //通過"com.android.systemui.action.PLUGIN_OVERLAY"查詢到對(duì)應(yīng)的ResolveInfo列表
            List<ResolveInfo> result =
                    mPm.queryIntentServices(intent, 0);
            if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins");
            if (result.size() > 1 && !mAllowMultiple) {
                //如果列表大于1的話,不處理
                Log.w(TAG, "Multiple plugins found for " + mAction);
                return;
            }
            for (ResolveInfo info : result) {
                //拿到PLUGIN_OVERLAY對(duì)應(yīng)的ComponentName 
                ComponentName name = new ComponentName(info.serviceInfo.packageName,
                        info.serviceInfo.name);
                //生成PluginInfo對(duì)象
                PluginInfo<T> t = handleLoadPlugin(name);
                if (t == null) continue;
                //通知listener,pluginInfo對(duì)象已經(jīng)成功連接了
                mMainHandler.obtainMessage(mMainHandler.PLUGIN_CONNECTED, t).sendToTarget();
                mPlugins.add(t);
            }
        }
        
         protected PluginInfo<T> handleLoadPlugin(ComponentName component) {
            // This was already checked, but do it again here to make extra extra sure, we don't
            // use these on production builds.
            if (!isDebuggable) {
                // Never ever ever allow these on production builds, they are only for prototyping.
                Log.d(TAG, "Somehow hit second debuggable check");
                return null;
            }
            //獲取包名和類名
            String pkg = component.getPackageName();
            String cls = component.getClassName();
            try {
                ApplicationInfo info = mPm.getApplicationInfo(pkg, 0);
                // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on
                if (mPm.checkPermission(PLUGIN_PERMISSION, pkg)
                        != PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "Plugin doesn't have permission: " + pkg);
                    return null;
                }
                //創(chuàng)建PluginManagerImpl的ClassLoader上荡,這樣我們就可以使用我們自己的代碼作為父代碼趴樱。
                ClassLoader classLoader = mManager.getClassLoader(info.sourceDir, info.packageName);
                //構(gòu)建PluginContextWrapper
                Context pluginContext = new PluginContextWrapper(
                        mContext.createApplicationContext(info, 0), classLoader);
                //通過反射生成對(duì)應(yīng)的plugin對(duì)象
                Class<?> pluginClass = Class.forName(cls, true, classLoader);
                // TODO: Only create the plugin before version check if we need it for
                // legacy version check.
                T plugin = (T) pluginClass.newInstance();
                try {
                    VersionInfo version = checkVersion(pluginClass, plugin, mVersion);
                    if (DEBUG) Log.d(TAG, "createPlugin");
                    //最終創(chuàng)建PluginInfo對(duì)象
                    return new PluginInfo(pkg, cls, plugin, pluginContext, version);
                } catch (InvalidVersionException e) {
                    final int icon = mContext.getResources().getIdentifier("tuner", "drawable",
                            mContext.getPackageName());
                    final int color = Resources.getSystem().getIdentifier(
                            "system_notification_accent_color", "color", "android");
                    final Notification.Builder nb = new Notification.Builder(mContext,
                            PluginManager.NOTIFICATION_CHANNEL_ID)
                                    .setStyle(new Notification.BigTextStyle())
                                    .setSmallIcon(icon)
                                    .setWhen(0)
                                    .setShowWhen(false)
                                    .setVisibility(Notification.VISIBILITY_PUBLIC)
                                    .setColor(mContext.getColor(color));
                    String label = cls;
                    try {
                        label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString();
                    } catch (NameNotFoundException e2) {
                    }
                    if (!e.isTooNew()) {
                        // Localization not required as this will never ever appear in a user build.
                        nb.setContentTitle("Plugin \"" + label + "\" is too old")
                                .setContentText("Contact plugin developer to get an updated"
                                        + " version.\n" + e.getMessage());
                    } else {
                        // Localization not required as this will never ever appear in a user build.
                        nb.setContentTitle("Plugin \"" + label + "\" is too new")
                                .setContentText("Check to see if an OTA is available.\n"
                                        + e.getMessage());
                    }
                    Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData(
                            Uri.parse("package://" + component.flattenToString()));
                    PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0);
                    nb.addAction(new Action.Builder(null, "Disable plugin", pi).build());
                    mContext.getSystemService(NotificationManager.class)
                            .notifyAsUser(cls, SystemMessage.NOTE_PLUGIN, nb.build(),
                                    UserHandle.ALL);
                    // TODO: Warn user.
                    Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion()
                            + ", expected " + mVersion);
                    return null;
                }
            } catch (Throwable e) {
                Log.w(TAG, "Couldn't load plugin: " + pkg, e);
                return null;
            }
        }
    }

3.第三步,回調(diào)onPluginConnected

    private class MainHandler extends Handler {
        private static final int PLUGIN_CONNECTED = 1;
        private static final int PLUGIN_DISCONNECTED = 2;

        public MainHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case PLUGIN_CONNECTED:
                    if (DEBUG) Log.d(TAG, "onPluginConnected");
                    PluginPrefs.setHasPlugins(mContext);
                    PluginInfo<T> info = (PluginInfo<T>) msg.obj;
                    mManager.handleWtfs();
                    if (!(msg.obj instanceof PluginFragment)) {
                        // Only call onDestroy for plugins that aren't fragments, as fragments
                        // will get the onCreate as part of the fragment lifecycle.
                        info.mPlugin.onCreate(mContext, info.mPluginContext);
                    }
                    //回調(diào)onPluginConnected結(jié)果
                    mListener.onPluginConnected(info.mPlugin, info.mPluginContext);
                    break;
                case PLUGIN_DISCONNECTED:
                    if (DEBUG) Log.d(TAG, "onPluginDisconnected");
                    mListener.onPluginDisconnected((T) msg.obj);
                    if (!(msg.obj instanceof PluginFragment)) {
                        // Only call onDestroy for plugins that aren't fragments, as fragments
                        // will get the onDestroy as part of the fragment lifecycle.
                        ((T) msg.obj).onDestroy();
                    }
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }

通過SystemUIApplication.java里面的函數(shù)酪捡,我們知道onPluginConnected成功以后叁征,會(huì)先拿到StatusBar對(duì)象,并通過plugin的setup函數(shù)將StatusBar對(duì)象傳遞出去逛薇。

4.第四步捺疼,startListener(PluginManagerImpl.java)

    private void startListening() {
        if (mListening) return;
        mListening = true;
        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(PLUGIN_CHANGED);
        filter.addAction(DISABLE_PLUGIN);
        filter.addDataScheme("package");
        mContext.registerReceiver(this, filter);
        filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
        mContext.registerReceiver(this, filter);
    }
    
    
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
            for (PluginInstanceManager manager : mPluginMap.values()) {
                manager.loadAll();
            }
        } else if (DISABLE_PLUGIN.equals(intent.getAction())) {
            //組件不可用
            Uri uri = intent.getData();
            ComponentName component = ComponentName.unflattenFromString(
                    uri.toString().substring(10));
            mContext.getPackageManager().setComponentEnabledSetting(component,
                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                    PackageManager.DONT_KILL_APP);
            mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(),
                    SystemMessage.NOTE_PLUGIN);
        } else {
            Uri data = intent.getData();
            String pkg = data.getEncodedSchemeSpecificPart();
            if (mOneShotPackages.contains(pkg)) {
                int icon = mContext.getResources().getIdentifier("tuner", "drawable",
                        mContext.getPackageName());
                int color = Resources.getSystem().getIdentifier(
                        "system_notification_accent_color", "color", "android");
                String label = pkg;
                try {
                    PackageManager pm = mContext.getPackageManager();
                    label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString();
                } catch (NameNotFoundException e) {
                }
                // Localization not required as this will never ever appear in a user build.
                final Notification.Builder nb =
                        new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                                .setSmallIcon(icon)
                                .setWhen(0)
                                .setShowWhen(false)
                                .setPriority(Notification.PRIORITY_MAX)
                                .setVisibility(Notification.VISIBILITY_PUBLIC)
                                .setColor(mContext.getColor(color))
                                .setContentTitle("Plugin \"" + label + "\" has updated")
                                .setContentText("Restart SysUI for changes to take effect.");
                Intent i = new Intent("com.android.systemui.action.RESTART").setData(
                            Uri.parse("package://" + pkg));
                PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0);
                nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build());
                mContext.getSystemService(NotificationManager.class).notifyAsUser(pkg,
                        SystemMessage.NOTE_PLUGIN, nb.build(), UserHandle.ALL);
            }
            if (clearClassLoader(pkg)) {
                Toast.makeText(mContext, "Reloading " + pkg, Toast.LENGTH_LONG).show();
            }
            if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
                for (PluginInstanceManager manager : mPluginMap.values()) {
                    manager.onPackageChange(pkg);
                }
            } else {
                for (PluginInstanceManager manager : mPluginMap.values()) {
                    manager.onPackageRemoved(pkg);
                }
            }
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市永罚,隨后出現(xiàn)的幾起案子啤呼,更是在濱河造成了極大的恐慌,老刑警劉巖呢袱,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件官扣,死亡現(xiàn)場離奇詭異,居然都是意外死亡羞福,警方通過查閱死者的電腦和手機(jī)惕蹄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來治专,“玉大人焊唬,你說我怎么就攤上這事】纯浚” “怎么了赶促?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長挟炬。 經(jīng)常有香客問我鸥滨,道長嗦哆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任婿滓,我火速辦了婚禮老速,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凸主。我一直安慰自己橘券,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布卿吐。 她就那樣靜靜地躺著旁舰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嗡官。 梳的紋絲不亂的頭發(fā)上箭窜,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音衍腥,去河邊找鬼磺樱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛婆咸,可吹牛的內(nèi)容都是我干的竹捉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尚骄,長吁一口氣:“原來是場噩夢啊……” “哼活孩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乖仇,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤憾儒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后乃沙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體起趾,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年警儒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了训裆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜀铲,死狀恐怖边琉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情记劝,我是刑警寧澤变姨,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站厌丑,受9級(jí)特大地震影響定欧,放射性物質(zhì)發(fā)生泄漏渔呵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一砍鸠、第九天 我趴在偏房一處隱蔽的房頂上張望扩氢。 院中可真熱鬧,春花似錦爷辱、人聲如沸录豺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽双饥。三九已至,卻和暖如春示启,著一層夾襖步出監(jiān)牢的瞬間兢哭,已是汗流浹背领舰。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工夫嗓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冲秽。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓舍咖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锉桑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子排霉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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