Android 你需要掌握的知識(二)

目錄

Service.png

Broadcast Receiver.png

一.Service詳解

一.Service基礎

1.什么是Service

Service是Android中實現程序后臺運行的解決方案,是一個可以在后臺執行長時間運行操作而沒有用戶界面的應用組件
Service可由其他應用(如:Activity,Broadcast)組件啟動,服務在啟動后會一直在后臺運行。即使啟動它的Activity或Broadcast已經被銷毀也不會受影響,另外也可以把Service綁定到Activity,可以讓Service和Activity之間進行數據交互,甚至Service和Activity有可能在不同的進程中也可以用進程間通信來進行數據傳輸。
注意:Service(服務)和Broadcast(廣播)有一個共同點。它們都是運行在主線程當中,不能耗時操作。

2.Service和Thread的區別

1.Thread是程序執行的最小單元,線程,可以用它來執行一些異步操作
Service是安卓的一種機制,當它運行的時候,如果是本地的Service,那么它對應的Service是運行在主線程上的。

2.Thread 運行是相對獨立的,而Service運行是依賴于創建服務時所在的應用程序進程。

服務和后臺是兩個概念,android 的后臺指的是它的運行完全不依賴于UI線程,即使Activity被銷毀了,或者程序被關閉了,這個服務進程仍然存在,會在后臺進行一些如:計算,數據統計等。Service仍然可以繼續運作,如果要Service做耗時操作,依舊要在Service中創建子線程。在里面做耗時邏輯。

Activity中創建子線程和Service中創建子線程的不同就是,Activity中很難對子線程控制,Activity在被銷毀后就無法獲取之前的子線程實例。
如果在Service中就可以很放心的銷毀Activity,不用擔心無法控制的情況。

二.開啟Service的兩種方式和區別

1.startService

1.定義一個類繼承Service

public class MyService extends Service 

2.在AndroidManifest.xml中配置該service

<application...
        <service android:name=".MyService"></service>
    </application>

3.使用Context的startService(startIntent)方法啟動該Service

Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);

4.不在使用時調用stopService(stopIntent)方法停止該服務。

Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);

MyService 繼承 Service
服務啟動后,會先調用MyService中的onCreate()的方法。

  /**
     * 首次創建服務時,系統會調用此方法來執行一次性設置程序(在調用onStartCommand()或onBind()之前)
     * 如果服務已在運行,則不會調用此方法,該方法只被調用一次
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate() executed");
    }

在用startService啟動這個服務時,就會調用onStartCommand方法,一旦調用這個方法,這個服務就會被正式開啟。可以在這個方法中做自己的操作。

  /**
   * 每次通過startService方法啟動Service時都會被回調
   */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand() executed");
        return super.onStartCommand(intent, flags, startId);
    }

在onStartCommand方法中有一個返回值是int,源碼中是這樣:

public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
        onStart(intent, startId);
        return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
    }

如果onStartCommand方法返回的是START_STICKY,意味著當整個Service因為內存不足而被系統殺掉的之后,一段時間內存再度空閑的時候,系統會嘗試重新創建這個Service,創建成功后,又會調用onStartCommand方法,但其中的intent是空,這個值適合循環播放,天氣預報這種服務。

當服務不在被使用的時候調用onDestroy()方法,Service可以在這個方法里面進行一些資源的清理(線程,注冊的監聽器,Content Provider),會進行回收。

/**
     * 服務銷毀時的回調
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy() executed");
    }
2.bindService

bindService意味著在綁定服務的時候,服務和Activity已經處于綁定狀態,綁定服務提供了一個客戶端和服務端的接口,相當于Activity和Service交互的接口,它允許Activity和Service進行數據交互,發出請求獲取結果等等。甚至如果Activity和Service在不同進程當中的時候,可以進行進程間通信來傳輸數據,這僅僅是在Activity和Service綁定之后才能運行,同時多個Activity可以綁定一個Service,但是綁定取消后,這個服務就會自動被銷毀,并不像startService一樣必須要調用stopService才能被銷毀

bindService啟動的方式:
1.創建Service,繼承自Service并在類中,創建一個實現IBinder接口的實例對象并提供公共方法給Activity調用
2.從 onBind()回調方法返回此Binder實例。
3.Activity,從onServiceConnected()回調方法接收Binder,并使用提供的方法調用綁定服務。

和startService不同的是,它在MyService中實現了一個Binder的內部類

  /**
     * 創建Binder對象,返回給Activity使用,提供數據交換的接口
     */
    class DownloadBinder extends Binder {
        //聲明一個方法 getService (提供給Activity調用)
        MyService getService() {
            //返回當前對象,這樣就可以在Activice 中調用Service的公共方法
            return MyService.this;
        }

    }

值得注意的是和startService不同的是,在onBind方法中它返回了一個Binder,這個downloadBinder是我們自己創建的

private DownloadBinder downloadBinder = new DownloadBinder();
    @Override
    public IBinder onBind(Intent intent) {
        return downloadBinder;
    }

在Activity中我們要創建一個ServiceConnection的類,它代表的是Activity和Service的連接。

 private MyService.DownloadBinder downloadBinder;
    /**
     * ServiceConnection代表與服務的鏈接,它只有兩個方法。
     * onServiceConnected和onServiceDisconnected
     * 前者是在操作者在連接一個服務成功時被調用,而后者是在服務崩潰或者殺死導致的連接中斷時被調用
     */
    private ServiceConnection connection = new ServiceConnection() {
        /**
         *  與服務端交互的接口方法,綁定服務的時候被回調,在這個方法獲取綁定Service傳遞過來的IBinder對象
         *  通過這個IBinder對象,實現Activity和Service交互
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.getService().getCount();
        }
        /**
         * 取消綁定的時候被回調,但正常情況下是不被調用的,它的調用時機是當Service服務被意外摧毀時
         * 如內存資源不足時這個方法被自動調用
         */

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

通過bindService(Intent service, ServiceConnection conn, int flags)方法,來啟動Service

Intent startIntent = new Intent(this, MyService.class);
bindService(startIntent,connection,BIND_AUTO_CREATE);

通過unbindService(ServiceConnection conn) 方法來停止Service

unbindService(connection);

二.Broadcast Receiver詳解

一.廣播

1.廣播的定義

廣播是一種廣泛運用的在應用程序之間傳輸信息的機制,廣播使用了設計模式中的觀察者模式,當被觀察者數據變換時會通知觀察者做相應的數據處理,android中要發送的廣播內容是一個Intent。在這個Intent里可以攜帶一些我們要傳送的數據。
要記住這兩點
1.廣播實現了不同程序之間的數據傳輸與共享,只要和發送廣播的Action相同的接收者都能接收這個廣播,就是發送一個廣播可以被很多廣播Receive所接受,典型的就是系統應用(短信,電話等通知)。只要我們實現了他們Action的Broadcast Receiver,就能接收他們的數據。
2.廣播接收者還有一個通知的作用,比如在服務中可以發送程序給Activity,讓它更新UI,我們知道Service是沒有界面的,它是在后臺運行的一個組件,所以這時候我們就可以通過廣播接收者專門接收從Service發過來的數據和通知,而不要讓Service直接和Activity進行交互了。

2.廣播的場景

1.同意app內在具有多個進程的不同組件之間的消息通信(在一個app內會開啟多個進程,比如app中會有定位進程,主進程,兩個進程之間的通信就可以用到廣播)
2.不同app之間的組件之間的消息通信(兩個app間通過廣播來傳遞對方的消息,比如一個公司的多個app進行推廣,就會用到通知)

3.廣播的種類

1)Normal Broadcast(普通廣播) : 會調用Content.sendBroadcast這個方法進行處理
2)System Broadcast(系統廣播,有序廣播):會調用Content.sendOrderedBroadcast這個方法進行處理
3)Local Broadcast(本地廣播):只在APP內傳播

二.實現廣播-receiver

1.靜態注冊:注冊完成就一直運行。

直接把廣播接收者寫在AndroidManifest.xml文件中。特點,它所依賴的Activity銷毀了,但是仍然能夠接收廣播,甚至把進程殺死,仍然能接收廣播,但是和動態注冊相比,還是不是太靈活。

2.動態注冊:跟隨activity生命周期。

在代碼中通過調用Context的registerReceiver()方法進行動態注冊BroadcastReceiver,要記住動態廣播最好在Activity的onResume()注冊、onPause()注銷,否則會導致內存泄露。同時動態注冊還有這個特點,它的生命周期是跟隨activity生命周期,所以當activity生命周期銷毀時,這個動態注冊的廣播接收者也就失效了。

三.內部實現機制

1.自定義廣播接收者BroadcastReceiver,并復寫onRecvice()方法;

2.通過Binder機制向AMS(Activity Manager Service)進行注冊;
AMS(Activity Manager Service):是貫穿android系統組件的一個核心服務,負責啟動四大組件的啟動,切換,調度以及應用程序的調度和管理工作。所以AMS是負責BroadcastReceiver的創建。
Biner機制:是android進程間通信的一個核心,它的整體設計架構是客戶端/服務端結構,C/S結構,客戶端進程可以獲取到服務端進程的代理,并通過這個代理接口,通過方法來讀取數據,來完成進程間的數據通信。

3.廣播發送者通過Binder機制向ASM發送廣播

4.ASM查找符合相應條件(IntentFilter / Permission等)的BroadcastReceiver,將廣播發送到BroadcastReceiver(一般情況下是Activity)相應的消息循環隊列中

5.消息循環執行拿到此廣播,回調BroadcastReceiver中的onReceive()方法。

四.LocalBroadcastManager詳解(參考文章

1.使用它發送廣播將只在自身App內傳播,因此不必擔心泄露隱私數據
2.其他App無法對你的App發送該廣播,因為你的App根本就不可能接受到非自身應用發送的該廣播,因此你不必擔心有安全漏洞可以利用。
3.比系統的全局廣播更加高效。

還要強調,安全漏洞隱私數據

1.如果不是本地廣播,代碼被反編譯后,知道了你的Action,那么第三方應用就能發送與該Action匹配的廣播,而你的應用也可以接收到,這樣第三方應用就可以用你的廣播做很多事情,(分享鏈接,植入廣告)。
2.如果被別人知道了你應用的Action,當你使用廣播來傳遞數據時,別的應用也能接收到你的廣播,通過Action匹配,就能獲取你的私密數據(用戶數據,發送惡意信息等)

看一下LocalBroadcastManager的源碼
先看一下它的構造方法:

  private LocalBroadcastManager(Context context) {
        mAppContext = context;
        mHandler = new Handler(context.getMainLooper()) {

            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_EXEC_PENDING_BROADCASTS:
                        executePendingBroadcasts();
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };
    }

它內部是由單例實現的而私有化構造函數,它內部是一個Handler對象,對象傳入的是getMainLooper(),它是主線程,它代表它是在主線程運作的Looper。
再看一下它里邊3個比較重要的集合類

    private final HashMap<BroadcastReceiver, ArrayList<IntentFilter>> mReceivers
            = new HashMap<BroadcastReceiver, ArrayList<IntentFilter>>();
    private final HashMap<String, ArrayList<ReceiverRecord>> mActions
            = new HashMap<String, ArrayList<ReceiverRecord>>();

    private final ArrayList<BroadcastRecord> mPendingBroadcasts
            = new ArrayList<BroadcastRecord>();

mReceivers可以看到是一個HashMap類,它里面的key是BroadcastReceiver,value是ArrayList,ArrayList里面的數據是IntentFilter,IntentFilter是一個過濾類,它會過濾我們的廣播,為什么是一個集合呢,因為廣播接收器可以接收到不同Action的廣播,所以它這里就一定要維護一張表,一個BroadcastReceiver和一個Action是一一對應的。
mActions也是一個HashMap類,它的key 就是一個Action,它的value是ArrayList,ArrayList里面的數據是ReceiverRecord,它這個集合維護著Action所對應的ReceiverRecord,一一對應的一個HashMap表。
mPendingBroadcasts是一個ArrayList集合,它存儲的就是BroadcastRecord對象。

    private static class BroadcastRecord {
        final Intent intent;
        final ArrayList<ReceiverRecord> receivers;

        BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) {
            intent = _intent;
            receivers = _receivers;
        }
    }

而BroadcastRecord對象它存儲的是一個intent和receivers,所以說mPendingBroadcasts這個集合的作用就是存儲和發送廣播的Action匹配的ReceiverRecord集合,在執行處理廣播的時候,會將遍歷這個集合里面的廣播接收器,說白了它是一個存儲了廣播接收器的存儲器。

它是一個廣播,肯定要注冊,看一下registerReceiver這個方法

    public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        synchronized (mReceivers) {
            ReceiverRecord entry = new ReceiverRecord(filter, receiver);
            ArrayList<IntentFilter> filters = mReceivers.get(receiver);
            if (filters == null) {
                filters = new ArrayList<IntentFilter>(1);
                mReceivers.put(receiver, filters);
            }
           //為廣播添加器添加指定的過濾規則
            filters.add(filter);
          //。。。。。。。。。
            for (int i=0; i<filter.countActions(); i++) {
                String action = filter.getAction(i);
                ArrayList<ReceiverRecord> entries = mActions.get(action);
                if (entries == null) {
                    entries = new ArrayList<ReceiverRecord>(1);
                    mActions.put(action, entries);
                }
               //把IntentFilter里面的所有Action分別建立對ArrayList<ReceiverRecord>的映射,也就是為相應的Action添加廣播接收器,表示這個廣播接收器可以接收此Action的廣播
                entries.add(entry);
              //。。。。。。。
            }
        }
    }

看它所對應的unregisterReceiver

    public void unregisterReceiver(BroadcastReceiver receiver) {
        synchronized (mReceivers) {
            ArrayList<IntentFilter> filters = mReceivers.remove(receiver);
            if (filters == null) {
                return;
            }
            for (int i=0; i<filters.size(); i++) {
                IntentFilter filter = filters.get(i);
                for (int j=0; j<filter.countActions(); j++) {
                    String action = filter.getAction(j);
                    ArrayList<ReceiverRecord> receivers = mActions.get(action);
                    if (receivers != null) {
                        for (int k=0; k<receivers.size(); k++) {
                            if (receivers.get(k).receiver == receiver) {
                                 //移除mReceivers表中廣播接收器
                                receivers.remove(k);
                                k--;
                            }
                        }
                        if (receivers.size() <= 0) {
                            //移除mActions表中的廣播接收器
                            mActions.remove(action);
                        }
                    }
                }
            }
        }
    }

它做了兩件事

1、移除mReceivers表中廣播接收器
2、移除mActions表中的廣播接收器

再看一下最重要的sendBroadcast

 public boolean sendBroadcast(Intent intent) {
        synchronized (mReceivers) {
            final String action = intent.getAction();
            final String type = intent.resolveTypeIfNeeded(
                    mAppContext.getContentResolver());
            final Uri data = intent.getData();
            final String scheme = intent.getScheme();
            final Set<String> categories = intent.getCategories();

            final boolean debug = DEBUG ||
                    ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
            if (debug) Log.v(
                    TAG, "Resolving type " + type + " scheme " + scheme
                    + " of intent " + intent);
            //通過得到我們Intent的Action來獲得該Action對應的所有的廣播接收器的集合
            ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
            if (entries != null) {
                if (debug) Log.v(TAG, "Action list: " + entries);

                ArrayList<ReceiverRecord> receivers = null;
                //然后遍歷該集合,進行匹配
                for (int i=0; i<entries.size(); i++) {
                    ReceiverRecord receiver = entries.get(i);
                    if (debug) Log.v(TAG, "Matching against filter " + receiver.filter);

                    if (receiver.broadcasting) {
                        if (debug) {
                            Log.v(TAG, "  Filter's target already added");
                        }
                        continue;
                    }
                    //當action,type,scheme,data,categories都完全相同時,這時匹配成功
                    int match = receiver.filter.match(action, type, scheme, data,
                            categories, "LocalBroadcastManager");
                    if (match >= 0) {
                        //匹配成功
                        if (debug) Log.v(TAG, "  Filter matched!  match=0x" +
                                Integer.toHexString(match));
                        if (receivers == null) {
                            receivers = new ArrayList<ReceiverRecord>();
                        }
                        //然后把匹配成功的ReceiverRecord對象添加到一個集合中去
                        receivers.add(receiver);
                        receiver.broadcasting = true;
                    } else {
                        if (debug) {
                            String reason;
                            switch (match) {
                                case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
                                case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
                                case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
                                case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
                                default: reason = "unknown reason"; break;
                            }
                            Log.v(TAG, "  Filter did not match: " + reason);
                        }
                    }
                }

                if (receivers != null) {
                    for (int i=0; i<receivers.size(); i++) {
                        receivers.get(i).broadcasting = false;
                    }
                    //最后再把存儲匹配成功的ReceiverRecord對象集合添加到mPendingBroadcasts中去,而最終我們的廣播接收是通過遍歷mPendingBroadcasts這個集合來一一對這個集合里面的廣播接收器進行廣播的接收
                    mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));

                    if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
//這個非常重要,因為一開始我們就知道了在構造函數中會創建一個handler,而這個方法的名為sendBroadcast()其實不是真正的發送一個廣播,而是通過handler來發送一個Message,然后在handlerMessage()回調方法中進行消息的處理,所以這也證實了這是一個本地廣播,其它應用根本無法獲取到,因為LocalBroadcastManager內部是通過Handler實現廣播的發送的  
                    mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                    }
                    return true;
                }
            }
        }
        return false;
    }

這個sendBroadcast()方法看起來比較長,其實一半的代碼都是在做匹配,這段代碼也做了三件事:

1、把我們發送的Intent的規則和mActions中對應action的ArrayList<ReceiverRecord>集合里面的廣播接收器的規則進行匹配,匹配成功則加入一個匹配成功的集合中
2、把匹配成功的集合加入到mPendingBroadcasts集合中
3、最重要的一點,它其實是通過handler發送一個Message來實現的

總結 :LocalBroadcastManager高效的原因主要是因為它內部是通過Handler實現的,它的sendBroadcast()方法含義并非和我們平時所用的一樣,它的sendBroadcast()方法其實是通過handler發送一個Message實現的(當然這個方法還有規則匹配等作用)
既然是它內部是通過Handler來實現廣播的發送的,那么相比與系統廣播通過Binder實現那肯定是更高效了,同時使用Handler來實現,別的應用無法向我們的應用發送該廣播,而我們應用內發送的廣播也不會離開我們的應用
LocalBroadcastManager內部協作主要是靠這兩個Map集合:mReceivers和mActions,當然還有一個List集合mPendingBroadcasts,這個主要就是存儲待接收的廣播對象
假如我們需要讓一個廣播接收器能接收多個不同Action的廣播,我們可以在注冊時候通過IntentFilter為該廣播添加多條Action規則,這樣只要符合其中一條該廣播接收器就可以接收到了,如下:

 //注冊
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.sunzxy.demo.BROADCAST_ACTION_1");
        filter.addAction("com.sunzxy.demo.BROADCAST_ACTION_2");
        filter.addAction("com.sunzxy.demo.BROADCAST_ACTION_3");
        LocalBroadcastManager.getInstance(this).registerReceiver(new MyMyBroadcastReceiver(),filter);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容