ภาพรวม
บทความนี้สาธิตวิธีตรวจสอบสถานะการเชื่อมต่ออุปกรณ์ Bluetooth (Connected/Disconnected) ใน Android โดยใช้ BroadcastReceiver ฟัง BluetoothDevice.ACTION_ACL_CONNECTED และ ACTION_ACL_DISCONNECTED พร้อมแนวทาง Modern API สำหรับ Bluetooth LE และ Permission ใหม่
ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่
สร้างโปรเจกต์ Android ใหม่ใน Android Studio
ขั้นตอนที่ 2: Layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://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">
<TextView
android:id="@+id/tvStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="สถานะ Bluetooth: กำลังตรวจสอบ..."
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center" />
<TextView
android:id="@+id/tvDeviceInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="อุปกรณ์: -"
android:textSize="14sp"
android:gravity="center" />
<Button
android:id="@+id/btnOpenSettings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="เปิด Bluetooth Settings" />
</LinearLayout>
ขั้นตอนที่ 3: MainActivity.java - รองรับ Modern Bluetooth API
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private TextView tvStatus, tvDeviceInfo;
private Button btnOpenSettings;
private BluetoothAdapter bluetoothAdapter;
private BroadcastReceiver bluetoothReceiver;
private BroadcastReceiver connectionReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvStatus = findViewById(R.id.tvStatus);
tvDeviceInfo = findViewById(R.id.tvDeviceInfo);
btnOpenSettings = findViewById(R.id.btnOpenSettings);
// Initialize Bluetooth Adapter
BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
bluetoothAdapter = bluetoothManager.getAdapter();
if (bluetoothAdapter == null) {
tvStatus.setText("อุปกรณ์ไม่รองรับ Bluetooth");
btnOpenSettings.setEnabled(false);
return;
}
// ตรวจสอบสถานะปัจจุบัน
updateBluetoothState();
// Register Receivers
registerReceivers();
btnOpenSettings.setOnClickListener(v -> {
startActivity(new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS));
});
}
private void registerReceivers() {
// 1. Bluetooth Adapter State (On/Off)
bluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
updateBluetoothState();
}
}
};
registerReceiver(bluetoothReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
// 2. Device Connection State (Connected/Disconnected)
connectionReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device == null) return;
String deviceName = device.getName() != null ? device.getName() : "Unknown Device";
String deviceAddress = device.getAddress();
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
tvStatus.setText("🟢 Bluetooth: Connected");
tvDeviceInfo.setText("อุปกรณ์: " + deviceName + " (" + deviceAddress + ")");
Toast.makeText(context, "เชื่อมต่อกับ " + deviceName, Toast.LENGTH_SHORT).show();
} else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
tvStatus.setText("🔴 Bluetooth: Disconnected");
tvDeviceInfo.setText("อุปกรณ์: - (Last: " + deviceName + ")");
Toast.makeText(context, "ตัดการเชื่อมต่อ: " + deviceName, Toast.LENGTH_SHORT).show();
} else if (BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED.equals(action)) {
tvStatus.setText("🟡 Bluetooth: Disconnecting...");
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
registerReceiver(connectionReceiver, filter);
}
private void updateBluetoothState() {
if (bluetoothAdapter == null) return;
int state = bluetoothAdapter.getState();
switch (state) {
case BluetoothAdapter.STATE_ON:
tvStatus.setText("🟢 Bluetooth: เปิดอยู่");
checkConnectedDevices();
break;
case BluetoothAdapter.STATE_OFF:
tvStatus.setText("🔴 Bluetooth: ปิดอยู่");
tvDeviceInfo.setText("อุปกรณ์: -");
break;
case BluetoothAdapter.STATE_TURNING_ON:
tvStatus.setText("🟡 Bluetooth: กำลังเปิด...");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
tvStatus.setText("🟡 Bluetooth: กำลังปิด...");
break;
default:
tvStatus.setText("❓ Bluetooth: Unknown State");
}
}
private void checkConnectedDevices() {
// ตรวจสอบอุปกรณ์ที่เชื่อมต่ออยู่แล้ว (Profile: A2DP, Headset, HID, etc.)
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
tvDeviceInfo.setText("อุปกรณ์: ต้องการ Permission BLUETOOTH_CONNECT");
return;
}
// Classic Bluetooth Profiles
int[] profiles = {
BluetoothProfile.A2DP, // Audio Streaming
BluetoothProfile.HEADSET, // Headset/Handsfree
BluetoothProfile.HEALTH, // Health Device
BluetoothProfile.HID_DEVICE, // HID (Keyboard/Mouse)
BluetoothProfile.GATT_SERVER // BLE GATT Server
};
for (int profile : profiles) {
if (bluetoothAdapter.getProfileConnectionState(profile) == BluetoothProfile.STATE_CONNECTED) {
// ได้อุปกรณ์ที่เชื่อมต่อ แต่ต้องใช้ Profile Proxy เพื่อดูรายละเอียด
}
}
// bonded (paired) devices
// bluetoothAdapter.getBondedDevices() -> Set
}
// ==================== Modern Bluetooth LE (API 21+) ====================
/**
* สำหรับ Bluetooth Low Energy (BLE) ใช้ BluetoothLeScanner
* ต้อง Permission: BLUETOOTH_SCAN, BLUETOOTH_CONNECT (Android 12+)
*/
private void scanBleDevices() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12+
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
return;
}
}
// BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner();
// scanner.startScan(callback);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (bluetoothReceiver != null) unregisterReceiver(bluetoothReceiver);
if (connectionReceiver != null) unregisterReceiver(connectionReceiver);
}
}
ขั้นตอนที่ 4: AndroidManifest.xml - Permissions ตาม Version
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="app.com.sample">
<!-- Legacy Permissions (Android 11 และก่อน) -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Android 12+ (API 31+) - Runtime Permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- Features -->
<uses-feature android:name="android.hardware.bluetooth" android:required="true" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
ตาราง Permission ตาม Android Version
| Android Version | API Level | Permissions Required | ประเภท |
|---|---|---|---|
| ≤ 10 (Q) | ≤ 29 | BLUETOOTH, BLUETOOTH_ADMIN | Install-time |
| 11 (R) | 30 | BLUETOOTH, BLUETOOTH_ADMIN + Location Permission | Runtime (Location) |
| 12 (S) | 31 | BLUETOOTH_CONNECT, BLUETOOTH_SCAN, BLUETOOTH_ADVERTISE | Runtime |
| 13+ (T+) | 33+ | BLUETOOTH_CONNECT, BLUETOOTH_SCAN, BLUETOOTH_ADVERTISE + NEARBY_DEVICES | Runtime |
Action Intents สำคัญ
| Action | Extra | คำอธิบาย |
|---|---|---|
| BluetoothAdapter.ACTION_STATE_CHANGED | EXTRA_STATE, EXTRA_PREVIOUS_STATE | Bluetooth เปิด/ปิด |
| BluetoothDevice.ACTION_ACL_CONNECTED | EXTRA_DEVICE, EXTRA_CLASS | Classic BT เชื่อมต่อสำเร็จ |
| BluetoothDevice.ACTION_ACL_DISCONNECTED | EXTRA_DEVICE | Classic BT ตัดการเชื่อมต่อ |
| BluetoothDevice.ACTION_BOND_STATE_CHANGED | EXTRA_DEVICE, EXTRA_BOND_STATE, EXTRA_PREVIOUS_BOND_STATE | Pair/Unpair |
| BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED | EXTRA_CONNECTION_STATE, EXTRA_DEVICE | Profile Connection (A2DP, Headset) |
Best Practices
- Runtime Permission: ขอ Permission ก่อน Scan/Connect (Android 12+)
- Foreground Service: สำหรับ BLE Scanning ยาวนาน ต้องใช้ Foreground Service Type "connectedDevice"
- Battery Optimization: ขอความยินยอม Ignore Battery Optimization สำหรับ Background BLE
- Companion Device Manager: ใช้สำหรับ Pairing อุปกรณ์เฉพาะ (Smartwatch, Earbuds) ไม่ต้อง Location Permission
- Unregister Receiver: ต้อง Unregister ใน onDestroy() กัน Memory Leak