Android进阶:Binder机制、AIDL进程通信学习(模拟支付宝支付案例)

目录

  • Binder是什么?
  • 从面向对象的思想看 Binder IPC
  • 进程空间的划分
  • Binder机制是如何跨进程通信的
  • Binder 到底是什么
  • 理解Java层的Binder 代码
  • Android中的AIDL
  • android开发AIDL使用模拟支付宝支付

Binder是什么?

Binder是Android 系统中最重要的特性之一:称之为"粘合剂",粘合了两个不同的进程,是系统间各个组件通信的桥梁,是一种跨进程通信机制。Binder基于Client-Server通信模式。

从面向对象的思想看 Binder IPC?

Binder使用Client-Server通信方式:

  • 对Binder而言,Binder 可以看成 Server 提供的实现某个特定服务的访问接入点, Client 通过这个‘地址’向Server 发送请求来使用该服务;对 Client 而言,Binder 可以看成是通向 Server 的管道入口,要想和某个Server 通信首先必须建立这个管道并获得管道入口。

  • Binder 是一个实体位于 Server 中的对象,该对象提供了一套方法用以实现对服务的请求,就像类的成员函数方法。遍布于 Client 中的入口 可以看成指向这个 Binder对象的 引用, 一旦获得了这个引用就可以调用该对象的方法访问 Server 。在 Client 看来通过 Binder引用调用 Server 提供的方法 和通过引用调用本地对象的方法并无区别,尽管这个Binder 引用的实体位于 Server 端,从通信角度中 Client 中的Binder 引用也可以看作是 Server Binder 的 "代理" ,在本地代表远端 Server 为 Client 提供的服务。

  • 面向对象思想的引入将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布于系统的各个进程之中。最诱人的是,这个引用和java里引用一样既可以是强类型,也可以是弱类型,而且可以从一个进程传给其它进程,让大家都能访问同一Server,就象将一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。形形色色的Binder对象以及星罗棋布的引用仿佛粘接各个应用程序的胶水,这也是Binder在英文里的原意。

摘录:Android Bander设计与实现 - 设计篇

进程空间的划分

Android系统基于Linux内核 ,进程都有隔离机制

  • 一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & 内核 隔离开来
  • 二者区别:
    1. 进程间,用户空间的数据不可共享,所以用户空间 = 不可共享空间
    2. 进程间,内核空间的数据可共享,所以内核空间 = 可共享空间

进程隔离是为保护操作系统中进程互不干扰而设计的一组不同硬件和软件的技术。这个技术是为了避免进程A写入进程B的情况发生。 进程的隔离实现,使用了虚拟地址空间。进程A的虚拟地址和进程B的虚拟地址不同,这样就防止进程A将数据信息写入进程B。

不同进程之间,数据不共享;而需要进程间通信,则需要某种机制才能完成。Linux内核拥有着非常多的跨进程通信机制,比如管道,System V,Socket等;

而Android 为什么要用 Binder ?主要从性能和安全上

性能:

  • 在移动设备上,广泛地使用跨进程通信肯定对通信机制本身提出了严格的要求;Binder相对出传统的Socket方式,更加高效;

安全:

  • 另外,传统的进程通信方式对于通信双方的身份并没有做出严格的验证,只有在上层协议上进行架设;比如Socket通信ip地址是客户端手动填入的,都可以进行伪造;而Binder机制从协议本身就支持对通信双方做身份校检,传输过程只需一次拷贝,为发送发添加UID/PID身份,既支持实名Binder也支持匿名Binder,因而大大提升了安全性。这个也是Android权限模型的基础

Binder机制是如何跨进程通信的?

通信过程的四个角色模型:

a. Client(客户端), 使用服务的进程

b. Server(服务端),提供服务的进程

c. ServiceManager(SM 类似于通信录或路由器),管理 Service注册与查询(将字符形式的Binder 名字转化成 Client中对该 Binder的引用 )

d. driver(binder驱动),连接 Server 进程、Client进程 和 ServiceManage的桥梁;作用为:1传递进程间数据,通过内存映射;2 实现线程控制,采用BInder 的线程池,由Binder驱动自身进行管理

Server,Client,ServiceManager运行于【用户空间】;驱动运行于【内核空间】。

通信步骤:

  1. Server 进程向 SM 注册,并告知自己有什么能力,我叫zhangsan 有一个 Object 对象,里面有 add() 方法。
  2. Client 向SM 查询,我要找 zhangsan 需要使用 Object 对象中的 add() 方法;这时驱动在数据流动的时候,它并不会给 Client 进程返回一个真正的 Object 对象,而是返回一个和Object 一样的代理对象 objectProxy,这个objectProxy 也有 add()方法,这个代理对象把参数包装后交给驱动。
  3. 驱动收到这个消息,发现是objectProxy ; 驱动通知 Server 调用真正的 Object 的add() 方法,然后把结果发给驱动,驱动再把结果返回给 Client ,于是整个过程就完成了。

实际上,由于SM与Server通常不在一个进程,Server进程向SM注册的过程也是跨进程通信,驱动也会对这个过程进行暗箱操作:SM中存在的Server端的对象实际上也是代理对象,后面Client向SM查询的时候,驱动会给Client返回另外一个代理对象。Sever进程的本地对象仅有一个,其他进程所拥有的全部都是它的代理。

Binder 跨进程并不是真把一个对象传递到另一个进程,是通过代理对象经过 Binder 驱动来查找并调用真正对象的方法和属性。

事实上 Client 进程只不过持有了 Server 端的代理,代理对象协助驱动完成了跨进程的通信。

而相同进程下直接通过驱动返回真实的对象。

在这里插入图片描述

Binder 到底是什么?

  • 通常意义下,Binder指的是一种通信机制;我们说AIDL使用Binder进行通信,指的就是Binder这种IPC机制。在Android 中实现跨进程通信
  • Binder是一种虚拟的物理设备驱动,即 Binder 驱动;作用于连接 Server 进程、Client进程 和 ServiceManage进程
  • 从Framework层角度看,Binder是ServiceManager连接各种Manager和相应的ManagerService的桥梁
  • 从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
  • 在Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信,较为简单;而Messenger的底层其实是AIDL,正是Binder的核心工作机制。

理解Java层的Binder 代码

IBinder/IInterface/Binder/BinderProxy/Stub

我们使用AIDL接口的时候,经常会接触到这些类,那么这每个类代表的是什么呢?

  • IBinder是一个接口,它代表了一种跨进程传输的能力;只要实现了这个接口,就能将这个对象进行跨进程传递;这是驱动底层支持的;在跨进程数据流经驱动的时候,驱动会识别IBinder类型的数据,从而自动完成不同进程Binder本地对象以及Binder代理对象的转换。
  • IBinder负责数据传输,那么client与server端的调用契约(这里不用接口避免混淆)呢?这里的IInterface代表的就是远程server对象具有什么能力。具体来说,就是aidl里面的接口。
  • Java层的Binder类,代表的其实就是Binder本地对象。BinderProxy类是Binder类的一个内部类,它代表远程进程的Binder对象的本地代理;这两个类都继承自IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder驱动会自动完成这两个对象的转换。
  • 在使用AIDL的时候,编译工具会给我们生成一个Stub的静态内部类;这个类继承了Binder, 说明它是一个Binder本地对象,它实现了IInterface接口,表明它具有远程Server承诺给Client的能力;Stub是一个抽象类,具体的IInterface的相关实现需要我们手动完成,这里使用了策略模式。

Android中的AIDL

1. AIDL的简介

AIDL (Android Interface Definition Language) 是一种接口定义语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数,来完成进程间通信。

简言之,AIDL能够实现进程间通信,其内部是通过Binder机制来实现的

2. AIDL的具体使用

AIDL的实现一共分为三部分,一部分是客户端,调用远程服务。一部分是服务端,提供服务。最后一部分,也是最关键的是AIDL接口,用来传递的参数,提供进程间通信。

大致流程:首先建一个Service和一个AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub类中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑定服务端Service,建立连接后就可以访问远程服务端的方法了。

  1. AIDL支持的数据类型:基本数据类型、StringCharSequenceArrayListHashMapParcelable以及AIDL

  2. 某些类即使和AIDL文件在同一个包中也要显式import进来;

  3. AIDL中除了基本数据类,其他类型的参数都要标上方向:inout或者inout

    称之为定向Tag:

    InOut 输入输出类型

    In 类型 输入类型

    Out类型 输出类型

  4. AIDL接口中支持方法,不支持声明静态变量;

  5. 为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样做的好处是,当客户端是另一个应用的时候,可以直接把整个包复制到客户端工程中。

  6. RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,因为所有的AIDL接口都继承自IInterface`接口。


android开发AIDL使用模拟支付宝支付

alipayMp4 (3).gif
模拟实现功能

模拟支付宝支付,客户端进行充值调用 支付宝的服务,支付宝处理充值请求,返回充值成功或失败的结果给客户端

创建两个应用项目,AlipayServiceDemo 和 AlipayClientDemo 端

Service流程:

1、定义一个 Service 服务 ,并在 manifest中注册服务,定义意图 并 设置 android:exported="true" 可被外部应用使用该服务;

2、 服务端 编写 AIDL 文件 ,定义发起支付接口,以及回调,ReBuild Project 生成对应的 .java 文件;

3、发起支付请求,通过在Service 的onBind 中根据 action 的不同, 返回一个指定了支付宝 action 的 IBinder 对象 ,继承自 AIDL 构建的 xxx.Stub 对象 ,(是根据AIDL 自动生成的.java文件 )。继承Stub 并实现接口定义的方法;

requestPay(); //实现,打开一个支付界面

paySuccess();//回调告诉第三方应用支付成功

payFailed();//回调第三方应用支付失败

4、 在打开的页面回绑该服务 并在服务中实现支付宝的支付服务逻辑动作

Client流程:

1、创建 AIDL文件夹 ,将 Service 端的 AIDL 文件包 复制过来 Client 中进行替换 (需要一模一样),进行 Rebuild Project 生成.java 文件

2、绑定支付宝的服务,之后在onServiceConnected() 连接中 并使用 :.Stub.asInterface(service); 转换成接口的实现

3、使用该接口方法,判空并 catch 异常


AlipayServiceDemo 服务端实现

AndroidMenifest.xml
  <application
      ....>
        <service
            android:name=".PayService"
            android:exported="true">
                <!--定义可被其它应用使用该服务,定义意图-->
            <intent-filter>
                <action android:name="com.alibaba.alipay.THIRD_PART_PAY" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

        </service>
    </application>
2个AIDL文件的创建:
package com.alibaba.alipay;
//使用要导入完整包名!!
import com.alibaba.alipay.ThirdPartPayResult;

interface ThirdPartPayAction {
    /*
    发起支付请求 接口
    */
    void requestPay( String orderInfo, float payMoney,ThirdPartPayResult callBack);
}
----------------------------------------------------------------------------------------
package com.alibaba.alipay;
interface ThirdPartPayResult {
    /*
    支付成功的回调
    */
    void onPaySuccess();
         /*
    支付失败的回调
    */
    void onPayFaild(in int errorCode , in String msg);

}


PayService.java
package com.alibaba.alipay;

/**
 * @author:thisfeng
 * @time 2019/4/18 22:41
 * 支付服务流程:
 * 1、首先接收到第三方应用的绑定,请求支付。
 * <p>
 * 2、然后拉起我们的支付页面Activity ,拉起之后回绑服务,服务内定义并返回支付的动作类 给 PayActivity,
 * <p>
 * 3、通过用户输入的密码判断是否正确, 从服务连接中拿到 IBinder 也是就是这个支付的动作类, 进行服务的动作回调
 */
public class PayService extends Service {


    private static final String TAG = "PayService";

    private ThirdPartPayImpl thirdPartPay;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {

        String action = intent.getAction();
        Log.d(TAG, "action --->" + action);

        if (!TextUtils.isEmpty(action)) {
            if ("com.alibaba.alipay.THIRD_PART_PAY".equals(action)) {
                //通过 action 说明这是第三方要求我们支付宝进行支付
                thirdPartPay = new ThirdPartPayImpl();
                //提取指全局供外部交互时调用
                return thirdPartPay;
            }
        }

        //PayActivity 回绑服务 返回的对象 进行交互,无指定action 默认返回此对象
        return new PayAction();
    }

    /**
     * 定义支付宝的支付 服务逻辑动作
     */
    public class PayAction extends Binder {

        /**
         * 实际的支付是比较复杂的,比如说加密,向服务器发起请求,等待服务器的结果,多次握手等
         * <p>
         * 支付方法
         */
        public void pay(float payMoney) {
            Log.d(TAG, "pay money is --->" + payMoney);

            if (thirdPartPay != null) {
                //回调告诉远程第三方 支付成功
                thirdPartPay.onPaySuccess();
            }
        }

        /**
         * 用户点击界面上的取消/退出
         */
        public void onUserCancel() {
            if (thirdPartPay != null) {
                //回调告诉远程 支付失败
                thirdPartPay.onPayFaild(-1, "user cancel pay...");
            }
        }
    }

    /**
     * 第三方调用起 跨进程 进行支付
     */
    private class ThirdPartPayImpl extends ThirdPartPayAction.Stub {


        private ThirdPartPayResult callBack;

        @Override
        public void requestPay(String orderInfo, float payMoney, ThirdPartPayResult callBack) throws RemoteException {
            this.callBack = callBack;

            Log.d(TAG, "requestPay --->orderInfo:" + orderInfo + " payMoney:" + payMoney);
          
            //第三方应用发起请求,拉起 打开一个支付页面
            Intent intent = new Intent();
            intent.setClass(PayService.this, PayActivity.class);
            intent.putExtra(Const.KEY_BILL_INFO, orderInfo);
            intent.putExtra(Const.KEY_PAY_MONEY, payMoney);
            //新的 task 中打开
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        }

        /**
         * 定义相同的方法,进行回调 ,给外部调用
         */
        public void onPaySuccess() {
            try {
                callBack.onPaySuccess();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        public void onPayFaild(int errorCode, String errorMsg) {
            try {
                callBack.onPayFaild(errorCode, errorMsg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}
PayActivity.java
package com.alibaba.alipay;

/**
 * @author:thisfeng
 * @time 2019/4/18 22:52
 * 支付页面
 */

public class PayActivity extends AppCompatActivity {

    private final String TAG = "PayActivity";

    private boolean isBind;

    private PayService.PayAction payAction;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pay);
      
        doBindService();
        initView();
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        if (payAction != null) {
            payAction.onUserCancel();
        }
    }

    private void initView() {
        Intent intent = getIntent();
        String billInfo = intent.getStringExtra(Const.KEY_BILL_INFO);
        final float payMoney = intent.getFloatExtra(Const.KEY_PAY_MONEY, 0);

        TextView tvPayInfo = findViewById(R.id.tvPayInfo);
        TextView tvPayMoney = findViewById(R.id.tvPayMoney);
        final EditText edtPayPwd = findViewById(R.id.edtPayPwd);

        tvPayInfo.setText("支付账单:" + billInfo);
        tvPayMoney.setText("支付金额:¥" + payMoney);

        findViewById(R.id.btnCommit).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String password = edtPayPwd.getText().toString().trim();

                if ("123456".equals(password) && payAction != null) {
                    //模拟如果密码输入成功就去调用支付,实际上应该请求后端进行加密验证
                    payAction.pay(payMoney);
                    Toast.makeText(PayActivity.this, "支付成功", Toast.LENGTH_SHORT).show();
                    finish();
                } else {
                    Toast.makeText(PayActivity.this, "支付密码错误!", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    /**
     * 支付页面回绑服务,等待获取服务中的支付结果
     * 因为我们的Activity 也要跟 服务 进行通讯,告诉服务通讯结果,所以也要绑定服务
     * <p>
     * 綁定服務
     */
    private void doBindService() {

        Intent intent = new Intent(this, PayService.class);
//        intent.setAction("com.alibaba.alipay.THIRD_PART_PAY");//回绑页面不需要指定这个了,这里不指定设置的话就会默认返回 了 PayAction 这个对象
//        intent.addCategory(Intent.CATEGORY_DEFAULT);
//        intent.setPackage(this.getPackageName());
        isBind = bindService(intent, connection, BIND_AUTO_CREATE);

        Log.d(TAG, "Bind Pay Service..");
    }

    private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            //从服务连接中拿到 支付动作
            payAction = (PayService.PayAction) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };


    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (isBind && connection != null) {
            unbindService(connection);
            Log.d(TAG, "unBind Pay Service..");
            connection = null;
            isBind = false;
        }

    }
}


AlipayClientDemo 客户端实现

MainActivity.java

拷贝服务端的 AIDL 整个文件到本地后定义一个充值页面。

package com.thisfeng.alipayclientdemo;


public class MainActivity extends AppCompatActivity {

    private static final String TAG = "Client MainActivity";
  
    private AlipayConnection alipayConnection;
    private boolean isBind;
    private ThirdPartPayAction thirdPartPayAction;
    private TextView tvTitle;
    private TextView tvMoney;

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

    /**
     * 綁定支付寶的服務,在现在开发中,其实这部分动作是由支付宝的 SDK 完成
     */
    private void bindAlipayService() {

        Intent intent = new Intent();
        intent.setAction("com.alibaba.alipay.THIRD_PART_PAY");//复制服务端的action 和包名,建議提取至全局
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.setPackage("com.alibaba.alipay");

        alipayConnection = new AlipayConnection();

        isBind = bindService(intent, alipayConnection, BIND_AUTO_CREATE);

        Log.d(TAG, "Client bind service ....");
    }

    private void initView() {

        tvTitle = findViewById(R.id.tvTitle);
        tvMoney = findViewById(R.id.tvMoney);

        findViewById(R.id.btnRecharge).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (thirdPartPayAction != null) {
                    //进行充值
                    try {
                        thirdPartPayAction.requestPay("充值100QB", 100, new PayCallBack());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }

                }

            }
        });

    }

    private class PayCallBack extends ThirdPartPayResult.Stub {

        @Override
        public void onPaySuccess() throws RemoteException {
            //支付成功,修改 UI内容
            //实际上是取修改数据库,其实支付宝是通过回调URL地址,直接通知我们的后台服务器,后台返回我们客户端进行通知
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    tvMoney.setText("100");

                    Log.d(TAG, "Client onPaySuccess() -----充值成功 !");

                    Toast.makeText(MainActivity.this, "充值成功!", Toast.LENGTH_SHORT).show();
                }
            });
        }

        @Override
        public void onPayFaild(int errorCode, String msg) throws RemoteException {

            Log.d(TAG, "Client onPayFaild() ----- errorCode:" + errorCode + "---msg:" + msg);

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this, "充值失敗!", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

    private class AlipayConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            //获取支付宝的服务
            thirdPartPayAction = ThirdPartPayAction.Stub.asInterface(service);
            Log.d(TAG, "Client onServiceConnected----->" + name);
        }

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (isBind && alipayConnection != null) {
            unbindService(alipayConnection);
            Log.d(TAG, "Client unbind service ....");
            alipayConnection = null;
            isBind = false;
        }
    }
}

以上为Service 和 Client 的 全部代码。便于理解请务必手动敲一遍实例加深印象。

完整案例代码

学习参考:

Android Bander设计与实现 - 设计篇

Binder学习指南

Android跨进程通信:图文详解 Binder机制 原理

Android艺术探索

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

推荐阅读更多精彩内容