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

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

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

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

1.刷新蓝牙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();
        }
    }

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

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

问题描述

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

解决方案
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问题,系统脑残管不了

3.多次打开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;
    }

4.扫描不到设备

前面说了好几个扫描不到设备的原因, 这里还有呢…
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秒 才开始扫描, 以防止用户一直点扫描按钮 , 或者使用动态计算 以减少用户等待时间

5.其他注意点

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 毫秒, 因为刚刚连接上, 系统有些东西需要刷新,同理,遇到任何问题 延时一下看看能否解决, 因为有些系统的蓝牙很慢很卡,甚至手动关闭蓝牙 都卡死在那 ,偶尔还死机重启了…

6.补充

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


    @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+扫描+重连, , 若检测到无数次返回这种错误,没一次连接成功的情况 记录下次数, 达到一定数量时 提示让用户关闭/开启飞行模式 然后重试吧. 这种因素很多 有手机蓝牙辣鸡的,有代码有问题的比如不扫描就连接, 有蓝牙设备有问题的 各种因素都有.

7.调试

调试设备的工具 有 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

8.趁早弃坑

安卓BLE开发是个巨坑, 现在安卓9.0快出来了, 说不能反射使用非SDK API, 上面的代码可能在9.0 都会出问题.
国产很多辣鸡智障工程师开发的 系统, 我直接点名了: 比如小米 华为 魅族 蓝绿厂等 ,平时为了兼容它们,只能反射反射这样子, 现在再加上谷歌9.0不能反射 的夹攻, 我们该何去何从?
所以 趁早弃坑吧…

猜你喜欢

转载自blog.csdn.net/u014418171/article/details/81219297