IPC機(jī)制 -- IPC方式 -- AIDL(1)

一腿短、AIDL基礎(chǔ)

文件類型:

后綴是 .aidl屏箍,而不是 .java绘梦。

數(shù)據(jù)類型:

1.默認(rèn)支持的數(shù)據(jù)類型
在使用這些數(shù)據(jù)類型的時(shí)候是不需要導(dǎo)包的。
(1)Java中的八種基本數(shù)據(jù)類型赴魁,包括 byte卸奉,short,int颖御,long榄棵,float,double潘拱,boolean疹鳄,char。
(2)String 類型芦岂。
(3)CharSequence類型瘪弓。
(4)List類型:只支持ArrayList,里面的所有元素都必須能夠被AIDL支持禽最。
注意:CopyOnWriteArrayList腺怯,支持并發(fā)讀/寫,可在服務(wù)端中使用川无,在Binder中會(huì)按照List的規(guī)范去訪問(wèn)數(shù)據(jù)并最終形成一個(gè)新的ArrayList傳遞給客戶端呛占。
(5)Map類型:只支持HashMap,里面的所有元素都必須能夠被AIDL支持懦趋,包括key和value栓票。
注意:ConcurrentHashMap,支持并發(fā)讀/寫愕够,可在服務(wù)端中使用走贪,在Binder中會(huì)按照Map的規(guī)范去訪問(wèn)數(shù)據(jù)并最終形成一個(gè)新的HashMap傳遞給客戶端。

2.非默認(rèn)支持的數(shù)據(jù)類型
在使用這些數(shù)據(jù)類型的時(shí)候必須導(dǎo)包惑芭,就算目標(biāo)文件與當(dāng)前正在編寫的 .aidl 文件在同一個(gè)包下也一樣要導(dǎo)包坠狡。
(1)Parcelable:所有實(shí)現(xiàn)了Parcelable接口的對(duì)象。
(2)AIDL:所有的AIDL接口本身也可以在AIDL文件中使用遂跟。

定向tag:

AIDL中的定向 tag 表示了在跨進(jìn)程通信中數(shù)據(jù)的流向逃沿,其中 in 表示數(shù)據(jù)只能由客戶端流向服務(wù)端, out 表示數(shù)據(jù)只能由服務(wù)端流向客戶端幻锁,而 inout 則表示數(shù)據(jù)可在服務(wù)端與客戶端之間雙向流通凯亮。其中,數(shù)據(jù)流向是針對(duì)客戶端中傳入方法的對(duì)象參數(shù)而言的哄尔。
in:表現(xiàn)為服務(wù)端將會(huì)接收到一個(gè)對(duì)象參數(shù)的完整數(shù)據(jù)假消,但是客戶端的那個(gè)對(duì)象參數(shù)不會(huì)因?yàn)榉?wù)端對(duì)接收到的對(duì)象參數(shù)的修改而發(fā)生變動(dòng);
out:表現(xiàn)為服務(wù)端將會(huì)接收到一個(gè)對(duì)象參數(shù)的空對(duì)象岭接,但是在服務(wù)端對(duì)接收到的空對(duì)象有任何修改之后客戶端將會(huì)同步變動(dòng)富拗;
inout:表現(xiàn)為服務(wù)端將會(huì)接收到一個(gè)對(duì)象參數(shù)的完整數(shù)據(jù)臼予,并且在服務(wù)端對(duì)接收到的對(duì)象參數(shù)有任何修改之后客戶端將會(huì)同步變動(dòng)。
另外啃沪,Java 中的基本類型和 String 粘拾,CharSequence 的定向 tag 默認(rèn)且只能是 in。

AIDL文件分類:

所有的AIDL文件大致可以分為兩類:
第一類:用來(lái)定義parcelable對(duì)象创千,以供其他AIDL文件使用AIDL中非默認(rèn)支持的數(shù)據(jù)類型的缰雇;
第二類:用來(lái)定義方法接口,以供系統(tǒng)使用來(lái)完成跨進(jìn)程通信的追驴。
可以看到寓涨,兩類文件都是在"定義"些什么,而不涉及具體的實(shí)現(xiàn)氯檐,這就是為什么它叫做"Android接口定義語(yǔ)言"。
注意:所有的非默認(rèn)支持的數(shù)據(jù)類型必須通過(guò)第一類AIDL文件定義才能被使用体捏。

其它:

1.AIDL接口中只支持方法冠摄,不支持聲明靜態(tài)常量。

二几缭、AIDL使用

1.使數(shù)據(jù)類實(shí)現(xiàn)Parcelable接口

由于不同的進(jìn)程有著不同的內(nèi)存區(qū)域河泳,并且它們只能訪問(wèn)自己的那一塊內(nèi)存區(qū)域,因此我們無(wú)法從源進(jìn)程向目標(biāo)進(jìn)程傳遞一個(gè)句柄(句柄指向的是一個(gè)內(nèi)存區(qū)域)年栓,而必須將要傳輸?shù)臄?shù)據(jù)轉(zhuǎn)化為能夠在內(nèi)存之間流通的形式拆挥。這個(gè)轉(zhuǎn)化的過(guò)程就叫做序列化與反序列化。
序列化與反序列化的大概流程為:我們要將一個(gè)對(duì)象的數(shù)據(jù)從客戶端傳到服務(wù)端某抓,我們就可以在客戶端對(duì)這個(gè)對(duì)象進(jìn)行序列化的操作纸兔,將其中包含的數(shù)據(jù)轉(zhuǎn)化為序列化流,然后將這個(gè)序列化流傳輸?shù)椒?wù)端的內(nèi)存中否副,再在服務(wù)端對(duì)這個(gè)數(shù)據(jù)流進(jìn)行反序列化的操作汉矿,從而還原其中包含的數(shù)據(jù)。通過(guò)這種方式备禀,我們就達(dá)到了在一個(gè)進(jìn)程中訪問(wèn)另一個(gè)進(jìn)程的數(shù)據(jù)的目的洲拇。

//數(shù)據(jù)類實(shí)現(xiàn)Parcelable接口
public class Book implements Parcelable{
    private String name;
    private int price;

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

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

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

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

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

    //AIDL文件中,方法參數(shù)的定向tag為 out 跟 inout 時(shí)曲尸,需要用到此方法
    //作用:服務(wù)端修改客戶端傳遞的對(duì)象參數(shù)赋续,傳回到客戶端,客戶端通過(guò)此方法讀取傳回的數(shù)據(jù)
    public void readFromParcel(Parcel dest) {
        //注意另患,此處的讀值順序應(yīng)當(dāng)是和writeToParcel()方法中一致的
        name = dest.readString();
        price = dest.readInt();
    }

    @Override
    public String toString() {
        return "name : " + name + " , price : " + price;
    }
}

2.創(chuàng)建AIDL文件

鼠標(biāo)移到app上面去纽乱,點(diǎn)擊右鍵,然后 new==>AIDL==>AIDL File昆箕,按下鼠標(biāo)左鍵就會(huì)彈出一個(gè)框提示生成AIDL文件了迫淹。項(xiàng)目的目錄比起以前多了一個(gè)叫做 aidl 的包秘通,而且它的層級(jí)是和 java 包相同的,并且 aidl 包里默認(rèn)有著和 java 包里默認(rèn)的包結(jié)構(gòu)敛熬。

//Book.aidl
//第一類AIDL文件
//作用是引入了一個(gè)序列化對(duì)象 Book 供其他的AIDL文件使用
//注意:Book.aidl與Book.java的包名應(yīng)當(dāng)是一樣的
package com.tomorrow.androidtest7.aidl;

//注意parcelable是小寫
parcelable Book;


//BookManager.aidl
//第二類AIDL文件
//作用是定義方法接口
package com.tomorrow.androidtest7.aidl;
//導(dǎo)入所需要使用的非默認(rèn)支持?jǐn)?shù)據(jù)類型的包
import com.tomorrow.androidtest7.aidl.Book;

interface BookManager {

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

    //傳參時(shí)除了Java基本類型以及String,CharSequence之外的類型
    //都需要在前面加上定向tag应民,具體加什么量需而定
    void addBookWithTagIn(in Book book);
    void addBookWithTagOut(out Book book);
    void addBookWithTagInOut(inout Book book);
}

3.移植相關(guān)文件

我們需要保證话原,在客戶端和服務(wù)端中都有我們需要用到的 .aidl 文件(如Book.aidl、BookManager.aidl)和其中涉及到的 .java 文件(如Book.java)诲锹,因此不管在哪一端寫這些文件繁仁,寫完之后我們都要把這些文件復(fù)制到另一端去,并且保證在客戶端跟服務(wù)端有相同的目錄归园。

4.編寫服務(wù)端代碼

在我們寫完AIDL文件并 clean 或者 rebuild 項(xiàng)目之后黄虱,編譯器會(huì)根據(jù)AIDL文件為我們生成一個(gè)與AIDL文件同名的 .java 文件,這個(gè) .java 文件與跨進(jìn)程通信密切相關(guān)庸诱。
基本的操作流程就是:在服務(wù)端實(shí)現(xiàn)AIDL中定義的方法接口的具體邏輯捻浦,然后在客戶端調(diào)用這些方法接口,從而達(dá)到跨進(jìn)程通信的目的桥爽。

//服務(wù)端代碼
public class AIDLService extends Service {

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

    //包含Book對(duì)象的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, "zwm, invoking getBooks() method , now the list is : " + mBooks.toString());
                if (mBooks != null) {
                    return mBooks;
                }
                return new ArrayList<>();
            }
        }

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

        @Override
        public void addBookWithTagOut(Book book) throws RemoteException {
            Log.e(TAG, "zwm, addBookWithTagOut");
            addBookWithTagIn(book);
        }

        @Override
        public void addBookWithTagInOut(Book book) throws RemoteException {
            Log.e(TAG, "zwm, addBookWithTagInOut");
            addBookWithTagIn(book);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "zwm, onCreate");
        Book book = new Book();
        book.setName("Android開發(fā)藝術(shù)探索");
        book.setPrice(88);
        mBooks.add(book);
    }

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

//在AndroidManifest.xml注冊(cè)Service
<service
    android:name=".service.AIDLService"
    android:exported="true">
    <intent-filter>
        <action android:name="com.tomorrow.aidl"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</service>

5.編寫客戶端代碼

客戶端的工作主要是綁定服務(wù)端钠四,并調(diào)用服務(wù)端的方法盗扒。

//客戶端代碼
public class AIDLActivity extends AppCompatActivity {
    //由AIDL文件生成的Java類
    private BookManager mBookManager = null;

    //標(biāo)志當(dāng)前與服務(wù)端連接狀況的布爾值,false為未連接缀去,true為連接中
    private boolean mBound = false;

    //包含Book對(duì)象的list
    private List<Book> mBooks;

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

    /**
     * 按鈕的點(diǎn)擊事件侣灶,點(diǎn)擊之后調(diào)用服務(wù)端的addBook方法
     */
    public void addBook() {
        Log.e(getLocalClassName(), "zwm, addBook");
        //如果與服務(wù)端的連接處于未連接狀態(tài),則嘗試連接
        if (!mBound) {
            attemptToBindService();
            Toast.makeText(this, "當(dāng)前與服務(wù)端處于未連接狀態(tài)缕碎,正在嘗試重連炫隶,請(qǐng)稍后再試", Toast.LENGTH_SHORT).show();
            return;
        }
        if (mBookManager == null) return;

        Book book = new Book();
        book.setName("APP研發(fā)錄");
        book.setPrice(66);
        try {
            mBooks = mBookManager.getBooks();
            Log.e(getLocalClassName(), "zwm, before add book, 參數(shù):" + book.toString());
            Log.e(getLocalClassName(), "zwm, before add book, 返回值:" + mBooks.toString());
            mBookManager.addBookWithTagInOut(book);
            mBooks = mBookManager.getBooks();
            Log.e(getLocalClassName(), "zwm, after add book, 參數(shù):" + book.toString());
            Log.e(getLocalClassName(), "zwm, after add book, 返回值:" + mBooks.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

        /**
         * 嘗試與服務(wù)端建立連接
         */
    private void attemptToBindService() {
        Log.e(getLocalClassName(), "zwm, attemptToBindService");
        Intent intent = new Intent();
        intent.setClassName("com.tomorrow.androidtest7", "com.tomorrow.androidtest7.service.AIDLService");
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.e(getLocalClassName(), "zwm, onStart");
        if (!mBound) {
            attemptToBindService();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.e(getLocalClassName(), "zwm, onStop");
        if (mBound) {
            unbindService(mServiceConnection);
            mBound = false;
        }
    }

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

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

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

//在AndroidManifest.xml注冊(cè)Activity
<activity android:name=".activity.AIDLActivity"/>

6.測(cè)試

將兩個(gè)app同時(shí)運(yùn)行在同一臺(tái)手機(jī)上,就可以開始進(jìn)程間通信了阎曹。

三伪阶、高級(jí)用法

1.給Binder設(shè)置死亡代理

Binder運(yùn)行在服務(wù)端進(jìn)程,如果服務(wù)端進(jìn)程由于某種原因異常終止处嫌,這個(gè)時(shí)候我們到服務(wù)端的Binder連接斷裂(稱為Binder死亡)栅贴,會(huì)導(dǎo)致我們的遠(yuǎn)程調(diào)用失敗。更關(guān)鍵的是熏迹,如果我們不知道Binder連接已經(jīng)斷裂檐薯,那么客戶端的功能就會(huì)受到影響。這時(shí)我們可以給Binder設(shè)置一個(gè)死亡代理,當(dāng)Binder死亡時(shí)坛缕,我們就會(huì)收到通知墓猎,然后可以重新發(fā)起連接請(qǐng)求從而恢復(fù)連接。Binder提供兩個(gè)配對(duì)方法linkToDeathunlinkToDeath赚楚,使用代碼如下:

//AIDLActivity
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {

    @Override
    public void binderDied() {
        Log.e(getLocalClassName(), "zwm, binderDied");
        if(mBookManager == null)
            return;
        mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
        mBookManager = null;
        //重新綁定遠(yuǎn)程service
        attemptToBindService();
    }
};

private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mBookManager = BookManager.Stub.asInterface(service);
        try {
            service.linkToDeath(mDeathRecipient, 0); //第二個(gè)參數(shù)直接設(shè)為0即可
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        //省略
    }
    //省略
}

注意:binderDied方法運(yùn)行在Binder線程池毙沾。
另外,通過(guò)Binder的方法isBinderAlive也可以判斷Binder是否死亡宠页。

2.觀察者模式

定義AIDL監(jiān)聽接口左胞,實(shí)現(xiàn)客戶端對(duì)服務(wù)端的數(shù)據(jù)進(jìn)行監(jiān)聽,當(dāng)服務(wù)端的數(shù)據(jù)改變時(shí)會(huì)通知客戶端举户。

//AIDL監(jiān)聽接口
package com.tomorrow.androidtest7.aidl;
import com.tomorrow.androidtest7.aidl.Book;
interface OnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}

//在原有的接口中添加兩個(gè)新方法
package com.tomorrow.androidtest7.aidl;
import com.tomorrow.androidtest7.aidl.Book;
import com.tomorrow.androidtest7.aidl.OnNewBookArrivedListener;
interface BookManager {
    List<Book> getBooks();
    void addBookWithTagIn(in Book book);
    void addBookWithTagOut(out Book book);
    void addBookWithTagInOut(inout Book book);

    void registerListener(OnNewBookArrivedListener  listener); //注冊(cè)監(jiān)聽
    void unregisterListener(OnNewBookArrivedListener  listener); //解注冊(cè)監(jiān)聽
}

問(wèn)題:
客戶端創(chuàng)建OnNewBookArrivedListener.Stub Binder對(duì)象烤宙,調(diào)用服務(wù)端BookManager.Stub.Proxy#registerListener(listener)方法進(jìn)行注冊(cè)監(jiān)聽,注冊(cè)監(jiān)聽之后客戶端能成功接收到服務(wù)端的通知俭嘁。
客戶端再調(diào)用服務(wù)端BookManager.Stub.Proxy#unregisterListener(listener)方法進(jìn)行解注冊(cè)監(jiān)聽躺枕,解注冊(cè)監(jiān)聽之后客戶端仍然能成功接收到服務(wù)端的通知。
原因:
對(duì)客戶端來(lái)說(shuō)供填,注冊(cè)監(jiān)聽跟解注冊(cè)監(jiān)聽的listener對(duì)象是同一個(gè)拐云。但是對(duì)服務(wù)端來(lái)說(shuō),服務(wù)端接收到的注冊(cè)監(jiān)聽跟解注冊(cè)監(jiān)聽的listener對(duì)象卻是不同的捕虽,因?yàn)閷?duì)象不能跨進(jìn)程直接傳輸,對(duì)象的跨進(jìn)程傳輸本質(zhì)上都是反序列化的過(guò)程坡脐,因此在服務(wù)端重新生成了兩個(gè)對(duì)象泄私,導(dǎo)致解注冊(cè)失敗。
方案:
在服務(wù)端使用系統(tǒng)專門提供的用于刪除跨進(jìn)程listener的接口RemoteCallbackList备闲。
RemoteCallbackList是一個(gè)泛型晌端,支持管理任意的AIDL接口。雖然多次跨進(jìn)程傳輸客戶端的同一個(gè)對(duì)象會(huì)在服務(wù)端生成不同的對(duì)象恬砂,但是這些新生成的對(duì)象有一個(gè)共同點(diǎn)咧纠,那就是它們底層的Binder對(duì)象是同一個(gè),RemoteCallbackList就是利用這個(gè)特性來(lái)完成注冊(cè)監(jiān)聽跟解注冊(cè)監(jiān)聽功能泻骤。

//服務(wù)端BookManager$Stub的實(shí)現(xiàn)代碼
private RemoteCallbackList<OnNewBookArrivedListener> mListenerList = new RemoteCallbackList<OnNewBookArrivedListener>();

@Override
public void registerListener(OnNewBookArrivedListener listener) throws RemoteException {
    mListenerList.register(listener);
}

@Override
public void unregisterListener(OnNewBookArrivedListener listener) throws RemoteException {
    mListenerList.unregister(listener);
}

private void onNewBookArrived(Book book) throws RemoteException {
    mBookList.add(book);
    final int N = mListenerList.beginBroadcast(); //要跟finishBroadcast匹配
    for(int i = 0; i < N; i++) {
        OnNewBookArrivedListener  listener = mListenerList.getBroadcastItem(i);
        if(listener != null) {
            try {
                listener.onNewBookArrived(book);
            } catch(RemoteException e) {
                e.printStackTrace();
            }
        }
    }
    mListenerList.finishBroadcast(); //要跟beginBroadcast匹配
}

3.在AIDL中進(jìn)行權(quán)限驗(yàn)證

方法一:在服務(wù)端的AIDL方法中進(jìn)行權(quán)限驗(yàn)證

//在服務(wù)端的AndroidManifest.xml中聲明所需的權(quán)限
<permission
    android:name="com.tomorrow.androidtest7.permission.ACCESS_BOOK_SERVICE"
    android:protectionLevel="normal" />

//服務(wù)端的AIDL方法
int check = checkCallingOrSelfPermission("com.tomorrow.androidtest7.permission.ACCESS_BOOK_SERVICE");
if(check == PackageManager.PERMISSION_DENIED) {
    return;
}
...

//如果客戶端想綁定服務(wù)漆羔,需要在AndroidManifest.xml中使用所需的權(quán)限
<uses-permission android:name="com.tomorrow.androidtest7.permission.ACCESS_BOOK_SERVICE" />

方法二:在服務(wù)端Stub的onTransact方法中進(jìn)行權(quán)限驗(yàn)證

@Override  
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)  
        throws RemoteException {  
    // TODO Auto-generated method stub  
    int checkPermission = checkCallingOrSelfPermission("com.tomorrow.androidtest7.permission.ACCESS_BOOK_SERVICE");  
    if (checkPermission == PackageManager.PERMISSION_DENIED) {  
        return false;  
    }  
    String packageName = "";  
    //通過(guò)getCallingUid()方法得到客戶端的uid,  
    //接著通過(guò)PackageManager的getPackagesForUid(int uid)方法得到客戶端Package相關(guān)信息  
    String[] packages = getPackageManager().getPackagesForUid(getCallingUid());  
    if (packages != null && packages.length > 0) {  
        //字符串?dāng)?shù)組packages中第一個(gè)字符串是包名信息  
        packageName = packages[0];  
    }  
    //如果包名不是以"com.tomorrow"開頭狱掂,直接返回false演痒,權(quán)限驗(yàn)證失敗  
    if (!packageName.startsWith("com.tomorrow")) {  
        return false;  
    }  
    return super.onTransact(code, data, reply, flags);  
}

方法三:為服務(wù)端Service指定android:permission屬性
方法四:其它方法

最后編輯于
?著作權(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)離奇詭異蹦锋,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)欧芽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門莉掂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人渐裸,你說(shuō)我怎么就攤上這事巫湘。” “怎么了昏鹃?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵尚氛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我洞渤,道長(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)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼厢呵!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起傀顾,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤襟铭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(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
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)做祝。三九已至,卻和暖如春株搔,著一層夾襖步出監(jiān)牢的瞬間剖淀,已是汗流浹背纯蛾。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工纤房, 沒(méi)想到剛下飛機(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