Android IPC机制2-AIDL的使用

相关概念

序列化

Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization是一种将这些字节重建成一个对象的过程。从用途角度来说,如果对象需要持久化或者在不同进程间传输,就需要序列化。

Java原生提供了Serializable接口支持序列化,Serializable比较强大,持久化和数据传输都可以支持,但是在序列化开销比较大,故Android为Binder通信等只在内存中传输的场景设计了另一套序列化机制--Parcelable。

Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如activity间传输数据,而Serializable可将数据持久化方便保存,所以在需要保存或网络传输数据时选择Serializable,因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化。

一个简单的示例:

 1 public class MyParcelable implements Parcelable {  
 2      private int mData;  
 3      private String mStr;  
 4   
 5      public int describeContents() {  
 6          return 0;  
 7      }  
 8   
 9      // 写数据进行保存  
10      public void writeToParcel(Parcel out, int flags) {  
11          out.writeInt(mData);  
12          out.writeString(mStr);  
13      }  
14   
15      // 用来创建自定义的Parcelable的对象  
16      public static final Parcelable.Creator<MyParcelable> CREATOR  
17              = new Parcelable.Creator<MyParcelable>() {  
18          public MyParcelable createFromParcel(Parcel in) {  
19              return new MyParcelable(in);  
20          }  
21   
22          public MyParcelable[] newArray(int size) {  
23              return new MyParcelable[size];  
24          }  
25      };  
26        
27      // 读数据进行恢复  
28      private MyParcelable(Parcel in) {  
29          mData = in.readInt();  
30          mStr = in.readString();  
31      }  
32  }  

android studio 可以通过android parcelable code generator插件快速生成一个JavaBean对象的序列化相关代码

AIDL的使用

由上一讲我们知道android IPC是通过Binder实现的,但是Binder相关的概念非常复杂,为了方便开发者google就推出了AIDL(安卓接口定义语言)。通过编写AIDL文件,eclipse或者android studio 就可以帮我们生成Binder通信的相关代码。开发者即使不了解Binder机制也可以实现IPC了。

AIDl的关键字

  • oneway
    正常情况下Client调用AIDL接口方法时会阻塞,直到Server进程中该方法被执行完。oneway可以修饰AIDL文件里的方法,oneway修饰的方法在用户请求相应功能时不需要等待响应可直接调用返回,非阻塞效果,该关键字可以用来声明接口或者声明方法,如果接口声明中用到了oneway关键字,则该接口声明的所有方法都采用oneway方式。(注意,如果client和Server在同一进程中,oneway修饰的方法还是会阻塞)
  • in
    非基本数据类型和string的参数类型必须加参数修饰符,in的意思是只输入,既最终server端执行完后不会影响到参数对象
  • out
    与in相反,out修饰的参数只能由server写入并传递到client,而client传入的值并不会传递到server
  • inout
    被inout修饰的参数,既可以从client传递到server,也可以server传递到client

AIDL自动生成文件详解

不多说,直接上代码
aidl 文件

//方法参数选择了Intent类型,其他任何实现了Parceable的类型都可以作为方法参数
interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */

    void meth1(int args);
    void meth2(in Intent args);
    void meth3(out Intent args);
    void meth4(inout Intent args);
    oneway void meth5(inout Intent args);
    int meth6(in Intent args);
    int meth7(out Intent args);
    int meth8(inout Intent args);
    oneway int meth9(in Intent args);
    Intent meth10(in Intent args);
}

自动生成代码

public interface IMyAidlInterface extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.xns.aidldemo.IMyAidlInterface {
        private static final java.lang.String DESCRIPTOR = "com.xns.aidldemo.IMyAidlInterface";

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

        /**
         * Cast an IBinder object into an com.xns.aidldemo.IMyAidlInterface interface,
         * generating a proxy if needed.
         */
        public static com.xns.aidldemo.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            //先调用queryLocalInterface,这个方法是IBinder定义的,默认实现是返回NULL,而在BBinder的子类BnInterface中,重载了该方法,返回this,而
            //BpInterface并没有重载,使用IBinder的默认实现,返回NULL。
            //简单的说就是如果server调用binder对象在一个进程就直接返回其本身,如果不在一个进程就返回代理对象
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.xns.aidldemo.IMyAidlInterface))) {
                return ((com.xns.aidldemo.IMyAidlInterface) iin);
            }
            return new com.xns.aidldemo.IMyAidlInterface.Stub.Proxy(obj);
        }

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

        /**
         * 代理类中的每个方法都会通过onTransact来调用server端的接口的方法,此方法运行在server端binder线程池
         * @param code
         * @param data
         * @param reply
         * @param flags
         * @return
         * @throws android.os.RemoteException
         */
        @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_meth1: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    this.meth1(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_meth2: {
                    data.enforceInterface(DESCRIPTOR);
                    android.content.Intent _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = android.content.Intent.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.meth2(_arg0);
                    return true;
                }
                ...
                ...
                ...
               
                case TRANSACTION_meth10: {
                    data.enforceInterface(DESCRIPTOR);
                    android.content.Intent _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = android.content.Intent.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    android.content.Intent _result = this.meth10(_arg0);
                    reply.writeNoException();
                    if ((_result != null)) {
                        reply.writeInt(1);
                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
        /**
        * server调用接口在client的代理类
        */
        private static class Proxy implements com.xns.aidldemo.IMyAidlInterface {
            private android.os.IBinder mRemote;

            /**
             *
             * @param remote  server与client通信的中介,通过binder驱动交互
             */
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

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

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

            /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             */
            @Override
            public void meth1(int args) 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.writeInt(args);
                    //向binder驱动传输数据,server端对应的onTransact会被执行
                    mRemote.transact(Stub.TRANSACTION_meth1, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void meth2(android.content.Intent args) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((args != null)) {
                        _data.writeInt(1);
                        //args为in 修饰,此处会将args写入到需要传输的_data中
                        args.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    //oneway方法,不会阻塞,也就没有_reply
                    mRemote.transact(Stub.TRANSACTION_meth2, _data, null, android.os.IBinder.FLAG_ONEWAY);
                } finally {
                    _data.recycle();
                }
            }

            @Override
            public void meth3(android.content.Intent args) 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_meth3, _data, _reply, 0);
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        //args为out类型,所以方法执行完了会回写args,但是由于没有in,所以相当于args只是在方法调用完了接受结果,却不能传递参数
                        args.readFromParcel(_reply);
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void meth4(android.content.Intent args) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((args != null)) {
                        _data.writeInt(1);
                        args.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_meth4, _data, _reply, 0);
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        args.readFromParcel(_reply);
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void meth5(android.content.Intent args) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((args != null)) {
                        _data.writeInt(1);
                        args.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_meth5, _data, null, android.os.IBinder.FLAG_ONEWAY);
                    //虽然args定义为inout,但是由于该方法被oneway修饰,所以不会回写args,out属性无效
                } finally {
                    _data.recycle();
                }
            }

           ...
           ...
           ...
             

            @Override
            public int meth9(android.content.Intent args) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((args != null)) {
                        _data.writeInt(1);
                        args.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_meth9, _data, null, android.os.IBinder.FLAG_ONEWAY);
                } finally {
                    _data.recycle();
                }
                //该方法是有返回值的,但是被oneway修饰后该方法不会接受_reply,所以不会返回值.
                return _result;
            }

            @Override
            public android.content.Intent meth10(android.content.Intent args) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                android.content.Intent _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((args != null)) {
                        _data.writeInt(1);
                        args.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_meth10, _data, _reply, 0);
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        _result = android.content.Intent.CREATOR.createFromParcel(_reply);
                    } else {
                        _result = null;
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }
        //所有方法的id,onTransact中switch通过这些id判断调用哪个方法
        
        static final int TRANSACTION_meth1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_meth2 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_meth3 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
        static final int TRANSACTION_meth4 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
        static final int TRANSACTION_meth5 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
        static final int TRANSACTION_meth6 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5);
        static final int TRANSACTION_meth7 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 6);
        static final int TRANSACTION_meth8 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 7);
        static final int TRANSACTION_meth9 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 8);
        static final int TRANSACTION_meth10 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 9);
    }

    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    public void meth1(int args) throws android.os.RemoteException;

    public void meth2(android.content.Intent args) throws android.os.RemoteException;

    public void meth3(android.content.Intent args) throws android.os.RemoteException;

    public void meth4(android.content.Intent args) throws android.os.RemoteException;

    public void meth5(android.content.Intent args) throws android.os.RemoteException;

    public int meth6(android.content.Intent args) throws android.os.RemoteException;

    public int meth7(android.content.Intent args) throws android.os.RemoteException;

    public int meth8(android.content.Intent args) throws android.os.RemoteException;

    public int meth9(android.content.Intent args) throws android.os.RemoteException;

    public android.content.Intent meth10(android.content.Intent args) throws android.os.RemoteException;
}

总结下,IDE其实用我们编写的AIDL文件帮我们做了这些事:

  1. 创建了IMyAidlInterface的实现类Stub和Stub的子类Proxy
  2. Stub类中实现了有IBinder对象转换为IMyAidlInterface类型的asInterface,asInterface中通过queryLocalInterface(DESCRIPTOR)方法查看本进程是否有IMyAidlInterface在server端的实现类(既判断Server与Client是否在同一进程),如果是同一进程就直接返回Server端的IMyAidlInterface实现者,如果不在同一进程就返回代理对象
  3. Proxy类中实现了aidl中定义的方法,根据oneway、in、out、inout修饰符来生成不同的代码,决定是否向binder驱动写入数据或者执行完后向方法参数回写数据。注意:oneway修饰一个方法后,该方法不阻塞client调用线程,但是方法没有返回值,方法参数在执行方法执行完后也不会回写。
  4. Proxy类中实现的方法最终通过transact()方法向Binder驱动写入数据(运行再client进程),最终Stub类中的onTransact()方法会被调用到(运行在server进程),就这样完成一次跨进程方法调用。

异常处理

Binder死亡处理

在进程间通信过程中,很可能出现一个进程死亡的情况。如果这时活着的一方不知道另一方已经死了就会出现问题。那我们如何在A进程中获取B进程的存活状态呢?
android肯定给我们提供了解决方式,那就是BinderlinkToDeathunlinkToDeath方法,linkToDeath方法需要传入一个DeathRecipient对象,DeathRecipient类里面有个binderDied方法,当binder对象的所在进程死亡,binderDied方法就会被执行,我们就可以在binderDied方法里面做一些异常处理,释放资源等操作了。示例如下:

    ...
    mClientCallBack = IRemoteCallBack.Stub.asInterface(callback);
    if (mClientDeathHandler == null) {
          mClientDeathHandler = new ClientDeathRecipient();
    }
    mClientCallBack.asBinder().linkToDeath(new ClientDeathRecipient(), 0);
    ...
private class ClientDeathRecipient implements IBinder.DeathRecipient {

        @Override
        public void binderDied() {
            mCallbackList.unregister(mClientCallBack);
            mClientCallBack = null;
            Logger.d(TAG,"client  is died");
        }
    }

上面是我在server端对client的回调接口的binder对象设置的DeathRecipient。在client死亡时,解注册client的回调,并且置空。

client注册回调接口

之前一直说的都是client向server的通信,那如果server要调用client呢?
一个比较容易想到的办法就是通过AIDL在server端设置一个client的回调。这样的话就相当于client端是server端的server了。
有注册回调就肯定有解注册,但是client端与server不在一个进程,server是无法得知client解注册时传入的回调接口是哪一个(client调用解注册时,是通过binder传输到server端,所以解注册时的回调接口是新创建的,而不是注册时的回调接口)。为了解决这个问题,android提供了RemoteCallbackList这个类来专门管理remote回调的注册与解注册。
用法如下:

//TaskCallback.aidl 用于存放要回调client端的方法
package com.xns.demo.server;   
  
interface ITaskCallback {   
    void actionPerformed(int actionId);  
} 
//ITaskBinder.aidl 用于存放供给client端调用的方法
package com.xns.demo.server;   
  
import com.xns.demo.server.ITaskCallback;   
  
interface ITaskBinder {   
    boolean isTaskRunning();   
    void stopRunningTask();   
    void registerCallback(ITaskCallback cb);   
    void unregisterCallback(ITaskCallback cb);   
}

//myservice
package com.xns.demo.server;   
  
import com.xns.demo.server.ITaskBinder;  
import com.xns.demo.server.ITaskCallback;  
  
import android.app.Service;   
import android.content.Intent;   
import android.os.IBinder;   
import android.os.RemoteCallbackList;   
import android.os.RemoteException;   
import android.util.Log;   
  
public class MyService extends Service {   
    private static final String TAG = "aidltest";  
  
    ...
      
    @Override  
    public IBinder onBind(Intent t) {  
        printf("service on bind");  
        return mBinder;   
    }  
     
    @Override  
    public boolean onUnbind(Intent intent) {   
        printf("service on unbind");  
        return super.onUnbind(intent);   
    }  
      
    void callback(int val) {   
        final int N = mCallbacks.beginBroadcast();  
        for (int i=0; i<N; i++) {   
            try {  
                mCallbacks.getBroadcastItem(i).actionPerformed(val);   
            }  
            catch (RemoteException e) {   
                // The RemoteCallbackList will take care of removing   
                // the dead object for us.     
            }  
        }  
        mCallbacks.finishBroadcast();  
    }  
      
    private final ITaskBinder.Stub mBinder = new ITaskBinder.Stub() {  
          
        public void stopRunningTask() {  
              
        }  
      
        public boolean isTaskRunning() {   
            return false;   
        }   
          
        public void registerCallback(ITaskCallback cb) {   
            if (cb != null) {   
                mCallbacks.register(cb);  
            }  
        }  
          
        public void unregisterCallback(ITaskCallback cb) {  
            if(cb != null) {  
                mCallbacks.unregister(cb);  
            }  
        }  
    };   
      
    final RemoteCallbackList <ITaskCallback>mCallbacks = new RemoteCallbackList <ITaskCallback>();  
  
}
//client端
package com.xns.demo;   
  
...
  
import com.xns.demo.server.*;  
  
public class MyActivity extends Activity {   
  
    private static final String TAG = "aidltest";  
    private Button btnOk;   
    private Button btnCancel;  
  
...
      
    ITaskBinder mService;   
      
    private ServiceConnection mConnection = new ServiceConnection() {   
          
        public void onServiceConnected(ComponentName className, IBinder service) {  
            mService = ITaskBinder.Stub.asInterface(service);   
            try {   
                mService.registerCallback(mCallback);  
            } catch (RemoteException e) {  
                  
            }  
        }  
          
        public void onServiceDisconnected(ComponentName className) {   
            mService = null;  
        }   
    };   
      
    private ITaskCallback mCallback = new ITaskCallback.Stub() {  
          
        public void actionPerformed(int id) {   
            printf("callback id=" + id);  
        }   
    };   
  
}  

RemoteCallbackList可以实现正常注册于解注册的原因在于注册与解注册时虽然对应的回调接口不是同一个,但是其对应的Binder对象却是同一个。

Messenger与AIDL的异同

其实Messenger的底层也是用AIDL实现的,但用起来还是有些不同的,这里总结了几点区别:
1. Messenger本质也是AIDL,只是进行了封装,开发的时候不用再写.aidl文件。
结合我自身的使用,因为不用去写.aidl文件,相比起来,Messenger使用起来十分简单。但前面也说了,Messenger本质上也是AIDL,故在底层进程间通信这一块,两者的效率应该是一样的。
2. 在service端,Messenger处理client端的请求是单线程的,而AIDL是多线程的。
使用AIDL的时候,service端每收到一个client端的请求时,就在BInder线程池中取一个线程去执行相应的操作。而Messenger,service收到的请求是放在Handler的MessageQueue里面,Handler大家都用过,它需要绑定一个Thread,然后不断poll message执行相关操作,这个过程是同步执行的。
3. client的方法,使用AIDL获取返回值是同步的,而Messenger是异步的。
Messenger只提供了一个方法进行进程间通信,就是send(Message msg)方法,发送的是一个Message,没有返回值,要拿到返回值,需要把client的Messenger作为msg.replyTo参数传递过去,service端处理完之后,在调用客户端的Messenger的send(Message msg)方法把返回值传递回client,这个过程是异步的,而AIDL你可以自己指定方法,指定返回值,它获取返回值是同步的(如果没有用oneway修饰方法的话)。

总的来说,AIDL灵活性更高,如果需要IPC通信的地方比较多,还是更推荐自定义AIDL一点。

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

推荐阅读更多精彩内容