Android AIDL淺析及異步調(diào)用

AIDL:Android Interface Definition Language,即 Android 接口定義語言男图。

AIDL 是什么

Android 系統(tǒng)中的進(jìn)程之間不能共享內(nèi)存,因此磺送,需要提供一些機(jī)制在不同進(jìn)程之間進(jìn)行數(shù)據(jù)通信车份。

為了使其他的應(yīng)用程序也可以訪問本應(yīng)用程序提供的服務(wù)谋减,Android 系統(tǒng)采用了遠(yuǎn)程過程調(diào)用(Remote Procedure Call,RPC)方式來實(shí)現(xiàn)扫沼。與很多其他的基于 RPC 的解決方案一樣出爹,Android 使用一種接口定義語言(Interface Definition Language,IDL)來公開服務(wù)的接口缎除。我們知道 Android 四大組件中的 3 種(Activity严就、BroadcastReceiver和ContentProvider)都可以進(jìn)行跨進(jìn)程訪問,另外一種 Android 組件 Service 同樣可以器罐。因此梢为,可以將這種可以跨進(jìn)程訪問的服務(wù)稱為 AIDL(Android Interface Definition Language)服務(wù)。

在介紹 AIDL 的使用以及其它特性前轰坊,我們先來了解下 AIDL 的核心——Binder铸董。

Android 的 Binder 機(jī)制淺析

看過一些關(guān)于 Binder 的文章,總得來說 Binder 機(jī)制的底層實(shí)現(xiàn)很復(fù)雜肴沫,相當(dāng)復(fù)雜粟害,要完全搞清楚,得花大量的時(shí)間颤芬。從某種角度來說我磁,個(gè)人覺得孽文,對于 Binder,我們只需要了解其上層原理以及使用方法即可夺艰。

直觀來看,從代碼的角度來說沉衣,Binder 是 Android 系統(tǒng)源碼中的一個(gè)類郁副,它實(shí)現(xiàn)了 IBinder 接口;從 IPC 角度來說豌习,Binder 是 Android 中的一種跨進(jìn)程通信方式存谎;從 Android Framework 角度來講,Binder 是 ServiceManager 連接各種 Manager(ActivityManager肥隆、WindowManager 等等)和相應(yīng) ManagerService 的橋梁既荚;從 Android 應(yīng)用層來說,Binder 是客戶端和服務(wù)端進(jìn)行通信的媒介栋艳,當(dāng) bindService 的時(shí)候恰聘,服務(wù)端會(huì)返回一個(gè)包含了服務(wù)端業(yè)務(wù)調(diào)用的 Binder 對象,通過這個(gè) Binder 對象吸占,客戶端就可以和服務(wù)端進(jìn)行通信晴叨,這里的服務(wù)包括普通服務(wù)和基于 AIDL 的服務(wù)。

接下來矾屯,我們通過一個(gè) AIDL 示例兼蕊,來分析 Binder 的工作機(jī)制。在工程目錄中新建一個(gè)名為 aidl 的 package件蚕,然后新建 Book.Java孙技、Book.aidl(創(chuàng)建此文件時(shí),as 會(huì)提示已存在排作,需要先用其它命令牵啦,創(chuàng)建成功后再重命名為 Book.aidl )和 IBookManager.aidl,代碼如下:

// Book.java
package com.cy.ipcsample.aidl;

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

/**
 * 數(shù)據(jù)類
 * @author cspecialy
 * @version v1.0.0
 * @date 2018/5/14 21:38
 */
public class Book implements Parcelable {

    public int bookId;
    public String bookName;

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

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

    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];
        }
    };

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

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(bookId);
        parcel.writeString(bookName);
    }
 
    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", bookName='" + bookName + '\'' +
                '}';
    }
}
// Book.aidl
package com.cy.ipcsample.aidl;

parcelable Book;
// IBookManager.aidl
package com.cy.ipcsample.aidl;

import com.cy.ipcsample.aidl.Book;
import com.cy.ipcsample.aidl.IOnNewBookArrivedListener;

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

創(chuàng)建好三個(gè)文件之后纽绍,編譯一下蕾久,as 會(huì)在 app/build/generated/source/aidl/debug 目錄下的 com.cy.ipcsample 包中生成一個(gè)名為 IBookManager.Java 的類,如下圖所示:

這是系統(tǒng)生成的 Binder 類拌夏,接下來我們要利用這個(gè)類來分析 Binder 的工作原理僧著。其代碼如下:(生成的代碼格式很亂,可以格式化代碼之后看)

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: G:\\Android\\Github\\Bugly-Android-Demo\\sample\\ipcsample\\src\\main\\aidl\\com\\cy\\ipcsample
 * \\aidl\\IBookManager.aidl
 */
package com.cy.ipcsample.aidl;

public interface IBookManager extends android.os.IInterface {
    public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException;

    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.cy.ipcsample.aidl.IBookManager {
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        private static final java.lang.String DESCRIPTOR = "com.cy.ipcsample.aidl.IBookManager";

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

        /**
         * Cast an IBinder object into an com.cy.ipcsample.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.cy.ipcsample.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.cy.ipcsample.aidl.IBookManager))) {
                return ((com.cy.ipcsample.aidl.IBookManager) iin);
            }
            return new com.cy.ipcsample.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.cy.ipcsample.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.cy.ipcsample.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.cy.ipcsample.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.cy.ipcsample.aidl.IBookManager {
            private android.os.IBinder mRemote;

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

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            @Override
            public java.util.List<com.cy.ipcsample.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.cy.ipcsample.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.cy.ipcsample.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.cy.ipcsample.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();
                }
            }


        }
    }
}

可見障簿,系統(tǒng)為我們生成了一個(gè) IBookManager 接口盹愚,它繼承了 IInterface 這個(gè)接口,所以這里要注意下站故,所有可以在 Binder 中傳輸?shù)慕涌诮耘拢夹枰^承 IInterface 接口毅舆。

接下來分析下,該類的工作機(jī)制愈腾。仔細(xì)看憋活,可以發(fā)現(xiàn),該類主要分成三個(gè)部分:

  • 定義自身方法( getBookList 方法和 addBook 方法)虱黄;
  • 內(nèi)部靜態(tài)類-Stub悦即,該類繼承 Binder,同時(shí)實(shí)現(xiàn) IBookManager 接口
  • Stub的內(nèi)部代理類-Proxy橱乱,也實(shí)現(xiàn) IBookManager 接口

第一部分我們不用管它辜梳,主要看 Stub 類和 Proxy 類。在 Stub 中泳叠,首先聲明了兩個(gè)用于標(biāo)識(shí) IBookManager 方法的整型變量作瞄,這兩個(gè)變量用于標(biāo)識(shí)在 transact 過程中客戶端所請求的是哪個(gè)方法。接著危纫,是 asInterface 方法宗挥,該方法用于將服務(wù)端的 Binder 對象轉(zhuǎn)換成客戶端所需的 AIDL 接口類型的對象,該方法通過調(diào)用 Binder 的 queryLocalInterface 方法叶摄,判斷客戶端和服務(wù)端是否處于同一進(jìn)程属韧,如果客戶端、服務(wù)端處于同一進(jìn)程蛤吓,那么此方法直接返回服務(wù)端的 Stub宵喂,否則,返回 Stub 的代理對象 Proxy会傲。queryLocalInterface 實(shí)現(xiàn)如下:

/**
 * Use information supplied to attachInterface() to return the
 * associated IInterface if it matches the requested
 * descriptor.
 */
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

mOwner 是在 Stub 構(gòu)造函數(shù)中傳進(jìn)去的 this 參數(shù)锅棕。

接下來是 Stub 的代理類 Stub.Proxy,由上面的分析可知淌山,Stub.Proxy 是運(yùn)行在客戶端的(由 asInterface 方法返回給客戶端的對象)裸燎,Stub.Proxy對象創(chuàng)建后,持有服務(wù)端的 Binder 對象泼疑,用于客戶端請求時(shí)調(diào)用服務(wù)端方法進(jìn)行遠(yuǎn)程調(diào)用德绿。客戶端在向服務(wù)端發(fā)起請求時(shí)退渗,調(diào)用 Stub.Proxy 的相應(yīng)方法移稳,Stub.Proxy 方法的流程如下:

  • 首先創(chuàng)建該方法所需要的輸入型 Parcel 對象 _data、輸出型對象 _reply 和返回值對象(如果有)会油;
  • 然后把該方法的參數(shù)信息寫入 _data 中(如果有)个粱;
  • 接著調(diào)用 transact 方法進(jìn)行 RPC(遠(yuǎn)程過程調(diào)用)請求,同時(shí)當(dāng)前線程掛起翻翩;
  • 然后在 transact 方法通過服務(wù)端的 Binder 對象調(diào)用服務(wù)端的 onTransact 方法都许,即 Stub 中的 onTransact 方法稻薇;
  • onTransact 方法返回后,當(dāng)前線程繼續(xù)執(zhí)行胶征,并從 _reply 中取出 RPC 過程返回的結(jié)果塞椎;
  • 最后返回 _reply 中的數(shù)據(jù)(如果客戶端請求方法需要返回值)。

以上睛低,就是系統(tǒng)生成的 IBookManager 的工作過程忱屑,需要注意下,服務(wù)端的 onTransact 方法是運(yùn)行在 Binder 線程池中的暇昂。由于 IBookManager.Stub 類繼承 Binder,所以上述分析即 Binder 的工作機(jī)制伴嗡,簡單總結(jié)下:

  • 在客戶端和服務(wù)端連接時(shí)(一般通過 bindService 方法)急波,服務(wù)端通過 asInterface 方法給客戶端返回一個(gè) IInterface 接口類型的對象(如果客戶端和服務(wù)端在同一個(gè)進(jìn)程,則返回服務(wù)端的 Binder 對象瘪校,否則返回 服務(wù)端 Binder 對象的代理)澄暮;
  • 客戶端通過該對象向服務(wù)端進(jìn)行請求,如果客戶端和服務(wù)端不在同一個(gè)進(jìn)程阱扬,則通過該對象所代理的 Binder 對象進(jìn)行 RPC 請求泣懊,否則,直接通過 Binder 對象調(diào)用服務(wù)端相應(yīng)方法麻惶。

或者參考下圖理解下:


Binder 的工作機(jī)制

由此可見馍刮,Binder 在 AIDL 中承載著重要的職能,是 AIDL 的核心窃蹋,理解了 Binder 的工作機(jī)制卡啰,其實(shí)在很多方面都很有用。

AIDL 的使用

一套 AIDL 服務(wù)搭建的步驟如下:

  • 創(chuàng)建 .aidl 文件警没,系統(tǒng)生成相應(yīng)的繼承 IInterface 的接口類(暴露給客戶端的接口)匈辱。
  • 創(chuàng)建一個(gè) Service(服務(wù)端),實(shí)現(xiàn) .aidl 文件中的接口杀迹。
  • 創(chuàng)建客戶端亡脸,綁定服務(wù)端的 Service。
  • 客戶端綁定服務(wù)端成功后树酪,將服務(wù)端返回的 Binder 對象轉(zhuǎn)成 AIDL 接口所屬的 IInterface 類型浅碾。調(diào)用 AIDL 中的方法,實(shí)現(xiàn)和服務(wù)端的通信嗅回。

接下來及穗,我們使用上面的 IBookManager 來實(shí)現(xiàn) AIDL。

  1. 創(chuàng)建 .aidl 文件
    直接使用上面創(chuàng)建好的 Book.aidl绵载、IBookManager.aidl 文件即可

  2. 創(chuàng)建服務(wù)端
    創(chuàng)建一個(gè) Service埂陆,命名為 BookManagerService苛白,代碼如下:

    package com.cy.ipcsample.aidl
    
    import android.app.Service
    import android.content.Intent
    import android.os.IBinder
    import android.os.RemoteCallbackList
    import android.util.Log
    import java.util.concurrent.CopyOnWriteArrayList
    
    class BookManagerService : Service() {
        private val TAG = "BookManagerService"
    
        private val mBookList = CopyOnWriteArrayList<Book>()
        private val mListenerList = RemoteCallbackList<IOnNewBookArrivedListener>()
    
        /**
         * 實(shí)現(xiàn) AIDL 接口的 Binder 對象,客戶端綁定服務(wù)端時(shí)焚虱,直接返回此 Binder 對象
         */
        private val mBinder = object : IBookManager.Stub() {
    
            override fun getBookList(): MutableList<Book> {
                return mBookList
            }
    
            override fun addBook(book: Book?) {
                mBookList.add(book)
            }
    
        }
    
        override fun onBind(intent: Intent): IBinder {
            return mBinder
        }
    
        override fun onCreate() {
            super.onCreate()
    
            // 創(chuàng)建兩本圖書
            mBookList.add(Book(1, "Android"))
            mBookList.add(Book(2, "iOS"))
        }
    }
    

    然后在 AndroidManifest 中注冊 Service购裙,注意啟動(dòng)多進(jìn)程:

    <service
        android:name=".aidl.BookManagerService"
        android:process="com.cy.ipcsample.bookManagerService">
    </service>
    
  3. 客戶端綁定服務(wù)端的 Service
    客戶端的創(chuàng)建,直接使用 Activity 即可鹃栽,綁定遠(yuǎn)程服務(wù)的代碼如下:

    private val mConnection = object : ServiceConnection {
    
        override fun onServiceDisconnected(name: ComponentName?) {
        }
    
        /**
         * 連接遠(yuǎn)程服務(wù)成功的回調(diào)
         */
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        }
    
    }
    
    // 綁定遠(yuǎn)程服務(wù)
    bindService(Intent(this, BookManagerService::class.java),
            mConnection, Context.BIND_AUTO_CREATE)
    
  4. 與服務(wù)端通信
    與服務(wù)器綁定成功后躏率,首先在 ServiceConnection 的回調(diào)中,將服務(wù)端返回的 Binder 對象轉(zhuǎn)換成 AIDL 接口所屬的對象民鼓,就可以調(diào)用相應(yīng)方法和服務(wù)端通信了薇芝,代碼如下:

    // 將服務(wù)端返回的 Binder 對象轉(zhuǎn)換成 IBookManager 對象
    val bookManager = IBookManager.Stub.asInterface(service)
    
    // 與服務(wù)端通信
    try {
        // 獲取圖書列表
        val list = bookManager.bookList
        Log.i(TAG, "query book list, list type: ${list.javaClass.canonicalName}")
        Log.i(TAG, "query book list: $list")
    
        // 添加一本圖書
        val book = Book(3, "Android開發(fā)藝術(shù)探索")
        bookManager.addBook(book)
        Log.i(TAG, "add book: $book")
    
        // 獲取圖書列表
        val newList = bookManager.bookList
        Log.i(TAG, "query book list: $newList")
    } catch (e: RemoteException) {
        e.printStackTrace()
    }
    

    代碼中,我們先查詢了服務(wù)端的圖書列表丰嘉,接著向服務(wù)端添加一本書Android藝術(shù)開發(fā)探索夯到,然后再次查詢看是否添加成功。運(yùn)行下看 log饮亏,如下圖所示:


    可見耍贾,運(yùn)行結(jié)果和預(yù)期結(jié)果一致。完整的客戶端代碼如下:

    package com.cy.ipcsample.aidl
    
    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.util.Log
    import com.cy.ipcsample.R
    
    class BookManagerActivity : AppCompatActivity() {
    
        private val TAG = "BookManagerActivity"
    
        private val mConnection = object : ServiceConnection {
    
            override fun onServiceDisconnected(name: ComponentName?) {
                Log.d(TAG, "binder died.")
            }
    
            /**
             * 連接遠(yuǎn)程服務(wù)成功的回調(diào)
             */
            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                // 將服務(wù)端返回的 Binder 對象轉(zhuǎn)換成 IBookManager 對象
                val bookManager = IBookManager.Stub.asInterface(service)
     
                // 與服務(wù)端通信
                try {
                    // 獲取圖書列表
                    val list = bookManager.bookList
                    Log.i(TAG, "query book list, list type: ${list.javaClass.canonicalName}")
                    Log.i(TAG, "query book list: $list")
     
                    // 添加一本圖書
                    val book = Book(3, "Android開發(fā)藝術(shù)探索")
                    bookManager.addBook(book)
                    Log.i(TAG, "add book: $book")
     
                    // 獲取圖書列表
                    val newList = bookManager.bookList
                    Log.i(TAG, "query book list: $newList")
                } catch (e: RemoteException) {
                    e.printStackTrace()
                }
            }
     
        }
     
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_book_manager)
     
            // 綁定遠(yuǎn)程服務(wù)
            bindService(Intent(this, BookManagerService::class.java),
                    mConnection, Context.BIND_AUTO_CREATE)
        }
     
        override fun onDestroy() {
            unbindService(mConnection)
            super.onDestroy()
        }
    }
    

到此路幸,一個(gè)簡單的 AIDL 示例就完成了荐开,當(dāng)然,AIDL 的使用遠(yuǎn)沒有那么簡單简肴,還有很多情景需要考慮的晃听,比如:客戶端需要隨時(shí)服務(wù)端在狀態(tài)變化時(shí)同時(shí)客戶端,類似觀察這模式着帽,那么訂閱與反訂閱怎么實(shí)現(xiàn)杂伟;Binder 意外死亡,怎么重連等等仍翰,更多內(nèi)容赫粥,可以參考《Android藝術(shù)開發(fā)探索》電子書下載

AIDL 的異步調(diào)用

AIDL 的調(diào)用過程是同步還是異步的予借?

這個(gè)問題其實(shí)看這一節(jié)的標(biāo)題大家都知道了越平,AIDL 的調(diào)用過程是同步的。同時(shí)灵迫,上面分析 Binder 的機(jī)制時(shí)秦叛,也提到了,客戶端進(jìn)行遠(yuǎn)程 RPC 請求時(shí)瀑粥,線程會(huì)掛起挣跋,等待結(jié)果,由此也可知狞换,AIDL 的調(diào)用過程是同步的避咆,下面來驗(yàn)證下舟肉。

首先,在服務(wù)端的 BookManagerService 中實(shí)現(xiàn)的 Binder 對象的 getBookList 方法添加延時(shí)執(zhí)行查库,如下圖所示:
服務(wù)端添加延時(shí)

然后路媚,在客戶端的 BookManagerActivity 中添加一個(gè)按鈕,點(diǎn)擊按鈕時(shí)調(diào)用服務(wù)端 Binder 的 getBookList 做 RPC 請求樊销。代碼如下:
客戶端添加按鈕做 RPC 請求

運(yùn)行后整慎,連續(xù)點(diǎn)擊按鈕,結(jié)果如下圖所示:


由圖所知围苫,連續(xù)點(diǎn)擊按鈕過后一段時(shí)間裤园,出現(xiàn)了無響應(yīng)錯(cuò)誤( ANR ),由此可知剂府,客戶端向服務(wù)端做 RPC 請求時(shí)比然,是同步的,也就是說:AIDL 的調(diào)用過程是同步的周循。

AIDL 的調(diào)用過程是同步的,當(dāng)我們需要服務(wù)端做耗時(shí)操作時(shí)万俗,肯定是不能使用同步調(diào)用的湾笛,否則輕者影響用戶體驗(yàn),重者直接 ANR 或者應(yīng)用崩潰闰歪。那么如何使 AIDL 的調(diào)用過程是異步的呢嚎研?

其實(shí)也很簡單,只需要把調(diào)用放到非 UI 線程即可库倘,如果要對調(diào)用的返回做 UI 更新的話临扮,再通過 Handler 處理即可。如下圖所示:
AIDL 異步調(diào)用

本文參考

  • 《Android開發(fā)藝術(shù)探索》 第二章
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末教翩,一起剝皮案震驚了整個(gè)濱河市杆勇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饱亿,老刑警劉巖蚜退,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異彪笼,居然都是意外死亡钻注,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門配猫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來幅恋,“玉大人,你說我怎么就攤上這事泵肄±唬” “怎么了淑翼?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長零渐。 經(jīng)常有香客問我窒舟,道長,這世上最難降的妖魔是什么诵盼? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任惠豺,我火速辦了婚禮,結(jié)果婚禮上风宁,老公的妹妹穿的比我還像新娘洁墙。我一直安慰自己,他們只是感情好戒财,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布热监。 她就那樣靜靜地躺著,像睡著了一般饮寞。 火紅的嫁衣襯著肌膚如雪孝扛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天幽崩,我揣著相機(jī)與錄音苦始,去河邊找鬼。 笑死慌申,一個(gè)胖子當(dāng)著我的面吹牛陌选,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蹄溉,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼咨油,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了柒爵?” 一聲冷哼從身側(cè)響起役电,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棉胀,沒想到半個(gè)月后宴霸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡膏蚓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年瓢谢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驮瞧。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡氓扛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情采郎,我是刑警寧澤千所,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蒜埋,受9級(jí)特大地震影響淫痰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜整份,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一待错、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烈评,春花似錦火俄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至竿开,卻和暖如春谱仪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背否彩。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工芽卿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胳搞。 一個(gè)月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像称杨,于是被迫代替她去往敵國和親肌毅。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

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