Android IPC 之獲取服務(wù)(IBinder)

前言

IPC 系列文章:
建議按順序閱讀祭埂。

Android IPC 之Service 還可以這么理解
Android IPC 之Binder基礎(chǔ)
Android IPC 之Binder應(yīng)用
Android IPC 之AIDL應(yīng)用(上)
Android IPC 之AIDL應(yīng)用(下)
Android IPC 之Messenger 原理及應(yīng)用
Android IPC 之服務(wù)端回調(diào)
Android IPC 之獲取服務(wù)(IBinder)
Android Binder 原理換個(gè)姿勢(shì)就頓悟了(圖文版)

通過前面的文章我們知道恩伺,要進(jìn)行進(jìn)程通信的核心是能拿到另一個(gè)進(jìn)程暴露出來的IBiner引用睦授。本篇將重點(diǎn)分析獲取IBinder的方式及其原理涩嚣。
通過本篇文章驮宴,你將了解到:

1桩了、獲取系統(tǒng)服務(wù)
2踱侣、獲取自定義服務(wù)
3、兩者區(qū)別與聯(lián)系

本篇文章趟畏,系統(tǒng)服務(wù)贡歧、自定義服務(wù)里的服務(wù)并非單純是指Service,而是提供某一類功能的"服務(wù)"赋秀。

1利朵、獲取系統(tǒng)服務(wù)

簡(jiǎn)單例子

以手機(jī)振動(dòng)為例:

        Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
        vibrator.vibrate(1000);

調(diào)用Context 方法getSystemService(xx),xx表示服務(wù)名字猎莲,最終返回Vibrator绍弟。
拿到Vibrator 引用后就可以調(diào)用相應(yīng)的方法讓手機(jī)振動(dòng)。
繼續(xù)沿著方法調(diào)用分析:

#ContextImpl.java
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

#SystemServiceRegistry
    public static Object getSystemService(ContextImpl ctx, String name) {
        //從map 里獲取鍵值
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

這個(gè)map從哪里來呢著洼?在SystemServiceRegistry 靜態(tài)代碼塊里注冊(cè)的:

#SystemServiceRegistry.java
    static {
        ...
        registerService(Context.VIBRATOR_SERVICE, Vibrator.class,
                new CachedServiceFetcher<Vibrator>() {
                    @Override
                    public Vibrator createService(ContextImpl ctx) {
                        return new SystemVibrator(ctx);
                    }});
        ...
    }

可以看出返回了SystemVibrator樟遣,它是Vibrator(抽象類)的子類。
Vibrator.vibrate(xx)最終調(diào)用了如下方法:

#SystemVibrator.java
    private final IVibratorService mService;

    public SystemVibrator(Context context) {
        super(context);
        //獲取服務(wù)端提供的接口
        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
    }
    
    public void vibrate(int uid, String opPkg, VibrationEffect effect,
                        String reason, AudioAttributes attributes) {
        if (mService == null) {
            Log.w(TAG, "Failed to vibrate; no vibrator service.");
            return;
        }
        try {
            //真正調(diào)用之處
            mService.vibrate(uid, opPkg, effect, usageForAttributes(attributes), reason, mToken);
        } catch (RemoteException e) {
            Log.w(TAG, "Failed to vibrate.", e);
        }
    }

了解過AIDL的同學(xué)都會(huì)知道身笤,熟悉的套路:

  • mService 為服務(wù)端提供的接口豹悬,客戶端調(diào)用其提供的方法即可實(shí)現(xiàn)相應(yīng)的功能。
  • 客戶端為當(dāng)前待使用振動(dòng)服務(wù)的App進(jìn)程液荸,服務(wù)端為提供振動(dòng)服務(wù)的進(jìn)程瞻佛。
image.png

獲取IBinder

振動(dòng)服務(wù)的IBinder是通過:

ServiceManager.getService("vibrator")

獲取的。

#ServiceManager.java
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                //獲取IBinder
                return Binder.allowBlocking(rawGetService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        //獲取服務(wù)端的ServiceManager
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }


    private static IBinder rawGetService(String name) throws RemoteException {
        ...
        final IBinder binder = getIServiceManager().getService(name);
        ...
        return binder;
    }

又是熟悉的套路娇钱,IServiceManager 為ServiceManager服務(wù)端提供的接口涤久,通過該接口獲取振動(dòng)服務(wù)的IBinder引用涡尘。
其中BinderInternal.getContextObject()) 獲取ServiceManager的IBinder。
此處需要說明一下:

Client 需要從ServiceManager獲取震動(dòng)服務(wù)的IBinder响迂,而Client本身需要和ServiceManager通信考抄,要通信那么得有IBinder吧。BinderInternal.getContextObject())就是為了獲取ServiceManager的IBinder蔗彤,該方法從Binder驅(qū)動(dòng)獲取了IBinder引用川梅。

注冊(cè)服務(wù)

ServiceManager是如何找到振動(dòng)服務(wù)的呢?
Android 系統(tǒng)啟動(dòng)后然遏,會(huì)開啟system_server進(jìn)程贫途,該進(jìn)程里開啟了很多系統(tǒng)服務(wù),包括AMS待侵、WMS丢早、振動(dòng)服務(wù)等。

#SystemServer.java
    private void startOtherServices() {
        ...
        VibratorService vibrator = null;
        ...
        vibrator = new VibratorService(context);
        //向ServiceManager注冊(cè)振動(dòng)服務(wù)
        ServiceManager.addService("vibrator", vibrator);
        ...
    }

繼續(xù)來看addService(xx):

#ServiceManager.java
    public static void addService(String name, IBinder service) {
        addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
    }

    public static void addService(String name, IBinder service, boolean allowIsolated,
                                  int dumpPriority) {
        try {
            //IPC 調(diào)用注冊(cè)服務(wù)
            getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
    }

調(diào)用ServiceManager接口添加服務(wù)到ServiceManager里秧倾。

小結(jié)

好了怨酝,現(xiàn)在從頭到尾再捋一下。

1那先、ServiceManager 進(jìn)程啟動(dòng)
2农猬、system_server 進(jìn)程啟動(dòng),并將各個(gè)服務(wù)(包括振動(dòng)服務(wù))添加到ServiceManager里
3售淡、客戶端從ServiceManager里獲取振動(dòng)服務(wù)

用圖表示:


image.png

其中 Client斤葱、ServiceManager、SystemServer 分別運(yùn)行于三個(gè)不同的進(jìn)程揖闸,三者之間通過Binder進(jìn)行IPC揍堕。實(shí)線為其調(diào)用目的,虛線為其調(diào)用手段汤纸。

1衩茸、SystemServer 通過IPC1 向ServiceManager注冊(cè)服務(wù)的IBinder引用
2、Client想要使用服務(wù)(如振動(dòng)服務(wù))蹲嚣,先通過IPC2 向ServiceManager獲取
3、Client拿到服務(wù)IBinder后祟牲,調(diào)用服務(wù)接口(IPC3)隙畜,使用服務(wù)提供的具體功能

為了減少多次無用IPC調(diào)用,因此Client會(huì)將拿到的各種服務(wù)緩存到數(shù)組里说贝,當(dāng)要查詢的服務(wù)已經(jīng)存在议惰,則不用進(jìn)行IPC2,直接使用IPC3乡恕。

系統(tǒng)提供的服務(wù)如AMS言询、WMS俯萎、PMS等都將IBinder封裝在xxManager(如WindowManager等)里,通過xxManager就可以進(jìn)行IPC使用具體的服務(wù)运杭。

2夫啊、獲取自定義服務(wù)

上面說了系統(tǒng)提供的服務(wù)需要注冊(cè)到ServiceManager里,以便后來者查詢使用之辆憔。那么我們自己定義的服務(wù)該如何使用呢撇眯?

Service 的綁定流程

先來看看典型的綁定流程:
服務(wù)端代碼:

    IStudentServer iStudentServer = new IStudentServer.Stub() {
        @Override
        public void say(String world) throws RemoteException {
            Log.d(TAG, "hello " + world);
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return iStudentServer.asBinder();
    }

客戶端代碼:

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //重點(diǎn)在service 類型
            IStudentServer iStudentServer = IStudentServer.Stub.asInterface(service);
            try {
                iStudentServer.say("hello");   
            } catch (Exception e) {
                
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private void bindService() {
        Intent intent = new Intent(MainActivity.this, MyService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

大致闡述上述流程:

1、Service 構(gòu)造Binder對(duì)象虱咧,并將IBinder在onBind(xx)傳遞出去
2熊榛、客戶端在綁定Service成功后會(huì)收到服務(wù)端傳遞過來的IBinder
3、通過該IBinder獲取關(guān)聯(lián)的接口操作服務(wù)端

可以看出腕巡,我們?cè)赟ervice里定義業(yè)務(wù)邏輯(Server端)玄坦,并開放了接口,通過Service的綁定功能將接IBinder傳遞給客戶端绘沉,這和獲取系統(tǒng)服務(wù)的邏輯是一樣的煎楣,核心都是IBinder的傳遞,接下來從源頭入手查看IBinder的傳遞梆砸。

從Context.bindService(xx)開始

由于涉及到的代碼較多转质,此處就不貼完整源碼了,重點(diǎn)關(guān)注關(guān)鍵之處和IPC 流程帖世,多用圖示之休蟹。
綁定流程圖:


image.png

大致解釋上圖元素構(gòu)成:
最頂上方框?yàn)轭惷?br> 紅色表示它們都運(yùn)行在同一進(jìn)程,暫且稱之為客戶端進(jìn)程日矫。
綠色表示它們都運(yùn)行在同一進(jìn)程赂弓,暫且稱之為系統(tǒng)服務(wù)進(jìn)程。
黃色表示它們都運(yùn)行在同一進(jìn)程哪轿,暫且稱之為服務(wù)端進(jìn)程盈魁。

紅色箭頭表示該調(diào)用為進(jìn)程間調(diào)用,用IPC 表示之窃诉。其余為本進(jìn)程內(nèi)的對(duì)象調(diào)用杨耙。

分別來分析重點(diǎn)1、2飘痛、3珊膜。
重點(diǎn)1
客戶端發(fā)起綁定操作,傳入ServiceConnection 引用宣脉,該引用在ContextImpl.bindServiceCommon(xx)里被封裝在ServiceDispatcher里车柠,而ServiceDispatcher又持有InnerConnection引用,InnerConnection 繼承自IServiceConnection.Stub 可以跨進(jìn)程調(diào)用。
也就是說竹祷,客戶端進(jìn)程留下了一個(gè)"樁"谈跛,等待別的進(jìn)程調(diào)用。

重點(diǎn)2
AMS 收到客戶端的綁定指令后塑陵,發(fā)起綁定操作感憾,通過IPC 調(diào)用服務(wù)端接口。
最終調(diào)用到服務(wù)端的onBind(xx)方法猿妈,該方法里返回服務(wù)端的IBinder引用吹菱。

重點(diǎn)3
服務(wù)端返回IBinder引用后,委托AMS 發(fā)布這個(gè)IBinder彭则,IBinder找到對(duì)應(yīng)的客戶端進(jìn)程鳍刷。而在重點(diǎn)1里客戶端已經(jīng)留下了"樁",此時(shí)AMS 順勢(shì)找到這個(gè)"樁"直接調(diào)用ServiceConnection的onServiceConnected(xx)俯抖,就能將IBinder傳遞給客戶端输瓜。

可能比較繞,我們從進(jìn)程的角度再簡(jiǎn)化一下:


image.png

可以看出芬萍,以上發(fā)生了四次IPC 操作(當(dāng)然里面還涉及到其它的IPC尤揣,此處忽略)。IBinder傳遞要經(jīng)過兩次IPC柬祠。

IBinder 傳遞

上面分析了通過綁定流程返回服務(wù)端的IBinder引用北戏。
但是運(yùn)行的過程中卻發(fā)現(xiàn)問題:
服務(wù)端返回的IBinder是:IStudentServer
而客戶端收到的IBinder是:BinderProxy
這個(gè)是怎么回事呢?
既然IBinder是通過進(jìn)程間傳遞的漫蛔,看看其是否是支持序列化嗜愈。

    public interface IBinder {
        ...
    }

    public class Binder implements android.os.IBinder {
        ...
    }

發(fā)現(xiàn)它們都沒有實(shí)現(xiàn)Parcelable 接口。它是怎么支持序列化的呢莽龟?
那只能從Parcel本身分析了蠕嫁。
Parcel 除了支持

readInt()
writeInt()
...

等基本數(shù)據(jù)類型外,還支持

    public final IBinder readStrongBinder() {
        return nativeReadStrongBinder(mNativePtr);
    }

    public final void writeStrongBinder(IBinder val) {
        nativeWriteStrongBinder(mNativePtr, val);
    }

顧名思義毯盈,應(yīng)該是專門讀寫IBinder的方法剃毒,也就是說雖然沒有實(shí)現(xiàn)Parcelable,但是Parcel 內(nèi)置支持了IBinder搂赋。
接著繼續(xù)查看其native方法赘阀,看看有何奧妙之處。

static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        return javaObjectForIBinder(env, parcel->readStrongBinder());
    }
    return NULL;
}

static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));
        if (err != NO_ERROR) {
            signalExceptionForError(env, clazz, err);
        }
    }
}

注:方法在/frameworks/core/jni/android_os_Parcel.cpp

先分析寫入IBinder的情況:
parcel->writeStrongBinder(xx) 調(diào)用了Parcel.cpp里的writeStrongBinder(xx)進(jìn)而調(diào)用flatten_binder(xx)函數(shù)

    status_t flatten_binder(const sp<ProcessState>& /*proc*/,
    const sp<IBinder>& binder, Parcel* out)
    {
        flat_binder_object obj;
        ...
        if (binder != NULL) {
            IBinder *local = binder->localBinder();
            if (!local) {
                //本地引用不存在
                BpBinder *proxy = binder->remoteBinder();
                if (proxy == NULL) {
                    ALOGE("null proxy");
                }
            const int32_t handle = proxy ? proxy->handle() : 0;
                //type 標(biāo)記為非本地Binder
                obj.hdr.type = BINDER_TYPE_HANDLE;
                obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
                obj.handle = handle;
                obj.cookie = 0;
            } else {
                //IBinder為本地的Binder引用脑奠,也就是和Server處在同一進(jìn)程
                //type 標(biāo)記為本地Binder
                obj.hdr.type = BINDER_TYPE_BINDER;
                obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
                obj.cookie = reinterpret_cast<uintptr_t>(local);
            }
        } else {
            ...
        }

        return finish_flatten_binder(binder, obj, out);
    }

可以看出基公,根據(jù)傳入的IBinder是不是本地Binder然后打上type標(biāo)記。
再來看看讀取IBinder的情況
parcel->readStrongBinder()里最終調(diào)用了:

    status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
    {
    const flat_binder_object* flat = in.readObject(false);

        if (flat) {
            //根據(jù)Type 標(biāo)記判斷
            switch (flat->hdr.type) {
                case BINDER_TYPE_BINDER:
                    //本地引用
                *out = reinterpret_cast<IBinder*>(flat->cookie);
                    return finish_unflatten_binder(NULL, *flat, in);
                case BINDER_TYPE_HANDLE:
                    //非本地引用捺信,獲取代理對(duì)象
                *out = proc->getStrongProxyForHandle(flat->handle);
                    return finish_unflatten_binder(
                            static_cast<BpBinder*>(out->get()), *flat, in);
            }
        }
        return BAD_TYPE;
    }

由此可見酌媒,如果是Server端的IBinder與Client端不在同一進(jìn)程,則會(huì)轉(zhuǎn)換為Proxy對(duì)象迄靠,最終體現(xiàn)在Java層的就是BinderProxy類型秒咨。
注:函數(shù)在/frameworks/native/libs/binder/Parcel.cpp

綜上所述,IBinder跨進(jìn)程傳遞時(shí):

  • 如果客戶端掌挚、服務(wù)端同一進(jìn)程雨席,則服務(wù)端回傳的IBinder為當(dāng)前引用
  • 如果客戶端、服務(wù)端處在不同進(jìn)程吠式,則服務(wù)端回傳的IBinder為BinderProxy

3陡厘、兩者區(qū)別與聯(lián)系

獲取系統(tǒng)服務(wù)
系統(tǒng)服務(wù)會(huì)往ServiceManager注冊(cè),ServiceManager運(yùn)行在單獨(dú)的進(jìn)程里特占,客戶端進(jìn)程需要先向ServiceManager里請(qǐng)求IBinder糙置,再使用IBinder獲取關(guān)聯(lián)接口進(jìn)而使用系統(tǒng)服務(wù)。
獲取自己定義的服務(wù)
服務(wù)端進(jìn)程開啟后是目,暴露出IBinder谤饭。客戶端通過綁定服務(wù)端進(jìn)程里的Service懊纳,將IBinder跨進(jìn)程傳遞至客戶端揉抵,客戶端再使用IBinder獲取關(guān)聯(lián)接口進(jìn)而使用自定義服務(wù)。此過程沒有借助于ServiceManager嗤疯。

不論是哪種方式冤今,核心都需要獲得IBinder,IBinder的獲取需要IPC茂缚。

至此戏罢,Android IPC 系列文章已經(jīng)分析完畢

本文基于Android 10.0。

您若喜歡阱佛,請(qǐng)點(diǎn)贊帖汞、關(guān)注,您的鼓勵(lì)是我前進(jìn)的動(dòng)力

持續(xù)更新中凑术,和我一起步步為營(yíng)系統(tǒng)翩蘸、深入學(xué)習(xí)Android

1、Android各種Context的前世今生
2淮逊、Android DecorView 必知必會(huì)
3催首、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5泄鹏、Android事件分發(fā)全套服務(wù)
6郎任、Android invalidate/postInvalidate/requestLayout 徹底厘清
7、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8备籽、Android事件驅(qū)動(dòng)Handler-Message-Looper解析
9舶治、Android 鍵盤一招搞定
10分井、Android 各種坐標(biāo)徹底明了
11、Android Activity/Window/View 的background
12霉猛、Android Activity創(chuàng)建到View的顯示過
13尺锚、Android IPC 系列
14、Android 存儲(chǔ)系列
15惜浅、Java 并發(fā)系列不再疑惑
16瘫辩、Java 線程池系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市坛悉,隨后出現(xiàn)的幾起案子伐厌,更是在濱河造成了極大的恐慌,老刑警劉巖裸影,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挣轨,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡轩猩,警方通過查閱死者的電腦和手機(jī)刃唐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來界轩,“玉大人画饥,你說我怎么就攤上這事∽腔” “怎么了抖甘?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)葫慎。 經(jīng)常有香客問我衔彻,道長(zhǎng),這世上最難降的妖魔是什么偷办? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任艰额,我火速辦了婚禮,結(jié)果婚禮上椒涯,老公的妹妹穿的比我還像新娘柄沮。我一直安慰自己,他們只是感情好废岂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布祖搓。 她就那樣靜靜地躺著,像睡著了一般湖苞。 火紅的嫁衣襯著肌膚如雪拯欧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天财骨,我揣著相機(jī)與錄音镐作,去河邊找鬼藏姐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛该贾,可吹牛的內(nèi)容都是我干的包各。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼靶庙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了娃属?” 一聲冷哼從身側(cè)響起六荒,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎矾端,沒想到半個(gè)月后掏击,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秩铆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年砚亭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殴玛。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捅膘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滚粟,到底是詐尸還是另有隱情寻仗,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布凡壤,位于F島的核電站署尤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏亚侠。R本人自食惡果不足惜曹体,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望硝烂。 院中可真熱鬧箕别,春花似錦、人聲如沸滞谢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爹凹。三九已至厨诸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間禾酱,已是汗流浹背微酬。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工绘趋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颗管。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓陷遮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親垦江。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帽馋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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