IPC 机制(下)

2.4 Android 中的 IPC 方式


2.4.1 使用 Bundle

Bundle 实现了 Parcelable 接口,可以通过 Intent 传递数据,Intent有一个成员变量Bundle mExtras存储自己的数据, 而Bundle实际上就是一个ArrayMap, 以键值对的方式保存着各种类型的数据。

2.4.2 使用文件共享

典型例子是 SharedPreferences , 它通过键值对的方式在 xml 文件中存储数据,文件位于 /data/data/package_name/shared_prefs目录下. 如果应用是多进程模式的, 不同进程就会读写同一份 xml 文件,也就实现了跨进程通信。 但由于 SharedPreferences 有缓存策略, 即在内存中有一份SharedPreferences。因此多进程下容易发生数据丢失, 因此不建议采用 SharedPreferences 实现跨进程通信。

多个进程对同一份文件进行读写,文件共享方式适合在对数据同步要求不高的进程之间进行通信。

2.4.3 使用 Messenger

  • Messenger(信使),通过 Messenger 可以在不同进程中传递消息(Message)。
  • Messenger 是一种轻量级的 IPC 方法,底层实现是 AIDL。
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
    //Stub.asInterface 就是 AIDL 中的
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }

使用方法很简单,由于它一次处理一个请求,所以服务端我们不同考虑线程同步问题。
1.服务端进程

public class MessengerService extends Service {

    private static final String TAG = "MessengerService";

    private final Messenger mMessenger = new Messenger(new MessengerHandler());

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MyConstants.MSG_FROM_CLIENT:
                    Log.d(TAG, "消息来自客户端" + msg.getData().getString("msg"));
                    Messenger client = msg.replyTo;
                    Message replyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
                    Bundle data = new Bundle();
                    data.putString("reply", "你好,我是服务端!");
                    replyMessage.setData(data);
                    try {
                        client.send(replyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    break;
            }
            super.handleMessage(msg);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

2.客服端进程

public class MainActivity extends AppCompatActivity {

    private Messenger mService;
    private static final String TAG = "MessengerService";

    private Messenger mGetService = new Messenger(new MessengerHandle());

    @SuppressLint("HandlerLeak")
    private static class MessengerHandle extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MyConstants.MSG_FROM_SERVICE:
                    Bundle data = msg.getData();
                    Log.d(TAG, data.getString("reply"));
                    break;
                default:
                    break;
            }
            super.handleMessage(msg);
        }
    }

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "链接成功");
            // mService 是个信使
            mService = new Messenger(service);
            // msg 是一个消息,data 是消息内容
            Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
            Bundle data = new Bundle();
            data.putString("msg", "你好,服务端");
            msg.setData(data);
            msg.replyTo = mGetService;
            try {
                // 信使传递消息。
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, MessengerService.class);
        //绑定服务
        bindService(intent, conn, Context.BIND_AUTO_CREATE);//绑定服务
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);//取消绑定
    }
}
        <service
            android:name=".MessengerService"
            android:process=":remote"/>

2.4.4 使用 AIDL

AIDL 支持的类型 :
  • 基本数据类型;
  • String 和 CharSequence;
  • List:只支持 ArrayList,里面每个元素都必须能够被 AIDL 支持;
  • Map:只支持 HashMap,里面的每个元素都必须被 AIDL 支持,包括 key 和 value;
  • Parcelable:所有实现了 Parcelable 接口的对象;
  • AIDL :所有的 AIDL 接口本身也可以在 AIDL 文件中使用。

其中自定义的 Parcelable 对象和 AIDL 对象必须要显示 import 进来,如果 AIDL 文件中用到了自定义的 Parcelabale 对象,那么必须新建一个和它同名的 AIDL 文件。

下面案例目录结构

使用案例来演示 AIDL

1.AIDL文件: Book .aidl、IBookManager .aidl、IOnNewBookArrivedListener .aidl

package com.kjn.chaptermessenger.aidl;
import com.kjn.chaptermessenger.aidl.Book;

parcelable Book ;
package com.kjn.chaptermessenger.aidl;
import com.kjn.chaptermessenger.aidl.Book;
import com.kjn.chaptermessenger.aidl.IOnNewBookArrivedListener;

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

}
package com.kjn.chaptermessenger.aidl;
import com.kjn.chaptermessenger.aidl.Book;
interface IOnNewBookArrivedListener {

    void onNewBookArrived(in Book newBook);
}

2.客户端:BookManagerActivity

public class BookManagerActivity extends AppCompatActivity {

    private static final String TAG = "BookManagerActivity";
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;

    private IBookManager mRemoteBookManager;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.d(TAG, "收到新书:" + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            mRemoteBookManager = bookManager;

            try {
                //设置 binder 死亡代理
                mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
                List<Book> list = bookManager.getBookList();
                Log.d(TAG, "查询图书列表,列表类型: " + list.getClass().getCanonicalName());
                Log.d(TAG, "查询图书列表: " + list.toString());
                Book book = new Book(3, "Android 开发艺术探索");
                bookManager.addBook(book);
                Log.d(TAG, "new book:" + book);
                List<Book> newList = bookManager.getBookList();
                Log.d(TAG, "查询图书列表: " + newList.toString());
                //注册新书到达监听器
                bookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteBookManager = null;
            Log.d(TAG, "binder died.");
        }
    };
    //Binder 链接断裂监听
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());
            if (mRemoteBookManager == null) {
                return;
            }
            mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mRemoteBookManager = null;
            //TODO:这里重新绑定 Service
        }
    };
    //新书到达监听器
    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {

        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    public void checkIt(View view) {
    }

    @Override
    protected void onDestroy() {
        if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) {
            Log.d(TAG, "onDestroy: " + mOnNewBookArrivedListener);
            try {
                mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(conn);
        super.onDestroy();
    }
}

3.服务端:BookManagerService

public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";
    // 原子操作,保证在多线程中不是出现意外的变化
    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    // CopyOnWriteArrayList 支持并发写
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    // RemoteCallbackList  专门用于跨进程删除监听
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
    //mBinder 对象获得来自 AIDL
    private Binder mBinder = new IBookManager.Stub() {
        //获取图书列表
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }
        //添加图书
        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
        //注册监听器:监听新书到来通知客户端
        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
            listenerSize();
        }
        //注销监听器
        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            boolean success = mListenerList.unregister(listener);
            if (success) {
                Log.d(TAG, "注销成功.");
            } else {
                Log.d(TAG, "没有找到,不能注销.");
            }
            listenerSize();
        }
        // onTransact 方法会被 getBookList()、addBook(Book book)、registerListener和unregisterListener调用
        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            int check = checkCallingOrSelfPermission("com.kjn.chaptermessenger.permission.ACCESS_BOOK_SERVICE");
            if (check == PackageManager.PERMISSION_DENIED) {
                return false;
            }
            // 验证包名
            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            if (packageName != null && !packageName.startsWith("com.kjn")) {
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }
    };
   // N 的获取要调用 beginBroadcast()和finishBroadcast() 两个对应方法。
    private void listenerSize() {
        final int N = mListenerList.beginBroadcast();
        mListenerList.finishBroadcast();
        Log.d(TAG, "registerListener, current size:" + N);
    }
    //新书到达时,通知监听器
    public void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        Log.d(TAG, "新书到达时,通知监听器:" + mListenerList);
        final int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            Log.d(TAG, "新书到达时,通知监听器:" + listener);
            if (listener != null) {
                listener.onNewBookArrived(book);
            }
        }
        mListenerList.finishBroadcast();
    }
    //耗时5秒发送新书通知
    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            while (!mIsServiceDestoryed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBookList.size() + 1;
                Book newBook = new Book(bookId, "new book#" + bookId);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "Ios"));
        new Thread(new ServiceWorker()).start();
    }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        mIsServiceDestoryed.set(true);
    }
}

4.AndroidManifest 中设置权限和多进程

<uses-permission android:name="com.kjn.chaptermessenger.permission.ACCESS_BOOK_SERVICE" />
或者
 <permission
        android:name="com.kjn.chaptermessenger.permission.ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal"/>
...
<service
            android:name=".BookManagerService"
            android:process=":remote"/>

代码多,含义写法都写在代码注释中,这里讲上述代码实现的步骤

  1. 远程服务端 Service 的实现,完成 mBinder 的返回,并在 onCreate 中添加两本书。
  2. 客户端的实现,绑定 Service,并获取书籍列表。
  3. Service 中完成注册绑定新书监听器,添加新书 ServiceWorker 的操作。
  4. 客户端注册新书监听器,用 handler 完成接收。
  5. 设置权限验证和死亡代理。

2.4.5 使用 ContentProvider

ContentProvider 是 Android 中提供的专门用于不同应用间进行数据共享的方式。和 Messenger 一样,ContentProvider 的底层同样也是 Binder。
  系统预置了许多 ContentProvider,比如通讯录信息、日程信息表等,只要通过 ContentReslover 的 query、update、insert 和 delete 方法即可。

2.4.6 使用 Socket

Scoket 也称为“套接字”,是网络通信中的概念。

  • 流式套接字:网络的传输控制层中的 TCP;
  • TCP 协议是面向连接的协议,提供稳定的双向通信功能,TCP链接的建立需要经过 “三次握手” 才能完成,本身提供了超时重传机制,因此具有很高的稳定性;
  • 用户数据报套接字:网络的传输控制层中的 UDP协议;
  • UDP 是无链接的,提供不稳定的单向通信功能,也可以实现双向通信功能,但是不稳定不推荐。

下面一个简单的演示案例,敲一边,梳理一下就清楚 如何 用Socket 来进程间通信的实例

客户端代码

     public class TCPClientActivity extends Activity implements View.OnClickListener {

    private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
    private static final int MESSAGE_SOCKET_CONNECTED = 2;

    private Button mSendButton;
    private TextView mMessageTextView;
    private EditText mMessageEditText;

    private PrintWriter mPrintWriter;
    private Socket mClientSocket;

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {//接收到新消息
                case MESSAGE_RECEIVE_NEW_MSG: {
                    mMessageTextView.setText(mMessageTextView.getText()
                            + (String) msg.obj);
                    break;
                }//链接失败
                case MESSAGE_SOCKET_CONNECTED: {
                    mSendButton.setEnabled(true);
                    break;
                }
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tcpclient);
        mMessageTextView = (TextView) findViewById(R.id.msg_container);
        mSendButton = (Button) findViewById(R.id.send);
        mSendButton.setOnClickListener(this);
        mMessageEditText = (EditText) findViewById(R.id.msg);
        Intent service = new Intent(this, TCPServerService.class);
        startService(service);
        new Thread() {
            @Override
            public void run() {
                connectTCPServer();
            }
        }.start();
    }

    @Override
    protected void onDestroy() {
        if (mClientSocket != null) {
            try {
                mClientSocket.shutdownInput();
                mClientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        super.onDestroy();
    }

    @Override
    public void onClick(View v) {
        if (v == mSendButton) {
            final String msg = mMessageEditText.getText().toString();
            if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
                mPrintWriter.println(msg);
                mMessageEditText.setText("");
                String time = formatDateTime(System.currentTimeMillis());
                final String showedMsg = "self " + time + ":" + msg + "\n";
                mMessageTextView.setText(mMessageTextView.getText() + showedMsg);
            }
        }
    }

    @SuppressLint("SimpleDateFormat")
    private String formatDateTime(long time) {
        return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
    }

    private void connectTCPServer() {
        Socket socket = null;
        //超市重连策略:socket 为null 时,不停的重连
        while (socket == null) {
            try {
                socket = new Socket("localhost", 8688);
                mClientSocket = socket;
                mPrintWriter = new PrintWriter(new BufferedWriter(
                        new OutputStreamWriter(socket.getOutputStream())), true);
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
                System.out.println("connect server success");
            } catch (IOException e) {
                SystemClock.sleep(1000);
                System.out.println("connect tcp server failed, retry...");
            }
        }

        try {
            // 接收服务器端的消息
            //这是通信编程,clientSocket.getInputStream()获取服务端传来的字节流、
            //在用InputStreamReader(类是从字节流到字符流的桥梁)在缓存到BufferedReader缓冲区
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    socket.getInputStream()));
            while (!TCPClientActivity.this.isFinishing()) {
                String msg = br.readLine();
                System.out.println("receive :" + msg);
                if (msg != null) {
                    String time = formatDateTime(System.currentTimeMillis());
                    final String showedMsg = "server " + time + ":" + msg
                            + "\n";
                    mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg)
                            .sendToTarget();
                }
            }
            System.out.println("quit...");
            MyUtils.close(mPrintWriter);
            MyUtils.close(br);
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服务器

public class TCPServerService extends Service {

    private boolean mIsServiceDestoryed = false;
    private String[] mDefinedMessages = new String[]{
            "你好啊,哈哈", "请问你叫什么名字",
            "今天杭州的天气不错啊,shy",
            "你知道吗,我可是可以和多个人聊天的",
            "给你讲个笑话,我中五百万了。"
    };

    @Override
    public void onCreate() {
        new Thread(new TcpServer()).start();
        super.onCreate();
    }

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

    @Override
    public void onDestroy() {
        mIsServiceDestoryed = true;
        System.out.println("Service onDestroy");
        super.onDestroy();
    }

    private class TcpServer implements Runnable {

        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                //监听/建立本地 8688 端口
                serverSocket = new ServerSocket(8688);

            } catch (IOException e) {
                System.out.println("建立 TCP 服务器断开失败,端口:8688");
                e.printStackTrace();
                return;
            }
            while (!mIsServiceDestoryed) {
                try {
                    //接收客户端请求
                    final Socket client = serverSocket.accept();
                    System.out.println("接受");
                    new Thread() {
                        @Override
                        public void run() {
                            try {
                                responseClient(client);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //响应客户端,把响应的信息转换成字符流传入客户端
    private void responseClient(Socket client) throws IOException {
        //用于接收客户端消息转换成字符流
        BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));

        //用于向客户端发送消息
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
        out.println("欢迎来到聊天室");
        while (!mIsServiceDestoryed) {
            String str = in.readLine();
            System.out.println("msg from client:" + str);
            if (str == null) {
                //客户端断开链接
                break;
            }

            int i = new Random().nextInt(mDefinedMessages.length);
            String msg = mDefinedMessages[i];
            out.println(msg);
            System.out.println("send:" + msg);

        }
        System.out.println("client quit.");
        MyUtils.close(out);
        MyUtils.close(in);
        client.close();
    }
}

加入权限和多进程

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
...
    <service
            android:name=".socket.TCPServerService"
            android:process=":remote" />

2.5 Binder 连接池


简单讲就是使用一个 Service,一个 Binder 连接池来替代多个 Service 的实现。毕竟多个服务进程会让应用看起来繁重,用户有想卸载的欲望。

源码链接

  1. 服务端 onBind 返回 BinderPool 的 IBinder 实例,它实现了 queryBinder 方法,这个接口能够根据业务模块的特征来返回相应的 Binder 对象给它们,不同的业务模块拿到所需的 Binder 对象后就可以进行远程方法调用了。
@Override
public IBinder onBind(Intent intent) {
 Log.d(TAG, "onBind");
 return new BinderPool.BinderPoolImpl();
}
  1. Binder 连接池的主要作用就是将每个业务模块的 Binder 请求统一转发到远程Service去执行,从而避免了创建多个 Service。
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
 IBinder binder = null;
 switch (binderCode) {
     case BINDER_SECURITY_CENTER: {
         binder = new SecurityCenterImpl();
         break;
     }
     case BINDER_COMPUTE: {
         binder = new ComputeImpl();
         break;
     }
     default:
         break;
 }
 return binder;
}

2.6 选用合适的 IPC 方式

名称 有点 缺点 使用场景
Bundle 简单易用 智能传输 Bundle 支持的数据 四大组件的进程间通信
文件共享 简单易用 不合适高并发场景,并且无法做到进程间的即时通信 无并发访问情形,交互含简单的数据实时性不高的场景
AIDL 功能强大,支持一对多并发通信 使用稍微复杂,需要处理好线程同步 一对多通信且有 RPC 需求
Messenger 功能一般,支持一对多串行通信,支持实时通信 不能很好处理高并发情形,不支持 RPC ,数据通过 Messager 进行传输,因此只能传输 Bundle 支持的数据类型 第并发的一对多即时通信,无 RPC 需求,或者无须要返回结果的 RPC 需求
ContentProvider 在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call 方法扩展其他操作 可以理解为受约束的 AIDL,主要提供数据源的 CRUD 操作 一对多进程间的数据共享
Socket 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 实现细节稍微有点繁琐,不支持直接的 RPC 网络数据交换
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容