您的足迹:首页 > 蓝牙BLE >Android BLE开发小总结

Android BLE开发小总结

______________________________________________________________

Android BLE 框架发布,功能全面,简单易用:

https://github.com/a1anwang/okble

______________________________________________________________

Android ble开发和IOS ble开发基本差不多,有点小区别。IOS 表现是特别稳定特别好用,重连飞快,需要操心的少。 Android目前坑还是比较多,各种错误,蓝牙奔溃等等。但,细心调理,Android也还是可以用的。


本篇文章主讲 Android, IOS的简单点,可以下次讲。

先来介绍几个类 :BluetoothGatt,BluetoothGattService,BluetoothGattCharacteristic,BluetoothGattCallback   


  官方介绍网址是   https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html.有兴趣的童鞋可以翻墙去看。以下是我尽可能形象的通 俗表达
BluetoothGatt
       BluetoothGatt 是开发里最重要最常用到的东西了,我把它理解为 手机与 设备通信的管道。有了这个管道,收发数据就容易了。
         BluetoothGatt 是通过蓝牙设备连接获得:  bluetoothDevice.connectGatt(this.context, false, gattCallback);
                                 先来谈一下这个方法:
                         BluetoothGatt android.bluetooth.BluetoothDevice.connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)


                          public BluetoothGatt connectGatt (Context context, boolean autoConnect, BluetoothGattCallback callback)                           Added in API level 18

                          Connect to GATT Server hosted by this device. Caller acts as GATT client. The callback is used to deliver results to Caller,           
                            such as connection status as well as any further GATT client operations. The method returns a BluetoothGatt instance. 
                             You   can use BluetoothGatt to conduct GATT client operations.

                               Parameters
                             autoConnect Whether to directly connect to the remote device (false) or to automatically connect as soon as the remote device becomes available (true).
                                callback GATT callback handler that will receive asynchronous callbacks.

                                Throws
                           IllegalArgumentException if callback is null
                         --------------------------------------------------------------------------------------
                          autoConnect  为false  立刻发起一次连接
为true  自动连接,只要蓝牙设备变得可用



                           实测发现,用false连接比较好,比较快, true会等个十几秒甚至几分钟才会连接上。  开发过程中一般都是用false,扫描到bluetoothdevice之后,直接用false连接即可。
                           BluetoothGattCallback   是非常重要的回调函数,手机与蓝牙设备的一切通信结果都在这里体现。待会细讲。


BluetoothGattService
           蓝牙设备所拥有的服务。在这里比喻成 班级 吧。bluetoothdevice就比喻成学校吧。 一个学校可以有很多个班级。班级 根据UUID来区别。
BluetoothGattCharacteristic
           蓝牙设备所拥有的特征。比喻成 学生。 一个班级里也可以有很多个学生。学生也是根据UUID 来区别

                当你需要用手机来和蓝牙设备通信的时候,就相当于 你想 和一个学生交流,你得先知道 这个学生的学号,所在班级号,就是开发中的所说的CharacUUID, ServiceUUID


BluetoothGattCallback   
      所有操作的回调函数。
           private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {

                @Override
                public void onCharacteristicChanged(BluetoothGatt gatt,
                                BluetoothGattCharacteristic characteristic) {

                        super.onCharacteristicChanged(gatt, characteristic);
                       //收到设备notify值 (设备上报值)
                }

                @Override
                public void onCharacteristicRead(BluetoothGatt gatt,
                                BluetoothGattCharacteristic characteristic, int status) {
                        super.onCharacteristicRead(gatt, characteristic, status);
                        //读取到值

                }

                @Override
                public void onCharacteristicWrite(BluetoothGatt gatt,
                                BluetoothGattCharacteristic characteristic, int status) {
                        super.onCharacteristicWrite(gatt, characteristic, status);
                                if (status == BluetoothGatt.GATT_SUCCESS) {
                                         //write成功(发送值成功)
                                }
                         
                }

                @Override
                public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                int newState) {
                        super.onConnectionStateChange(gatt, status, newState);
                        if (status == BluetoothGatt.GATT_SUCCESS) {
                                if (newState == BluetoothGatt.STATE_CONNECTED) {
                                     // 连接成功
                                 
                                } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                                      // 断开连接
                                         
                                }
                        }
                }

                @Override
                public void onDescriptorRead(BluetoothGatt gatt,
                                BluetoothGattDescriptor descriptor, int status) {
                        super.onDescriptorRead(gatt, descriptor, status);
                }

                @Override
                public void onDescriptorWrite(BluetoothGatt gatt,
                                BluetoothGattDescriptor descriptor, int status) {
                        super.onDescriptorWrite(gatt, descriptor, status);

                }

                @Override
                public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
                        super.onReadRemoteRssi(gatt, rssi, status);
                        if (status == BluetoothGatt.GATT_SUCCESS) {
                                  //获取到RSSI,  RSSI 正常情况下 是 一个 负值,如 -33 ; 这个值的绝对值越小,代表设备离手机越近
                                 //通过mBluetoothGatt.readRemoteRssi();来获取
                        }
                }

                @Override
                public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
                        super.onReliableWriteCompleted(gatt, status);
                }

                @Override
                public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                        super.onServicesDiscovered(gatt, status);

                        if (status == BluetoothGatt.GATT_SUCCESS) {
                                       //寻找到服务
                        }
                }
        };
       ---------------------------------------------------------------------------------------------------------------------------------------
       当调用了连接函数 mBluetoothGatt =  bluetoothDevice.connectGatt(this.context, false, gattCallback);之后,
       如果连接成功就会 走到 连接状态回调:
                    @Override
                public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                int newState) {
                                if (status == BluetoothGatt.GATT_SUCCESS) {
                                        //首先判断这个status   如果等于 BluetoothGatt.GATT_SUCCESS(value=0)代表这个回调是正常的,
                                       //如果不等于 0,那边就代表没有成功,也不需要进行其他操作了,
                                       // 连接成功和断开连接都会走到这里
                                         if (newState == BluetoothGatt.STATE_CONNECTED) {
                                                   // 连接成功


                                                   //连接成功之后,我们应该立刻去寻找服务(上面提到的BluetoothGattService),只有寻找到服务之后,才可以和设备进行通信
                                                   gatt.discoverServices();// 寻找服务
                                          } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                                                // 断开连接
                                          }

                               }
                }
         当判断到连接成功之后,会去寻找服务, 这个过程是异步的,会耗点时间,当寻找到服务之后,会走到回调:
                @Override
                public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                        super.onServicesDiscovered(gatt, status);

                        if (status == BluetoothGatt.GATT_SUCCESS) {
                                       //寻找到服务
                                      //寻找服务之后,我们就可以和设备进行通信,比如下发配置值,获取设备电量什么的
                                     
                                      readBatrery();  //读取电量操作
                                     sendSetting(); //下发配置值

                        }
                }
           /***读操作***/
             void   readBatrery(){
                         //如上面所说,想要和一个学生通信,先知道他的班级(ServiceUUID)和学号(CharacUUID
                         BluetoothGattService batteryService=mBluetoothGatt.getService(UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb")); //此处的0000180f...是举例,实际开发需要询问硬件那边
                         if(batteryService!=null){
                        BluetoothGattCharacteristic batteryCharacteristic=batteryService.getCharacteristic(UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb"));//此处的00002a19...是举例,实际开发需要询问硬件那边
                        if(batteryCharacteristic!=null){
                                mBluetoothGatt.readCharacteristic(batteryCharacteristic); //读取电量, 这是读取batteryCharacteristic值的方法,读取其他的值也是如此,只是它们的ServiceUUID 和CharacUUID不一样
                        }
                }
              }
            如果读取电量(或者读取其他值)成功之后 ,会来到 回调:
                @Override
                public void onCharacteristicRead(BluetoothGatt gatt,
                                BluetoothGattCharacteristic characteristic, int status) {
                        super.onCharacteristicRead(gatt, characteristic, status);
                        //读取到值,根据UUID来判断读到的是什么值
                     if (characteristic.getUuid().toString()
                                        .equals("00002a19-0000-1000-8000-00805f9b34fb")) {// 获取到电量
                             int battery = characteristic.getValue()[0];
                     }

                }
         /***写操作***/
         void   sendSetting(){
                BluetoothGattService sendService=mBluetoothGatt.getService(UUID.fromString("00001805-0000-1000-8000-00805f9b34fb"));//此处的00001805...是举例,实际开发需要询问硬件那边
                if(sendService!=null){
                        BluetoothGattCharacteristic sendCharacteristic=sendService.getCharacteristic(UUID.fromString("00002a08-0000-1000-8000-00805f9b34fb"));//此处的00002a08...是举例,实际开发需要询问硬件那边
                        if(sendCharacteristic!=null){
                                sendCharacteristic.setValue(new byte[] { 0x01,0x20,0x03  });//随便举个数据

                                mBluetoothGatt.writeCharacteristic(sendCharacteristic);//写命令到设备, 
                        }
                }
         }


          如果下发配置成功之后,会来到回调:
               @Override
                public void onCharacteristicWrite(BluetoothGatt gatt,
                                BluetoothGattCharacteristic characteristic, int status) {
                        super.onCharacteristicWrite(gatt, characteristic, status);
                                if (status == BluetoothGatt.GATT_SUCCESS) {
                                         //write成功(发送值成功),可以根据 characteristic.getValue()来判断是哪个值发送成功了,比如 连接上设备之后你有一大串命令需要下发,你调用多次写命令, 这样你需要判断是不是所有命令都成功了,因为android不太稳定,有必要来check命令是否成功,否则你会发现你明明调用 写命令,但是设备那边不响应
                                         
                                         
                                }
                         
                }


       讲解完读写操作,还有一个重要操作 就是  Notify(通知)
        notify 就是让设备 可以发送通知给你,也可以说上报值给你(发送命令给你)
              首先你得打开设备的通知功能:    //参数 enable 就是打开还是关闭, characteristic就是你想让具有通知功能的BluetoothGattCharacteristic 
                private boolean enableNotification(boolean enable,
                        BluetoothGattCharacteristic characteristic) {
                if (mBluetoothGatt == null || characteristic == null)
                        return false;
                if (!mBluetoothGatt.setCharacteristicNotification(characteristic,
                                enable))
                        return false;
                BluetoothGattDescriptor clientConfig = characteristic
                                .getDescriptor(UUIDUtils.CCC);
                if (clientConfig == null)
                        return false;

                if (enable) {
                        clientConfig
                                        .setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                } else {
                        clientConfig
                                        .setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                }
                return mBluetoothGatt.writeDescriptor(clientConfig);
           }
           一旦设备那边notify 数据给你,你会在回调里收到:
                @Override
                public void onCharacteristicChanged(BluetoothGatt gatt,
                                BluetoothGattCharacteristic characteristic) {

                        super.onCharacteristicChanged(gatt, characteristic);
                       //收到设备notify值 (设备上报值),根据 characteristic.getUUID()来判断是谁发送值给你,根据characteristic.getValue()来获取这个值
                }





=========以上属于BLE基本的操作,下面是进阶================

1. 关于发送命令, 比如刚连接上设备,你会把很多配置都发送给设备,此时可能会调用多次 write操作,这个时候你应该需要在 write操作之间 加点间隔 例如开启线程,用 Thread.sleep(150), 因为只有收到onCharacteristicWrite回调之后才代表你的值成功了,如果
     不加间隔,你会发现可能只会成功一个值。目前测试下来,我才用的是间隔150毫秒比较好,不会太慢,成功率也会高, 可自己实践慢慢调试,这个数值还与设备端update速率有关
           
    在Write 操作的时候,还有个办法可以增快下发速度,且不需要加sleep
     就是设置characteristic .setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);  Wrtite characteristic without requiring a response by the remote device, 就是不需        要回复,这样速度会快,测试下来连续发送9个数据,设备端那边打印数据都收到且是正确的,但回调值有点奇怪:
        图1 是我的发送指令 ,连续无间隔发送9个命令:
                 
       图2,3是 onCharacteristicWrite 回调打印信息:
                    
          从图片看出,有的值没有收到 onCharacteristicWrite 回调,如第一个值 { 1, 0x08, 3 }   ,有的值回调了2次 如 { 1, 0x04, 3 },可能会觉得有的值没有发送成功吧?
     以下是 设备端收到值的打印信息:(0x00FF是用做分隔符,可忽略)
                 
         
           可以发现,9个值均接收正常, 所以说 这种快速发值,还是有点可靠的。测试的不多,各位可斟酌使用


2.关于重连     
  提到ble,估计想的最多的就是重连了。重连当然得是自动重连。可能有人想到的的就是 断开之后再来一次bluetoothDevice.connectGatt(this.context, false, gattCallback)行不行? 


先看看官方的说法 :
           
       官方说  mbluetoothGatt.connect()方法就是用来重连的, 只要你断线之后 调用此方法就好了。 
       测试下来 确实可以,但不同手机体验不一样, 但大家知道 android 碎片化现象很严重,什么 flyme,什么MIUI,emotion之类的。所以需要做什么适配。
      只调用 mbluetoothGatt.connect()来重连,我归类为2种  三星 手机  和 非三星手机。
      三星手机现象:
             重连非常迅速 ,断开之后,只要一回到有效范围内。立马重连成功。 就算隔一天你回来 靠近设备,立马连接成功。这简直就是太棒了。
     非三星手机现象:
            也会重连成功,但这时间无法保证,有可能1分钟,有可能5分钟,10分钟,甚至就连不上了。体验非常不好。

     说一下解决办法
           判断手机是否为三星手机:
              private boolean checkIsSamsung() { //此方法是我自行使用众多三星手机总结出来,不一定很准确
                String brand = android.os.Build.BRAND;
                Log.e("", " brand:" + brand);
                if (brand.toLowerCase().equals("samsung")) {
                        return true;
                }
                return false;
              }
         在设备断线之后, 我们需要进入重连流程:




              if(isSamsung){
                   //这里最好 Sleep 300毫秒,测试发现有时候三星手机断线之后立马调用connect会容易蓝牙奔溃
                   mbluetoothGatt.connect();
              }else{
                   connectGatt();
                   handler.removeCallbacks(runnableReconnect  );
                  handler.postDelayed(runnableReconnect  , 30*1000);
              }
             流程就是断线之后 三星手机 调用官方的connect()函数,而非三星手机 使用 connectGatt() ,相当于重新建立连接,并且每30s去循环询问是否需要重新建立连接,如果已经连接好,取消这个循环。这样能保证过一天回来你也有机会重连上,非三星手机通过这个方法重连体验特别好,也可以达到回来之后瞬间连接的效果

               
               这样的方案目前是比较好的,手机APP也可以很省电。,像很多其他的防丢器类APP,比如nut.研究过这个app,这个软件总是在扫描,一直scan,不管你连上还是不连上。 android的scan是很耗电的,app功能完善之后同时也要考虑电量优化。
               这里测试过 手机的耗电量,用示波器来接手机测试的,
                三星断线之后 重连的阶段,耗电量大概在20ma-30ma(锁屏电量 3ma),
                魅族MX2 在30s循环期间,耗电量大概在15ma(锁屏电量 15ma),
                    这里推测三星的connect()期间它系统自己在后台使用低频率的扫描,这样可以使他重连是很快的。scan就要耗电,所以它电量会比锁屏上升一点。
                   魅族MX2采用每30s重新建立连接,没有scan,基本没有额外耗电。
               这种 重连流程 用身边的手机 三星S3,S4,note2,MX2,M3,Nexus5测试过都很好用。亲们也可以用其他测试一下。




        public void connectGatt() {


                if (bluetoothDevice != null) {
                        if (mBluetoothGatt != null) {
                                mBluetoothGatt.close();
                                mBluetoothGatt.disconnect();


                                mBluetoothGatt = null;
                        }
                        mBluetoothGatt = bluetoothDevice.connectGatt(this.context, false,
                                        gattCallback);


                } else {
                        Log.e(tagString,
                                        "the bluetoothDevice is null, please reset the bluetoothDevice");
                }
        }



            Runnable runnableReconnect = new Runnable() {


                        @Override
                        public void run() {
                                if (! isConnected() ) {
                                        handler.postDelayed(this, 30 * 1000);
                                         connectGatt();
                                }
                        }
                };
3.关于多设备连接       核心就是 创建并保存多个 BluetoothGatt对象而已 
     为了方便管理,举个例子,可以自定义类 :MYDevice{
        private BluetoothDevice bluetoothDevice;
        private BluetoothGatt mBluetoothGatt;

        public BluetoothDevice getBluetoothDevice() {
                return bluetoothDevice;
        }

        public void setBluetoothDevice(BluetoothDevice bluetoothDevice) {
                this.bluetoothDevice = bluetoothDevice;
        }


          public boolean connect() {
                if (bluetoothDevice != null) {
                        if (mBluetoothGatt != null) {
                                mBluetoothGatt.close();
                                mBluetoothGatt.disconnect();


                                mBluetoothGatt = null;
                        }
                        mBluetoothGatt = bluetoothDevice.connectGatt(this.context, false,
                                        gattCallback);
                        if (mBluetoothGatt != null)
                                return mBluetoothGatt.connect();
                } else {
                        Log.e(tagString,
                                        "the bluetoothDevice is null, please reset the bluetoothDevice");
                        return false;
                }
                return false;
        }

    }


         每连接一台设备,就创建一个MYDevice实例, 用setBluetoothDevice(BluetoothDevice bluetoothDevice)设置bluetoothDevice对象,然后调用 connect()方法进行连接。
       有5台设备就有5个MYDevice实例,可以把这5个实力放入list里面,方便使用


4.问题总结
    三星的很容易出现failed to register callback,或者waiting for callback   如图
             
        解决办法: 如网上所说接方法放入UI线程操作 , 亲测有效


       出现133错误  如图
           
         调用connectGatt(this.context, false, gattCallback)(立刻重连);之后如果30秒之内没连上,会有133出现,这个超时时间文档也没看到过, 自己测 试出来的。
          出现这个错误之后会很难再连上设备,除非重启手机蓝牙(有时甚至要重启手机) 或者重启设备。
        这个问题或多或少与app代码,设备2边都有关系。目前 本人使用上文提到的重连方法 加上本人的设备 现在很少很少遇到这个错误了,总之就是尽量的不好频繁的断开,重新连接


        手机蓝牙奔溃,譬如再次掉用 scan方法会阻塞主线程,且扫不到设备

这出现这个问题一般都会伴随下面的现象:
              
          执行连接函数的时候 会系统打印这些log。 但是 对比一下下图:
             
           下面的图多了一句 onclientregistered;  下图这个情况是正常的,不会引起蓝牙异常,而一旦出现上图情况,手机蓝牙必奔溃
            这多半和app代码有关, 在gatt寻找到服务之后不要过多的有异常操作

           
  BluetoothManager.getConnectionState(bluetoothDevice, BluetoothGatt.GATT) 最好 不要 调用这个方法。  测试到,经常蓝牙奔溃 或重连 遇到133错误。  此操作应该也是属于一种 耗资源操作,而不是 一种 直接的get方法。所以蓝牙操作一多,就会造成不稳定。

本博客所有文章如无特别注明均为原创。作者:AlanWang复制或转载请以超链接形式注明转自 AlanWang的博客-专注android和蓝牙BLE技术分享
原文地址《Android BLE开发小总结

相关推荐

发表评论

路人甲 表情
看不清楚?点图切换 Ctrl+Enter快速提交

网友评论(12)

大家好!
我的名字是雅娜,我住在瑞典,一个美丽的金发女郎,因为它应该是瑞典妇女)
我有孩子,是时候做学校作业和上课了,真正的恐怖开始了
在学校经常缺乏睡眠,神经,检查和评估。.
我甚至停止与我的丈夫睡觉,我只是没有心情。 一位知名的朋友建议有家庭作业的网站,在那里你可以找到答案并快速解决课程,然后把自己交给你最喜欢的活动!
顺便说一句,一个很好的网站 https://www.hometask.ru
没有广告,方便搜索和大量关于试卷的有价值的信息!
说实话,我开始平静地睡觉,家庭中的性与和平得到了恢复,这要归功于这样的网站,你可以找到解决方案并获得自由!
祝你好运!
JanaLep 1周前 (2023-03-14) 回复
<a href=https://megaremont.pro/grodno-restavratsiya-vann>修复浴缸</a>
Hiramcax 2周前 (2023-03-11) 回复
朋友们好!

我的名字是伊琳娜,我不再是一个女孩,我33岁,过着有趣的,有时是放荡的生活,陷入严重的抑郁症。
我去了不同的医生和心理学家,在花费了大量的金钱和时间之后,我找到了我的解决方案,你问什么?
我刚开始阅读笑话,每天10分钟,更经常参加与幽默有关的活动,这开始产生积极的活力,改变了我的生活。
顺便说一句,一个很好的笑话网站 - https://www.anekdotas.ru
我经常读它,从许多其他人,我劝你)
为什么加载自己的问题和忧虑。

笑,长寿!
Iraelask 1个月前 (2023-02-12) 回复
你好!

我的名字是索菲亚,25年,我来自马耳他)嗯,3年前我开始怀疑交易或加密交易,这种方式成为我的主要生活方式!
探索许多有趣的硬币和金融工具是非常有趣的,在非常严肃的理论课程之后的第一年,我只通过Binance,Huobi,Bybit等许多工具内的模拟账户进行交易。
我的交易课程老师推荐我一个独特的服务,在那里你可以尝试交易,测试策略,创建机器人,使用投资组合工具和所有这一切绝对免费!
你可以在这里查看这个系统– https://capico.space
现在我是一个真正的交易者,可以赚取正常的利润并生活在马耳他! 这是一个很难的职业,不要相信所有教授和说加密或外汇市场的博主的99%的话是每个人的天堂!
这些人只是bla bla驴子和骗子!!
阅读有关交易的好书,尝试培养纪律,保持交易者的日记,并尝试当然交易)

祝你好运!
Sofiadwend 2个月前 (2023-01-31) 回复
你好!

定期,我研究关于性和色情的各种材料,我开始意识到,通过正确的方法,它会得到回报。
性和人的生活一直是和将在一起,并正确理解心理学和与伴侣关系的要点,
它们有助于使性和关系变得有趣和富有成效,而不仅仅是因为家庭生活的必要性而通常发生在大多数夫妇身上。
我想说,我最喜欢的网站之一,关于色情,性和夫妻生活的文章 – https://www.sex18only.ru
一切都是相当准确的,到点,没有分心的图片或粗俗的横幅,只是信息和有用的文章性别!
如果这个资源对你有用,或者你可以告诉我一些更有价值的东西,我会很高兴。

祝你好运!
AnnaSob 2个月前 (2023-01-20) 回复
你好!

最近我遇到了一个私人性故事的网站,我从来没有读过这个,这是一个非常有趣的经历!
它正在阅读其他人的色情思想和想法,他们的真实或虚构的性故事,这些故事会让人兴奋好几倍,
强迫想象力工作,想象文章中的图片。
我找到的网站 – https://www.sexsrasskazy.ru
值得注意的是,没有广告,污垢和色情图片,只是文字和你的想象力,这真是太棒了!
说实话,不断的兴奋给我的生活增添了色彩,我和我的伴侣开始更频繁地发生性行为并建立实验。

我推荐上面的网站,并祝您阅读愉快!
Dinadom 2个月前 (2023-01-20) 回复
水微晶玻尿酸 - 八千代


https://yachiyo.com.tw/hyadermissmile-injection/
DavidLoock 5个月前 (2022-10-27) 回复
點子數位科技有限公司

https://spot-digital.com.tw/
TommyziZ 6个月前 (2022-09-16) 回复
Xevil5.0自动解决大多数类型的captchas,
包括这类验证码: ReCaptcha-2, ReCaptcha v.3, Hotmail, Google, SolveMedia, BitcoinFaucet, Steam, +12k
+ hCaptcha 支持新的Xevil6.0! 只需在YouTube中搜索XEvil6.0

有兴趣吗? 只是谷歌XEvil 5
P.S. 免费XEvil演示可用 !!

此外,还有一个巨大的折扣可供购买,直到7月30日: -30%!
Xevil价格的独立完整许可证仅为59美元

http://xrumersale.site/

查看YouTube中的新视频:
"XEvil 6.0 <Beta>1] + XRumer multhithreading hCaptcha test"
Margaritapa 7个月前 (2022-08-10) 回复
太達數位媒體


https://deltamarketing.com.tw/
AndresWaf 8个月前 (2022-07-16) 回复
1 2