Ringkasan Bluetooth low energy

Android 4.3 (API level 18) memperkenalkan dukungan platform bawaan untuk Bluetooth Hemat Energi (BLE) pada peran sentral dan menyediakan API yang dapat digunakan aplikasi untuk menemukan perangkat, mengirimkan kueri layanan, dan mengirim informasi.

Kasus penggunaan umum meliputi:

  • Mentransfer sejumlah kecil data antar-perangkat terdekat.
  • Berinteraksi dengan sensor kedekatan seperti Google Beacons untuk memberi pengguna pengalaman khusus berdasarkan lokasi mereka saat ini.

Berbeda dengan Bluetooth Klasik, Bluetooth Hemat Energi (BLE) dirancang untuk memberikan konsumsi daya yang jauh lebih rendah. Hal ini memungkinkan aplikasi Android berkomunikasi dengan perangkat BLE yang memiliki persyaratan daya yang lebih ketat, seperti sensor kedekatan, pemantau denyut jantung, dan perangkat kebugaran.

Perhatian: Jika seorang pengguna memasangkan perangkat mereka dengan perangkat lain menggunakan BLE, data yang dikomunikasikan antara kedua perangkat dapat diakses oleh semua aplikasi pada perangkat pengguna.

Karena alasan ini, jika aplikasi Anda mengambil data sensitif, Anda harus menerapkan keamanan layer aplikasi untuk melindungi privasi data tersebut.

Konsep dan istilah kunci

Berikut ini ringkasan konsep dan istilah BLE kunci:

  • Profil Atribut Generik (GATT)—Profil GATT adalah spesifikasi umum untuk mengirim dan menerima potongan data pendek yang dikenal sebagai "atribut" melalui link BLE. Semua profil aplikasi Hemat Energi saat ini didasarkan pada GATT.
    • Bluetooth SIG mendefinisikan banyak profil untuk perangkat Hemat Energi. Profil adalah spesifikasi untuk cara kerja perangkat dalam aplikasi tertentu. Perhatikan bahwa perangkat dapat menerapkan lebih dari satu profil. Misalnya, perangkat dapat berisi monitor denyut jantung dan detektor level baterai.
  • Protokol Atribut (ATT)—GATT dibuat di atas Protokol Atribut (ATT). Ini juga disebut sebagai GATT/ATT. ATT dioptimalkan untuk dijalankan pada perangkat BLE. Untuk tujuan ini, ATT menggunakan sesedikit mungkin byte. Setiap atribut diidentifikasi secara unik oleh Universally Unique Identifier (UUID), yang merupakan format 128-bit terstandardisasi untuk ID string yang digunakan untuk mengidentifikasi informasi secara unik. Atribut yang diangkut oleh ATT diformat sebagai karakteristik dan layanan.
  • Karakteristik—Karakteristik berisi nilai tunggal dan deskriptor 0-n yang menjelaskan nilai karakteristik. Karakteristik dapat dianggap sebagai jenis, analog dengan class. 
  • Deskriptor—Deskriptor adalah atribut yang didefinisikan yang menjelaskan nilai karakteristik. Misalnya, deskriptor dapat menentukan deskripsi yang dapat dibaca manusia, rentang yang dapat diterima untuk nilai karakteristik, atau unit ukuran yang khusus untuk nilai karakteristik.
  • Layanan—Layanan adalah kumpulan karakteristik. Misalnya, Anda dapat memiliki layanan yang disebut "Pemantau Denyut Jantung" yang mencakup karakteristik seperti "pengukuran denyut jantung." Anda dapat menemukan daftar profil dan layanan berbasis GATT yang ada di bluetooth.org.

Peran dan tanggung jawab

Berikut adalah peran dan tanggung jawab yang berlaku ketika perangkat Android berinteraksi dengan perangkat BLE:

  • Sentral vs. periferal. Ini berlaku untuk koneksi BLE itu sendiri. Perangkat dalam peran sentral memindai, mencari iklan, dan perangkat dalam peran periferal membuat iklan.
  • Server GATT vs. klien GATT. Ini menentukan bagaimana dua perangkat berkomunikasi satu sama lain begitu berhasil membuat koneksi.

Untuk memahami perbedaannya, bayangkan Anda memiliki ponsel Android dan pelacak aktivitas yang merupakan perangkat BLE. Telepon mendukung peran sentral; pelacak aktivitas mendukung peran periferal (untuk membuat koneksi BLE, Anda perlu salah satu dari masing-masing—dua hal yang hanya mendukung periferal tidak dapat saling berkomunikasi, juga dua hal yang hanya mendukung sentral).

Setelah ponsel dan pelacak aktivitas membuat koneksi, mereka mulai mentransfer metadata GATT satu sama lain. Bergantung pada jenis data yang ditransfer, satu atau yang lain mungkin bertindak sebagai server. Misalnya, jika pelacak aktivitas ingin melaporkan data sensor ke ponsel, mungkin masuk akal bagi pelacak aktivitas untuk bertindak sebagai server. Jika pelacak aktivitas ingin menerima pembaruan dari telepon, maka masuk akal jika telepon bertindak sebagai server.

Pada contoh yang digunakan dalam dokumen ini, aplikasi Android (berjalan pada perangkat Android) adalah klien GATT. Aplikasi ini mendapatkan data dari server GATT, yang merupakan monitor denyut jantung BLE yang mendukung Profil Denyut Jantung. Namun Anda bisa juga mendesain aplikasi Android untuk memainkan peran server GATT. Lihat BluetoothGattServer untuk informasi selengkapnya.

Izin BLE

Untuk menggunakan fitur Bluetooth dalam aplikasi, Anda harus mendeklarasikan dua izin BLUETOOTH. Anda memerlukan izin ini untuk melakukan komunikasi Bluetooth, seperti meminta koneksi, menerima koneksi, dan mentransfer data.

Anda juga harus mendeklarasikan izin ACCESS_FINE_LOCATION, mengingat beacon LE sering dikaitkan dengan lokasi. Tanpa izin ini, pemindaian tidak akan menampilkan hasil apa pun.

Catatan: Jika aplikasi Anda menargetkan Android 9 (API level 28) atau yang lebih rendah, Anda dapat mendeklarasikan izin ACCESS_COARSE_LOCATION alih-alih izin ACCESS_FINE_LOCATION.

Jika ingin aplikasi memulai pencarian perangkat atau memanipulasi setelan Bluetooth, Anda juga harus mendeklarasikan izin BLUETOOTH_ADMIN. Catatan: Jika Anda menggunakan izin BLUETOOTH_ADMIN, maka Anda juga harus memiliki izin BLUETOOTH.

Mendeklarasikan izin dalam file manifes aplikasi Anda. Sebagai contoh:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

<!-- If your app targets Android 9 or lower, you can declare
     ACCESS_COARSE_LOCATION instead. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Jika ingin mendeklarasikan bahwa aplikasi Anda hanya tersedia untuk perangkat yang memiliki kemampuan BLE, sertakan yang berikut ini dalam manifes aplikasi Anda:

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

Namun, jika ingin membuat aplikasi Anda tersedia untuk perangkat yang tidak mendukung BLE, Anda harus tetap memasukkan elemen ini dalam manifes aplikasi Anda, tetapi tetapkan required="false". Kemudian pada saat run-time Anda dapat menentukan ketersediaan BLE dengan menggunakan PackageManager.hasSystemFeature():

Kotlin

private fun PackageManager.missingSystemFeature(name: String): Boolean = !hasSystemFeature(name)
...

packageManager.takeIf { it.missingSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) }?.also {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show()
    finish()
}

Java

// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

Menyiapkan BLE

Sebelum aplikasi Anda dapat berkomunikasi melalui BLE, Anda perlu memverifikasi bahwa BLE didukung pada perangkat, dan jika demikian, pastikan BLE diaktifkan. Ingat bahwa pemeriksaan ini hanya diperlukan jika <uses-feature.../> disetel ke false.

Jika BLE tidak didukung, maka Anda harus menonaktifkan fitur BLE dengan baik. Jika BLE didukung, tetapi dinonaktifkan, maka Anda dapat meminta pengguna mengaktifkan Bluetooth tanpa keluar dari aplikasi Anda. Penyiapan ini dilakukan dalam dua langkah, menggunakan BluetoothAdapter.

  1. Dapatkan BluetoothAdapter

    Diperlukan BluetoothAdapter untuk setiap dan semua aktivitas Bluetooth. BluetoothAdapter merepresentasikan adaptor Bluetooth perangkat itu sendiri (radio Bluetooth). Ada satu adaptor Bluetooth untuk seluruh sistem, dan aplikasi Anda bisa berinteraksi dengannya menggunakan objek ini. Cuplikan di bawah ini menunjukkan cara mendapatkan adaptor. Perhatikan bahwa pendekatan ini menggunakan getSystemService() untuk mengembalikan instance BluetoothManager, yang kemudian digunakan untuk mendapatkan adaptor. Android 4.3 (API Level 18) memperkenalkan BluetoothManager:

    Kotlin

    private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
        val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        bluetoothManager.adapter
    }
    

    Java

    private BluetoothAdapter bluetoothAdapter;
    ...
    // Initializes Bluetooth adapter.
    final BluetoothManager bluetoothManager =
            (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    bluetoothAdapter = bluetoothManager.getAdapter();
    
  2. Mengaktifkan Bluetooth

    Berikutnya, Anda harus memastikan bahwa Bluetooth diaktifkan. Panggil isEnabled() untuk memeriksa apakah Bluetooth saat ini diaktifkan. Jika metode ini kembali ke false, maka Bluetooth dinonaktifkan. Cuplikan berikut memeriksa apakah Bluetooth diaktifkan. Jika tidak, cuplikan menampilkan error yang meminta pengguna untuk membuka Setelan untuk mengaktifkan Bluetooth:

    Kotlin

    private val BluetoothAdapter.isDisabled: Boolean
        get() = !isEnabled
    ...
    
    // Ensures Bluetooth is available on the device and it is enabled. If not,
    // displays a dialog requesting user permission to enable Bluetooth.
    bluetoothAdapter?.takeIf { it.isDisabled }?.apply {
        val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
    }
    

    Java

    // Ensures Bluetooth is available on the device and it is enabled. If not,
    // displays a dialog requesting user permission to enable Bluetooth.
    if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }
    

    Catatan: Konstanta REQUEST_ENABLE_BT diteruskan ke startActivityForResult(android.content.Intent, int) adalah integer yang ditentukan secara lokal (yang harus lebih besar dari 0) yang dikembalikan sistem kepada Anda dalam implementasi onActivityResult(int, int, android.content.Intent) sebagai parameter requestCode.

Mencari perangkat BLE

Untuk menemukan perangkat BLE, Anda menggunakan metode startLeScan(). Metode ini menggunakan BluetoothAdapter.LeScanCallback sebagai parameter. Anda harus menerapkan callback ini, karena itulah cara hasil pemindaian ditampilkan. Karena pemindaian intensif baterai, Anda harus mematuhi panduan berikut:

  • Begitu Anda menemukan perangkat yang diinginkan, hentikan pemindaian.
  • Jangan pernah memindai dalam loop, dan menetapkan batas waktu pada pemindaian Anda. Perangkat yang sebelumnya tersedia mungkin telah bergerak keluar dari jangkauan, dan terus memindai yang menguras baterai.

Cuplikan berikut ini menunjukkan cara memulai dan menghentikan pemindaian:

Kotlin

private const val SCAN_PERIOD: Long = 10000

/**
 * Activity for scanning and displaying available BLE devices.
 */
class DeviceScanActivity(
        private val bluetoothAdapter: BluetoothAdapter,
        private val handler: Handler
) : ListActivity() {

    private var mScanning: Boolean = false

    private fun scanLeDevice(enable: Boolean) {
        when (enable) {
            true -> {
                // Stops scanning after a pre-defined scan period.
                handler.postDelayed({
                    mScanning = false
                    bluetoothAdapter.stopLeScan(leScanCallback)
                }, SCAN_PERIOD)
                mScanning = true
                bluetoothAdapter.startLeScan(leScanCallback)
            }
            else -> {
                mScanning = false
                bluetoothAdapter.stopLeScan(leScanCallback)
            }
        }
    }
}

Java

/**
 * Activity for scanning and displaying available BLE devices.
 */
public class DeviceScanActivity extends ListActivity {

    private BluetoothAdapter bluetoothAdapter;
    private boolean mScanning;
    private Handler handler;

    // Stops scanning after 10 seconds.
    private static final long SCAN_PERIOD = 10000;
    ...
    private void scanLeDevice(final boolean enable) {
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScanning = false;
                    bluetoothAdapter.stopLeScan(leScanCallback);
                }
            }, SCAN_PERIOD);

            mScanning = true;
            bluetoothAdapter.startLeScan(leScanCallback);
        } else {
            mScanning = false;
            bluetoothAdapter.stopLeScan(leScanCallback);
        }
        ...
    }
...
}

Jika Anda ingin memindai hanya untuk jenis periferal tertentu, Anda bisa memanggil startLeScan(UUID[], BluetoothAdapter.LeScanCallback), memberikan array objek UUID yang menentukan layanan GATT yang didukung aplikasi Anda.

Berikut ini adalah implementasi dari BluetoothAdapter.LeScanCallback, yang merupakan antarmuka yang digunakan untuk memberikan hasil pemindaian BLE:

Kotlin

val leDeviceListAdapter: LeDeviceListAdapter = ...

private val leScanCallback = BluetoothAdapter.LeScanCallback { device, rssi, scanRecord ->
    runOnUiThread {
        leDeviceListAdapter.addDevice(device)
        leDeviceListAdapter.notifyDataSetChanged()
    }
}

Java

private LeDeviceListAdapter leDeviceListAdapter;
...
// Device scan callback.
private BluetoothAdapter.LeScanCallback leScanCallback =
        new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
        runOnUiThread(new Runnable() {
           @Override
           public void run() {
               leDeviceListAdapter.addDevice(device);
               leDeviceListAdapter.notifyDataSetChanged();
           }
       });
   }
};

Catatan: Anda hanya dapat memindai perangkat Bluetooth LE atau memindai perangkat Bluetooth Klasik, seperti yang dijelaskan dalam Bluetooth. Anda tidak dapat memindai Bluetooth LE dan perangkat klasik secara bersamaan.

Terhubung ke server GATT

Langkah pertama dalam berinteraksi dengan perangkat BLE adalah menghubungkannya— lebih khusus, menghubungkan ke server GATT pada perangkat. Untuk terhubung ke server GATT pada perangkat BLE, Anda menggunakan metode connectGatt(). Metode ini mengambil tiga parameter: objek Context, autoConnect (boolean menunjukkan apakah akan terhubung secara otomatis ke perangkat BLE begitu tersedia), dan referensi ke a BluetoothGattCallback:

Kotlin

var bluetoothGatt: BluetoothGatt? = null
...

bluetoothGatt = device.connectGatt(this, false, gattCallback)

Java

bluetoothGatt = device.connectGatt(this, false, gattCallback);

Ini terhubung ke server GATT yang dihosting oleh perangkat BLE, dan mengembalikan instance BluetoothGatt, yang dapat Anda gunakan untuk melakukan operasi klien GATT. Pemanggil (aplikasi Android) adalah klien GATT. BluetoothGattCallback digunakan untuk memberikan hasil kepada klien, seperti status koneksi, serta operasi klien GATT lebih lanjut.

Dalam contoh ini, aplikasi BLE menyediakan aktivitas (DeviceControlActivity) untuk menghubungkan, menampilkan data, dan menampilkan layanan dan karakteristik GATT yang didukung oleh perangkat. Berdasarkan input pengguna, aktivitas ini berkomunikasi dengan Service bernama BluetoothLeService, yang berinteraksi dengan perangkat BLE melalui Android BLE API:

Kotlin

private val TAG = BluetoothLeService::class.java.simpleName
private const val STATE_DISCONNECTED = 0
private const val STATE_CONNECTING = 1
private const val STATE_CONNECTED = 2
const val ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"
const val ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"
const val ACTION_GATT_SERVICES_DISCOVERED =
        "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"
const val ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"
const val EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA"
val UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT)

// A service that interacts with the BLE device via the Android BLE API.
class BluetoothLeService(private var bluetoothGatt: BluetoothGatt?) : Service() {

    private var connectionState = STATE_DISCONNECTED

    // Various callback methods defined by the BLE API.
    private val gattCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(
                gatt: BluetoothGatt,
                status: Int,
                newState: Int
        ) {
            val intentAction: String
            when (newState) {
                BluetoothProfile.STATE_CONNECTED -> {
                    intentAction = ACTION_GATT_CONNECTED
                    connectionState = STATE_CONNECTED
                    broadcastUpdate(intentAction)
                    Log.i(TAG, "Connected to GATT server.")
                    Log.i(TAG, "Attempting to start service discovery: " +
                            bluetoothGatt?.discoverServices())
                }
                BluetoothProfile.STATE_DISCONNECTED -> {
                    intentAction = ACTION_GATT_DISCONNECTED
                    connectionState = STATE_DISCONNECTED
                    Log.i(TAG, "Disconnected from GATT server.")
                    broadcastUpdate(intentAction)
                }
            }
        }

        // New services discovered
        override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
            when (status) {
                BluetoothGatt.GATT_SUCCESS -> broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED)
                else -> Log.w(TAG, "onServicesDiscovered received: $status")
            }
        }

        // Result of a characteristic read operation
        override fun onCharacteristicRead(
                gatt: BluetoothGatt,
                characteristic: BluetoothGattCharacteristic,
                status: Int
        ) {
            when (status) {
                    BluetoothGatt.GATT_SUCCESS -> {
                        broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic)
                    }
            }
        }
    }
}

Java

// A service that interacts with the BLE device via the Android BLE API.
public class BluetoothLeService extends Service {
    private final static String TAG = BluetoothLeService.class.getSimpleName();

    private BluetoothManager bluetoothManager;
    private BluetoothAdapter bluetoothAdapter;
    private String bluetoothDeviceAddress;
    private BluetoothGatt bluetoothGatt;
    private int connectionState = STATE_DISCONNECTED;

    private static final int STATE_DISCONNECTED = 0;
    private static final int STATE_CONNECTING = 1;
    private static final int STATE_CONNECTED = 2;

    public final static String ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
    public final static String ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
    public final static String ACTION_GATT_SERVICES_DISCOVERED =
            "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
    public final static String ACTION_DATA_AVAILABLE =
            "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
    public final static String EXTRA_DATA =
            "com.example.bluetooth.le.EXTRA_DATA";

    public final static UUID UUID_HEART_RATE_MEASUREMENT =
            UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);

    // Various callback methods defined by the BLE API.
    private final BluetoothGattCallback gattCallback =
            new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status,
                int newState) {
            String intentAction;
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                intentAction = ACTION_GATT_CONNECTED;
                connectionState = STATE_CONNECTED;
                broadcastUpdate(intentAction);
                Log.i(TAG, "Connected to GATT server.");
                Log.i(TAG, "Attempting to start service discovery:" +
                        bluetoothGatt.discoverServices());

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = ACTION_GATT_DISCONNECTED;
                connectionState = STATE_DISCONNECTED;
                Log.i(TAG, "Disconnected from GATT server.");
                broadcastUpdate(intentAction);
            }
        }

        @Override
        // New services discovered
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }

        @Override
        // Result of a characteristic read operation
        public void onCharacteristicRead(BluetoothGatt gatt,
                BluetoothGattCharacteristic characteristic,
                int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }
     ...
    };
...
}

Setelah suatu panggilan balik terpicu, ia memanggil metode bantuan broadcastUpdate() yang sesuai dan memberikannya suatu tindakan. Perhatikan bahwa penguraian data di bagian ini dilakukan sesuai dengan spesifikasi profil Pengukuran Denyut Jantung Bluetooth:

Kotlin

private fun broadcastUpdate(action: String) {
    val intent = Intent(action)
    sendBroadcast(intent)
}

private fun broadcastUpdate(action: String, characteristic: BluetoothGattCharacteristic) {
    val intent = Intent(action)

    // This is special handling for the Heart Rate Measurement profile. Data
    // parsing is carried out as per profile specifications.
    when (characteristic.uuid) {
        UUID_HEART_RATE_MEASUREMENT -> {
            val flag = characteristic.properties
            val format = when (flag and 0x01) {
                0x01 -> {
                    Log.d(TAG, "Heart rate format UINT16.")
                    BluetoothGattCharacteristic.FORMAT_UINT16
                }
                else -> {
                    Log.d(TAG, "Heart rate format UINT8.")
                    BluetoothGattCharacteristic.FORMAT_UINT8
                }
            }
            val heartRate = characteristic.getIntValue(format, 1)
            Log.d(TAG, String.format("Received heart rate: %d", heartRate))
            intent.putExtra(EXTRA_DATA, (heartRate).toString())
        }
        else -> {
            // For all other profiles, writes the data formatted in HEX.
            val data: ByteArray? = characteristic.value
            if (data?.isNotEmpty() == true) {
                val hexString: String = data.joinToString(separator = " ") {
                    String.format("%02X", it)
                }
                intent.putExtra(EXTRA_DATA, "$data\n$hexString")
            }
        }

    }
    sendBroadcast(intent)
}

Java

private void broadcastUpdate(final String action) {
    final Intent intent = new Intent(action);
    sendBroadcast(intent);
}

private void broadcastUpdate(final String action,
                             final BluetoothGattCharacteristic characteristic) {
    final Intent intent = new Intent(action);

    // This is special handling for the Heart Rate Measurement profile. Data
    // parsing is carried out as per profile specifications.
    if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
        int flag = characteristic.getProperties();
        int format = -1;
        if ((flag & 0x01) != 0) {
            format = BluetoothGattCharacteristic.FORMAT_UINT16;
            Log.d(TAG, "Heart rate format UINT16.");
        } else {
            format = BluetoothGattCharacteristic.FORMAT_UINT8;
            Log.d(TAG, "Heart rate format UINT8.");
        }
        final int heartRate = characteristic.getIntValue(format, 1);
        Log.d(TAG, String.format("Received heart rate: %d", heartRate));
        intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
    } else {
        // For all other profiles, writes the data formatted in HEX.
        final byte[] data = characteristic.getValue();
        if (data != null && data.length > 0) {
            final StringBuilder stringBuilder = new StringBuilder(data.length);
            for(byte byteChar : data)
                stringBuilder.append(String.format("%02X ", byteChar));
            intent.putExtra(EXTRA_DATA, new String(data) + "\n" +
                    stringBuilder.toString());
        }
    }
    sendBroadcast(intent);
}

Kembali ke DeviceControlActivity, peristiwa ini ditangani oleh BroadcastReceiver:

Kotlin

// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a
// result of read or notification operations.
private val gattUpdateReceiver = object : BroadcastReceiver() {

    private lateinit var bluetoothLeService: BluetoothLeService

    override fun onReceive(context: Context, intent: Intent) {
        val action = intent.action
        when (action){
            ACTION_GATT_CONNECTED -> {
                connected = true
                updateConnectionState(R.string.connected)
                (context as? Activity)?.invalidateOptionsMenu()
            }
            ACTION_GATT_DISCONNECTED -> {
                connected = false
                updateConnectionState(R.string.disconnected)
                (context as? Activity)?.invalidateOptionsMenu()
                clearUI()
            }
            ACTION_GATT_SERVICES_DISCOVERED -> {
                // Show all the supported services and characteristics on the
                // user interface.
                displayGattServices(bluetoothLeService.getSupportedGattServices())
            }
            ACTION_DATA_AVAILABLE -> {
                displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA))
            }
        }
    }
}

Java

// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a
// result of read or notification operations.
private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
            connected = true;
            updateConnectionState(R.string.connected);
            invalidateOptionsMenu();
        } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
            connected = false;
            updateConnectionState(R.string.disconnected);
            invalidateOptionsMenu();
            clearUI();
        } else if (BluetoothLeService.
                ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
            // Show all the supported services and characteristics on the
            // user interface.
            displayGattServices(bluetoothLeService.getSupportedGattServices());
        } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
            displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
        }
    }
};

Membaca atribut BLE

Setelah aplikasi Android Anda terhubung ke server GATT dan menemukan layanan, aplikasi tersebut dapat membaca dan menulis atribut, jika didukung. Misalnya, cuplikan ini berulang melalui layanan dan karakteristik server dan menampilkannya di UI:

Kotlin

class DeviceControlActivity : Activity() {

    // Demonstrates how to iterate through the supported GATT
    // Services/Characteristics.
    // In this sample, we populate the data structure that is bound to the
    // ExpandableListView on the UI.
    private fun displayGattServices(gattServices: List<BluetoothGattService>?) {
        if (gattServices == null) return
        var uuid: String?
        val unknownServiceString: String = resources.getString(R.string.unknown_service)
        val unknownCharaString: String = resources.getString(R.string.unknown_characteristic)
        val gattServiceData: MutableList<HashMap<String, String>> = mutableListOf()
        val gattCharacteristicData: MutableList<ArrayList<HashMap<String, String>>> =
                mutableListOf()
        mGattCharacteristics = mutableListOf()

        // Loops through available GATT Services.
        gattServices.forEach { gattService ->
            val currentServiceData = HashMap<String, String>()
            uuid = gattService.uuid.toString()
            currentServiceData[LIST_NAME] = SampleGattAttributes.lookup(uuid, unknownServiceString)
            currentServiceData[LIST_UUID] = uuid
            gattServiceData += currentServiceData

            val gattCharacteristicGroupData: ArrayList<HashMap<String, String>> = arrayListOf()
            val gattCharacteristics = gattService.characteristics
            val charas: MutableList<BluetoothGattCharacteristic> = mutableListOf()

            // Loops through available Characteristics.
            gattCharacteristics.forEach { gattCharacteristic ->
                charas += gattCharacteristic
                val currentCharaData: HashMap<String, String> = hashMapOf()
                uuid = gattCharacteristic.uuid.toString()
                currentCharaData[LIST_NAME] = SampleGattAttributes.lookup(uuid, unknownCharaString)
                currentCharaData[LIST_UUID] = uuid
                gattCharacteristicGroupData += currentCharaData
            }
            mGattCharacteristics += charas
            gattCharacteristicData += gattCharacteristicGroupData
        }
    }
}

Java


public class DeviceControlActivity extends Activity {
    ...
    // Demonstrates how to iterate through the supported GATT
    // Services/Characteristics.
    // In this sample, we populate the data structure that is bound to the
    // ExpandableListView on the UI.
    private void displayGattServices(List<BluetoothGattService> gattServices) {
        if (gattServices == null) return;
        String uuid = null;
        String unknownServiceString = getResources().
                getString(R.string.unknown_service);
        String unknownCharaString = getResources().
                getString(R.string.unknown_characteristic);
        ArrayList<HashMap<String, String>> gattServiceData =
                new ArrayList<HashMap<String, String>>();
        ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
                = new ArrayList<ArrayList<HashMap<String, String>>>();
        mGattCharacteristics =
                new ArrayList<ArrayList<BluetoothGattCharacteristic>>();

        // Loops through available GATT Services.
        for (BluetoothGattService gattService : gattServices) {
            HashMap<String, String> currentServiceData =
                    new HashMap<String, String>();
            uuid = gattService.getUuid().toString();
            currentServiceData.put(
                    LIST_NAME, SampleGattAttributes.
                            lookup(uuid, unknownServiceString));
            currentServiceData.put(LIST_UUID, uuid);
            gattServiceData.add(currentServiceData);

            ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
                    new ArrayList<HashMap<String, String>>();
            List<BluetoothGattCharacteristic> gattCharacteristics =
                    gattService.getCharacteristics();
            ArrayList<BluetoothGattCharacteristic> charas =
                    new ArrayList<BluetoothGattCharacteristic>();
           // Loops through available Characteristics.
            for (BluetoothGattCharacteristic gattCharacteristic :
                    gattCharacteristics) {
                charas.add(gattCharacteristic);
                HashMap<String, String> currentCharaData =
                        new HashMap<String, String>();
                uuid = gattCharacteristic.getUuid().toString();
                currentCharaData.put(
                        LIST_NAME, SampleGattAttributes.lookup(uuid,
                                unknownCharaString));
                currentCharaData.put(LIST_UUID, uuid);
                gattCharacteristicGroupData.add(currentCharaData);
            }
            mGattCharacteristics.add(charas);
            gattCharacteristicData.add(gattCharacteristicGroupData);
         }
    ...
    }
...
}

Menerima notifikasi GATT

Sudah umum bagi aplikasi BLE untuk meminta agar diberi tahu ketika karakteristik tertentu berubah pada perangkat. Cuplikan ini menunjukkan cara mengatur pemberitahuan untuk karakteristik, menggunakan metode setCharacteristicNotification():

Kotlin

lateinit var bluetoothGatt: BluetoothGatt
lateinit var characteristic: BluetoothGattCharacteristic
var enabled: Boolean = true
...
bluetoothGatt.setCharacteristicNotification(characteristic, enabled)
val uuid: UUID = UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)
val descriptor = characteristic.getDescriptor(uuid).apply {
    value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
}
bluetoothGatt.writeDescriptor(descriptor)

Java

private BluetoothGatt bluetoothGatt;
BluetoothGattCharacteristic characteristic;
boolean enabled;
...
bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
...
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
        UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);

Setelah notifikasi diaktifkan untuk karakteristik, callback onCharacteristicChanged() dipicu jika karakteristik berubah pada perangkat jarak jauh:

Kotlin

// Characteristic notification
override fun onCharacteristicChanged(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic
) {
    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic)
}

Java

@Override
// Characteristic notification
public void onCharacteristicChanged(BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic) {
    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}

Menutup aplikasi klien

Setelah aplikasi Anda selesai menggunakan perangkat BLE, maka harus memanggil close() sehingga sistem dapat merilis sumber daya dengan tepat:

Kotlin

fun close() {
    bluetoothGatt?.close()
    bluetoothGatt = null
}

Java

public void close() {
    if (bluetoothGatt == null) {
        return;
    }
    bluetoothGatt.close();
    bluetoothGatt = null;
}