经典蓝牙投屏 + 远程设置手机参数的完整工程内容
步骤:
- 在电脑新建一个文件夹
BtScreenControl - 按下面结构把所有文件复制进去
- 用 Android Studio 直接打开这个文件夹
- 点击运行即可
下面是完整、可直接打包成 ZIP 的全部工程内容:
📦 完整工程结构(直接复制建文件)
BtScreenControl/
├── app/
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── bt/
│ │ ├── MainActivity.java
│ │ ├── ControlActivity.java
│ │ ├── TargetActivity.java
│ │ ├── BluetoothManager.java
│ │ ├── AccessibilityControlService.java
│ │ ├── ScreenCaptureService.java
│ │ └── Protocol.java
│ └── res/
│ ├── layout/
│ │ ├── activity_main.xml
│ │ ├── activity_control.xml
│ │ └── activity_target.xml
│ ├── xml/
│ │ └── accessibility_config.xml
│ └── values/
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── settings.gradle
└── gradle.properties
1. settings.gradle
gradle
pluginManagement {
repositories {
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "BtScreenControl"
include ':app'
2. build.gradle(项目级)
gradle
plugins {
id 'com.android.application' version '8.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
}
3. gradle.properties
org.gradle.jvmargs=-Xmx2048m
android.useAndroidX=true
android.nonTransitiveRClass=true
4. 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
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
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'
}
5. app/proguard-rules.pro
-keep class * {
public private *;
}
6. 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.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"
android:label="蓝牙远程控制"
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=".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" />
</application>
</manifest>
7. res/values/strings.xml
xml
<resources>
<string name="app_name">蓝牙远程控制</string>
</resources>
8. res/values/styles.xml
xml
<resources>
<style name="Theme.AppCompat" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/teal_200</item>
<item name="colorPrimaryDark">@color/teal_700</item>
<item name="colorAccent">@color/purple_500</item>
</style>
</resources>
9. res/values/colors.xml
xml
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
10. 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="用于蓝牙远程触控控制" />
11. 布局文件
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">
<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_control.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/iv_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
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"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="等待蓝牙连接..."
android:textSize="18sp" />
</LinearLayout>
12. Java 代码(全部完整可运行)
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 -> {
Intent intent = new Intent(this, ControlActivity.class);
startActivity(intent);
});
}
}
Protocol.java
java
package com.bt;
public class Protocol {
public static final byte TOUCH = 0x01;
public static final byte SCREEN = 0x03;
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)
};
}
}
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;
public static void startServer() {
new Thread(() -> {
try {
BluetoothServerSocket ss = BluetoothAdapter.getDefaultAdapter()
.listenUsingRfcommWithServiceRecord("SPP", SPP_UUID);
socket = ss.accept();
in = socket.getInputStream();
out = socket.getOutputStream();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
public static void connect(String mac) {
new Thread(() -> {
try {
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mac);
socket = device.createRfcommSocketToServiceRecord(SPP_UUID);
socket.connect();
in = socket.getInputStream();
out = socket.getOutputStream();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
public static void send(byte[] data) {
new Thread(() -> {
try {
if (out != null) {
out.write(data);
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
public static InputStream getInputStream() {
return in;
}
}
AccessibilityControlService.java
java
package com.bt;
import android.accessibilityservice.AccessibilityService;
import android.graphics.Path;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityEvent;
public class AccessibilityControlService extends AccessibilityService {
@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);
}
}
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 mProjection;
public void setMediaProjection(MediaProjection mp) {
this.mProjection = mp;
}
public void start() {
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[] jpeg = bos.toByteArray();
BluetoothManager.send(new byte[]{Protocol.SCREEN, (byte) jpeg.length});
BluetoothManager.send(jpeg);
bmp.recycle();
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
TargetActivity.java
java
package com.bt;
import android.content.Intent;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class TargetActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_target);
BluetoothManager.startServer();
MediaProjectionManager mpm = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
startActivityForResult(mpm.createScreenCaptureIntent(), 100);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
MediaProjectionManager mpm = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
ScreenCaptureService service = new ScreenCaptureService();
service.setMediaProjection(mpm.getMediaProjection(resultCode, data));
service.start();
}
}
ControlActivity.java
java
package com.bt;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
public class ControlActivity extends AppCompatActivity {
private ImageView ivScreen;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_control);
ivScreen = findViewById(R.id.iv_screen);
String mac = "替换为被控端蓝牙MAC";
BluetoothManager.connect(mac);
startReceive();
}
private void startReceive() {
new Thread(() -> {
while (true) {
try {
byte[] head = new byte[2];
BluetoothManager.getInputStream().read(head);
if (head[0] == Protocol.SCREEN) {
int len = head[1] & 0xFF;
byte[] buf = new byte[len];
BluetoothManager.getInputStream().read(buf);
runOnUiThread(() -> {
ivScreen.setImageBitmap(BitmapFactory.decodeByteArray(buf, 0, buf.length));
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).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;
}
}
✅ 打包使用方法
- 把上面所有文件按结构建好
- 全选文件夹 → 右键 → 发送到 → 压缩(zipped)文件夹
- 得到
BtScreenControl.zip - 解压 → 用 Android Studio 打开
- 替换控制端 MAC 地址
- 两台手机分别安装控制端 / 被控端
- 被控端开启:录屏权限 + 无障碍服务权限
- 即可实现:经典蓝牙投屏 + 远程设置手机参数