经典蓝牙双机控制 APP-最终完整版 2

经典蓝牙双机控制 APP-最终完整版 2


📦 BtScreenControl_Final(完整可编译)

1. 项目根目录文件

settings.gradle

gradle 复制代码
pluginManagement {
    repositories {
        google()
        mavenCentral()
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = "BtScreenControl"
include ':app'

build.gradle(项目级)

gradle 复制代码
plugins {
    id 'com.android.application' version '8.2.2' apply false
}

gradle.properties

复制代码
android.useAndroidX=true
android.nonTransitiveRClass=true
org.gradle.jvmargs=-Xmx2048m

2. app 模块

app/build.gradle

gradle 复制代码
plugins {
    id 'com.android.application'
}

android {
    namespace 'com.bt'
    compileSdk 34

    defaultConfig {
        applicationId "com.bt.sppcontrol"
        minSdk 26
        targetSdk 34
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.11.0'
}

app/proguard-rules.pro

复制代码
-keep class com.bt.** { *; }

3. AndroidManifest.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

    <application
        android:allowBackup="true"
        android:label="蓝牙远程控制"
        android:persistent="true"
        android:theme="@style/Theme.AppCompat">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".BluetoothScanActivity" />
        <activity android:name=".ControlActivity" />
        <activity android:name=".TargetActivity" />

        <service
            android:name=".AccessibilityControlService"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_config" />
        </service>

        <service android:name=".ScreenCaptureService" />
        <service android:name=".KeepAliveService" />

        <receiver
            android:name=".BootReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

    </application>

</manifest>

4. 全部 Java 代码

Protocol.java

java 复制代码
package com.bt;

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};
    }
}

BluetoothManager.java

java 复制代码
package com.bt;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

public class BluetoothManager {
    public 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;
    private static boolean isConnected = false;

    public static void startServer() {
        new Thread(() -> {
            try {
                BluetoothServerSocket ss = BluetoothAdapter.getDefaultAdapter()
                        .listenUsingRfcommWithServiceRecord("SPP", SPP_UUID);
                socket = ss.accept();
                in = socket.getInputStream();
                out = socket.getOutputStream();
                isConnected = true;
                startHeartBeat();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }

    public static void connect(String mac) {
        targetMac = mac;
        new Thread(() -> {
            try {
                BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mac);
                socket = device.createRfcommSocketToServiceRecord(SPP_UUID);
                socket.connect();
                in = socket.getInputStream();
                out = socket.getOutputStream();
                isConnected = true;
                startHeartBeat();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }

    private static void startHeartBeat() {
        new Thread(() -> {
            while (isConnected) {
                try {
                    send(new byte[]{Protocol.HEARTBEAT});
                    Thread.sleep(3000);
                } catch (Exception e) {
                    isConnected = false;
                    reconnect();
                }
            }
        }).start();
    }

    public static void reconnect() {
        try {
            Thread.sleep(1000);
            if (targetMac != null) connect(targetMac);
        } catch (Exception ignored) {}
    }

    public static void send(byte[] data) {
        new Thread(() -> {
            try {
                if (out != null) out.write(data);
            } catch (Exception e) {
                isConnected = false;
            }
        }).start();
    }

    public static InputStream getInputStream() {
        return in;
    }
}

AccessibilityControlService.java

java 复制代码
package com.bt;

import android.accessibilityservice.AccessibilityService;
import android.content.Context;
import android.graphics.Path;
import android.media.AudioManager;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityEvent;

public class AccessibilityControlService extends AccessibilityService {
    private static AccessibilityControlService instance;

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
    }

    public static AccessibilityControlService getInstance() {
        return instance;
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {}

    @Override
    public void onInterrupt() {}

    public void injectTouch(int action, int x, int y) {
        Path path = new Path();
        if (action == MotionEvent.ACTION_DOWN) {
            path.moveTo(x, y);
        } else {
            path.lineTo(x, y);
        }

        GestureDescription.StrokeDescription stroke =
                new GestureDescription.StrokeDescription(path, 0, 100);
        GestureDescription gesture = new GestureDescription.Builder()
                .addStroke(stroke)
                .build();

        dispatchGesture(gesture, null, null);
    }

    public void execVolumeUp() {
        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        am.adjustVolume(AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
    }

    public void execVolumeDown() {
        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        am.adjustVolume(AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI);
    }
}

ScreenCaptureService.java

java 复制代码
package com.bt;

import android.app.Service;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.projection.MediaProjection;
import android.os.IBinder;
import java.io.ByteArrayOutputStream;

public class ScreenCaptureService extends Service {
    private MediaProjection mp;

    public void setMediaProjection(MediaProjection mp) {
        this.mp = mp;
    }

    public void startCapture() {
        new Thread(() -> {
            while (true) {
                try {
                    Bitmap bmp = Bitmap.createBitmap(320, 240, Bitmap.Config.ARGB_8888);
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    bmp.compress(Bitmap.CompressFormat.JPEG, 10, bos);
                    byte[] data = bos.toByteArray();
                    BluetoothManager.send(new byte[]{Protocol.SCREEN_DATA, (byte) data.length});
                    BluetoothManager.send(data);
                    bmp.recycle();
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

BootReceiver.java

java 复制代码
package com.bt;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class BootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent i = new Intent(context, TargetActivity.class);
        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(i);
    }
}

KeepAliveService.java

java 复制代码
package com.bt;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;

public class KeepAliveService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1001, new NotificationCompat.Builder(this, "channel_1")
                .setContentTitle("被控服务运行中")
                .setSmallIcon(R.mipmap.ic_launcher)
                .build());
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

MainActivity.java

java 复制代码
package com.bt;

import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnTarget = findViewById(R.id.btn_target);
        Button btnControl = findViewById(R.id.btn_control);

        btnTarget.setOnClickListener(v -> {
            startActivity(new Intent(this, TargetActivity.class));
        });

        btnControl.setOnClickListener(v -> {
            startActivity(new Intent(this, BluetoothScanActivity.class));
        });
    }
}

BluetoothScanActivity.java

java 复制代码
package com.bt;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;

public class BluetoothScanActivity extends AppCompatActivity {
    private ListView listView;
    private ArrayList<String> deviceList = new ArrayList<>();
    private ArrayAdapter<String> adapter;
    private BluetoothAdapter bluetoothAdapter;

    private final BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                String name = device.getName() == null ? "未知设备" : device.getName();
                String mac = device.getAddress();
                String item = name + "\n" + mac;
                if (!deviceList.contains(item)) {
                    deviceList.add(item);
                    adapter.notifyDataSetChanged();
                }
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bluetooth_scan);
        listView = findViewById(R.id.listView);
        adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, deviceList);
        listView.setAdapter(adapter);

        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        registerReceiver(receiver, filter);
        bluetoothAdapter.startDiscovery();

        listView.setOnItemClickListener((parent, view, position, id) -> {
            String info = deviceList.get(position);
            String mac = info.substring(info.length() - 17);
            Intent intent = new Intent(BluetoothScanActivity.this, ControlActivity.class);
            intent.putExtra("mac", mac);
            startActivity(intent);
            finish();
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);
    }
}

ControlActivity.java

java 复制代码
package com.bt;

import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.Button;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

public class ControlActivity extends AppCompatActivity {
    private ImageView ivScreen;
    private InputStream in;
    private final ByteArrayOutputStream screenBuffer = new ByteArrayOutputStream();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_control);
        ivScreen = findViewById(R.id.iv_screen);

        String mac = getIntent().getStringExtra("mac");
        BluetoothManager.connect(mac);
        in = BluetoothManager.getInputStream();

        initButtons();
        startReceiveThread();
    }

    private void initButtons() {
        Button btnVolUp = findViewById(R.id.btn_vol_up);
        Button btnVolDown = findViewById(R.id.btn_vol_down);
        Button btnBrightUp = findViewById(R.id.btn_bright_up);
        Button btnBrightDown = findViewById(R.id.btn_bright_down);
        Button btnWifi = findViewById(R.id.btn_wifi);
        Button btnBt = findViewById(R.id.btn_bt);

        btnVolUp.setOnClickListener(v -> BluetoothManager.send(Protocol.encodeSystemCmd(Protocol.SYS_VOL_UP)));
        btnVolDown.setOnClickListener(v -> BluetoothManager.send(Protocol.encodeSystemCmd(Protocol.SYS_VOL_DOWN)));
        btnBrightUp.setOnClickListener(v -> BluetoothManager.send(Protocol.encodeSystemCmd(Protocol.SYS_BRIGHT_UP)));
        btnBrightDown.setOnClickListener(v -> BluetoothManager.send(Protocol.encodeSystemCmd(Protocol.SYS_BRIGHT_DOWN)));
        btnWifi.setOnClickListener(v -> BluetoothManager.send(Protocol.encodeSystemCmd(Protocol.SYS_WIFI_TOGGLE)));
        btnBt.setOnClickListener(v -> BluetoothManager.send(Protocol.encodeSystemCmd(Protocol.SYS_BT_TOGGLE)));
    }

    private void startReceiveThread() {
        new Thread(() -> {
            byte[] buf = new byte[4096];
            while (true) {
                try {
                    int len = in.read(buf);
                    if (len <= 0) continue;

                    for (int i = 0; i < len; i++) {
                        byte b = buf[i];
                        if (b == Protocol.SCREEN_DATA) {
                            screenBuffer.reset();
                        }
                        screenBuffer.write(b);

                        byte[] data = screenBuffer.toByteArray();
                        if (data.length > 100 && data.length < 65535) {
                            runOnUiThread(() -> {
                                try {
                                    ivScreen.setImageBitmap(BitmapFactory.decodeByteArray(data, 0, data.length));
                                } catch (Exception ignored) {}
                            });
                        }
                    }
                } catch (Exception e) {
                    try {
                        Thread.sleep(1000);
                        BluetoothManager.reconnect();
                    } catch (Exception ignored) {}
                }
            }
        }).start();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        BluetoothManager.send(Protocol.encodeTouch(event.getAction(), x, y));
        return true;
    }
}

TargetActivity.java

java 复制代码
package com.bt;

import android.content.Intent;
import android.media.projection.MediaProjectionManager;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import androidx.appcompat.app.AppCompatActivity;
import java.io.InputStream;

public class TargetActivity extends AppCompatActivity {
    private InputStream in;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_target);

        BluetoothManager.startServer();
        in = BluetoothManager.getInputStream();
        startCmdThread();

        MediaProjectionManager mpm = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
        startActivityForResult(mpm.createScreenCaptureIntent(), 100);

        startService(new Intent(this, KeepAliveService.class));
    }

    private void startCmdThread() {
        new Thread(() -> {
            byte[] buf = new byte[1024];
            while (true) {
                try {
                    int len = in.read(buf);
                    if (len <= 0) continue;

                    for (int i = 0; i < len; i++) {
                        if (buf[i] == Protocol.TOUCH) {
                            int action = buf[++i] & 0xFF;
                            int x = ((buf[++i] & 0xFF) << 8) | (buf[++i] & 0xFF);
                            int y = ((buf[++i] & 0xFF) << 8) | (buf[++i] & 0xFF);
                            AccessibilityControlService acc = AccessibilityControlService.getInstance();
                            if (acc != null) acc.injectTouch(action, x, y);
                        } else if (buf[i] == Protocol.SYSTEM_CMD) {
                            int cmd = buf[++i] & 0xFF;
                            runOnUiThread(() -> execSysCmd(cmd));
                        }
                    }
                } catch (Exception ignored) {}
            }
        }).start();
    }

    private void execSysCmd(int cmd) {
        AccessibilityControlService acc = AccessibilityControlService.getInstance();
        switch (cmd) {
            case Protocol.SYS_VOL_UP:
                if (acc != null) acc.execVolumeUp();
                break;
            case Protocol.SYS_VOL_DOWN:
                if (acc != null) acc.execVolumeDown();
                break;
            case Protocol.SYS_BRIGHT_UP:
                setBright(100);
                break;
            case Protocol.SYS_BRIGHT_DOWN:
                setBright(20);
                break;
            case Protocol.SYS_WIFI_TOGGLE:
                WifiManager wifi = (WifiManager) getSystemService(WIFI_SERVICE);
                wifi.setWifiEnabled(!wifi.isWifiEnabled());
                break;
            case Protocol.SYS_BT_TOGGLE:
                android.bluetooth.BluetoothAdapter.getDefaultAdapter().enable();
                break;
        }
    }

    private void setBright(int val) {
        Window window = getWindow();
        WindowManager.LayoutParams lp = window.getAttributes();
        lp.screenBrightness = val / 100f;
        window.setAttributes(lp);
    }

    @Override
    protected void onActivityResult(int req, int res, Intent data) {
        super.onActivityResult(req, res, data);
        MediaProjectionManager mpm = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
        ScreenCaptureService s = new ScreenCaptureService();
        s.setMediaProjection(mpm.getMediaProjection(res, data));
        s.startCapture();
    }
}

5. 布局文件

activity_main.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    android:gravity="center">

    <Button
        android:id="@+id/btn_target"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="被控端(被控制)"/>

    <Button
        android:id="@+id/btn_control"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:text="控制端(扫描并控制)"/>

</LinearLayout>

activity_bluetooth_scan.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

activity_control.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/iv_screen"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="8dp">

        <Button android:id="@+id/btn_vol_up"       android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="音量+"/>
        <Button android:id="@+id/btn_vol_down"     android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="音量-"/>
        <Button android:id="@+id/btn_bright_up"   android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="亮度+"/>
        <Button android:id="@+id/btn_bright_down" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="亮度-"/>
        <Button android:id="@+id/btn_wifi"         android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="WIFI"/>
        <Button android:id="@+id/btn_bt"           android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="蓝牙"/>

    </LinearLayout>

</LinearLayout>

activity_target.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="被控服务已启动
请在控制端搜索连接"
        android:textSize="18sp"
        android:gravity="center"/>

</LinearLayout>

res/xml/accessibility_config.xml

xml 复制代码
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:canPerformGestures="true"
    android:description="蓝牙远程控制服务"/>

✅ 最终使用步骤

  1. 新建文件夹 BtScreenControl_Final
  2. 按上面结构把所有文件放进去
  3. 压缩为 ZIP
  4. 用 Android Studio 打开
  5. 两台手机安装
  6. 被控端开启:无障碍权限、录屏权限、电池不优化
  7. 控制端扫描 → 点击设备 → 自动连接
  8. 可远程:触控、音量、亮度、WiFi、蓝牙、重连、自启动

修复编译小问题、补全通知渠道、优化分包逻辑、生成可直接安装的 APK

相关推荐
华普微HOPERF3 小时前
无人机已成当代“空中尖兵”,开发者如何进一步提升其控制精度?
物联网·无人机·解决方案
程序员陆业聪8 小时前
从 OpenClaw 到 Android:Harness Engineering 是怎么让 Agent 变得可用的
android
hnlgzb10 小时前
常见的Android Jetpack库会有哪些?这些库中又有哪些常用类的?
android·android jetpack
钛态14 小时前
Flutter 三方库 http_mock_adapter — 赋能鸿蒙应用开发的高效率网络接口 Mock 与自动化测试注入引擎(适配鸿蒙 HarmonyOS Next ohos)
android·网络协议·flutter·http·华为·中间件·harmonyos
王码码203514 小时前
Flutter for OpenHarmony:Flutter 三方库 algoliasearch 毫秒级云端搜索体验(云原生搜索引擎)
android·前端·git·flutter·搜索引擎·云原生·harmonyos
左手厨刀右手茼蒿14 小时前
Flutter for OpenHarmony: Flutter 三方库 shamsi_date 助力鸿蒙应用精准适配波斯历法(中东出海必备)
android·flutter·ui·华为·自动化·harmonyos
代码飞天14 小时前
wireshark的高级使用
android·java·wireshark
2501_9159184115 小时前
苹果App Store上架审核卡住原因分析与解决方案指南
android·ios·小程序·https·uni-app·iphone·webview
skiy15 小时前
MySQL Workbench菜单汉化为中文
android·数据库·mysql