AIDL實例,雙向通信

服務端實現

接下來的過程演示了服務端怎么實現Android復雜的AIDL通信實例
首先需要明白的一個事情,調用一個方法,涉及到三個重要概念:參數的個數,參數類型,和方法輸出類型

AIDL支持的參數類型

1. 簡單數據類型

  • Java基本類型,即int、long、char等;
  • String;
  • CharSequence;
  • List
    List中的所有元素都必須是AIDL支持的數據類型、其他AIDL接口或你之前聲明的Parcelable實現類。
  • Map
    Map中的所有元素都必須是AIDL支持的數據類型、其他AIDL接口或你之前聲明的Parcelable實現類。

2. 復雜數據類型

  • 在AIDL中傳遞的復雜參數類型對象必須要實現Parcelable接口,這是因為Parcelable允許Android系統將復雜對象分解成基本類型以便在進程間傳輸。

  • 若客戶端組件和服務分開在不同APP,必須要把該Parcelable實現類.java文件拷貝到客戶端所在的APP,包路徑要一致。

  • 另外需要為這個Parcelable實現類定義一個相應的.aidl文件。

  • 復雜數據類型,必須要有import語句,即使它跟.aidl是同一個包下。

  • 方法可有零、一或多個參數,可有返回值或void。

  • 將復雜數據類型作為AIDL接口的形參時,記得加上in。

所有非基本類型的參數都需要標簽來表明這個數據的去向:
in,表示此變量由客戶端設置;
out,表示此變量由服務端設置;
inout,表示此變量可由客戶端和服務端設置;
而基本類型只能是in。
只expose方法,不會expose靜態變量。

3. 回調類參數類型
回調類型的參數類型和復雜數據類型用法是一樣的,但是它對應的是binder類型的參數,而不是單純的數據類型。回調類型的參數允許回調客戶端代碼及結果,相當于服務端調用客戶端當中的方法,同樣的 回調類參數類型也屬于非基本類型參數,也需要用標簽標明設置情況

需要定義一個AIDL服務需要以下幾個步驟

  1. 創建.aidl文件
  2. SDK生成對應.java文件和Stub內部類
  3. 通過Service子類將接口暴露給外界

1. 創建.aidl文件

  • 創建對象類型的AIDL文件
    簡單類型對象并不需要單獨創建一個aidl文件表示,但是復雜對象需要創建一個對應的實體類型的aidl文件,如下:
//AIDL復雜對象定義

//注意點:這個包名需要和你的實體類com.visualing.application.RequestEntity包名一致
//同時也意味著,你需要頂一個實現Parcelable接口的實體類
package com.visualing.application;

//parcelable化的實體
parcelable RequestEntity;

該aidl文件對應的實體類實現了Parcelable接口,這是一個常規的實現了Parcelable接口的實例

package com.visualing.application;

import android.os.Parcel;
import android.os.Parcelable;

public class RequestEntity implements Parcelable {

    public String name;

    public long money;

    public int age;

    public byte sex;


    protected RequestEntity(Parcel in) {
        name = in.readString();
        money = in.readLong();
        age = in.readInt();
        sex = in.readByte();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeLong(money);
        dest.writeInt(age);
        dest.writeByte(sex);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<RequestEntity> CREATOR = new Creator<RequestEntity>() {
        @Override
        public RequestEntity createFromParcel(Parcel in) {
            return new RequestEntity(in);
        }

        @Override
        public RequestEntity[] newArray(int size) {
            return new RequestEntity[size];
        }
    };

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getMoney() {
        return money;
    }

    public void setMoney(long money) {
        this.money = money;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public byte getSex() {
        return sex;
    }

    public void setSex(byte sex) {
        this.sex = sex;
    }
}

  • 創建方法類型的AIDL文件
    以下是創建一個供客戶端調用的aidl文件的實例
// IMyAidlInterface.aidl
package com.visualing.application;
//導入需要別的進程訪問的類對象,這些內容在客戶端需要有一份拷貝,生成一模一樣的內容
import com.visualing.application.RequestEntity;
import com.visualing.application.AidlCallback;
// Declare any non-default types here with import statements

interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    //基本類型演示接口
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    //復雜類型演示接口
    int addValues(int anInt,int bnInt,in RequestEntity request);

    //計算回調類型演示接口
    int progressCallback(int anInt,int bnInt,in AidlCallback callback,in RequestEntity request);
}

我們可以看到上面有一個計算回調類型演示接口

 int progressCallback(int anInt,int bnInt,in AidlCallback callback,in RequestEntity request);

那其中的AidlCallback是怎么實現的呢,如下,是AidlCallback一個回調類型的方法的aidl文件的實例,和本身調用者的定義方式是一樣的

  • 創建回調類型的AIDL文件
// AidlCallback.aidl
package com.visualing.application;

//此處需要主動導入你寫入的復雜的實體類
import com.visualing.application.RequestEntity;
// Declare any non-default types here with import statements


//回調的AIDL寫法,和普通的調用方法寫法一致,只是這個表示從服務端調用客戶端的方法而已
interface AidlCallback {

    /**
     * 方法可有零、一或多個參數,可有返回值或void。所有非基本類型的參數都需要標簽來表明這個數據的去向:
       in,表示此變量由客戶端設置;
       out,表示此變量由服務端設置;
       inout,表示此變量可由客戶端和服務端設置;
       基本類型只能是in。
     */
    //注意復雜對象的參數是,前面需要帶in,或者out,表明自己是輸入還是輸出
    void callback(in RequestEntity entity);
}

由上,已經定義了程序所需要的所有的aidl文件。

2. SDK生成對應.java文件和Stub內部類
當編譯APP時,SDK工具會將項目/src/<SourceSet>/aidl目錄下的.aidl文件一個個在項目/build/generated/source/aidl目錄下生成IBinder接口.java文件。兩個文件名一樣,只是后綴不同。如AidlCallback.aidl生成AidlCallback.java。
AidlCallback.java

package com.visualing.application;
// Declare any non-default types here with import statements
//回調的AIDL寫法,和普通的調用方法寫法一致,只是這個表示從服務端調用客戶端的方法而已

public interface AidlCallback extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.visualing.application.AidlCallback {
        private static final java.lang.String DESCRIPTOR = "com.visualing.application.AidlCallback";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.visualing.application.AidlCallback interface,
         * generating a proxy if needed.
         */
        public static com.visualing.application.AidlCallback asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.visualing.application.AidlCallback))) {
                return ((com.visualing.application.AidlCallback) iin);
            }
            return new com.visualing.application.AidlCallback.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_callback: {
                    data.enforceInterface(DESCRIPTOR);
                    com.visualing.application.RequestEntity _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.visualing.application.RequestEntity.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.callback(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.visualing.application.AidlCallback {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            /**
             * 方法可有零、一或多個參數,可有返回值或void。所有非基本類型的參數都需要標簽來表明這個數據的去向:
             * in,表示此變量由客戶端設置;
             * out,表示此變量由服務端設置;
             * inout,表示此變量可由客戶端和服務端設置;
             * 基本類型只能是in。
             *///注意復雜對象的參數是,前面需要帶in,或者out,表明自己是輸入還是輸出
            @Override
            public void callback(com.visualing.application.RequestEntity entity) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((entity != null)) {
                        _data.writeInt(1);
                        entity.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_callback, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_callback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    /**
     * 方法可有零、一或多個參數,可有返回值或void。所有非基本類型的參數都需要標簽來表明這個數據的去向:
     * in,表示此變量由客戶端設置;
     * out,表示此變量由服務端設置;
     * inout,表示此變量可由客戶端和服務端設置;
     * 基本類型只能是in。
     *///注意復雜對象的參數是,前面需要帶in,或者out,表明自己是輸入還是輸出
    public void callback(com.visualing.application.RequestEntity entity) throws android.os.RemoteException;
}

IMyAidlInterface.java

public interface IMyAidlInterface extends android.os.IInterface {
      public static abstract class Stub extends android.os.Binder implements com.visualing.application.IMyAidlInterface {
       
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            //執行代碼分類,為不同的接口方法執行不同的過程裝載
            switch (code) {
                case INTERFACE_TRANSACTION: {
                                  }
                case TRANSACTION_basicTypes: {
                                   }
                case TRANSACTION_addValues: {
                                  }
                case TRANSACTION_progressCallback: {
                                        return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.visualing.application.IMyAidlInterface {
         
            @Override
            public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
              
            }

            @Override
            public int addValues(int anInt, int bnInt, com.visualing.application.RequestEntity request) throws android.os.RemoteException {
               
            }

            @Override
            public int progressCallback(int anInt, int bnInt, com.visualing.application.AidlCallback callback, com.visualing.application.RequestEntity request) throws android.os.RemoteException {
                           
        }
        //這個與服務器對應的方法標識
        static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addValues = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_progressCallback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    }

   //服務接口的方法定義
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;

    public int addValues(int anInt, int bnInt, com.visualing.application.RequestEntity request) throws android.os.RemoteException;

    public int progressCallback(int anInt, int bnInt, com.visualing.application.AidlCallback callback, com.visualing.application.RequestEntity request) throws android.os.RemoteException;
}

從上面我們看到了一個叫做Stub內部類,里面有兩個關鍵的方法
public boolean onTransact()

public static com.visualing.application.AidlCallback asInterface(android.os.IBinder obj)
作用已經在上一篇文章中說明。他們是binder的橋梁和轉換,是聯系兩個進程的關鍵點。

什么是Stub內部類,使用Stub內部類需要注意的地方
Stub內部類

  • .aidl文件編譯后生成的.java文件中自動生成的內部類。
  • public static abstract聲明。
  • extends android.os.Binder。
  • 實現.aidl文件中定義的接口,且聲明其所有方法。

實現Stub內部類要注意

  1. 對于傳過來的調用,無法保證是在主線程中執行的。
  2. Service必須要考慮多線程和線程安全。
  3. 默認情況下,RPC都是異步的。避免在主線程中調用AIDL,不然可能會導致ANR。
  4. 不能給調用方回拋異常。

3. 通過Service子類將接口暴露給外界

需要創建Stub內部類的子類并實現接口的相關方法,然后通過asBinder返回給客戶端使用。

package com.visualing.application.server;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;

import com.visualing.application.AidlCallback;
import com.visualing.application.IMyAidlInterface;
import com.visualing.application.RequestEntity;

public class ServerApplication extends Service {

    IMyAidlInterface mAidlInterface = new IMyAidlInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
            //方法實現
        }

        @Override
        public int addValues(int anInt, int bnInt, RequestEntity request) throws RemoteException {
            //具體方法實現
            return anInt + bnInt + request.age;
        }

        @Override
        public int progressCallback(int anInt, int bnInt, AidlCallback callback, RequestEntity request) throws RemoteException {
            //具體方法實現
            request.age = addValues(anInt, bnInt, request) * 10000;
            callback.callback(request);
            return request.age;
        }

    };

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //簡單的類型強轉
        return mAidlInterface.asBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }
}

然后在AndroidManifest.xml進行注冊

<!--關鍵點:android:exported="true"-->
        <service
            android:name=".ServerApplication"
            android:exported="true">
            <intent-filter>
                <action android:name="com.visualing.application.server.MyService"/>
            </intent-filter>
        </service>

那么到此服務端的AIDL就已經全部實現了。
看看服務端的文件結構圖是什么樣的


server_aidl.jpg

客戶端實現

如果已經寫好了服務端的AIDL,那么客戶端的AIDL也就寫好了,因為它們是一一對應的,需要從服務端拷貝一份AIDL文件到客戶端然后同步相關代碼,那么就會生成相同的類對象。
同時還需要做的事情是將AIDL中涉及到的java類文件也需要拷貝一份,因為他們是相同的處理方式,都是通過Binder進行處理的。
以下是客戶端的復制文件結構圖


aidl客戶端.jpg

可以看打大部分文件都是服務端文件的復制,那么客戶端怎么和服務端進行溝通呢,請看關鍵的AidlActivity.java文件。

package com.visualing.application.client;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.visualing.application.AidlCallback;
import com.visualing.application.IMyAidlInterface;
import com.visualing.application.RequestEntity;

public class AidlActivity extends AppCompatActivity {

    IMyAidlInterface mAidlInterface;

    //關鍵點服務連接器
    ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            //關鍵點,獲取連接服務接口
            /**
             * <pre>
             *     public static com.visualing.application.IMyAidlInterface asInterface(android.os.IBinder obj)
             {
             if ((obj==null)) {
             return null;
             }
             android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
             if (((iin!=null)&&(iin instanceof com.visualing.application.IMyAidlInterface))) {
             return ((com.visualing.application.IMyAidlInterface)iin);
             }
             return new com.visualing.application.IMyAidlInterface.Stub.Proxy(obj);
             }
             * </pre>
             */
            mAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mAidlInterface = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hello_sum_aidl);
        //綁定遠程服務,可能第一次無法綁上
        bindRomoteService();
        initActionView();
    }

    protected void bindRomoteService() {
        final Intent intent = new Intent();
        //---------關鍵點:確定服務的服務類型,或者加入其它權限
        intent.setAction("com.visualing.application.server.MyService");
        //---------關鍵點:確定服務所屬的包名
        intent.setPackage("com.visualing.application.server");
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);

    }

    @Override
    protected void onDestroy() {
        //解綁遠程服務
        unbindService(mServiceConnection);
        super.onDestroy();
    }

    protected void initActionView() {
        Button buttonCalc = (Button) findViewById(R.id.buttonCalc);
        buttonCalc.setOnClickListener(new View.OnClickListener() {

            EditText value1 = (EditText) findViewById(R.id.value1);
            EditText value2 = (EditText) findViewById(R.id.value2);
            TextView result = (TextView) findViewById(R.id.result);
            int v1 = 0, v2 = 0, res = -1;

            @Override
            public void onClick(View v) {
                //遠程方法操作
                if (mAidlInterface != null) {
                    try {
                        v1 = Integer.parseInt(value1.getText().toString());
                        v2 = Integer.parseInt(value2.getText().toString());
                    } catch (Exception e) {
                        e.printStackTrace();
                        return;
                    }
                    //此處是則是aidl運行線程
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            //執行遠程方法
                            try {
                                //非UI運行線程
                                res = mAidlInterface.addValues(v1, v2, new RequestEntity(20));
                                try {
                                    result.setText(Integer.valueOf(res).toString());
                                } catch (Exception e) {
                                    //執行上述代碼會報異常,因為在thread中執行
                                    e.printStackTrace();
                                }
                                mAidlInterface.progressCallback(v1, v2, new AidlCallback.Stub() {
                                    @Override
                                    public void callback(RequestEntity entity) throws RemoteException {
                                        try {
                                            result.setText(Integer.valueOf(entity.age).toString());
                                        } catch (Exception e) {
                                            //執行上述代碼會報異常,因為在thread中執行
                                            e.printStackTrace();
                                        }
                                    }
                                }, new RequestEntity(30));
                                result.postDelayed(new Runnable() {
                                    @Override
                                    public void run() {
                                        try {
                                            //UI運行線程
                                            mAidlInterface.progressCallback(v1, v2, new AidlCallback.Stub() {
                                                @Override
                                                public void callback(RequestEntity entity) throws RemoteException {
                                                    result.setText(Integer.valueOf(entity.age).toString());
                                                    //不會有異常,會得出最后的顯示結果
                                                }
                                            }, new RequestEntity(50));
                                        } catch (RemoteException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                }, 1000);
                                //如果也就說明一個問題,aidl是在那個線程中調用的,那么執行的回調也是在那個線程中
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();
                } else {
                    bindRomoteService();
                }
            }
        });
    }
}

執行上面的代碼執行過程是這樣的,首先是 bindRomoteService(),然后得到接口對象mAidlInterface ,然后調用相關的方法實現,最后需要早destory中解綁服務 unbindService(mServiceConnection);從上面的測試代碼也可以看出aidl是在那個線程中調用的,那么執行的回調也是在那個線程中,aidl是允許在任何線程中調用的。
由上基本上完成了復雜模式的AIDL雙向通信回調。

總結

客戶端使用

ServiceConnection mServiceConnection = new ServiceConnection()

鏈接服務端,得到服務端定義的接口

IMyAidlInterface   mAidlInterface = IMyAidlInterface.Stub.asInterface(service);

,客戶端使用mAidlInterface對象進行遠程調用,該對象是一個站樁對象,通過Binder的方式調用對應的接口實例

@Override 
public int progressCallback(int anInt, int bnInt, com.visualing.application.AidlCallback callback, com.visualing.application.RequestEntity request) 
{
...
//mRemote虛擬機接管的遠程實例對象
mRemote.transact(Stub.TRANSACTION_progressCallback, _data, _reply, 0);
...
}

,通過transact方法將信息溝通服務端,并把相關參數信息傳到客戶端實現。
接下來由服務端中的onBind方法中返回的Stub類型的Binder對象接收處理

public class ServerApplication extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        //簡單的類型強轉
        return mAidlInterface.asBinder();
    }
}

但是并不是直接調用Stub子類中的對應方法,而是通過Binder中的一個onTransact方法

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case TRANSACTION_progressCallback:
{
...
int _result = this.progressCallback(_arg0, _arg1, _arg2, _arg3);
...
}
...
}

處理后進行調用用戶實現的方法progressCallback()

 @Override
        public int progressCallback(int anInt, int bnInt, AidlCallback callback, RequestEntity request) throws RemoteException {
            //具體方法實現
            request.age = addValues(anInt, bnInt, request) * 10000;
            callback.callback(request);
            return request.age;
        }

的處理過程,可以看到該過程中有一個回調方法 callback.callback(request);該方法則又會通過上面的一個過程調用到客戶端的callback方法,只不過這次客戶端變成了服務端,服務端相當于客戶端了。這樣他們就實現了雙向通信。

部分引用自簡書

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

推薦閱讀更多精彩內容