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

871 阅读8分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

 github项目地址:GitHub - hnlbxb2004/BluetoothSocket: 此library 库可以方便的帮你使用经典蓝牙做socket 通讯。 网上的一些蓝牙游戏互联都是用的经典蓝牙做socket ,

目录

蓝牙相关知识

BluetoothAdapter

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

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

创建一个Client  

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

蓝牙通讯数据传输

adb logcat:抓包,抓日志

adb input 模拟系统输入


   

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

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();

进行连接:

接下来就是发送数据:

    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

3HOME 键
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 是音量增加。​编辑

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

​编辑​编辑​编辑

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