7 AIDL上

為什么要設計出這么一門語言静暂?
它有哪些語法?
我們應該如何使用它谱秽?
再深入一點洽蛀,我們可以思考,我們是如何通過它來達到我們的目的的疟赊?
更深入一點郊供,為什么要這么設計這門語言?會不會有更好的方式來實現(xiàn)我們的目的近哟?

1驮审、AIDL
AIDL是一個縮寫,全稱是Android Interface Definition Language吉执,也就是Android接口定義語言

2疯淫、為什么要設計這門語言?
每一個進程都有自己的Dalvik VM實例,都有自己的一塊獨立的內存戳玫,都在自己的內存上存儲自己的數(shù)據(jù)熙掺,執(zhí)行著自己的操作.
通過AIDL來制定一些規(guī)則,規(guī)定它們能進行哪些交流.

3咕宿、它有哪些語法

文件類型:用AIDL書寫的文件的后綴是 .aidl币绩,而不是 .java
數(shù)據(jù)類型:AIDL默認支持一些數(shù)據(jù)類型,在使用這些數(shù)據(jù)類型的時候是不需要導包的荠列;但是除了這些類型之外的數(shù)據(jù)類型,在使用之前必須導包载城,就算目標文件與當前正在編寫的 .aidl 文件在同一個包下肌似。

默認支持的數(shù)據(jù)類型包括:
Java中的八種基本數(shù)據(jù)類型,包括 byte诉瓦,short川队,int力细,long,float固额,double眠蚂,boolean,char斗躏。
String 類型逝慧。
CharSequence類型。
List類型:List中的所有元素必須是AIDL支持的類型之一啄糙,或者是一個其他AIDL生成的接口笛臣,或者是定義的parcelable(下文關于這個會有詳解)。List可以使用泛型隧饼。
Map類型:Map中的所有元素必須是AIDL支持的類型之一沈堡,或者是一個其他AIDL生成的接口,或者是定義的parcelable燕雁。Map是不支持泛型的诞丽。

定向tag:這是一個極易被忽略的點——這里的“被忽略”指的不是大家都不知道,而是很少人會正確的使用它拐格。在我的理解里僧免,定向 tag 是這樣的:AIDL中的定向 tag 表示了在跨進程通信中數(shù)據(jù)的流向,其中 in 表示數(shù)據(jù)只能由客戶端流向服務端禁荒, out 表示數(shù)據(jù)只能由服務端流向客戶端猬膨,而 inout 則表示數(shù)據(jù)可在服務端與客戶端之間雙向流通。其中呛伴,數(shù)據(jù)流向是針對在客戶端中的那個傳入方法的對象而言的勃痴。in 為定向 tag 的話表現(xiàn)為服務端將會接收到一個那個對象的完整數(shù)據(jù),但是客戶端的那個對象不會因為服務端對傳參的修改而發(fā)生變動热康;out 的話表現(xiàn)為服務端將會接收到那個對象的的空對象沛申,但是在服務端對接收到的空對象有任何修改之后客戶端將會同步變動;inout 為定向 tag 的情況下,服務端將會接收到客戶端傳來對象的完整信息脏答,并且客戶端將會同步服務端對該對象的任何變動型雳。另外,Java 中的基本類型和 String 著觉,CharSequence 的定向 tag 默認且只能是 in 。還有惊暴,請注意饼丘,請不要濫用定向 tag ,而是要根據(jù)需要選取合適的——要是不管三七二十一辽话,全都一上來就用 inout 肄鸽,等工程大了系統(tǒng)的開銷就會大很多——因為排列整理參數(shù)的開銷是很昂貴的卫病。

兩種AIDL文件:在我的理解里,所有的AIDL文件大致可以分為兩類典徘。一類是用來【定義parcelable對象】蟀苛,以供其他AIDL文件使用AIDL中非默認支持的數(shù)據(jù)類型的。一類是用來【定義方法接口】逮诲,以供系統(tǒng)使用來完成跨進程通信的帜平。可以看到汛骂,兩類文件都是在“定義”些什么罕模,而不涉及具體的實現(xiàn),這就是為什么它叫做“Android接口定義語言”帘瞭。
注:所有的非默認支持數(shù)據(jù)類型必須通過第一類AIDL文件定義才能被使用淑掌。

下面是兩個例子,對于常見的AIDL文件都有所涉及:

// Book.aidl
//第一類AIDL文件的例子
//這個文件的作用是引入了一個序列化對象 Book 供其他的AIDL文件使用
//注意:Book.aidl與Book.java的包名應當是一樣的
package com.lypeer.ipcclient;

//注意parcelable是小寫
parcelable Book;
// BookManager.aidl
//第二類AIDL文件的例子
package com.lypeer.ipcclient;
//導入所需要使用的非默認支持數(shù)據(jù)類型的包
import com.lypeer.ipcclient.Book;

interface BookManager {

    //所有的返回值前都不需要加任何東西蝶念,不管是什么數(shù)據(jù)類型
    List<Book> getBooks();
    Book getBook();
    int getBookCount();

    //傳參時除了Java基本類型以及String抛腕,CharSequence之外的類型
    //都需要在前面加上定向tag,具體加什么量需而定
    void setBookPrice(in Book book , int price)
    void setBookName(in Book book , String name)
    void addBookIn(in Book book);
    void addBookOut(out Book book);
    void addBookInout(inout Book book);
}

4媒殉、如何使用AIDL文件來完成跨進程通信担敌?
在進行跨進程通信的時候,在AIDL中定義的方法里包含非默認支持的數(shù)據(jù)類型與否廷蓉,我們要進行的操作是不一樣的全封。如果不包含,那么我們只需要編寫一個AIDL文件桃犬,如果包含刹悴,那么我們通常需要寫 n+1 個AIDL文件( n 為非默認支持的數(shù)據(jù)類型的種類數(shù))

4.1、使數(shù)據(jù)類實現(xiàn) Parcelable 接口
由于不同的進程有著不同的內存區(qū)域攒暇,并且它們只能訪問自己的那一塊內存區(qū)域土匀,所以我們不能像平時那樣,傳一個句柄過去就完事了——句柄指向的是一個內存區(qū)域形用。
現(xiàn)在目標進程根本不能訪問源進程的內存就轧,那把它傳過去又有什么用呢?所以我們必須將要傳輸?shù)臄?shù)據(jù)轉化為能夠在內存之間流通的形式田度。這個轉化的過程就叫做序列化與反序列化妒御。

而通常,在我們通過AIDL進行跨進程通信的時候镇饺,選擇的序列化方式是實現(xiàn) Parcelable 接口乎莉。

下面主要講一下如何快速的生成一個合格的可序列化的類(以Book.java為例)。
注:若AIDL文件中涉及到的所有數(shù)據(jù)類型均為默認支持的數(shù)據(jù)類型,則無此步驟梦鉴。因為默認支持的那些數(shù)據(jù)類型都是可序列化的。

4.1.1揭保、編譯器自動生成
Android Studio自帶了 Parcelable 接口的模板的肥橙,只需要我們敲幾下鍵盤就可以輕松的生成一個可序列化的 Parcelable 實現(xiàn)類。

首先秸侣,創(chuàng)建一個類存筏,正常的書寫其成員變量,建立getter和setter并添加一個無參構造味榛,比如:

public class Book{
    public String getName() {
        return name;
    }

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

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    private String name;

    private int price;

    public Book() {}

}

然后 implements Parcelable 椭坚,接著 as 就會報錯,將鼠標移到那里搏色,按下 alt+enter 讓它自動解決錯誤善茎,這個時候它會幫你完成一部分的工作。


image.png

在彈出來的框里選擇所有的成員變量频轿,然后確定垂涯。你會發(fā)現(xiàn)類里多了一些代碼,但是現(xiàn)在還是會報錯航邢,Book下面仍然有一條小橫線耕赘,再次將鼠標移到那里,按下 alt+enter 讓它自動解決錯誤:


image.png

這次解決完錯誤之后就不會報錯了膳殷,這個 Book 類也基本上實現(xiàn)了 Parcelable 接口操骡,可以執(zhí)行序列化操作了。
但是請注意赚窃,這里有一個坑:默認生成的模板類的對象只支持為 in 的定向 tag 册招。為什么呢?因為默認生成的類里面只有 writeToParcel() 方法考榨,而如果要支持為 out 或者 inout 的定向 tag 的話跨细,還需要實現(xiàn) readFromParcel() 方法——而這個方法其實并沒有在 Parcelable 接口里面,所以需要我們從頭寫

那么這個 readFromParcel() 方法應當怎么寫呢河质?這樣寫:

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

/**
 * 參數(shù)是一個Parcel,用它來存儲與傳輸數(shù)據(jù)
 * @param dest
 */
public void readFromParcel(Parcel dest) {
    //注意冀惭,此處的讀值順序應當是和writeToParcel()方法中一致的
    name = dest.readString();
    price = dest.readInt();
}

像上面這樣添加了 readFromParcel() 方法之后,我們的 Book 類的對象在AIDL文件里就可以用 out 或者 inout 來作為它的定向 tag 了掀鹅。

此時散休,完整的 Book 類的代碼是這樣的:

package com.lypeer.ipcclient;

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

/**
 * Book.java
 *
 * Created by lypeer on 2016/7/16.
 */
public class Book implements Parcelable{
    public String getName() {
        return name;
    }

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

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    private String name;
    private int price;
    public Book(){}

    public Book(Parcel in) {
        name = in.readString();
        price = in.readInt();
    }

    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 dest, int flags) {
        dest.writeString(name);
        dest.writeInt(price);
    }

    /**
     * 參數(shù)是一個Parcel,用它來存儲與傳輸數(shù)據(jù)
     * @param dest
     */
    public void readFromParcel(Parcel dest) {
        //注意,此處的讀值順序應當是和writeToParcel()方法中一致的
        name = dest.readString();
        price = dest.readInt();
    }

    //方便打印數(shù)據(jù)
    @Override
    public String toString() {
        return "name : " + name + " , price : " + price;
    }
}

至此乐尊,關于AIDL中非默認支持數(shù)據(jù)類型的序列化操作就完成了戚丸。

4.2、書寫AIDL文件
首先我們需要一個 Book.aidl 文件來將 Book 類引入使得其他的 AIDL 文件其中可以使用 Book 對象。那么第一步限府,如何新建一個 AIDL 文件呢夺颤?Android Studio已經(jīng)幫我們把這個集成進去了:


image.png

鼠標移到app上面去,點擊右鍵胁勺,然后 new->AIDL->AIDL File世澜,按下鼠標左鍵就會彈出一個框提示生成AIDL文件了。生成AIDL文件之后署穗,項目的目錄會變成這樣的:


image.png

比起以前多了一個叫做 aidl 的包寥裂,而且他的層級是和 java 包相同的,并且 aidl 包里默認有著和 java 包里默認的包結構案疲。那么如果你用的是 Eclipse 或者較低版本的 as 封恰,編譯器沒有這個選項怎么辦呢?沒關系褐啡,我們也可以自己寫诺舔。打開項目文件夾,依次進入 app->src->main备畦,在 main 包下新建一個和 java 文件夾平級的 aidl 文件夾混萝,然后我們手動在這個文件夾里面新建和 java 文件夾里面的默認結構一樣的文件夾結構,再在最里層新建 .aidl 文件就可以了:


image.png

注意看圖中的文件目錄萍恕。

Ok逸嘀,如何新建AIDL文件說的差不多了,接下來就該寫AIDL文件的內容了允粤。內容的話如果上一節(jié)有認真看的話基本上是沒什么問題的崭倘。在這里,我們需要兩個AIDL文件类垫,我是這樣寫的:

// Book.aidl
//第一類AIDL文件
//這個文件的作用是引入了一個序列化對象 Book 供其他的AIDL文件使用
//注意:Book.aidl與Book.java的包名應當是一樣的
package com.lypeer.ipcclient;

//注意parcelable是小寫
parcelable Book;
// BookManager.aidl
//第二類AIDL文件
//作用是定義方法接口
package com.lypeer.ipcclient;
//導入所需要使用的非默認支持數(shù)據(jù)類型的包
import com.lypeer.ipcclient.Book;

interface BookManager {

    //所有的返回值前都不需要加任何東西司光,不管是什么數(shù)據(jù)類型
    List<Book> getBooks();

    //傳參時除了Java基本類型以及String,CharSequence之外的類型
    //都需要在前面加上定向tag悉患,具體加什么量需而定
    void addBook(in Book book);
}

注意:這里又有一個坑残家!大家可能注意到了,在 Book.aidl 文件中售躁,我一直在強調:【Book.aidl與Book.java的包名應當是一樣的】坞淮。這似乎理所當然的意味著這兩個文件應當是在同一個包里面的——事實上,很多比較老的文章里就是這樣說的陪捷,他們說最好都在 aidl 包里同一個包下回窘,方便移植——然而在 Android Studio 里并不是這樣。如果這樣做的話市袖,系統(tǒng)根本就找不到 Book.java 文件啡直,從而在其他的AIDL文件里面使用 Book 對象的時候會報 Symbol not found 的錯誤。為什么會這樣呢?因為 Gradle 酒觅。大家都知道撮执,Android Studio 是默認使用 Gradle 來構建 Android 項目的,而 Gradle 在構建項目的時候會通過 sourceSets 來配置不同文件的訪問路徑舷丹,從而加快查找速度——問題就出在這里二打。Gradle 默認是將 java 代碼的訪問路徑設置在 java 包下的,這樣一來掂榔,如果 java 文件是放在 aidl 包下的話那么理所當然系統(tǒng)是找不到這個 java 文件的。那應該怎么辦呢症杏?

又要 java文件和 aidl 文件的包名是一樣的装获,又要能找到這個 java 文件——那么仔細想一下的話,其實解決方法是很顯而易見的厉颤。首先我們可以把問題轉化成:如何在保證兩個文件包名一樣的情況下穴豫,讓系統(tǒng)能夠找到我們的 java 文件?這樣一來思路就很明確了:要么讓系統(tǒng)來 aidl 包里面來找 java 文件逼友,要么把 java 文件放到系統(tǒng)能找到的地方去精肃,也即放到 java 包里面去。接下來我詳細的講一下這兩種方式具體應該怎么做:

修改 build.gradle 文件:在 android{} 中間加上下面的內容:

sourceSets {
    main {
        java.srcDirs = ['src/main/java', 'src/main/aidl']
    }
}

也就是把 java 代碼的訪問路徑設置成了 java 包和 aidl 包帜乞,這樣一來系統(tǒng)就會到 aidl 包里面去查找 java 文件司抱,也就達到了我們的目的。只是有一點黎烈,這樣設置后 Android Studio 中的項目目錄會有一些改變习柠,我感覺改得挺難看的。

把 java 文件放到 java 包下去:把 Book.java 放到 java 包里任意一個包下照棋,保持其包名不變资溃,與 Book.aidl 一致。只要它的包名不變烈炭,Book.aidl 就能找到 Book.java 溶锭,而只要 Book.java 在 java 包下,那么系統(tǒng)也是能找到它的符隙。但是這樣做的話也有一個問題趴捅,就是在移植相關 .aidl 文件和 .java 文件的時候沒那么方便,不能直接把整個 aidl 文件夾拿過去完事兒了霹疫,還要單獨將 .java 文件放到 java 文件夾里去驻售。
我們可以用上面兩個方法之一來解決找不到 .java 文件的坑,具體用哪個就看大家怎么選了更米,反正都挺簡單的欺栗。

到這里我們就已經(jīng)將AIDL文件新建并且書寫完畢了,clean 一下項目,如果沒有報錯迟几,這一塊就算是大功告成了消请。

4.3、移植相關文件
我們需要保證类腮,在客戶端和服務端中都有我們需要用到的 .aidl 文件和其中涉及到的 .java 文件臊泰,因此不管在哪一端寫的這些東西,寫完之后我們都要把這些文件復制到另一端去蚜枢。如果是用的上面兩個方法中的第一個解決的找不到 .java 文件的問題缸逃,那么直接將 aidl 包復制到另一端的 main 目錄下就可以了;如果是使用第二個方法的話厂抽,就除了把把整個 aidl 文件夾拿過去需频,還要單獨將 .java 文件放到 java 文件夾里去。

4.4筷凤、編寫服務端代碼
通過上面幾步昭殉,我們已經(jīng)完成了AIDL及其相關文件的全部內容,那么我們究竟應該如何利用這些東西來進行跨進程通信呢藐守?其實挪丢,在我們寫完AIDL文件并 clean 或者 rebuild 項目之后,編譯器會根據(jù)AIDL文件為我們生成一個與AIDL文件同名的 .java 文件卢厂,這個 .java 文件才是與我們的跨進程通信密切相關的東西乾蓬。事實上,基本的操作流程就是:在服務端實現(xiàn)AIDL中定義的方法接口的具體邏輯慎恒,然后在客戶端調用這些方法接口巢块,從而達到跨進程通信的目的。
接下來我直接貼上我寫的服務端代碼:

/**
 * 服務端的AIDLService.java
 * <p/>
 * Created by lypeer on 2016/7/17.
 */
public class AIDLService extends Service {

    public final String TAG = this.getClass().getSimpleName();

    //包含Book對象的list
    private List<Book> mBooks = new ArrayList<>();

    //由AIDL文件生成的BookManager
    private final BookManager.Stub mBookManager = new BookManager.Stub() {
        @Override
        public List<Book> getBooks() throws RemoteException {
            synchronized (this) {
                Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());
                if (mBooks != null) {
                    return mBooks;
                }
                return new ArrayList<>();
            }
        }


        @Override
        public void addBook(Book book) throws RemoteException {
            synchronized (this) {
                if (mBooks == null) {
                    mBooks = new ArrayList<>();
                }
                if (book == null) {
                    Log.e(TAG, "Book is null in In");
                    book = new Book();
                }
                //嘗試修改book的參數(shù)巧号,主要是為了觀察其到客戶端的反饋
                book.setPrice(2333);
                if (!mBooks.contains(book)) {
                    mBooks.add(book);
                }
                //打印mBooks列表族奢,觀察客戶端傳過來的值
                Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());
            }
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Book book = new Book();
        book.setName("Android開發(fā)藝術探索");
        book.setPrice(28);
        mBooks.add(book);   
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));
        return mBookManager;
    }
}

整體的代碼結構很清晰,大致可以分為三塊:
第一塊是初始化丹鸿。在 onCreate() 方法里面我進行了一些數(shù)據(jù)的初始化操作越走。
第二塊是重寫 BookManager.Stub 中的方法。在這里面提供AIDL里面定義的方法接口的具體實現(xiàn)邏輯靠欢。
第三塊是重寫 onBind() 方法廊敌。在里面返回寫好的 BookManager.Stub 。

接下來在 Manefest 文件里面注冊這個我們寫好的 Service 门怪,這個不寫的話我們前面做的工作都是無用功:

<service
    android:name=".service.AIDLService"
    android:exported="true">
        <intent-filter>
            <action android:name="com.lypeer.aidl"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
</service>

到這里我們的服務端代碼就編寫完畢了骡澈,如果你對里面的一些地方感覺有些陌生或者根本不知所云的話,說明你對 Service 相關的知識已經(jīng)有些遺忘了掷空,建議再去看看這兩篇博文
https://blog.csdn.net/luoyanglizi/article/details/51586437
https://blog.csdn.net/luoyanglizi/article/details/51594016

4.5肋殴、編寫客戶端代碼
前面說過囤锉,在客戶端我們要完成的工作主要是調用服務端的方法,但是在那之前护锤,我們首先要連接上服務端官地,完整的客戶端代碼是這樣的:

/**
 * 客戶端的AIDLActivity.java
 * 由于測試機的無用debug信息太多,故log都是用的e
 * <p/>
 * Created by lypeer on 2016/7/17.
 */
public class AIDLActivity extends AppCompatActivity {

    //由AIDL文件生成的Java類
    private BookManager mBookManager = null;

    //標志當前與服務端連接狀況的布爾值烙懦,false為未連接驱入,true為連接中
    private boolean mBound = false;

    //包含Book對象的list
    private List<Book> mBooks;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
    }

    /**
     * 按鈕的點擊事件,點擊之后調用服務端的addBookIn方法
     *
     * @param view
     */
    public void addBook(View view) {
        //如果與服務端的連接處于未連接狀態(tài)氯析,則嘗試連接
        if (!mBound) {
            attemptToBindService();
            Toast.makeText(this, "當前與服務端處于未連接狀態(tài)亏较,正在嘗試重連,請稍后再試", Toast.LENGTH_SHORT).show();
            return;
        }
        if (mBookManager == null) return;

        Book book = new Book();
        book.setName("APP研發(fā)錄In");
        book.setPrice(30);
        try {
            mBookManager.addBook(book);
            Log.e(getLocalClassName(), book.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 嘗試與服務端建立連接
     */
    private void attemptToBindService() {
        Intent intent = new Intent();
        intent.setAction("com.lypeer.aidl");
        intent.setPackage("com.lypeer.ipcserver");
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!mBound) {
            attemptToBindService();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mServiceConnection);
            mBound = false;
        }
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(getLocalClassName(), "service connected");
            mBookManager = BookManager.Stub.asInterface(service);
            mBound = true;

            if (mBookManager != null) {
                try {
                    mBooks = mBookManager.getBooks();
                    Log.e(getLocalClassName(), mBooks.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(getLocalClassName(), "service disconnected");
            mBound = false;
        }
    };
}

同樣很清晰掩缓,首先建立連接雪情,然后在 ServiceConnection 里面獲取 BookManager 對象,接著通過它來調用服務端的方法拾因。

4.6、開始通信吧

通過上面的步驟旷余,我們已經(jīng)完成了所有的前期工作绢记,接下來就可以通過AIDL來進行跨進程通信了!將兩個app同時運行在同一臺手機上正卧,然后調用客戶端的 addBook() 方法蠢熄,我們會看到服務端的 logcat 信息是這樣的:

//服務端的 log 信息,我把無用的信息頭去掉了炉旷,然后給它編了個號
1签孔,on bind,intent = Intent { act=com.lypeer.aidl pkg=com.lypeer.ipcserver }
2,invoking getBooks() method , now the list is : [name : Android開發(fā)藝術探索 , price : 28]
3窘行,invoking addBooks() method , now the list is : [name : Android開發(fā)藝術探索 , price : 28, name : APP研發(fā)錄In , price : 2333]

客戶端的信息是這樣的:

//客戶端的 log 信息
1饥追,service connected
2,[name : Android開發(fā)藝術探索 , price : 28]
3罐盔,name : APP研發(fā)錄In , price : 2333

所有的 log 信息都很正常并且符合預期——這說明我們到這里為止的步驟都是正確的但绕,按照上面說的來做是能夠正確的使用AIDL來進行跨進程通信的。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末惶看,一起剝皮案震驚了整個濱河市捏顺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纬黎,老刑警劉巖幅骄,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異本今,居然都是意外死亡拆座,警方通過查閱死者的電腦和手機主巍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懂拾,“玉大人煤禽,你說我怎么就攤上這事♂常” “怎么了檬果?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唐断。 經(jīng)常有香客問我选脊,道長,這世上最難降的妖魔是什么脸甘? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任恳啥,我火速辦了婚禮,結果婚禮上丹诀,老公的妹妹穿的比我還像新娘钝的。我一直安慰自己,他們只是感情好铆遭,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布硝桩。 她就那樣靜靜地躺著,像睡著了一般枚荣。 火紅的嫁衣襯著肌膚如雪碗脊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天橄妆,我揣著相機與錄音衙伶,去河邊找鬼。 笑死害碾,一個胖子當著我的面吹牛矢劲,可吹牛的內容都是我干的。 我是一名探鬼主播慌随,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼卧须,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了儒陨?” 一聲冷哼從身側響起花嘶,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹦漠,沒想到半個月后椭员,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡笛园,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年隘击,在試婚紗的時候發(fā)現(xiàn)自己被綠了侍芝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡埋同,死狀恐怖州叠,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情凶赁,我是刑警寧澤咧栗,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站虱肄,受9級特大地震影響致板,放射性物質發(fā)生泄漏。R本人自食惡果不足惜咏窿,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一斟或、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧集嵌,春花似錦萝挤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至咽块,卻和暖如春绘面,著一層夾襖步出監(jiān)牢的瞬間欺税,已是汗流浹背侈沪。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留晚凿,地道東北人亭罪。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像歼秽,于是被迫代替她去往敵國和親应役。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內容