Android學(xué)習(xí)筆記-四大組件-基礎(chǔ)知識2

BroadcastReceiver

Android應(yīng)用可以從Android系統(tǒng)和其他Android應(yīng)用發(fā)送或接收廣播消息辕羽,類似于 發(fā)布 - 訂閱 設(shè)計模式租冠。當(dāng)感興趣的事件發(fā)生時,發(fā)送這些廣播遏片。例如嘹害,Android系統(tǒng)在發(fā)生各種系統(tǒng)事件時發(fā)送廣播,例如系統(tǒng)啟動或設(shè)備開始充電時吮便。例如笔呀,應(yīng)用程序還可以發(fā)送自定義廣播,以通知其他應(yīng)用程序他們可能感興趣的內(nèi)容(例如髓需,已下載了一些新數(shù)據(jù))许师。

什么是廣播

由上述摘自官方文檔的描述我們可以知道,廣播可以用來在:APP內(nèi)部僚匆、不同APP之間枯跑、APP與Android系統(tǒng)之間傳遞消息。

廣播應(yīng)用場景

通過上述理解我們可以總結(jié)一下廣播的常用的應(yīng)用場景

  • 應(yīng)用內(nèi)外不同組件的通信
  • 多線程通信
  • 與Android系統(tǒng)在特定情況下的通信

廣播使用

廣播角色

使用廣播來進行消息傳遞白热,如果需要傳遞一個消息敛助,那么最少需要一個消息的發(fā)送者,一個消息的接收者屋确。廣播進行消息的傳遞也是使用了廣播發(fā)送廣播接收兩個角色

廣播接收

為了方便理解先說廣播接收纳击,即如何接收其他廣播發(fā)送來的消息续扔。廣播的接收使用BroadcastReceiver類,要實現(xiàn)一個廣播接收者需要兩個步驟
1焕数、繼承BroadcastReceiver并重寫onReceive()方法

public class MyBroadcastReceiver extends BroadcastReceiver {
   /**
    * 重寫該方法纱昧,當(dāng)接收到注冊的相應(yīng)廣播后會執(zhí)行該方法
    * @param context
    * @param intent
    */
   @Override
   public void onReceive(Context context, Intent intent) {
       Log.e("接收到一個廣播");
   }
}

廣播接收者示例代碼
2、注冊廣播接收者
廣播接收的注冊分為兩種方式堡赔。

  • 靜態(tài)注冊
    靜態(tài)注冊的廣播為常駐廣播识脆,即不會受到任何組件生命周期的影響。如果需要時刻監(jiān)聽某廣播則需要靜態(tài)注冊善已,如監(jiān)聽手機開機灼捂、鎖屏等操作。在App首次啟動時换团,系統(tǒng)會自動實例化繼承自BroadcastReceiver的類并注冊到系統(tǒng)中悉稠。
    優(yōu)點:應(yīng)用程序關(guān)閉后,程序依舊會被系統(tǒng)調(diào)用艘包。
    缺點:耗電占用內(nèi)存的猛。
    使用:在AndroidManifest.xml中通過<receive>標(biāo)簽聲明。
<receiver android:name="com.example.MyBroadcastReceiver">
    <intent-filter>
        <!-- 接收的廣播類型 -->
        <action android:name="MY_STATIC_BROADCAST" />
        <action android:name="Intent.ACTION_SCREEN_OFF" />
    </intent-filter>
</receiver>1

注意:Android8.0禁止了大多數(shù)靜態(tài)注冊
動態(tài)注冊
不常駐廣播想虎,跟隨組件生命周期變化卦尊,在特定時刻監(jiān)聽廣播可以使用動態(tài)注冊。
使用:在代碼中調(diào)用Context.registerReceiver()方法進行注冊

//動態(tài)注冊廣播
myBroadcastReceiver=new MyBroadcastReceiver(); //實例化廣播接收者和IntentFilter
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction(RECEIVER_ACTION);    //設(shè)置接收廣播類型
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);   //可以接收多個廣播類型
registerReceiver(mDynamicRegisterReceiver,intentFilter);    //注冊廣播

注意:動態(tài)廣播最好在onResume()中注冊onPause()中注銷舌厨,因為onPause()方法在app死亡之前一定會被執(zhí)行猫牡。其他則不一定。動態(tài)廣播注冊后必須注銷邓线,否則將導(dǎo)致內(nèi)存泄漏淌友。不允許重復(fù)注冊或者重復(fù)注銷。
動態(tài)注冊代碼示例

廣播發(fā)送

廣播發(fā)送根據(jù)所發(fā)送的消息類型以及發(fā)送方式分為了以下幾種廣播類型

  • 普通廣播
    開發(fā)者自定義的intent廣播骇陈,廣播發(fā)送后如果廣播接收中注冊的intentFilter的action與發(fā)送的intent相匹配震庭,則會接收此廣播并回調(diào)onReceive()。
    使用:
//發(fā)送動態(tài)廣播
Intent intent=new Intent();
intent.setAction(RECEIVER_ACTION);
sendBroadcast(intent);

注意:如果發(fā)送廣播需要某一權(quán)限接收廣播則也需要相應(yīng)權(quán)限你雌。

  • 系統(tǒng)廣播
    Android內(nèi)值得多個系統(tǒng)廣播器联,手機上的基本操作都會發(fā)出相應(yīng)的廣播。
    如:開關(guān)機婿崭,充電拨拓,電池狀態(tài),解鎖關(guān)閉屏幕等氓栈。
    注意:使用系統(tǒng)廣播只需要注冊廣播接收即可渣磷,廣播由系統(tǒng)在相應(yīng)的時刻自動發(fā)送。
  • 有序廣播
    發(fā)送出去的廣播被廣播接受者按照先后順序接收授瘦。
    1醋界、廣播接收順序規(guī)則:
    按照Priority屬性值從大到小排序竟宋。
    Priority屬性相同者,動態(tài)注冊廣播優(yōu)先形纺。
    2丘侠、有序廣播特點:
    接收廣播按順序接收。
    先接受廣播的廣播接收者可以對廣播進行截斷逐样,后續(xù)的廣播接收者將不會再收到此廣播蜗字。
    先接受廣播的廣播接收者可以對廣播進行修改,后續(xù)的廣播接收者收到的是被修改過的廣播脂新。
    3挪捕、如何發(fā)送有序廣播:
    sendBroadcast(intent)換成sendOrderdBroadcast(intent);
  • APP應(yīng)用內(nèi)廣播
    1戏羽、全局廣播導(dǎo)致的問題
    因為Android中的廣播可以跨App直接通信,所以可能會導(dǎo)致其他App不斷的發(fā)出針對性的廣播導(dǎo)致當(dāng)前App不斷接收廣播并處理楼吃,或者有其他App注冊與當(dāng)前App一致的intent-filter接收廣播始花,獲取廣播具體信息,導(dǎo)致出現(xiàn)安全性效率問題孩锡。使用App應(yīng)用內(nèi)廣播可以解決上述問題酷宵。
    2、應(yīng)用內(nèi)廣播的含義
    應(yīng)用內(nèi)廣播的廣播接收發(fā)送都屬于同一個app躬窜,與全局廣播相比應(yīng)用內(nèi)廣播安全性效率更高浇垦。
    3、應(yīng)用內(nèi)廣播的使用
    3.1荣挨、將全局廣播設(shè)置成局部廣播
    注冊廣播時將exported屬性設(shè)置為false(非本APP內(nèi)部發(fā)出的此廣播不被接收).
    廣播發(fā)送和接收時增加permission權(quán)限男韧,進行權(quán)限驗證。
    廣播發(fā)送時通過intent.setPackage(packageName)指定廣播接收器所在的包名默垄,則該廣播只會發(fā)送到App內(nèi)與之相匹配的廣播接收器中此虑。
    3.2、使用封裝好的LocalBrodcastManager
    注冊/取消廣播接收器和廣播發(fā)送器時將參數(shù)context變?yōu)?code>LocalBroadcastManager的單一實例口锭。
    使用LocalBroadcastManager方式發(fā)送的應(yīng)用內(nèi)廣播只能通過LocalBroadcastManager動態(tài)注冊朦前。
//注冊本地廣播
mDynamicRegisterReceiver=new DynamicRegisterReceiver();              
mLocalBroadcastManager=LocalBroadcastManager.getInstance(BroadcastReceiverActivity.this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(RECEIVER_ACTION);              
mLocalBroadcastManager.registerReceiver(mDynamicRegisterReceiver,intentFilter);
//發(fā)送本地廣播
Intent intent=new Intent();
intent.setAction(RECEIVER_ACTION);
mLocalBroadcastManager.sendBroadcast(intent);
  • 粘性廣播
    在Android5.0,API21中已失效鹃操。

廣播原理

廣播接收者通過Binder機制在AMS注冊韭寸,
廣播發(fā)送者通過Binder機制向AMS發(fā)送廣播,
AMS根據(jù)廣播發(fā)送者要求荆隘,在已注冊列表中恩伺,依據(jù)IntentFilter/Permission尋找合適的廣播接收者
AMS將廣播發(fā)送到合適的廣播接收者相應(yīng)的消息循環(huán)隊列中
廣播接受者通過消息隊列拿到廣播,并回調(diào)onReceive()方法椰拒。
廣播接收和注冊異步執(zhí)行莫其。

注意事項

  • 不同注冊方式的廣播接收器回調(diào)onReceive中的context返回值不同
    靜態(tài)注冊:
    ????????返回ReceiverRestrictedContext癞尚。
    全局廣播的動態(tài)注冊:
    ????????返回Activity Context
    應(yīng)用內(nèi)廣播LocalBroadcastManager的動態(tài)注冊:
    ????????返回Application Context乱陡。
    應(yīng)用內(nèi)廣播非LocalBroadcastManager的動態(tài)注冊:
    ????????返回Activity Context浇揩。
  • Andorid 8.0禁止使用大部分廣播的靜態(tài)注冊

上文代碼


ContentProvider

內(nèi)容提供程序管理對結(jié)構(gòu)化數(shù)據(jù)集的訪問。它們封裝數(shù)據(jù)憨颠,并提供用于定義數(shù)據(jù)安全性的機制胳徽。 內(nèi)容提供程序是連接一個進程中的數(shù)據(jù)與另一個進程中運行的代碼的標(biāo)準(zhǔn)界面。

什么是ContentProvider

ContentProvider內(nèi)容提供者爽彤,在Android四大組件的日常使用中出現(xiàn)次數(shù)相對較少养盗,但是重要性一點也沒有減少,由官方解釋我們可以知道它是用來統(tǒng)一管理數(shù)據(jù)進行數(shù)據(jù)共享并且可以進行跨進程通信适篙。

  • 使用場景
    如:我們1使用ContentResolverapp中得到手機通訊錄的信息或者通過相應(yīng)的uri訪問其他應(yīng)用的ContentProvider提供的信息等往核。

ContentProvider的使用

上面說道ContentProvider提供統(tǒng)一的數(shù)據(jù)管理和數(shù)據(jù)共享、進行跨進程通信并舉了相關(guān)例子嚷节,接下來詳細說明一下聂儒。

  • 統(tǒng)一數(shù)據(jù)管理、數(shù)據(jù)共享
    我們平常進行對數(shù)據(jù)操作的時候由于數(shù)據(jù)的不同類型有了許多不同的組織和使用方式硫痰,如對文件的存儲和數(shù)據(jù)庫的使用衩婚,便截然不同,而ContentProvider相當(dāng)于在這些不同的操作上又進行了一層包裝并以Uri的方式提供數(shù)據(jù)的訪問接口效斑,使得我們在對數(shù)據(jù)操作時非春,可以忽略底層的差異。統(tǒng)一使用ContentResolver調(diào)用上層接口進行數(shù)據(jù)操作
  • 跨進程通信
    我們知道一個APP就是一個進程缓屠,而不同進程之間通信是比較困難的奇昙,但是ContentProvider就提供了這種在不同APP間進行通信的功能,即跨進程通信敌完,也是ContentProvider被使用最多的方面敬矩。ContentProvider進行跨進程通信的底層原理使用了Binder

接下來說一下具體使用

URI蠢挡、UriMatcher弧岳、MIME

  • URI(Uniform Resource Identifier):統(tǒng)一資源標(biāo)識符
    可以用來唯一標(biāo)識ContentProvider和其中的數(shù)據(jù)
  • 格式
    一個uri由以下部分組成:
主題名 授權(quán)信息 表名 記錄
content com.example.provider User 1

如:content://com.example.provider/User/1
uri中可以使用通配符*#
*:任意長度的有效字符串
#:任意長度的數(shù)字字符串

  • UriMatcher
    UriMatcher簡單說來是一個對uri進行管理的類。它主要的方法有:
    1业踏、UriMatcher.addURI
    ContentProvider中注冊uri
//初始化
mUriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
//在ContentProvider中注冊uri
mUriMatcher.addURI(AUTHORITY,"user",user_code);

2禽炬、UriMatcher.match(uri)
根據(jù)uri返回匹配該uri的自定義代碼

  • MIME

用于指定某個擴展名的文件用某種應(yīng)用程序打開。
類型+子類型組成
1勤家、必須以vnd開頭
2凡辱、內(nèi)容uri以路徑結(jié)尾即表名結(jié)尾后接android.cursor.dir/淌山,如果以id結(jié)尾即數(shù)據(jù)庫表id結(jié)尾則后接android.cursor.item/
3钉寝、最后接上vnd.<authority>.<path>磺平,例:
content://com.chenlei.content_provider.user
對應(yīng)MIME類型為:vnd.android.cursor.dir/vnd.com.chenlei.content_provider.user
content://com.chenlei.content_provider.user/1
對應(yīng)MIME類型為:vnd.android.cursor.item/vnd.com.chenlei.content_provider.user

ContentProvider

ContentProvider即是內(nèi)容提供器渺绒,它可以用來統(tǒng)一管理和組織應(yīng)用數(shù)據(jù),向其他應(yīng)用程序提供接口 便于其他應(yīng)用程序?qū)?strong>本應(yīng)用允許的數(shù)據(jù)進行操作,以進行跨進程通訊。
簡單來說就是ContentProvider向其他APP提供近尚,自己APP內(nèi)數(shù)據(jù),的訪問接口
主要方法由以下幾個

  • onCreate
    初始化ContentProvider的使用使用场勤,如果對數(shù)據(jù)庫進行操作則通常完成數(shù)據(jù)庫的創(chuàng)建和升級操作
  • insert戈锻、delete、update和媳、query
    對數(shù)據(jù)進行操作的核心方法增刪改查格遭,方法中對應(yīng)方法名來對數(shù)據(jù)進行相應(yīng)的增刪改查操作。
  • getType
    用來得到數(shù)據(jù)類型留瞳,即返回當(dāng)前uri 所代表數(shù)據(jù)的MIME類型
    /**
     * 初始化ContentProvider的使用使用
     * 通常完成數(shù)據(jù)庫的創(chuàng)建和升級操作
     * @return true初始化成功拒迅,false初始化失敗
     */
    @Override
    public boolean onCreate() {
        mContext=getContext();
        mDBHelper=new DBHelper(getContext());
        sqLiteDatabase=mDBHelper.getWritableDatabase();
        //初始化數(shù)據(jù)庫表
        sqLiteDatabase.execSQL("delete from user");
        sqLiteDatabase.execSQL("insert into user values(1,'Carson');");
        sqLiteDatabase.execSQL("insert into user values(2,'Kobe');");
        sqLiteDatabase.execSQL("delete from job");
        sqLiteDatabase.execSQL("insert into job values(1,'Android');");
        sqLiteDatabase.execSQL("insert into job values(2,'iOS');");

        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        String tableName = getTableName(uri);
        sqLiteDatabase=mDBHelper.getReadableDatabase();
        Cursor cursor=null;

        cursor=sqLiteDatabase.query(tableName,strings,s,strings1,null,null,s1,null);

        return cursor;
    }

    /**
     * 根據(jù)傳入的內(nèi)容來返回相應(yīng)的MIME類型
     * @param uri
     * @return
     */
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        String mime = null;
        switch (mUriMatcher.match(uri)){
            case user_code:
                mime="vnd.android.cursor.dir/vnd.com.chenlei.content_provider.user";
                break;
            case job_code:
                mime="vnd.android.cursor.dir/vnd.com.chenlei.content_provider.job";
                break;
        }
        return mime;
    }

    /**
     * 插入數(shù)據(jù)
     * @param uri 數(shù)據(jù)的資源路徑
     * @param contentValues 要插入的數(shù)據(jù)內(nèi)容
     * @return 返回一個用于記錄新紀錄的uri
     */
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        String tableName=getTableName(uri);
        //插入數(shù)據(jù)
        sqLiteDatabase.insert(tableName,null,contentValues);

        mContext.getContentResolver().notifyChange(uri,null);

        return uri;
    }

示例代碼

AndoridManifest.xml中注冊

當(dāng)代碼完成后還需要在AndoridManifest.xml中注冊才能被訪問使用。

<!--聲明外界進程可訪問該Provider的權(quán)限(讀 & 寫)-->
<!--android:permission="com.chenlei.PROVIDER"-->
<!--權(quán)限可細分為讀 & 寫的權(quán)限-->
<!--外界需要聲明同樣的讀 & 寫的權(quán)限才可進行相應(yīng)操作她倘,否則會報錯-->
<!--android:readPermisson = "com.chenlei.Read"-->
<!--android:writePermisson = "com.chenlei.Write"-->
<!--設(shè)置此provider是否可以被其他進程使用-->
<!--android:exported="true"-->
<provider
    android:authorities="com.chenlei.content_provider" 
    android:name="com.example.androidprimarycodedemo.four_components.about_content_provider.CreateLocalContentProvider"
    android:permission="com.chenlei.PROVIDER"
    android:exported="true"
/>

ContentResolver

由于如果應(yīng)用需要對多個ContentProvider進行操作璧微,需要了解各個不同ContentProvider的實現(xiàn)等再進行操作。所以API中提供了ContentResolver類統(tǒng)一管理對ContentProvider的操作帝牡。
簡單來說就是通過ContentResolver往毡,來對其他APP內(nèi)的數(shù)據(jù)蒙揣,進行操作
ContentResolver使用示例靶溜。

    //user表的資源路徑
    private Uri uriUser=Uri.parse("content://com.chenlei.content_provider/user");
    /**
     * ContentResolver統(tǒng)一管理ContentProvider間的操作
     * 由于如果需要使用多個ContentProvider進行操作,需要了解各個不同ContentProvider的實現(xiàn)等再進行操作
     * 使用ContentResolver同一管理方便操作和使用懒震。
     */
    private ContentResolver contentResolver=null;
    /**
     * 向ContentProvider中插入數(shù)據(jù)
     */
    private void insertData(){
        contentResolver=getContentResolver();
        //插入表中的數(shù)據(jù)
        ContentValues contentValues=new ContentValues();
        contentValues.put("_id", 3);
        contentValues.put("name", "Iverson");

        contentResolver.insert(uriUser,contentValues);
        Logger.e("插入完成");
    }

    /**
     * 從ContentProvider中查詢數(shù)據(jù)
     */
    private void queryData(){
        contentResolver=getContentResolver();
        Cursor cursor=contentResolver.query(uriUser,new String[]{"_id","name"}, null, null, null);
        while (cursor.moveToNext()){
            Logger.e(cursor.getInt(0) +","+ cursor.getString(1));
        }
        cursor.close();
    }

示例代碼

  • AndoridManifest.xml中注冊權(quán)限
    <!--應(yīng)用跨進程通訊的權(quán)限-->
    <uses-permission android:name="com.chenlei.PROVIDER"/>

上文代碼

ContentProvider部分知識點參考自https://blog.csdn.net/carson_ho/article/details/76101093

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末罩息,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子个扰,更是在濱河造成了極大的恐慌瓷炮,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件递宅,死亡現(xiàn)場離奇詭異娘香,居然都是意外死亡,警方通過查閱死者的電腦和手機办龄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門烘绽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人俐填,你說我怎么就攤上這事安接。” “怎么了英融?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵盏檐,是天一觀的道長歇式。 經(jīng)常有香客問我,道長胡野,這世上最難降的妖魔是什么材失? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮给涕,結(jié)果婚禮上豺憔,老公的妹妹穿的比我還像新娘。我一直安慰自己够庙,他們只是感情好恭应,可當(dāng)我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耘眨,像睡著了一般昼榛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剔难,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天胆屿,我揣著相機與錄音,去河邊找鬼偶宫。 笑死非迹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的纯趋。 我是一名探鬼主播憎兽,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吵冒!你這毒婦竟也來了纯命?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤痹栖,失蹤者是張志新(化名)和其女友劉穎亿汞,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體揪阿,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡疗我,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了南捂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吴裤。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖黑毅,靈堂內(nèi)的尸體忽然破棺而出嚼摩,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布枕面,位于F島的核電站愿卒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏潮秘。R本人自食惡果不足惜琼开,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枕荞。 院中可真熱鬧柜候,春花似錦、人聲如沸躏精。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矗烛。三九已至辅柴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瞭吃,已是汗流浹背碌嘀。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留歪架,地道東北人股冗。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像和蚪,于是被迫代替她去往敵國和親止状。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,969評論 2 355