IPC、Binder及AIDL原理机制

一、IPC介绍

IPC是Inter-Process Communication的缩写,含义是进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。那么为什么需要开启多进程呢?原因有二,其一是一个应用因为某些原因需要独立运行在某个进程中。其二可能是加大一个应用可使用的内存空间,早期的一些版本,一个进程最大的内存空间是16MB。

1、Android中开启多进程

Android中开启多进程的方法只有一个,那就是给四大组件在Manifest中指定android:process属性,除此之外没有其他办法,也就说没有办法给一个线程或者一个实体类指定其运行时的进程。当然,还有另外一种非常规办法开启多进程,那就是通过JNI在Native层fork一个新的进程,这种方式不是常规的,因此暂时不考虑这种方式。

<activity 
    android:name="com.zbh.process.MainActivity" >
    <intent-filter>
        <action android:name="" />
        <category android:name="" />
    </intent-filter>
</activity>
<activity 
    android:name="com.zbh.process.SecondActivity" 
    android:process=":remote" >
</activity>
<activity 
    android:name="com.zbh.process.ThirdActivity" 
    android:process="com.zbh.process.remote" >
</activity>

这个Manifest对应的应用启动后,启动了SecondActivity和ThirdActivity后,会有3个进程存在。进程1是,其名字是com.zbh.process主进程,即默认进来时的进程。进程2是名字为com.zbh.process:remote的进程。进程3是名字为com.zbh.process.remote的进程。那么进程2和进程3有什么区别呢?
:是一种简写,是当前主进程的附属进程,属于私有进程。而com.zbh.process.remote是普通进程,属于全局进程。私有进程,其他应用组件是不可以和它跑在同一个进程中的。而全局进程,其他应用是可以通过ShareUID的形式和它跑在同一个进程中。当然,两个应该通过ShareUID的形式跑在同一个进程是有要求的,需要这两个应用有相同的ShareUID以及签名才可以。ShareUID可以在这个应用的Manifest中进行设置。

2、多进程产生的影响

(1)静态成员或单例模式失效
每个进程都会分配一个独立的虚拟机,不同的虚拟机在内存上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类对象,会产生多份副本。那么假设进程1修改了静态变量的值,进程2再去读的时候,由于是多份副本,它读到的是自己进程里的那份副本,所以就会出现值没有改变的现象。
(2)线程同步机制失效
这个问题的出现和(1)是一样的,既然都不是同一块内存,那么不管锁对象还是锁全局类都没办法保证线程同步,因为不同进程锁的对象不是同一个。
(3)SharedPreferences可靠性下降
这是因为SP不支持两个进程同时去执行读写操作,否则会导致一定几率的数据丢失,也就是并发问题不支持。
(4)Application会多次创建
多进程模式下,不同的进程组件会拥有独立的虚拟机、Application和内存空间。每一个进程的启动,都对应一个Application的创建。

二、Serializable和Parcelable接口

1、Serializable

Serializable是Java提供的一个序列化接口,使用非常简单。

public class Book implements Serializable {
    /**
     * serialVersionUID的作用是保证反序列的时候会校验失败
     * 序列化的时候,把数据从内存写到流里,会把这个ID也写进去
     * 反序列化的时候,把数据从流里读出来存刀内存对象里,
     * 在这之前会拿Book对象里的这个ID和流里的ID进行对比和校验,
     * 看是否一致来判断是否是同一个类对象
     */
    private static final long serialVersionUID = 4676767961613L;
    
    public String name;
    
    public int id;
    
    public int pageCount;
    
    public String author;
    
}
// 序列化过程
try {
    Book book = new Book("Android开发艺术探讨", 1, 507, "任玉刚");
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cache.txt"));
    oos.writeObject(book);
    oos.close();
} catch (Exception e) {
}

// 反序列化过程
try {
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cache.txt"));
    Book book = (Book) ois.readObject();
    ois.close();
} catch (Exception e) {
}
// 可以得出一个结论是,序列化前和反序列化后,得到的并不是同一个对象。

2、Parcelable

public class MyBook implements Parcelable {

    private String mName;

    private String mAuthor;

    public MyBook(String name, String author) {
        this.mName = name;
        this.mAuthor = author;
    }

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

    /**
     * 序列化到Parcel中
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mName);
        dest.writeString(mAuthor);
    }

    /**
     * 通过CREATOR的createFromParcel反序列化到对象内存中
     */
    public static final Parcelable.Creator<MyBook> CREATOR = new Creator<MyBook>() {
        @Override
        public MyBook createFromParcel(Parcel source) {
            String name = source.readString();
            String author = source.readString();
            return new MyBook(name, author);
        }

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

}

三、AIDL基本使用

普通的Service的使用不涉及到Binder进程间通信的问题,所以比较简单。而远程服务是基于Binder进行跨进程通信的,所以我们用AIDL来分析Binder的工作机制。
(1)先建一个服务端的项目,AIDL_Server
(2)建一个AIDL的目录及IPerson.aidl,其内容如下

/**
* IPerson.aidl
*/
interface IPerson {
    void setName(String name);
    String getName();
}

(3)创建一个service

public class MyService extends Service {

    private Binder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("zhangbh", "service 启动了");
        mBinder = new IPerson.Stub() {
            @Override
            public String getName() throws RemoteException {
                return "Hello ABC";
            }

            @Override
            public void setName(String name) throws RemoteException {
                Log.i("zhangbh", "name is:" + name);
            }
        };
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

}

(4)配置Manifest

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="test.service.start.action" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

(5)建一个客户端的项目,AIDL_Client,把AIDL_Server项目aidl目录下所有文件直接复制过来
(6)开启服务及调用服务

public class MainActivity extends AppCompatActivity {

    private IPerson mPerson;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.tv_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService();
            }
        });
        findViewById(R.id.tv_send).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    mPerson.setName("Hello world");
                } catch (Exception e) {
                }
            }
        });
        findViewById(R.id.tv_get).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    Log.i("zhangbh", mPerson.getName());
                } catch (Exception e) {
                }
            }
        });
    }

    private void startService() {
        Intent intent = new Intent();
        intent.setAction("test.service.start.action");
        intent.setPackage("com.zbh.aidl_server");
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.i("zhangbh", "服务绑定成功");
                mPerson = IPerson.Stub.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        }, BIND_AUTO_CREATE);
    }
}

(7)这样子就是借助AIDL完成了跨进程通信了,核心是Binder,但是怎么用,在哪里用,我们是没有看到,其实这些AIDL通过模板给我们生成了固定的代码。其源码如下

/**
* 在Build目录下自动生成的IPerson接口
*/
public interface IPerson extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.zbh.aidl_server.IPerson {
        private static final java.lang.String DESCRIPTOR = "com.zbh.aidl_server.IPerson";

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

        /**
         * Cast an IBinder object into an com.zbh.aidl_server.IPerson interface,
         * generating a proxy if needed.
         */
        public static com.zbh.aidl_server.IPerson asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.zbh.aidl_server.IPerson))) {
                return ((com.zbh.aidl_server.IPerson) iin);
            }
            return new com.zbh.aidl_server.IPerson.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 {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_setName: {
                    data.enforceInterface(descriptor);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    this.setName(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getName: {
                    data.enforceInterface(descriptor);
                    java.lang.String _result = this.getName();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.zbh.aidl_server.IPerson {
            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 setName(java.lang.String name) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(name);
                    mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public java.lang.String getName() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

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

    public void setName(java.lang.String name) throws android.os.RemoteException;

    public java.lang.String getName() throws android.os.RemoteException;
}

IPerson接口继承于IInterface接口,所有在Binder中传输的接口都要继承IInterface接口。IPerson接口里一共有一个静态内部类Stub和setName/getName方法,其中setName和getName就是我们在IPerson.aidl文件里声明的接口方法,最核心的就是这个静态内部类Stub,接下来我们就分析一个这个静态内部类Stub。

客户端ServiceConnected时拿的的IBinder要分为两种情况:
情况一,服务端和客户端在同一个进程中,那么这个IBinder实际上就是我们在服务端里创建并通过onBind返回的Stub对象。
情况二,服务端和客户端不在同一个进程中,即远程调用,这种情况就属于跨进程通信了。那么这个IBinder实际上是BinderProxy代理类,这个创建过程对我们是隐藏的。
从上面的代码我们可以看出,Stub实际上就一个Binder类,同时还实现了IPerson接口。Stub类主要有几个核心方法,
(1)构造方法里把DESCRIPTOR传递给父类Binder,DESCRIPTOR是一个唯一标识,一般用当前aidl包名+接口名
(2)asInterface,把Binder转化成当前的IPerson对象。这里会判断是否是跨进程还是同一进程。如果是同一进程,则直接返回我们传进来的IBinder,即我们服务端里创建的Stub对象。如果不是同一进程,这个时候就会创建一个Proxy对象,并且传入IBinder,上面已经分析过了,IBinder即BinderProxy。
(3)asBinder,这个没啥好说的,就是获取当前的Binder对象。
(4)Proxy是一个代理类,实现了IPerson接口,重写了setName和getName方法,而其构造方法里的参数remote是在asInterface中传入的,即BinderProxy。当我们在客户端中调用setName时,由于客户端的IPerson就是Proxy对象,因此会直接调用Proxy类的setName方法。接着把数据封装到Parcel里,调用mRemote的transact方法,上面说了,此时的mRemote就是BinderProxy对象,相当于调用BinderProxy的transact方法,BinderProxy会调用onTransact,即将Stub的onTransact方法。接下来就交由Binder驱动去完成数据包的传递工作给到Service了。
(5)onTransact这个方法是运行在服务端,通过code参数来确定客户端请求的目标方法是哪个。reply表示写入返回值。当客户端请求成功,这个方法就会返回true。如果这个方法返回false,表示客户端请求失败。


9999.png

四、Binder机制

1、Linux进程空间划分
一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & 内核 隔离开来,所有进程共用1个内核空间
进程间,用户空间的数据不可共享
进程间,内核空间的数据可共享
进程内 用户空间 & 内核空间 进行交互 需通过 系统调用,主要通过函数:
copy_from_user():将用户空间的数据拷贝到内核空间
copy_to_user():将内核空间的数据拷贝到用户空间


0000.png

为了保证 安全性 & 独立性,一个进程 不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的。
传统跨进程通信的基本原理


1111.png

而使用Binder进行跨进程通信作用如下:连接 两个进程,实现了mmap()系统调用,主要负责 创建数据接收的缓存空间 & 管理数据接收缓存,传统的跨进程通信需拷贝数据2次,但Binder机制只需1次,主要是使用到了内存映射。
2222.png

所以Binder驱动一共有两个作用
1.创建接受缓存区
2.通知client和service数据准备就绪
3.管理线程


4444.png

五、Android中的IPC方式

1、Bundle

我们知道,四大组件中Activity、Service、Receiver都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以在不同进程间传输。

2、使用共享文件

共享文件也是一种不错的IPC方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。通过文件共享的方式来共享数据对文件格式是没有具体要求的,比如可以是文本文件,也可以是XML文件,只要读/写约定数据格式即可。
通过文件共享的方式也是有局限性的,比如并发读/写的问题。有可能读的时候获取的不一定是最新写入的数据。因此我们要尽量避免并发写这种情况的发生或者考虑使用线程同步来限制多个线程的写操作。结论是,文件共享方式对数据同步要求不高的进程之间进行通信,并且要妥善处理好并发问题。

3、Messenger

Messenger可以翻译为信使,通过它可以在不同进程中传递Message,在Message中放入我们需要传递的数据,就可以轻松地实现数据在进程间传递了。Messenger是一种轻量的IPC解决方案,它的底层实现仍然是AIDL,它对AIDL进行了封装,使得我们可以更简便地进行进程间通信。同时,由于她一次处理一个请求,所以服务端也不用考虑线程同步问题,不存在并发执行的情况。
(1)服务端

public class MyService extends Service {

    private Messenger messenger;

    @Override
    public void onCreate() {
        super.onCreate();
        // 创建一个Messenger
        messenger = new Messenger(new MessengerHandler());
    }

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            // 这里处理客户端发来的消息
            Bundle bundle = msg.getData();
            // 得到客户端传过来的消息Hello world
            String value = bundle.getString("key"); 
            // 获取到客户端的信使,给它回消息
            Messenger clientMessenger = msg.replyTo;
            Message clientMsg = Message.obtain();
            Bundle clientBundle = new Bundle();
            clientBundle.putString("reply", "给客户端回消息了...");
            clientMsg.setData(clientBundle);
            try {
                clientMessenger.send(clientMsg);
            } catch (Exception e) {
            }
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        // new Stub
        return messenger.getBinder();
    }
}

(2)客户端

@Override
protected void onCreate() {
    Intent intent = new Intent(this, MyService.class);
    bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // IMessenger.Stub.asInterface(target);
            Messenger messenger = new Messenger(service);
            Message msg = Message.obtain();
            // 发送Bundle
            Bundle bundle = new Bundle();
            bundle.putString("key", "Hello world");
            msg.setData(bundle);
            // 设置这个是用于服务端回消息用的
            msg.replyTo = new Messenger(new ClientReplyHandler());
            try {
                messenger.send(msg);
            } catch (Exception e) {
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }, BIND_AUTO_CREATE);
}

private static class ClientReplyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        // 服务端回的消息在这里接收到
        Bundle bundle = msg.getData();
        // 获取到服务端回的消息
        String reply = bundle.getString("reply");
    }
}

4、AIDL

Messenger是以串行的方式来处理客户端发来的消息的,如果大量的消息同时发送到服务端,那么使用Messenger就不太合适了。同时,Messenger的作用主要是传递消息,很多时候我们需要跨进程调用服务端的某个方法,这种情况Messenger是做不到,而AIDL可以实现这些,虽然Messenger本质上是AIDL,但是由于对AIDL做了封装方便使用的同时,也出现了一定的局限性。关于AIDL的介绍,这个上面已经花费大量篇幅讲解过了,这里就不重复了。

5、ContentProvider

(1)基本使用

// 应用A暴露的ContentProvider,供别的应用访问
/**
 * 定义一个ContentProvider
 */
public class HistoryContentProvider extends ContentProvider {
    // 主机地址
    private static final String authority = "com.zbh.provider";

    private static UriMatcher mMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    private static final int MATCH_CODE = 1;

    static {
        // 添加访问的规则
        // com.zbh.provider/history
        mMatcher.addURI(authority, "history", MATCH_CODE);
    }

    @Override
    public boolean onCreate() {
        // 运行在主线程里
        // 其他五个方法都是运行在Binder的线程池的线程里
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        int code = mMatcher.match(uri);
        if (code == MATCH_CODE) {
            EZLog.i("===query===");
        }
        return null;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }
}
// 注册到系统里
<provider
    android:authorities="com.zbh.provider"
    android:exported="true"
    android:name=".receiver.HistoryContentProvider" />
// 应用B作为调用者,使用如下
// 协议content,主机名、路径和暴露的要一致
private String uri = "content://com.zbh.provider/history";
Cursor cursor = getContentResolver().query(Uri.parse(uri), null, null, null, null);
if (cursor != null) {
    cursor.close(); 
}

6、Socket

六、Binder连接池

假设现在有10个不同的模块,都需要使用AIDL来进行进程间的通信,那我们该怎么处理?按照AIDL的方式一个一个来实现?需要创建10个Service?Service也是系统四大组件,这样对系统开销就非常大,明显不是一种可取的办法。
每个业务模块创建自己的AIDL接口并实现该接口,而Service需要和另外一个AIDL接口来绑定,然后根据不同的查询类型,来返回对应业务模块的接口引用。

/**
* IBinderPool.aidl
*/
interface IBinderPool {
    IBinder queryBinder(int type);
}
/**
* IComputer.aidl
*/
interface IComputer {
    void add(int x, int y);
}
/**
* IBook.aidl
*/
interface IBook {
    void setName(String name);
    String getName();
}
/**
* IComputer实现类
*/
public class ComputerImpl extends IComputer.Stub {
    @Override
    public void add(int x, int y) throws RemoteException {
        Log.i("zhangbh", "computer : add");
    }
}
/**
* IBook实现类
*/
public class BookImpl extends IBook.Stub {

    @Override
    public void setName(String name) throws RemoteException {
        Log.i("zhangbh", "book : " + name);
    }

    @Override
    public String getName() throws RemoteException {
        return "Book name is ABC";
    }

}
/**
* Service类
*/
public class MyService extends Service {

    private Binder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("zhangbh", "service 启动了");
        // 实现IBinderPool
        mBinder = new IBinderPool.Stub() {
            @Override
            public IBinder queryBinder(int type) throws RemoteException {
                // 根据不同类型,返回具体业务的接口实现类
                if (type == 0) {
                    return new ComputerImpl();
                } else {
                    return new BookImpl();
                }
            }
        };
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}
/**
* 客户端调用如下
*/
private void startService() {
    Intent intent = new Intent();
    intent.setAction("test.service.start.action");
    intent.setPackage("com.zbh.aidl_server");
    intent.addCategory(Intent.CATEGORY_DEFAULT);
    bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i("zhangbh", "服务绑定成功");
            try {
                // 获取Service返回的IBinder,实际上是IBinderPool
                mBinderPool = IBinderPool.Stub.asInterface(service);
                
                // 调用IBinderPool的queryBinder方法,获取对应的业务Binder
                IBinder computerBinder = mBinderPool.queryBinder(0);
                // 获取到具体的Computer Binder
                IComputer computer = IComputer.Stub.asInterface(computerBinder);
                computer.add(5, 3);

                IBinder bookBinder = mBinderPool.queryBinder(1);
                IBook book = IBook.Stub.asInterface(bookBinder);
                book.setName("ABC");
                Log.i("zhangbh", book.getName());

            } catch (Exception e) {
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

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

推荐阅读更多精彩内容