【转】Android BLE4.+ 蓝牙开发国产手机兼容性解决方案

本文转自冼东芝的文章Android BLE4.+ 蓝牙开发国产手机兼容性解决方案
如有版权问题,请私信,谢谢。
转载请注明出处。https://blog.csdn.net/u014418171/article/details/81219297

算是做了n年的智能穿戴BLE开发了, 首先对国内的安卓开发者提醒下 , BLE开发是真的很坑, 特别是安卓, ios端也坑, 但没安卓坑 因为国产有很多手机 各种奇葩兼容都有,
其实这些方案我很早就写到云笔记里了,一直没公开, 这里的解决方案大部分都是网上搜不到 或者网上搜到类似的问题, 但回复基本上是回答[无法解决] 或 [重启手机解决]等 没意义的解决办法,让我很无语…

以下内容可能涉及到各种系统类源码
你可以通过这里阅读 https://www.androidos.net.cn/

废话不多说, 希望对你们有用


一.刷新蓝牙app的状态

问题描述:

某些手机用久了会出现扫描不到任何设备的bug,
此时是因为手机误认为本app不是[ble类] app , f**k!!!!! 还有这种操作???
但值得注意的是, 这只是一种原因,[ 扫描不到任何设备的bug] 有很多种原因, 详情请看第3点

解决方案:

[目前网上没有与我类似的解决办法, 所以具体副作用自测]
参考IBluetoothManager.aidl系统源码

出现该问题时于是通过查看系统源码找到isBleAppPresent方法 ,反射调用其后居然返回false ,
换了一台能正常使用的手机 调用该方法 返回true 因此证实了这个问题,
然后发现系统有私有的updateBleAppCount方法, 可以刷新ble类app的状态,反射调用之…
因此解决了 [偶尔ble设备扫描不出来]的bug),
通过传入你的app包名 以 刷新 蓝牙app的错误状态

 public static void refreshBleAppFromSystem(Context context, String packageName) {
        //6.0以上才有该功能,不是6.0以上就算了
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return;
        }

        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter == null) {
            return;
        }
        if (!adapter.isEnabled()) {
            return;
        }
        try {
            Object mIBluetoothManager = getIBluetoothManager(adapter);
            Method isBleAppPresentM = mIBluetoothManager.getClass().getDeclaredMethod("isBleAppPresent");
            isBleAppPresentM.setAccessible(true);
            boolean isBleAppPresent = (Boolean) isBleAppPresentM.invoke(mIBluetoothManager);
            if (isBleAppPresent) {
                return;
            }
            Field mIBinder = BluetoothAdapter.class.getDeclaredField("mToken");
            mIBinder.setAccessible(true);
            Object mToken = mIBinder.get(adapter);

            //刷新偶尔系统无故把app视为非 BLE应用 的错误标识 导致无法扫描设备
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                //8.0+ (部分手机是7.1.2 也是如此)
                Method updateBleAppCount = mIBluetoothManager.getClass().getDeclaredMethod("updateBleAppCount", IBinder.class, boolean.class, String.class);
                updateBleAppCount.setAccessible(true);
                //关一下 再开
                updateBleAppCount.invoke(mIBluetoothManager, mToken, false, packageName);
                updateBleAppCount.invoke(mIBluetoothManager, mToken, true, packageName);

            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

                try {
                    //6.0~7.1.1

                    Method updateBleAppCount = mIBluetoothManager.getClass().getDeclaredMethod("updateBleAppCount", IBinder.class, boolean.class);
                    updateBleAppCount.setAccessible(true);
                    //关一下 再开
                    updateBleAppCount.invoke(mIBluetoothManager, mToken, false);
                    updateBleAppCount.invoke(mIBluetoothManager, mToken, true);
                } catch (NoSuchMethodException e) {
                    //8.0+ (部分手机是7.1.2 也是如此)
                    try {
                        Method updateBleAppCount = mIBluetoothManager.getClass().getDeclaredMethod("updateBleAppCount", IBinder.class, boolean.class, String.class);
                        updateBleAppCount.setAccessible(true);
                        //关一下 再开
                        updateBleAppCount.invoke(mIBluetoothManager, mToken, false, packageName);
                        updateBleAppCount.invoke(mIBluetoothManager, mToken, true, packageName);
                    } catch (NoSuchMethodException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

二.明明gatt.disconnect() 断开蓝牙了,甚至关闭了手机的蓝牙,甚至飞行模式了, 设备仍然在 [已连接] 状态!!! 设备离手机远了才断开,说明,这压根就没断开啊!

看到标题知道国产手机奇葩了吧? 而且我相信不少人都遇到这个问题, 这个问题经常出现在华为>小米>魅族>(VIVO|OPPO) 上。

问题描述

首先导致这个原因可能是:

  • 操作:
    gatt.disconnect/connect断开,连接,断开,连接,断开… 反复重试n次, 有一定的几率导致系统残留了该gatt的引用, app这边没有拿到这个引用 (app操作蓝牙api是通过 remote aidl 操作远程的 系统service ), 系统蓝牙app 也没有了这个引用 ,于是即使你 直接关闭手机蓝牙, 也没有断开连接… 有些手机直到开启飞行模式 才会断开, 而有些手机 即使开启飞行模式也不会断开! 这得看这些手机 的 飞行模式 的实现代码的区别了,暂时没去研究.
  • 猜测连接设备后 被系统杀掉/ 或手动杀掉 也会导致这种情况

解决方案

1.首先你可以获取真正的连接状态:

[目前网上没有与我类似的解决办法, 所以具体副作用自测]
参看IBluetooth.aidl系统源码
出现这种假断开问题时, 笔者曾经尝试 各种gatt.getConnectionState(),BluetoothManager.getConnectionState都是 给我们开发者返回 已断开!, 但实际上没有断开, 经过一番研究后 发现 判断内部连接状态可以通过另一个办法 而不通过 gatt,
BluetoothDevice类 内部的isConnected()方法
这个方法被标记为@SystemApi@hide, 不能直接使用.
并且在低版本的手机上没有, 查看了源码isConnected是由IBluetooth.getConnectionState()实现的, 低版本有getConnectionState

    @SystemApi
    public boolean isConnected() {
        final IBluetooth service = sService;
        if (service == null) {
            // BT is not enabled, we cannot be connected.
            return false;
        }
        try {
            return service.getConnectionState(this) != CONNECTION_STATE_DISCONNECTED;
        } catch (RemoteException e) {
            Log.e(TAG, "", e);
            return false;
        }
    }

于是我们可以通过反射
BluetoothDevice.isConnected/ IBluetooth .getConnectionState实现内部连接状态的判断
高低版本兼容的代码如下:

public static final int CONNECTION_STATE_DISCONNECTED = 0;
public static final int CONNECTION_STATE_CONNECTED = 1;
public static final int CONNECTION_STATE_UN_SUPPORT = -1;

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @SuppressLint("PrivateApi")
    public static int getInternalConnectionState(String mac) {
        //该功能是在21 (5.1.0)以上才支持, 5.0 以及以下 都 不支持
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return CONNECTION_STATE_UN_SUPPORT;
        }
        if(Build.MANUFACTURER.equalsIgnoreCase("OPPO")){//OPPO勿使用这种办法判断, OPPO无解
            return CONNECTION_STATE_UN_SUPPORT;
        }
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        BluetoothDevice remoteDevice = adapter.getRemoteDevice(mac);
        Object mIBluetooth = null;
        try {
            Field sService = BluetoothDevice.class.getDeclaredField("sService");
            sService.setAccessible(true);
            mIBluetooth = sService.get(null);
        } catch (Exception e) {
            return CONNECTION_STATE_UN_SUPPORT;
        }
        if (mIBluetooth == null) return CONNECTION_STATE_UN_SUPPORT;

        boolean isConnected;
        try {
            Method isConnectedMethod = BluetoothDevice.class.getDeclaredMethod("isConnected");
            isConnectedMethod.setAccessible(true);
            isConnected = (Boolean) isConnectedMethod.invoke(remoteDevice);
            isConnectedMethod.setAccessible(false);
        } catch (Exception e) {
        //如果找不到,说明不兼容isConnected, 尝试去使用getConnectionState 判断
            try {
                Method getConnectionState = mIBluetooth.getClass().getDeclaredMethod("getConnectionState", BluetoothDevice.class);
                getConnectionState.setAccessible(true);
                int state = (Integer) getConnectionState.invoke(mIBluetooth, remoteDevice);
                getConnectionState.setAccessible(false);
                isConnected = state == CONNECTION_STATE_CONNECTED;
            } catch (Exception e1) {
                return CONNECTION_STATE_UN_SUPPORT;
            }
        }
        return isConnected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED;

    }

2.尝试断开

[目前网上没有与我类似的解决办法, 所以具体副作用自测]
参考AdapterService.java系统源码
仍然是从IBluetooth入手, 因为 应用层 能拿到的东西不多
研究源码发现 有两个函数可以尝试让 ble 服务关闭和启动,分别是onLeServiceUp / onBrEdrDown
示例:

    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void setLeServiceEnable(boolean isEnable) {

        Object mIBluetooth;
        try {
            Field sService = BluetoothDevice.class.getDeclaredField("sService");
            sService.setAccessible(true);
            mIBluetooth = sService.get(null);
        } catch (Exception e) {
            return;
        }
        if (mIBluetooth == null) return;

        try {
            if (isEnable) {
                Method onLeServiceUp = mIBluetooth.getClass().getDeclaredMethod("onLeServiceUp");
                onLeServiceUp.setAccessible(true);
                onLeServiceUp.invoke(mIBluetooth);
            } else {
                Method onLeServiceUp = mIBluetooth.getClass().getDeclaredMethod("onBrEdrDown");
                onLeServiceUp.setAccessible(true);
                onLeServiceUp.invoke(mIBluetooth);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

但该方法可能在某些手机上仍然无效,原因是 很多国产手机都重新修改了 蓝牙底层相关代码, 为了所谓的省电, 所以单靠看原生系统的源码可能是无意义的
之后琢磨出另一个解决办法 那就是…. 尝试连接~然后断开!
方法很简单,直接通过gatt.connectGatt()等待连接成功后disconnect一次, 此时设备终于断开了!
原因可能是connect后 刷新了残留的gatt引用 于是app又重新拿到了最新的引用, 此时可以操作设备断开了

...
gatt.connectGatt();
...
onConnectionStateChange(final BluetoothGatt gatt, final int status, int newState){
       if (newState == BluetoothProfile.STATE_CONNECTED){
          gatt.disconnect();
       }
}

不过你要注意下不要和你的正常连接逻辑冲突

以上操作,手机显示的蓝牙图标一直是关闭的, 你可能想问我 : 那手机蓝牙关了 怎么反射让他显示开… 这个你只能问这个手机的相关工程师为啥这么脑残了… 无解, 我们只考虑app问题,系统脑残管不了


三.多次打开app/退出app/后台被杀等, 导致扫描不到设备,并返回ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED 错误!

问题描述

但值得注意的是, 这只是第二种原因,[ 扫描不到任何设备的bug]还有其他原因, 详情请看第4点
扫描周围的BLE设备时某些手机会遇到 GATT_Register: cant Register GATT client, MAX client reached!
或者回调中的 onScanFailed 返回了 errorCode =2 则: ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED
具体表现则为 明明周围有很多设备,但是扫描不到任何东西
查到
1.https://blog.csdn.net/chy555chy/article/details/53788748
2.https://stackoverflow.com/questions/27516399/solution-for-ble-scans-scan-failed-application-registration-fail
3.http://detercode121.blogspot.com/2012/04/bluetooth-lowenergy-solution-for-ble.html
等等 没有一个是正常的解决办法,上面这些修复方案是用代码实现关闭蓝牙然后重新打开蓝牙来释放 可是 国产的手机会弹出蓝牙授权的 比如我们要后台扫描重连设备时遇到这种情况 难道要弹出授权让用户确定? 那还要后台重连功能干啥…
而且,有些手机即使关闭蓝牙再打开 也无法释放,有些手机关闭蓝牙后 再打开会卡死系统, 导致蓝牙图标一直卡在那很久 才打开了蓝牙…

解决方案

[目前网上没有与我类似的解决办法, 所以具体副作用自测]

参考IBluetoothGatt.aidl
参考BluetoothLeScanner.java
参考ScanManager.java
参考GattService.java

问题就在于 一些手机在startScan扫描的过程中还没来得及stopScan,就被系统强制杀掉了, 导致mClientIf未被正常释放,实例和相关蓝牙对象已被残留到系统蓝牙服务中,
打开app后又重新初始化ScanCallback多次被注册,导致每次的扫描mClientIf的值都在递增, 于是mClientIf的值
在增加到一定程度时(最大mClientIf数量视国产系统而定 不做深究),onScanFailed返回了errorCode =2至今网上无任何正常的解决办法
于是 我查看了系统源码 发现关键位置BluetoothLeScanner类下的 BleScanCallbackWrapper#startRegistration()扫描是通过registerClient传入 mClientIf 来实现的,
stopScan时调用了iGatt.stopScan()iGatt.unregisterClient()进行解除注册. 了解该原理后 我们就可以反射调用这个方法 , 至于解除mClientIf哪个值 需要你自己做存储记录
这里我写的是解除全部客户端 mClientIf的范围是 0~40
问题至此完美解决 这可能是目前全网唯一不用关闭/开启蓝牙就能完美解决该问题的方案

 public static boolean releaseAllScanClient() {
        try {
            Object mIBluetoothManager = getIBluetoothManager(BluetoothAdapter.getDefaultAdapter());
            if (mIBluetoothManager == null) return false;
            Object iGatt = getIBluetoothGatt(mIBluetoothManager);
            if (iGatt == null) return false;

            Method unregisterClient = getDeclaredMethod(iGatt, "unregisterClient", int.class);
            Method stopScan;
            int type;
            try {
                type = 0;
                stopScan = getDeclaredMethod(iGatt, "stopScan", int.class, boolean.class);
            } catch (Exception e) {
                type = 1;
                stopScan = getDeclaredMethod(iGatt, "stopScan", int.class);
            }

            for (int mClientIf = 0; mClientIf <= 40; mClientIf++) {
                if (type == 0) {
                    try {
                        stopScan.invoke(iGatt, mClientIf, false);
                    } catch (Exception ignored) {
                    }
                }
                if (type == 1) {
                    try {
                        stopScan.invoke(iGatt, mClientIf);
                    } catch (Exception ignored) {
                    }
                }
                try {
                    unregisterClient.invoke(iGatt, mClientIf);
                } catch (Exception ignored) {
                }
            }
            stopScan.setAccessible(false);
            unregisterClient.setAccessible(false);
            BLESupport.getDeclaredMethod(iGatt, "unregAll").invoke(iGatt);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

其中如果你想获得 mClientIf 的值,方便研究该问题 可以尝参考以下代码
其中参数ScanCallback类 是安卓6.0扫描回调类

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static boolean isScanClientInitialize(ScanCallback callback) {
        try {
            Field mLeScanClientsField = getDeclaredField(BluetoothLeScanner.class, "mLeScanClients");
            //  HashMap<ScanCallback, BleScanCallbackWrapper>()
            HashMap callbackList = (HashMap) mLeScanClientsField.get(BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner());
            int size = callbackList == null ? 0 : callbackList.size();
            if (size > 0) {
                Iterator iterator = callbackList.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry entry = (Map.Entry) iterator.next();
                    Object key = entry.getKey();
                    Object val = entry.getValue();
                    if (val != null && key != null && key == callback) {
                        int mClientIf = 0;
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                            Field mScannerIdField = getDeclaredField(val, "mScannerId");
                            mClientIf = mScannerIdField.getInt(val);

                        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                            Field mClientIfField = getDeclaredField(val, "mClientIf");
                            mClientIf = mClientIfField.getInt(val);
                        }
                        System.out.println("mClientIf=" + mClientIf);
                        return true;
                    }
                }
            } else {
                if (callback != null) {
                    return false;
                }
            }


        } catch (Exception ignored) {

        }

        return true;
    }

四、扫描不到设备

前面说了好几个扫描不到设备的原因, 这里还有呢…
1.未开启位置访问权限Manifest.permission.ACCESS_COARSE_LOCATION如果你是6.0系统 则需要申请该权限 才能扫描设备, 检查和申请网上有 这里不重复说了
2.检查GPS的 LOCATION_MODE是否开启,否则在OPPO/VIVO等手机 无法扫描设备。

//代码 反编译 nrfconnect 参考得来:
public static boolean hasLocationEnablePermission(Context context) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return true;
        }
        int locationMode = Settings.Secure.LOCATION_MODE_OFF;
        try {
            locationMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE);
        } catch (Exception ignored) {
        }
        if (locationMode != Settings.Secure.LOCATION_MODE_OFF) {
            return true;
        }
        return false;
}
//没有权限则跳转到 gps界面授权
if(!hasLocationEnablePermission(this)){
  Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  context.startActivity(intent);
}

3.安卓7.0不允许在30s内连续扫描5次,否则无法扫描到任何设备,只能重启app, 你可以写一个算法 比如每次先延时30/5=6秒 才开始扫描, 以防止用户一直点扫描按钮 , 或者使用动态计算 以减少用户等待时间


五、其他注意点

1.为了加快连接设备的速度 , 你可以不扫描设备直接通过mac地址连接 ,使用 gatt.connectGatt ,但有时候连接不上是因为 设备信息可能变化了, 但系统缓存没变,所以一直连接不上, 即使连接上了 马上返回各种-133 -192等错误, 解决办法是 你需要 重新扫描这个mac一下,找到了mac ,再连接

2.扫描设备的时候 要切记, 扫描到了后 先停止扫描, 过1秒左右 再连接, 避免扫描的时候连接, 导致连接过程中 缓存再次被刷新

3.无任何原因, app扫描不到该设备,但能搜索到其他设备, 而另一个手机却都能搜索到, 试试下载 nrf的 nrfconnect 去搜索测试(同一台手机), 若 nrfconnect 能搜索到 则是app代码问题, 否则检测 设备蓝牙晶振频率 是否不支持该手机的蓝牙频率发现范围! 联系相关开发人员解决

4.距离防丢功能, 通过rssi可以拿到设备距离手机的信号值来判断 设备是否远离手机 触发防丢警报, 但rssi信号 受各种环境因素影响, 所以有点坑 , 建议做延迟处理 ,意思是达到防丢rssi值时 延时n秒 才警报, 若在n秒内 又恢复, 说明只是信号突然弱了一下. 无需 警报. 还有就是因素太多了 ,和手机的蓝牙模块有关, 和设备的蓝牙天线有关, 功率有关等, 建议在app内添加 一个用户可以设定的rssi防丢范围, 因为程序没法精准计算

5.扫描蓝牙设备callback回调时 建议丢到另外一个线程用队列去处理, 不要在扫描回调里处理耗时逻辑., 同理 在onCharacteristicChanged 中接收设备notify通知返回的数据时, 不要在此方法内进行耗时处理, 否则大量数据过来时会100%丢包!!! 解决办法和前面的一样. (话说某BLE开源框架就有这个问题,还好我用我自己写的)

6.同步大量数据时, 某些手机完美正常, 某些手机出现丢包严重,建议修改连接间隔 同步前 使用gatt.requestConnectionPriority(CONNECTION_PRIORITY_HIGH) , 同步完成后 恢复原来的连接间隔CONNECTION_PRIORITY_BALANCED

7.对设备进行OTA升级后,直接使用mac来连接, 连接不上, 原因是系统缓存没刷新, 你需要扫描后停止扫描再连接

8.连接之前建议先把 gatt.close一下

9.使用gatt.discoverServices()发现服务之前,建议先 sleep 500 毫秒, 因为刚刚连接上, 系统有些东西需要刷新,同理,遇到任何问题 延时一下看看能否解决, 因为有些系统的蓝牙很慢很卡,甚至手动关闭蓝牙 都卡死在那 ,偶尔还死机重启了…


六、补充

贴出一些上面缺失的函数,因为方便和减少代码重复量, 所以上面没贴

    @SuppressLint("PrivateApi")
    public static Object getIBluetoothGatt(Object mIBluetoothManager) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Method getBluetoothGatt = getDeclaredMethod(mIBluetoothManager, "getBluetoothGatt");
        return getBluetoothGatt.invoke(mIBluetoothManager);
    }


    @SuppressLint("PrivateApi")
    public static Object getIBluetoothManager(BluetoothAdapter adapter) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Method getBluetoothManager = getDeclaredMethod(BluetoothAdapter.class, "getBluetoothManager");
        return getBluetoothManager.invoke(adapter);
    }


    public static Field getDeclaredField(Class<?> clazz, String name) throws NoSuchFieldException {
        Field declaredField = clazz.getDeclaredField(name);
        declaredField.setAccessible(true);
        return declaredField;
    }


    public static Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) throws NoSuchMethodException {
        Method declaredMethod = clazz.getDeclaredMethod(name, parameterTypes);
        declaredMethod.setAccessible(true);
        return declaredMethod;
    }


    public static Field getDeclaredField(Object obj, String name) throws NoSuchFieldException {
        Field declaredField = obj.getClass().getDeclaredField(name);
        declaredField.setAccessible(true);
        return declaredField;
    }


    public static Method getDeclaredMethod(Object obj, String name, Class<?>... parameterTypes) throws NoSuchMethodException {
        Method declaredMethod = obj.getClass().getDeclaredMethod(name, parameterTypes);
        declaredMethod.setAccessible(true);
        return declaredMethod;
    }

你可能还想问, 还有呢 还有最重要的 连接时总是返回 -133 -86 -192 这些怎么办啊 怎么解决啊
我只想和你说, 别抱着希望了 你可能在网上看到很多解决办法 但最终你使用了解决代码, 仍然无法解决…
放弃吧, 换一种思路, 遇到这种错误, 直接断开+sleep+扫描+重连, , 若检测到无数次返回这种错误,没一次连接成功的情况 记录下次数, 达到一定数量时 提示让用户关闭/开启飞行模式 然后重试吧. 这种因素很多 有手机蓝牙辣鸡的,有代码有问题的比如不扫描就连接, 有蓝牙设备有问题的 各种因素都有.


七、调试

调试设备的工具 有 ios的lightblue,
安卓的推荐nrf芯片公司开发的 nrf connect 调试工具]

我也写了两个小应用,有兴趣可以下来看看
BLE调试器
https://www.coolapk.com/apk/com.toshiba.ble
BLE指令协议窃取工具(需要xposed), 可以窃取手机上的某app和其对应的ble设备 正在进行的数据通讯, 你可以理解为蓝牙协议抓包 (仅供学习用途)
https://www.coolapk.com/apk/com.tos.bledetector


拓展阅读

android ble常见问题收集


如果您有更好的建议欢迎评论分享,如有错误,请批评指正,谢谢。

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