Binder和AIDL实例及原理解析

AIDL和Binder简介

  • 他们都与IPC(远程调用)有关
  • Binder是一个实现IBinder的类,提供了两个与Binder驱动通信的重要接口方法,你可以通过它实现自定义的RPC协议(ClientServerService Manager
    (1)Transact():客户端调用,用于发送调用请求
    (2)onTransact():服务端响应,用于接收调用请求
  • Service与客户端通信,有两种方式,AIDL和Messenger。AIDL基于Binder,而Messenger基于AIDL。
  • AIDL是android提供的接口定义语言,借助这个工具,你可以很轻松地实现IPC通信机制,根据需要灵活定义接口。
  • 作用范围:
    (1)Binder:如果是在一个应用里实现远程调用,使用Binder即可,没必要使用AIDL。
    (2)AIDL:如果涉及到在多个应用程序之间使用IPC通信,并且在服务又有多线程业务处理,这时可以使用AIDL。

Binder

pic1.jpeg

注意:Client、Server、ServiceManager之间是相互独立互不干涉,都是通过Binder驱动进行交互的

Binder的进程间通信,一共有四个角色:

  • Client:客户端(使用服务的进程)

  • Server:服务端(提供服务的进程)

  • ServiceManager:
    (1) ServiceManager 是 Binder 进程间通信的核心组件之一,扮演者 Binder 进程间通信机制的上下文管理者 ( Context Manager ) 的角色。
    (2)负责管理系统中的 Service (如AMS)组件,并且向 Client 组件提供获取 Service 代理对象的服务(开启循环,处理IPC请求)。

  • Binder驱动:底层的驱动架构与Linux驱动一样。binder驱动在以misc设备进行注册,作为虚拟设备,没有直接操作硬件,只是对设备内存的处理。主要是驱动设备的初始化binder_init,打开 binder_open,映射binder_mmap,数据操作binder_ioctl
    (1) binder_init: 注册驱动
    (2) binder_open: 创建一个struct binder_proc数据结构来保存打开设备文件/dev/binder的进程的上下文信息
    (3) binder_mmap : 进程空间和内核空间的虚拟地址映射到同一个物理页面,从而达到进程空间和内核空间共享一块物理页面的目的

    为什么要这么映射地址呢?

    把同一块物理页面同时映射到进程空间和内核空间时,当需要在两者之间传递数据时,只需要其中任意一方把数据拷贝到物理页面,另一方直接读取即可,也就是说,数据的跨进程传递,只需要一次拷贝就可以完成。

    传统的跨进程通信(发送进程发送数据到接收进程):

    第一次拷贝过程:发送进程—>用户空间—>(拷贝一次数据)—>内核空间
    第二次拷贝过程:接收进程—>内核空间—>(拷贝一次数据)—>用户空间

    Binder(内存映射)跨进程通信:

    第一次拷贝过程:发送进程—>用户空间—>(拷贝一次数据)—>内核空间(用户空间和内核空间映射到同一段物理地址,这样第一次复制到内核空间,其实目标的用户空间上也有这段数据了)—>用户空间

    这样相当于少了一次复制

    Binder和其他进程间通信的区别,总结一下,Binder具有以下优点:
    • 高效:Binder拷贝数据只需要1次,管道、Socket、消息队列都需要2次
    • 使用简单:采用C/S架构,实现面向对象调用方式,使用Binder跟调用本地对象一样操作简单
    • 安全性高:Binder给每个进程分配UID/PID作为身份标识,通信时会根据UID/PID校验身份,其他通信方式没有严格的校验过程

(4) binder_ioctl : 两个进程间收发IPC数据和IPC reply数据

除了Binder进程通信还有哪几种?
共享内存(Share Memory)(需拷贝0次)

共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在该进程的地址空间中。其他进程可以将同一段共享内存连接到自己的地址空间中。所有进程都可以访问共享内存中的地址,如果一个进程向共享内存中写入了数据,所做的改动将立刻被其他进程看到。

共享内存是IPC最快捷的方式,共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是同一块的物理空间,仅仅映射到各进程的地址不同而已,因此不需要进行复制,可以直接使用此段空间
共享内存本身并没有同步机制,需要程序员自己控制。

内存映射(Memory Map)

内存映射是由一个文件到一块内存的映射,在此之后进程操作文件,就像操作进程空间里的内存地址一样了。

管道 (需拷贝2次)

管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。
特点:

  • 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
  • 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
  • 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
消息队列 (需拷贝2次)

是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
特点:

  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  • 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
特点:

  • 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
  • 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。P操作是(pass通过)获取操作权,如果当前信号量大于0则使信号量减1并获取资源,否则阻塞等待;V操作是释放操作权,使当前信号量加1,若信号量大于0则从等待的P操作中唤醒一个继续执行。
  • 每次对信号量的 PV 操作不仅限于对信号量值减 1 或加 1,而且可以加减任意正整数。
  • 支持信号量组。
Socket (需拷贝2次)

Binder实例

Client-ServiceManager-Server时序图

pic6.jpeg
流程如下:
  • 第一步Server端通过Binder驱动向ServiceManager注册信息,在Binder驱动创建mRemote对象。(传递的参数打包成Parcel对象transact()
  • 第二步Client端通过Binder驱动向ServiceManager请求Server的Binder引用,然后Binder驱动将对应的mRemote对象返回
  • 第三步Client获得Binder引用(mRemote对象,该对象对应如下示例中的MyServiceProxy对象)后,通过调用mRemote对象中的callHi方法将传递的参数打包成Parcel对象再通过transact()向Binder驱动层写入,这个时候Client端的线程会挂起,而Server端中的onTransact方法收到Binder驱动层的回调后,会进行执行指令从而调用真正的callHi执行其逻辑,执行成功后会调用reply.writeNoException进行应答,而Binder驱动收到应答后会唤醒Client端被挂起的线程
    p8.jpeg
如果还是看不懂,我们直接上栗子来看吧,这样会更加直观些。

1 Server端

1.1 定义IMyService接口

public interface IMyService extends IInterface {
    //DESCRIPTOR是唯一标识
    static final java.lang.String DESCRIPTOR = " com.shengyuan.Server";
    //TRANSACTION_say是binder通信的cmd
    static final int TRANSACTION_say = android.os.IBinder.FIRST_CALL_TRANSACTION; 
    //service中的接口,提供给客户端用实现某些功能
    public void callHi(String str) throws RemoteException ;
}

1.2 继承Binder,实现IMyService接口,重写onTransact方法

public class MyService extends Binder implements IMyService{
//继承自Binder实现IMyService接口

    public MyService() {
        this.attachInterface(this, DESCRIPTOR);
    }

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

    public static com.shengyuan.IMyService asInterface(android.os.IBinder obj) {
        if ((obj == null)) return null;
        android.os.IInterface iInterface = obj.queryLocalInterface(DESCRIPTOR);
        if (( iInterface != null && iInterface instanceof com.shengyuan.IMyService)){
            return ((com.shengyuan.IMyService) iInterface);
        }
        return null;
    }

    @Override
    protected boolean onTransact(
            int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        //该方法主要用于接收binder驱动的回传信息
        switch (code) {
        case INTERFACE_TRANSACTION: {
            reply.writeString(DESCRIPTOR);
            return true;
        }
        case TRANSACTION_say: {//响应命令
            data.enforceInterface(DESCRIPTOR);
            String str = data.readString();
            sayHello(str);
            reply.writeNoException();
            return true;
        }}
        return super.onTransact(code, data, reply, flags);
    }

    @Override
    public void callHi(String str) { //实现接口
        System.out.println("MyService:: Hello, " + str);
    }
}

1.3 作为Server端需调用ServiceManager.addService方法进行注册

public class ServerDemo {
    public static void main(String[] args) {
        Looper.prepareMainLooper();
        android.os.Process.setThreadPriority(
                android.os.Process.THREAD_PRIORITY_FOREGROUND);
        ServiceManager.addService("MyService", new MyService()); //注册service
        Looper.loop();
    }
}

2 Client端

2.1 实现IMyService接口的代理

public class MyServiceProxy implements IMyService {
    private android.os.IBinder mRemote;

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

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

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

    @Override
    public void callHi(String str) throws RemoteException {
        android.os.Parcel data = android.os.Parcel.obtain();
        android.os.Parcel reply = android.os.Parcel.obtain();
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            data.writeString(str);
            mRemote.transact(TRANSACTION_say, _data, _reply, 0);//通过获取的mRemote(即service在本地的代理)发送命令
            reply.readException();
        } finally {
            reply.recycle();
            data.recycle();
        }
    }
}

2.2 与Server端通信

public class ClientDemo {
    public static void main(String[] args) throws RemoteException { 
        //调用getService方法,会根据传入的名称参数打包成Parcel对象调用
        //transact方法写入给binder驱动,Binder驱动将对应的mRemote对象返回
        IBinder binder = ServiceManager.getService("MyService"); 
        IMyService myService = new MyServiceProxy(binder); 
        myService.callHi("binder"); 
    }
}

AIDL

AIDL即Android Interface Definition Language(安卓接口定义语言),当我们创建了这个接口后,系统会自动生成其对应的Binder类,它继承了IInterface, 内部有一个静态抽象类Stub和Stub内部的Proxy类。其中Stub继承了Binder类,所以AIDL中的Stub即为一个Binder对象。 在服务端实现该接口后,支持在客户端远程调用(RPC)。
综上AIDL定义的接口,它除了是一个接口以外,它还是一个Binder对象,支持在接口和Binder之间相互转换(asBinder(), asInterface())。

一个进程既可以是服务端Stub,也可以是客户端Proxy。
一个IPC请求发起时,首先会调用Proxy去连接Binder驱动,然后Binder驱动再去连接Stub
要实现跨进程通信,两个进程必须要有相同的AIDL接口

bindservice绑定流程时序图

pic6.jpeg

注意:ContextImpl.bindServiceCommon中调用ActivityManagerNative.getDefault().bindService这个方法,是涉及到(Client-ServiceManager-Server)通信,具体可以看其Binder实例中的时序图。

流程如下:
  • 第一步定义AIDL接口
  • 第二步实现AIDL文件生成的JAVA接口Stub(即stubSerVice类)
  • 第三步定义一个自己的Service,在实现自己的Service时,为了让Client端可以通过bindService来和我们的Service进行交互,我们都要实现Service中的onBind()方法,并且返回一个继承了Binder的内部类(即stubSerVice类)
  • 第四步Client端调用bindService方法进行服务绑定(流程请看如上时序图),服务绑定成功后,serviceConnection.onServiceConnected接口回调,获取到BinderProxy对象(即Server端Binder引用),再利用asInterface方法将BinderProxy对象转化为接口对象,从而实现与Server端跨进程通信

AIDL实例

1 定义AIDL接口

接口定义注意

Service升级时,会在aidl文件里增加或修改接口,如果客户端不更新所使用的aidl文件,这就会出现上述不一致的情况。

因为TRANSACTION code是根据aidl里接口声明的顺序生成的。所以当aidl里面函数的声明顺序改变,或者新加,删除函数,都会造成TRANSACTION code的值会不同。这样使用旧aidl文件的应用就可能出现问题!

解决办法:

当service升级时,为了避免出现上面的问题,应该保证aidl的变化不影响到旧有接口的TRANSACTION code。所以新的aidl的编写有以下几个注意点。
新加函数接口应该在旧有函数的后面。
尽量避免删除旧有函数,如果真的要删的话,可以保留函数名字作为占位,返回一个错误码之类的来解决。
不能改变原来的接口声明顺序。

package multichoose.shengyuan.com.mytestdemo;
interface Publicmake {
    void dealwith();
}

2 Server端

public class AidlService extends Service {
    private static final String TAG = AidlService.class.getSimpleName();
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new SstubSerVice();
    }

    class SstubSerVice extends Publicmake.Stub {
        @Override
        public void dealwith() {
            AidlService.this.callHi();
        }
    }

    public void callHi() {
        Log.i(TAG, "MyService:: Hello");
    }
}

4 注册Service

<service android:name=".aidldemo.AidlService"
            android:process=":remote">
            <intent-filter>
                <action android:name="AidlService"/>
            </intent-filter>
</service>

5 新建AidlActivity绑定Service(Client端)

bindservice成功后 会创建一个IBinder实例 这个时候就可以通过它与另一端通信

public class AidlActivity extends AppCompatActivity {
    public Publicmake psb;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button bt = findViewById(R.id.button);
        Intent it = new Intent(this,AidlService.class);

        bindService(it, new serviceConnection(), BIND_AUTO_CREATE);

        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                clickup(v);
            }
        });
    }

    public void clickup(View v) {
        try {
            psb.dealwith();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    class serviceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //注意一定要利用asInterface转为接口对象
            psb = Publicmake.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }
}
public interface Publicmake extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements multichoose.shengyuan.com.mytestdemo.Publicmake {
        private static final java.lang.String DESCRIPTOR = "multichoose.shengyuan.com.mytestdemo.Publicmake";
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static multichoose.shengyuan.com.mytestdemo.Publicmake asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof multichoose.shengyuan.com.mytestdemo.Publicmake))) {
                //同一进程内直接返回
                return ((multichoose.shengyuan.com.mytestdemo.Publicmake) iin);
            }
            //不在同一进程使用代理获取远程服务
            return new multichoose.shengyuan.com.mytestdemo.Publicmake.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_dealwith: {
                    data.enforceInterface(DESCRIPTOR);
                    this.dealwith();
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
        /**
          * 代理类,调用transact方法。
          */
        private static class Proxy implements multichoose.shengyuan.com.mytestdemo.Publicmake {
            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 void dealwith() throws android.os.RemoteException {
                // 输入参数
                android.os.Parcel _data = android.os.Parcel.obtain();
                // 输出参数
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_dealwith, _data, _reply, 0);
                    //调用mRemote.transact方法后,会挂起当前线程,等待远程方法执行完后才会继续当前线程
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
           }
        }

    static final int TRANSACTION_dealwith = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    public void dealwith() throws android.os.RemoteException;
}

Binder和AIDL实例异同

综上,你会发现其实Binder实例和AIDL实例有很多相似之处。
Binder实例中Server端会先通过Binder驱动向ServiceManager进行注册,Client端则通过Binder驱动与AMS通信获得远程引用对象(proxy),然后通过这个远程引用对象与Server端通信。
而AIDL其实是一个匿名的Binder传输过程,并没有在ServiceManager中进行注册,匿名binder必须是建立在一个实名binder之上的,也就是指这个实名binder必须在ServiceManager中注册过的,而AIDL实例中通过调用bindService从而与AMS通信(AMS服务在ServiceManager中注册),调用ActivityManagerNative.publishService方法将传入的IBinder对象打包成parcel调用transact方法传入binder驱动层,再通过ActivityManagerNative.onTransact方法获取binder驱动返回的Server端binder引用(即BinderProxy),再调用其子类即ActivityManagerService.publishService(ActivityManagerService继承于ActivityManagerNative)方法,回传给客户端,从而让Client端获取到远程binder对象引用从而与Server端远程通信。

主要源码方法解析

ActivityManagerNative.java

时序图1.2.9在handleBindService 方法中 调用ActivityManagerNative.getService().publishService进入

public void publishService(IBinder token,
            Intent intent, IBinder service) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        data.writeStrongBinder(token);
        intent.writeToParcel(data, 0);
        data.writeStrongBinder(service);
        mRemote.transact(PUBLISH_SERVICE_TRANSACTION, data, reply, 0);
        //调用mRemote.transact方法后,会挂起当前线程,等待远程方法执行完后才会继续当前线程
        //transact方法 客户端调用,用于发送调用请求
        reply.readException();
        data.recycle();
        reply.recycle();
}
  //onTransact方法 服务端响应,用于接收调用请求
  @Override
  public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        switch (code) {
            ···
            case PUBLISH_SERVICE_TRANSACTION: {
            data.enforceInterface(IActivityManager.descriptor);
            IBinder token = data.readStrongBinder();
            Intent intent = Intent.CREATOR.createFromParcel(data);
            IBinder service = data.readStrongBinder();
            //此处的IBinder对象是Binder驱动层转换成BinderProxy返回回来的
            //因为ActivityManagerService继承于ActivityManagerNative,且ActivityManagerService重写了publishService方法,
            //所以如下调用的是ActivityManagerService.publishService
            publishService(token, intent, service);
            reply.writeNoException();
            return true;
            ···
        }
}
ActivityManagerService.java
public void publishService(IBinder token, Intent intent, IBinder service) {
        synchronized(this) {
            mServices.publishServiceLocked((ServiceRecord)token, intent, service);
        }
}
ActiveServices.java
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
        final long origId = Binder.clearCallingIdentity();
        try {
            if (r != null) {
                Intent.FilterComparison filter
                        = new Intent.FilterComparison(intent);
                IntentBindRecord b = r.bindings.get(filter);
                if (b != null && !b.received) {
                    b.binder = service;
                    b.requested = true;
                    b.received = true;
                    for (int conni=r.connections.size()-1; conni>=0; conni--) {
                        ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni);
                        for (int i=0; i<clist.size(); i++) {
                            ConnectionRecord c = clist.get(i);
                            try {
                               //binder在onTransact回调中已转化成BinderProxy,通过c.conn.connected回调最终进入ServiceConnection的onServiceConnected接口方法中传回给Client端,
                               //从而让Client端获取到Server端的Binder引用,从而实现远程通信。
                               //在一开始ContextImpl.java的bindServiceCommon函数中做了一些准备工作,就是创建这个回调类。
                               c.conn.connected(r.name, service, false);
                            } catch (Exception e) {
                            }
                        }
                    }
                }
                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false);
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
}
onServiceConnected接口方法

bindService是异步调用和Service进行绑定, 如果绑定成功, 则会调用ServiceConnection的onServiceConnected

pic5.jpeg

通过断点,你会发现onServiceConnected方法回调回来的service参数是已经转化成BinderProxy(即Server端Binder对象引用)了,而非通过onBind获取到的IBinder对象,这是为什么呢,通过上面时序图,其实这个对象转换是发生在binder驱动层,可以看时序图1.2.10那一步,开始将未转化的IBinder对象打包成parcel形式通过transact()方法传给binder驱动时序图1.2.11),然后通过onTransact获取Binder驱动返回的BinderProxy(即Server端Binder对象引用),再通过时序图1.2.15回传给客户端。

总结

看到这里,其实你会发现,Binder没有想象中难理解,无论是Client-ServiceManager-Server还是AIDL进行跨进程通信都离不开Binder,Binder是一个实现IBinder的类,提供了两个与Binder驱动通信的重要接口方法(Transact()onTransact()),你可以通过它实现自定义的RPC协议。
通过阅读引申一些思考,为什么要使用binder来实现进程间的通信呢?
推荐阅读下:https://blog.csdn.net/q1183345443/article/details/69831074

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容