Android的AIDL介紹

AIDL:Android Interface Definition Language,即Android接口定義語言潭兽。

AIDL文件的本質(zhì)是系統(tǒng)為我們提供了一種快速實現(xiàn)Binder的工具囊蓝,僅此而已。

AIDL進行進程間通信的流程:

  1. 服務(wù)端
    服務(wù)端首先要創(chuàng)建一個Service來監(jiān)聽客戶端的連接請求,然后創(chuàng)建一個AIDL文件诫睬,將暴露給客戶端的接口在這個AIDL文件中聲明,最后在Service中實現(xiàn)這個AIDL接口即可帕涌。

  2. 客戶端
    客戶端首先需要綁定服務(wù)端的Service摄凡,綁定成功后,將服務(wù)端返回的Binder對象轉(zhuǎn)成AIDL接口所屬的類型宵膨,接著就可以調(diào)用AIDL中的方法了架谎。

  3. AIDL接口的創(chuàng)建

// IBookManager.aidl
package com.wonder.myapp.tests.aidl;
import com.wonder.myapp.tests.aidl.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

系統(tǒng)為我們自動生成的Binder類如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: C:\\workspace\\TestApp\\app\\src\\main\\aidl\\com\\wonder\\myapp\\tests\\aidl\\IBookManager.aidl
 */
package com.wonder.myapp.tests.aidl;

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

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

        /**
         * Cast an IBinder object into an com.wonder.myapp.tests.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.wonder.myapp.tests.aidl.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.wonder.myapp.tests.aidl.IBookManager))) {
                return ((com.wonder.myapp.tests.aidl.IBookManager) iin);
            }
            return new com.wonder.myapp.tests.aidl.IBookManager.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_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.wonder.myapp.tests.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.wonder.myapp.tests.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.wonder.myapp.tests.aidl.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.wonder.myapp.tests.aidl.IBookManager {
            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;
            }

            @Override
            public java.util.List<com.wonder.myapp.tests.aidl.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.wonder.myapp.tests.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.wonder.myapp.tests.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.wonder.myapp.tests.aidl.Book book) 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 ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public java.util.List<com.wonder.myapp.tests.aidl.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.wonder.myapp.tests.aidl.Book book) throws android.os.RemoteException;
}
Binder類結(jié)構(gòu)圖.png

Binder工作機制分析:
類的結(jié)構(gòu)其實很簡單,首先它聲明了兩個方法getBookList和addBook辟躏,正是在IBookManager.aidl中所聲明的方法谷扣,同時還聲明了兩個整型的id標(biāo)識這兩個方法,用于標(biāo)識在transact過程中客戶端所請求的到底是哪個方法。接著会涎,它聲明了一個內(nèi)部類Stub裹匙,這是一個Binder類,當(dāng)客戶端和服務(wù)端都位于同一個進程時末秃,方法調(diào)用不會走跨進程的transact過程概页,而當(dāng)兩者位于不同進程時,方法調(diào)用需要走跨進程的transact過程练慕,這個邏輯有Stub的內(nèi)部代理類Proxy來完成惰匙。下面詳細每個方法的含義。

DESCRIPTOR
Binder的唯一標(biāo)識铃将,一般用當(dāng)前Binder的類名標(biāo)識项鬼。

asInterface(android.os.IBinder obj)
用于將服務(wù)端的Binder對象轉(zhuǎn)換成客戶端所需的AIDL接口類型的對象,這種轉(zhuǎn)換過程是區(qū)分進程的劲阎,如果客戶端和服務(wù)端位于同一進程绘盟,那么該方法返回的就是服務(wù)端的Stub對象本身,否則返回的是系統(tǒng)封裝后的Stub.Proxy對象悯仙。

asBinder()
此方法用于返回當(dāng)前Binder對象龄毡。

onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
這個方法運行在服務(wù)端的Binder線程池中,當(dāng)客戶端發(fā)起跨進程請求時锡垄,遠程請求會通過系統(tǒng)底層封裝后交由此方法來處理沦零。服務(wù)端通過code可以確定客戶端所請求的目標(biāo)方法是什么,接著從data中取出目標(biāo)方法所需的參數(shù)(目標(biāo)方法有參的話)偎捎,然后執(zhí)行目標(biāo)方法蠢终。當(dāng)目標(biāo)方法執(zhí)行完畢后,就向reply中寫入返回值(目標(biāo)方法有返回值的話)茴她。需要注意的是寻拂,如果此方法返回false,那么客戶端的請求會失敗丈牢,因此可以利用這個特性做權(quán)限驗證祭钉。

Proxy # getBookList
這個方法運行在客戶端,當(dāng)客戶端遠程調(diào)用此方法時己沛,內(nèi)部實現(xiàn)如下:首先創(chuàng)建該方法所需的輸入型Parcel對象_data慌核、輸出型Parcel對象_reply和返回值對象List;然后把該方法的參數(shù)信息寫入_data中(有參的話)申尼,接著調(diào)用transact方法來發(fā)起RPC請求垮卓,同時當(dāng)前線程掛起;然后服務(wù)端的onTransact方法會被調(diào)用师幕,知道RPC過程返回后粟按,當(dāng)前線程繼續(xù)執(zhí)行诬滩,并從_reply中取出RPC過程的返回結(jié)果;最后灭将,返回_reply中的數(shù)據(jù)疼鸟。

Proxy # addBook
這個方法運行在客戶端,執(zhí)行過程和getBookList是一樣的庙曙,不過這個方法沒有返回值空镜,不需要從_reply中取出數(shù)據(jù)。

注意:

  • 客戶端發(fā)起遠程請求時捌朴,當(dāng)前線程會掛起直至服務(wù)端進程返回數(shù)據(jù)吴攒,所以如果一個遠程方法是耗時的,則不能在UI線程發(fā)起此遠程請求男旗;
  • 服務(wù)端的Binder方法運行在Binder的線程池中舶斧,所以不管Binder方法是否耗時都應(yīng)該采用同步的方式去實現(xiàn),因為它已經(jīng)運行在一個線程中了察皇。
Binder的工作機制.jpg

Binder是可能意外死亡的,這往往是由于服務(wù)端進程意外停止泽台,為了程序的健壯性什荣,需要重新連接服務(wù)。有如下方法:

  • linkToDeath()方法可用于注冊一個DeathRecipient監(jiān)聽怀酷。DeathRecipient是一個接口稻爬,內(nèi)部只有一個方法binderDied,當(dāng)Binder死亡的時候蜕依,系統(tǒng)會回調(diào)該方法桅锄。該方法在客戶端的Binder線程池中被回調(diào);
  • 在onServiceDisconnected中重連遠程服務(wù)样眠,該方法在UI線程被回調(diào)友瘤。

從安全性考慮,遠程服務(wù)需要加入權(quán)限驗證功能檐束,避免客戶端隨意連接辫秧。AIDL的權(quán)限驗證常用方法介紹:

  • onBind中進行驗證,驗證不通過返回null被丧;
  • 在服務(wù)端的onTransact方法中進行驗證盟戏,驗證失敗就返回false。

補充知識:

  1. IBinder是輕量級遠程調(diào)用機制的核心甥桂,高性能地進行進程內(nèi)和進程間通信柿究。不要直接實現(xiàn)這個接口,從Binder進行擴展黄选。

  2. AIDL支持的所有數(shù)據(jù)類型

  • 基本數(shù)據(jù)類型(int蝇摸、long、char、boolean探入、double等)狡孔;
  • String 和 CharAequence;
  • List:只支持ArrayList蜂嗽,里面每個元素都必須被AIDL支持苗膝;
  • Map:只支持HashMap,里面每個元素都必須被AIDL支持植旧;
  • Parcelable:所有實現(xiàn)Parcelable接口的對象辱揭;
  • AIDL:所有的AISL接口本身也可以在AIDL文件中使用。
    自定義的Parcelable對象和AIDL對象必須要顯式import進來病附,不管它們是否和當(dāng)前的AIDL文件位于同一個包內(nèi)问窃;
    如果AIDL文件中用到了自定義的Parcelable對象,那么必須新建一個和它同名的AIDL文件完沪,并在其中聲明它為Parcelable類型域庇。
    AIDL中除了基本數(shù)據(jù)類型,其他類型的參數(shù)必須標(biāo)上方向:in覆积、out或inout听皿,in表示輸入型參數(shù),out表示輸出型參數(shù)宽档,inout表示輸入輸出型參數(shù)尉姨。要根據(jù)實際情況指定參數(shù)類型,不同的類型在底層實現(xiàn)上有不同的開銷吗冤。
    AIDL接口中只支持方法又厉,不支持聲明靜態(tài)常量。
  1. AIDL的包結(jié)構(gòu)在服務(wù)端和客戶端要保持一致椎瘟,否則會運行出錯覆致。因為客戶端需要反序列化服務(wù)端中和AIDL接口相關(guān)的所有類,如果類的完整路徑不一樣的話降传,就不能成功反序列化篷朵,程序也就無法正常運行。

  2. 服務(wù)端有List婆排、Map的時候声旺,可以用CopyOnWriteArrayList、ConcurrentHashMap段只。
    CopyOnWriteArrayList腮猖、ConcurrentHashMap支持并發(fā)讀/寫。
    前面有提到赞枕,AIDL中只支持ArrayList型的List澈缺,在這卻推薦使用CopyOnWriteArrayList(不是繼承自ArrayList)坪创,為什么能正常工作呢?因為AIDL中所支持的是抽象的List姐赡,而List只是一個接口莱预,因此雖然服務(wù)端返回的是CopyOnWriteArrayList,但是在Binder中會按照List的規(guī)范去訪問數(shù)據(jù)并最終形成一個新的ArrayList傳遞給客戶端项滑。

  3. 服務(wù)端管理客戶端注冊的監(jiān)聽器的時候依沮,可以用RemoteCallbackList
    RemoteCallbackList是系統(tǒng)專門提供的用于刪除跨進程listener的接口枪狂。它是一個泛型危喉,支持管理任意的AIDL接口。它的工作原理為:在它的內(nèi)部有一個Map結(jié)構(gòu)州疾,專門用來保存所有的AIDL回調(diào)辜限,這個Map的key是IBinder類型,value是Callback類型严蓖。
    RemoteCallbackList可以在客戶端進程終止后薄嫡,自動移除客戶端所注冊的listener;RemoteCallbackList內(nèi)部自動實現(xiàn)了線程同步的功能颗胡。
    出現(xiàn)原因:對象不能跨進程直接傳輸,對象的跨進程傳輸本質(zhì)上是反序列化的過程杭措。跨進程傳輸钾恢,客戶端的同一個對象會在服務(wù)端生成一個新的不同的對象手素,但它們底層的Binder對象是同一個,所以RemoteCallbackList可以解決此問題瘩蚪。

  4. 客戶端的onServiceConnected和onServiceDisconnected方法運行在UI線程泉懦。

  5. 服務(wù)端Binder的方法本身就運行在Binder線程池中,可以執(zhí)行大量耗時操作疹瘦,這個時候就不要在服務(wù)端方法中開啟子線程去執(zhí)行異步任務(wù)了崩哩,除非有特殊需求,否則不建議這么做言沐。

Demo代碼

package com.wonder.myapp.tests.service;

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

/**
 * Created by zxh on 2018/7/23.     
 * Book.java
 */

public class Book implements Parcelable {

    private int bookId;
    private String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

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

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

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", bookName='" + bookName + '\'' +
                '}';
    }
}
// Book.aidl
package com.wonder.myapp.tests.service;

parcelable Book;
// IBookListChangeListener.aidl
package com.wonder.myapp.tests.service;
import com.wonder.myapp.tests.service.Book;

interface IBookListChangeListener {
    void onNewBookArrived(in Book book);
}
// IBookManager.aidl
package com.wonder.myapp.tests.service;
import com.wonder.myapp.tests.service.Book;
import com.wonder.myapp.tests.service.IBookListChangeListener;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IBookListChangeListener listener);
    void unregisterListener(IBookListChangeListener listener);
}
package com.wonder.myapp.tests.service;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteCallbackList;
import android.os.RemoteException;

import com.wonder.myapp.bean.LogTag;
import com.wonder.myapp.utils.LogUtils;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class MyService extends Service {

    private CopyOnWriteArrayList<Book> bookList = new CopyOnWriteArrayList<>();
    private RemoteCallbackList<IBookListChangeListener> callbackList = new RemoteCallbackList<>();

    //IBookManager.Stub的四個方法都是在子線程執(zhí)行
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            LogUtils.d(LogTag.TAG_SERVICE, "getBookList " + (Looper.myLooper() == Looper.getMainLooper()));
            return bookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            bookList.add(book);
            int count = callbackList.beginBroadcast();
            for (int i = 0; i < count; i ++) {
                callbackList.getBroadcastItem(i).onNewBookArrived(book);
            }
            callbackList.finishBroadcast();
        }

        @Override
        public void registerListener(IBookListChangeListener listener) throws RemoteException {
            callbackList.register(listener);
        }

        @Override
        public void unregisterListener(IBookListChangeListener listener) throws RemoteException {
            callbackList.unregister(listener);
        }
    };

    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        LogUtils.d(LogTag.TAG_SERVICE, "onCreate " + (Looper.myLooper() == Looper.getMainLooper()));

        bookList.add(new Book(1, "語文書"));
        bookList.add(new Book(2, "數(shù)學(xué)書"));

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int j = 0; j < 10; j++) {
                        Thread.sleep(10 * 1000);
                        Book book = new Book(bookList.size() + 1, "Book # " + (bookList.size() + 1));
                        bookList.add(book);
                        int count = callbackList.beginBroadcast();
                        for (int i = 0; i < count; i++) {
                            try {
                                callbackList.getBroadcastItem(i).onNewBookArrived(book);
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                        }
                        callbackList.finishBroadcast();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtils.d(LogTag.TAG_SERVICE, "onStartCommand startId = " + startId);
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        LogUtils.d(LogTag.TAG_SERVICE, "onBind");
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        LogUtils.d(LogTag.TAG_SERVICE, "onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        LogUtils.d(LogTag.TAG_SERVICE, "onDestroy");
    }
}
package com.wonder.myapp.tests.service;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.view.View;

import com.wonder.myapp.R;
import com.wonder.myapp.bean.LogTag;
import com.wonder.myapp.ui.common.BaseActivity;
import com.wonder.myapp.utils.LogUtils;

import java.util.List;

import butterknife.OnClick;

public class ServiceTestActivity extends BaseActivity {

    private IBookManager mBinder;
    private IBookListChangeListener bookListChangeListener;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LogUtils.d(LogTag.TAG_SERVICE, "ServiceConnection onServiceConnected " + (Looper.myLooper() == Looper.getMainLooper()));
            mBinder = IBookManager.Stub.asInterface(service);
            try {
                bookListChangeListener = new IBookListChangeListener.Stub() {
                    @Override
                    public void onNewBookArrived(Book book) throws RemoteException {
                        LogUtils.d(LogTag.TAG_SERVICE, "onNewBookArrived " + book.toString());
                    }
                };
                mBinder.registerListener(bookListChangeListener);
                List<Book> list = mBinder.getBookList();
                for (Book book : list) {
                    LogUtils.d(LogTag.TAG_SERVICE, "bookList :" + book.toString());
                }
                mBinder.addBook(new Book(3, "人民法院細則"));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            LogUtils.d(LogTag.TAG_SERVICE, "ServiceConnection onServiceDisconnected");
        }
    };

    @Override
    protected void setContentView() {
        setContentView(R.layout.activity_service_test);
    }

    @OnClick({R.id.service_start, R.id.service_stop, R.id.service_bind, R.id.service_unbind})
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.service_start:
                LogUtils.d(LogTag.TAG_SERVICE, "click to startService");
                startService(new Intent(this, MyService.class));
                break;
            case R.id.service_stop:
                LogUtils.d(LogTag.TAG_SERVICE, "click to stopService");
                stopService(new Intent(this, MyService.class));
                break;
            case R.id.service_bind:
                LogUtils.d(LogTag.TAG_SERVICE, "click to bindService");
                bindService(new Intent(this, MyService.class), connection, BIND_AUTO_CREATE);
                break;
            case R.id.service_unbind:
                LogUtils.d(LogTag.TAG_SERVICE, "click to unbindService");
                //沒有bindService直接unbindService會報錯:java.lang.IllegalArgumentException: Service not registered:
                unbindService(connection);
                if (mBinder != null)
                    try {
                        mBinder.unregisterListener(bookListChangeListener);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                break;
            default:
                break;
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邓嘹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子险胰,更是在濱河造成了極大的恐慌汹押,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件起便,死亡現(xiàn)場離奇詭異棚贾,居然都是意外死亡窖维,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門妙痹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铸史,“玉大人,你說我怎么就攤上這事怯伊×战危” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵震贵,是天一觀的道長利赋。 經(jīng)常有香客問我,道長猩系,這世上最難降的妖魔是什么媚送? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮寇甸,結(jié)果婚禮上塘偎,老公的妹妹穿的比我還像新娘。我一直安慰自己拿霉,他們只是感情好吟秩,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绽淘,像睡著了一般涵防。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沪铭,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天壮池,我揣著相機與錄音,去河邊找鬼杀怠。 笑死椰憋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赔退。 我是一名探鬼主播橙依,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼硕旗!你這毒婦竟也來了窗骑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤卵渴,失蹤者是張志新(化名)和其女友劉穎慧域,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浪读,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡昔榴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年辛藻,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片互订。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡吱肌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仰禽,到底是詐尸還是另有隱情氮墨,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布吐葵,位于F島的核電站规揪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏温峭。R本人自食惡果不足惜猛铅,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凤藏。 院中可真熱鬧奸忽,春花似錦、人聲如沸揖庄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹄梢。三九已至疙筹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間禁炒,已是汗流浹背腌歉。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留齐苛,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓桂塞,卻偏偏與公主長得像凹蜂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子阁危,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

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