Android蓝牙远程控制:条件编译实现双端模式

1. 项目概述

这个蓝牙远程控制项目实现了一套完整的Android手机远程控制解决方案,通过条件编译技术,用同一套代码同时支持控制端和被控端两种模式。控制端可以扫描并连接被控端设备,实现屏幕投射、触控操作和系统快捷控制等功能;被控端则负责接收指令、执行操作并返回屏幕数据。

这种设计最大的优势在于维护一套代码库就能满足两种角色的需求,通过简单的编译开关切换就能生成不同功能的APK。对于开发者来说,这大大减少了代码重复和维护成本;对于用户来说,只需要安装不同模式的APK就能实现设备间的互控。

需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。

2. 工程结构解析

2.1 项目目录结构

项目采用标准的Android Studio工程结构,但有一些值得注意的特殊设计:

code复制BtRemoteControl/
├── app/
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/main/
│       ├── AndroidManifest.xml
│       ├── java/com/bt/
│       │   ├── BuildConfig.java          // 条件编译开关
│       │   ├── MainActivity.java          // 自动入口
│       │   ├── Protocol.java              // 通信协议
│       │   ├── BluetoothManager.java      // 蓝牙核心
│       │   ├── client/                    // 控制端代码
│       │   │   ├── BluetoothScanActivity.java
│       │   │   └── ControlActivity.java
│       │   └── server/                    // 被控端代码
│       │       ├── TargetActivity.java
│       │       ├── AccessibilityControlService.java
│       │       ├── ScreenCaptureService.java
│       │       ├── KeepAliveService.java
│       │       └── BootReceiver.java
│       └── res/
│           ├── layout/
│           │   ├── activity_main.xml
│           │   ├── activity_bluetooth_scan.xml
│           │   ├── activity_control.xml
│           │   └── activity_target.xml
│           └── xml/
│               └── accessibility_config.xml
├── build.gradle
├── settings.gradle
└── gradle.properties

关键设计点:

  1. 客户端和服务端代码物理分离但共享核心模块
  2. 通过BuildConfig.java中的SERVER_MODE开关控制编译方向
  3. 公共蓝牙通信和协议处理代码放在根目录下
  4. 角色特定的代码分别放在client和server子包中

2.2 条件编译实现原理

条件编译的核心在于BuildConfig.java中的SERVER_MODE标志:

java复制package com.bt;

public class BuildConfig {
    // ==============================
    // 编译模式
    // true  = 服务端(被控端)
    // false = 客户端(控制端)
    // ==============================
    public static final boolean SERVER_MODE = false;
}

MainActivity.java会根据这个标志决定启动路径:

java复制public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tvMode = findViewById(R.id.tv_mode);

        if (BuildConfig.SERVER_MODE) {
            tvMode.setText("模式:被控端(服务端)");
            startActivity(new Intent(this, com.bt.server.TargetActivity.class));
        } else {
            tvMode.setText("模式:控制端(客户端)");
            startActivity(new Intent(this, com.bt.client.BluetoothScanActivity.class));
        }
        finish();
    }
}

这种设计的好处是:

  • 编译时只需修改一个开关就能生成不同功能的APK
  • 避免了维护两套独立代码库的麻烦
  • 公共代码更新会自动应用到两端

3. 核心模块实现

3.1 蓝牙通信协议设计

Protocol.java定义了所有通信指令和编解码方法:

java复制public class Protocol {
    public static final byte TOUCH        = 0x01;  // 触控指令
    public static final byte SYSTEM_CMD   = 0x02;  // 系统命令
    public static final byte SCREEN_DATA  = 0x03;  // 屏幕数据
    public static final byte HEARTBEAT    = 0x04;  // 心跳包

    // 系统命令子类型
    public static final byte SYS_VOL_UP      = 1;
    public static final byte SYS_VOL_DOWN    = 2;
    public static final byte SYS_BRIGHT_UP   = 3;
    public static final byte SYS_BRIGHT_DOWN = 4;
    public static final byte SYS_WIFI_TOGGLE = 5;
    public static final byte SYS_BT_TOGGLE   = 6;

    // 触控事件编码
    public static byte[] encodeTouch(int action, int x, int y) {
        return new byte[]{
                TOUCH,
                (byte) action,
                (byte) (x >> 8),
                (byte) (x & 0xFF),
                (byte) (y >> 8),
                (byte) (y & 0xFF)
        };
    }

    // 系统命令编码
    public static byte[] encodeSystemCmd(byte cmd) {
        return new byte[]{SYSTEM_CMD, cmd};
    }
}

协议设计考虑:

  1. 使用字节流而非文本协议,更节省带宽
  2. 每个指令以类型字节开头,便于解析
  3. 坐标使用16位整数传输(高8位+低8位)
  4. 心跳包维持连接状态

3.2 蓝牙通信核心实现

BluetoothManager.java封装了所有蓝牙操作:

java复制public class BluetoothManager {
    private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    private static BluetoothSocket socket;
    private static InputStream in;
    private static OutputStream out;
    private static String targetMac;

    // 服务端:启动监听
    public static void startServer() {
        new Thread(() -> {
            try {
                BluetoothServerSocket ss = BluetoothAdapter.getDefaultAdapter()
                    .listenUsingRfcommWithServiceRecord("SPP", SPP_UUID);
                socket = ss.accept();
                in = socket.getInputStream();
                out = socket.getOutputStream();
                startHeartBeat();
            } catch (Exception e) {

内容推荐

已经到底了哦
已经到底了哦