蓝牙通讯数据传输,蓝牙UUID,BluetoothAdapter;adb input 模拟系统输入,adb logcat:抓包,抓日志,蓝牙通讯,adb命令实现

本文涉及的产品
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
阿里云百炼推荐规格 ADB PostgreSQL,4核16GB 100GB 1个月
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
简介: 蓝牙通讯数据传输,蓝牙UUID,BluetoothAdapter;adb input 模拟系统输入,adb logcat:抓包,抓日志,蓝牙通讯,adb命令实现

adb 命令 模拟滑动



input text
input keyevent
input tap
input swipe


  1. keyevent指的是android对应的keycode,比如home键的keycode=3,back键的keycode=4.


具体:  Android Keycode详解_小小攻城师的博客-CSDN博客_android keycode


举例:模拟home按键:adb shell input keyevent 3


  1. 关于tap的话,他模拟的是touch屏幕的事件,只需给出x、y坐标即可。


此x、y坐标对应的是真实的屏幕分辨率,所以要根据具体手机具体看,比如你想点击屏幕(x, y) = (250, 250)位置:


adb shell input tap 250 250


  1. 关于swipe同tap是一样的,只是他是模拟滑动的事件,给出起点和终点的坐标即可。例如从屏幕(250, 250), 到屏幕(300, 300)即

adb shell input swipe 250 250 300 300


还可以使用:android MotionEvent.obtain模拟事件


public static final int ACTION_DOWN = 0;
public static final int ACTION_HOVER_ENTER = 9;
public static final int ACTION_HOVER_EXIT = 10;
public static final int ACTION_HOVER_MOVE = 7;
public static final int ACTION_MOVE = 2;
复制代码
final long downTime = SystemClock.uptimeMillis();
                final MotionEvent downEvent = MotionEvent.obtain(
                        downTime, downTime, MotionEvent.ACTION_DOWN, 200, 200, 0);
                final MotionEvent upEvent = MotionEvent.obtain(
                        downTime, SystemClock.uptimeMillis() + 10, MotionEvent.ACTION_UP, 200, 200, 0);
                if (mDocView != null) {
                    mDocView.onTouchEvent(downEvent);
                    mDocView.onTouchEvent(upEvent);
                }
                downEvent.recycle();
                upEvent.recycle();

蓝牙相关知识



UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的,那么怎么才能生成这样的一串数字呢?下面就讲讲怎么生成这个数字。


BluetoothAdapter



ef8003f2569e47e5b8ddabca43ee322f_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


BluetoothAdapter——本地设备,对蓝牙操作首先就需要有一个BluetoothAdapter实例。常用的几个方法如下:


cancelDiscovery()——取消本地蓝牙设备的搜索操作,如果本地设备正在进行搜索,那么调用该方法后将停止搜索操作。


Disable()——关闭蓝牙设备。


Enable()——打开蓝牙设备。相信大家都有过打开蓝牙的经历,一般情况下都会弹出一个窗口,说正在请求打开蓝牙设备


getAddress()——获取蓝牙设备的MAC地址。


GetDefaultAdapter()——获取本地的蓝牙设备


getName()——获取本地蓝牙的名称


getRemoteDevice(String address)——根据远程设备的MAC地址来获取远程设备


startDiscovery()——蓝牙设备开始搜索周边设备


BuletoothDevice——远程设备。


// 获取本地的蓝牙适配器实例
             BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
             if(adapter!=null)
             {
                 if(!adapter.isEnabled())
                 {
                     //通过这个方法来请求打开我们的蓝牙设备
                     Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                     startActivity(intent);
                 }
             }
             else
             {
                 System.out.println("本地设备驱动异常!");
             }

配对一般都是发现设备后,由我们人工来选择配对。可以通过方法来获得配对的设备:



//通过getBondedDevices方法来获取已经与本设备配对的设备
                 Set<BluetoothDevice> device= adapter.getBondedDevices();
                 if(device.size()>0)
                 {
                     for(Iterator iterator=device.iterator();iterator.hasNext();)
                     {
                         BluetoothDevice bluetoothDevice=(BluetoothDevice)iterator.next();
                         System.out.println(bluetoothDevice.getAddress());
                     }
                 }

数据交换,首先是进行连接,在进行数据传输:



连接:


/**
     * 客户端主动发起连接,去连接服务端
     *
     * @param serviceDevice 服务端的蓝牙设备
     */
    public void connect(BluetoothDevice serviceDevice) {
        if (mTargThread != null) {
            mTargThread.cancle();
        }
        if (mDataThread != null) {
            mDataThread.cancle();
        }
        mTargThread = new BlueClientThread(serviceDevice, mSocketHandler);
        mTargThread.start();
    }
//另一个服务线程
    public BlueClientThread(BluetoothDevice serviceDevice, Handler handler) {
        super(handler);
        mServiceDevice = serviceDevice;
    }
    @Override
    public void run() {
        super.run();
        if (!isRunning) return;
        try {
            sendMessage(BlueSocketStatus.CONNECTIONING);
            mBlueSocket = mServiceDevice.createRfcommSocketToServiceRecord(UUID_ANDROID_DEVICE);
            mBlueSocket.connect();
            sendMessage(BlueSocketStatus.ACCEPTED);
        } catch (IOException e) {
            sendMessage(BlueSocketStatus.DISCONNECTION);
        }
    }
//关键sendmessage
    public void sendMessage(BlueSocketStatus status) {
        if (mHandler != null && isRunning)
            mHandler.obtainMessage(status.ordinal()).sendToTarget();//ordinal  :序数
    }
    public void sendMessage(BlueSocketStatus status, Object object) {
        if (mHandler != null && isRunning)
            mHandler.obtainMessage(status.ordinal(), object).sendToTarget();
//获取message并发送
    }

关键就是:


sendMessage(BlueSocketStatus.CONNECTIONING);
mBlueSocket = mServiceDevice.createRfcommSocketToServiceRecord(UUID_ANDROID_DEVICE);
mBlueSocket.connect();

1e7f22eebe5344159df15faa7b51c558_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

进行连接:


8a17acc0d6614d0ab533c06141e83267_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


接下来就是发送数据:


private ConcurrentLinkedQueue<IMessage> mQueue = new ConcurrentLinkedQueue<>();
    public synchronized boolean write(IMessage message) {
        message.toString();
        if (mNowStatus == BlueSocketBaseThread.BlueSocketStatus.CONNEDTIONED) {
            synchronized (BluetoothSppHelper.class) {
                if (mNowStatus == BlueSocketBaseThread.BlueSocketStatus.CONNEDTIONED) {
                    mQueue.add(message);
                    mDataThread.startQueue();
                    return true;
                }
                return false;
            }
        }
        return false;
    }

补充1:ConcurrentLinkedQueue:Java并发编程;


在并发编程中我们有时候需要使用线程安全的队列。如果我们要实现一个线程安全的队列有两种实现方式


  1. 一种是使用阻塞算法
  2. 另一种是使用非阻塞算法。
  • 使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现
  • 非阻塞的实现方式则可以使用循环CAS的方式来实现,下面我们一起来研究下Doug Lea是如何使用非阻塞的方式来实现线程安全队列ConcurrentLinkedQueue的。


ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。它采用了“wait-free”算法来实现,该算法在Michael & Scott算法上进行了一些修改。


补充2:createRfcommSocketToServiceRecord,listenUsingRfcommWithServiceRecord


服务端


通过调用BluetoothAdapter的listenUsingRfcommWithServiceRecord(String, UUID)方法来获取BluetoothServerSocket(UUID用于客户端与服务端之间的配对)


BluetoothServerSocket serverSocket = mAdapter.listenUsingRfcommWithServiceRecord(serverSocketName,UUID);
serverSocket.accept();

客户端:UUID 是 通用唯一识别码(Universally Unique Identifier)


调用BluetoothService的createRfcommSocketToServiceRecord(UUID)方法获取BluetoothSocket(该UUID应该同于服务端的UUID)。


调用BluetoothSocket的connect()方法(该方法为block方法),如果UUID同服务端的UUID匹配,并且连接被服务端accept,则connect()方法返回。


BluetoothServerSocket和BluetoothSocket两个类来建立Server端和Client端,还需要使用到一些关于流(Stream)的知识。


BluetoothServerSocket——服务端(监听端、监听器、接受请求的一端)


Accept()——阻塞宿主线程,直至收到客户端请求。返回BluetoothSocket对象。由于这个


Accept(int timeout)——阻塞宿主线程,直至收到客户端请求或等待时间超过timeout。返回BluetoothSocket对象。


Close()——关闭BluetoothServerSocket监听器。


可以看到,Accept方法是一个阻塞方法,所以在进行开发的时候,一般都需要用到多线程的知识。JAVA的多线程知识,可以在JAVA的JDK帮助文档中查看,就单纯的应用来说还是比较简单的。


BluetoothSocket——客户端(请求端)


Close()——关闭BluetoothSocket请求端。


Connect()——主动向服务端(监听端)发起连接请求。


如果一个设备需要和两个或多个设备连接时,就需要作为一个server来传输,服务器端套接字在接受(accepted) 一个客户发来的BluetoothSocket连接请求时作出相应的响应。服务器socket将监听进入的连接请求,一旦连接被接受,将产生一个BluetoothSocket。


创建一个Server


使用BluetoothAdapter类的listenUsingRfcommWithServiceRecord方法来新建一个ServerSocket。在listenUsingRfcommWithServiceRecord中有一个参数叫做UUID,UUID(Universally Unique Identifier)是一个128位的字符串ID,被用于唯一标识我们的蓝牙服务。你可以使用web上的任何一款UUID产生器为你的程序获取一个UUID,然后使用fromString(String)初始化一个UUID。

  

使用ServerSocket实例的accept方法进行监听,当监听到带有我们初始化的UUID参数的连接请求后作出响应,连接成功后返回一个BluetoothSocket对象。连接完成后,调用close方法关闭该Socket监听。


// Bluetooth的ServerSocket包装类
     class BluetoothServer {
         public BluetoothServer() throws IOException {
         }
         // 要建立一个ServerSocket对象,需要使用adapter.listenUsingRfcommWithServiceRecord方法
 // UUID可以在网上去申请
         private BluetoothServerSocket serverSocket = adapter.listenUsingRfcommWithServiceRecord("myServerSocket",
                         UUID.fromString("84D1319C-FBAF-644C-901A-8F091F25AF04"));
         BluetoothSocket socket = serverSocket.accept();
         void m() throws IOException {
             if (socket != null) {
                 InputStream inputStream = socket.getInputStream();
                 int read = -1;
                 final byte[] bytes = new byte[1024];
                 for (; (read = inputStream.read(bytes)) > -1;) {
                     final int count = read;
                     Thread _start = new Thread(new Runnable() {
                         @Override
                         public void run() { 
                             // TODO Auto-generated method stub
                             StringBuilder sb = new StringBuilder();
                             for (int i = 0; i < count; i++) {
                                 if (i > 0) {
                                     sb.append(' ');
                                 }
                                 String _s = Integer.toHexString(bytes[i] & 0xFF);
                                 if (_s.length() < 2) {
                                     sb.append('0');
                                 }
                                 sb.append(_s);
                             }
                             System.out.println(sb.toString());
                         }
                     });
                     _start.start();
                 }
             }
         }
     }

创建一个Client   



创建一个Client端,首先需要我们使用BluetoothDevice的实例的createRfcommSocketToServiceRecord方法来创建一个BluetoothSocket实例。在创建的时候,需要给createRfcommSocketToServiceRecord方法传入我们服务端的UUID值。然后使用BluetoothSocket实例的Connect方法对Server端进行连接请求,当连接成功后,Client端和Server端的传输通道就被打开。最后在连接完成后使用该实例的close方法来关闭这个连接。


class BluetoothClient {
         BluetoothDevice device = null;
         //通过构造函数来传入一个BluetoothDevice实例
         public BluetoothClient(BluetoothDevice device) {
             this.device = device;
         }
         BluetoothSocket socket = null;
         void connetServer() throws IOException {
             Thread _clientThread = new Thread(new Runnable() {
                 public void run() {
                     try {
                         //通过BluetoothDevice实例的createRfcommSocketToServiceRecord方法可以返回一个带有UUID的BluetoothSocket实例
                         socket = device.createRfcommSocketToServiceRecord(UUID.fromString("84D1319C-FBAF-644C-901A-8F091F25AF04"));
                     } catch (IOException e1) { 
                         // TODO Auto-generated catch block
                         e1.printStackTrace();
                     }
                     try {
                         socket.connect();
                     } catch (IOException e1) { 
                         // TODO Auto-generated catch block
                         e1.printStackTrace();
                     }
                     if (socket != null) {
                         try {
                             socket.close();
                         } catch (Exception e) {
                             // TODO: handle exception
                         }
                     }
                 }
             });
             _clientThread.start();
         }
     }

getInputStream()——获得一个可读的流,该流在连接不成功的情况下依旧可以获得,但是对其操作的话就会报IOException的异常。需要从外部获取的数据都从该流中获取。


getOutputStrem()——获得一个可写的流,该流在连接不成功的情况下依旧可以获得,但是对其操作的话就会报IOException的异常。需要往外部传输的数据都可以写到该流中传输出去。


数据传输的大致流程如下:



首先,分别通过getInputStream()和getOutputStream()获得管理数据传输的InputStream和OutputStream。


然后,开辟一个线程专门用于数据的读或写。这是非常重要的,因为read(byte[])和write(byte[])方法都是阻塞调用。read(byte[])从输入流(InputStream)中读取数据。write(byte[])将数据写入到OutputStream流中去,这个方法一般不会阻塞,但当远程设备的中间缓冲区已满而对方没有及时地调用read(byte[])时将会一直阻塞。所以,新开辟的线程中的主循环将一直用于从InputStream中读取数据。


还要补充一点,由于蓝牙设备是系统设备,所以需要有相应的权限支持。在AndroidManifest.xml文件中添加上权限。


<uses-permission android:name="android.permission.BLUETOOTH"></uses-permission>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"></uses-permission>

蓝牙通讯数据传输



蓝牙通讯用android 的api获取看牙设备进行连接,之后进行数据传输,建立各线程;设置监听,进行数据接受监听和状态监听;


数据传输都是以 流的方式进行;


不管是图片,视频还是直接的文字,都是转化成byte,再用数据流进行传输;


图片视频是以文件的形式,再转成byte,再加上头尾校验位,长度等等,再用流进行读写,流已经是Android给我们弄好的了;

 

private File mContent;   
FileInputStream fio = new FileInputStream(mContent);
private String mExtend;
    @Override
    public byte[] creatHeader() {
        byte[] extend = mExtend.getBytes();
        byte[] length = TypeUtils.longToBytes(getLength());
        byte[] header = new byte[10 + extend.length];
        header[0] = HEADER;                                         //魔数
        header[1] = getType();                                     //类型
        System.arraycopy(length, 0, header, 2, length.length);     //长度
        System.arraycopy(extend, 0, header, 10, extend.length);     //扩展信息
        return header;
    }
    private File mContent;
    public void writeContent(OutputStream outputStream) throws IOException {
        outputStream.write(creatHeader());
        outputStream.write(0xA);
        outputStream.write(0xD);
        FileInputStream fio = new FileInputStream(mContent);
        byte[] buffer = new byte[64 * 1024];
        int length = 0;
        while ((length = fio.read(buffer)) >= 0) {
            outputStream.write(buffer, 0, length);
            outputStream.flush();
        }
    }

string,int 等形式就是直接转成byte,再加上头尾校验位,长度等等,再用流进行读写;

  outputStream.write(creatHeader());

private String mExtend;
    @Override
    public byte[] creatHeader() {
        byte[] extend = mExtend.getBytes();
        byte[] length = TypeUtils.longToBytes(getLength());
        byte[] header = new byte[10 + extend.length];
        header[0] = HEADER;                                         //魔数
        header[1] = getType();                                     //类型
        System.arraycopy(length, 0, header, 2, length.length);     //长度
        System.arraycopy(extend, 0, header, 10, extend.length);     //扩展信息
        return header;
    }
   public void writeContent(OutputStream outputStream) throws IOException {
        outputStream.write(creatHeader());
        outputStream.write(0xA);
        outputStream.write(0xD);
        outputStream.write(contentByte);
    }

adb logcat:抓包,抓日志



adb logcat | find  "com.itep.bluetoothsocketservice" C:/hehe.txt

adb logcat 仅仅是输出应用的log信息;而不是android的系统日志;


adb input 模拟系统输入



KEYCODE_CALL 进入拨号盘 5

KEYCODE_ENDCALL 挂机键 6

KEYCODE_HOME 按键Home 3

KEYCODE_MENU 菜单键 82

KEYCODE_BACK 返回键 4

KEYCODE_SEARCH 搜索键 84

KEYCODE_CAMERA 拍照键 27

KEYCODE_FOCUS 拍照对焦键 80

KEYCODE_POWER 电源键 26

KEYCODE_NOTIFICATION 通知键 83

KEYCODE_MUTE 话筒静音键 91

KEYCODE_VOLUME_MUTE 扬声器静音键 164

KEYCODE_VOLUME_UP 音量增加键 24

KEYCODE_VOLUME_DOWN 音量减小键 25


控制键


KEYCODE_ENTER 回车键 66

KEYCODE_ESCAPE ESC键 111

KEYCODE_DPAD_CENTER 导航键 确定键 23

KEYCODE_DPAD_UP 导航键 向上 19

KEYCODE_DPAD_DOWN 导航键 向下 20

KEYCODE_DPAD_LEFT 导航键 向左 21

KEYCODE_DPAD_RIGHT 导航键 向右 22

KEYCODE_MOVE_HOME 光标移动到开始键 122

KEYCODE_MOVE_END 光标移动到末尾键 123

KEYCODE_PAGE_UP 向上翻页键 92

KEYCODE_PAGE_DOWN 向下翻页键 93

KEYCODE_DEL 退格键 67

KEYCODE_FORWARD_DEL 删除键 112

KEYCODE_INSERT 插入键 124

KEYCODE_TAB Tab键 61

KEYCODE_NUM_LOCK 小键盘锁 143

KEYCODE_CAPS_LOCK 大写锁定键 115

KEYCODE_BREAK Break/Pause键 121

KEYCODE_SCROLL_LOCK 滚动锁定键 116

KEYCODE_ZOOM_IN 放大键 168

KEYCODE_ZOOM_OUT 缩小键 169

利用命令“adb shell input keyevent <键值>”可以实现自动化。例如“adb shell input keyevent 3”就可以按下Home键。]

执行返回:adb shell input keyevent 4

执行灭屏亮屏:adb shell input keyevent 26

执行解锁屏幕:adb shell input keyevent 82


3 HOME 键
4 返回键
5 打开拨号应用
6 挂断电话
24 增加音量
25 降低音量
26 电源键
27 拍照(需要在相机应用里)
64 打开浏览器
82 菜单键
85 播放/暂停
86 停止播放
87 播放下一首
88 播放上一首
122 移动光标到行首或列表顶部
123 移动光标到行末或列表底部
126 恢复播放
127 暂停播放
164 静音
176 打开系统设置
187 切换应用
207 打开联系人
208 打开日历
209 打开音乐
210 打开计算器
220 降低屏幕亮度
221 提高屏幕亮度
223 系统休眠
224 点亮屏幕
231 打开语音助手
276 如果没有 wakelock 则让系统休眠


蓝牙通讯和adb命令实现手机之间互相操作:adb命令24 是音量增加。


c1730f86b6d5402d8d1cb245f9c7175a_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg


4b80f5f9a5a04c7fa53d8a36ba08ff28_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

虚拟按键通过AccessibilityService进行控制,实际就是一个service,实现button,监听系统暴露的Api:


d37adbb7cb344334adc6fa60035094e1_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

fe93fbc1de6c46f28d1a2e8be64582f3_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

eeb65b3a581541ceb8a67c06d4ef1164_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

源码就是在github上,自己拉下;


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
目录
相关文章
|
4月前
|
Shell Android开发
Android系统 adb shell push/pull 禁止特定文件
Android系统 adb shell push/pull 禁止特定文件
346 1
|
4月前
|
Java 开发工具 Windows
Windows环境下面启动jar包,输出的日志出现乱码的解决办法
Windows环境下面启动jar包,输出的日志出现乱码的解决办法
|
4月前
|
监控 数据处理 索引
整合LlamaIndex与LangChain构建高级的查询处理系统
该文阐述了如何结合LlamaIndex和LangChain构建一个扩展性和定制性强的代理RAG应用。LlamaIndex擅长智能搜索,LangChain提供跨平台兼容性。代理RAG允许大型语言模型访问多个查询引擎,增强决策能力和多样化回答。文章通过示例代码展示了如何设置LLM、嵌入模型、LlamaIndex索引及查询引擎,并将它们转换为LangChain兼容的工具,实现高效、精准的问题解答。通过多代理协作,系统能处理复杂查询,提高答案质量和相关性。
197 0
|
22天前
|
JavaScript Java API
Java日志通关(二) - Slf4j+Logback 整合及排包
作者日常在与其他同学合作时,经常发现不合理的日志配置以及五花八门的日志记录方式,后续作者打算在团队内做一次Java日志的分享,本文是整理出的系列文章第二篇。
|
28天前
|
开发框架 Unix Linux
LangChain 构建问题之在Unix/Linux系统上设置OpenAI API密钥如何解决
LangChain 构建问题之在Unix/Linux系统上设置OpenAI API密钥如何解决
32 0
|
2月前
|
索引 Cloud Native
云原生数据仓库问题之精确匹配查询如何解决
云原生数据仓库问题之精确匹配查询如何解决
43 0
|
3月前
|
消息中间件 Kafka Go
go语言并发实战——日志收集系统(五) 基于go-ini包读取日志收集服务的配置文件
go语言并发实战——日志收集系统(五) 基于go-ini包读取日志收集服务的配置文件
|
3月前
|
存储 监控 算法
go语言并发实战——日志收集系统(四) 利用tail包实现对日志文件的实时监控
go语言并发实战——日志收集系统(四) 利用tail包实现对日志文件的实时监控
|
3月前
|
消息中间件 算法 Java
go语言并发实战——日志收集系统(三) 利用sarama包连接KafKa实现消息的生产与消费
go语言并发实战——日志收集系统(三) 利用sarama包连接KafKa实现消息的生产与消费
|
4月前
|
机器学习/深度学习 Shell Perl
logcat 只打印符合包名的log
logcat 只打印符合包名的log
33 1