與 BLE 裝置互動的第一步是連線至裝置。更多內容
具體來說,就是連線至裝置上的 GATT 伺服器為了連線至 GATT
伺服器連線,請使用
connectGatt()
敬上
方法。這個方法使用三個參數:
Context
物件,autoConnect
(布林值
表示是否要在 BLE 裝置時自動與其連線
可用),以及指向
BluetoothGattCallback
:
var bluetoothGatt: BluetoothGatt? = null
...
bluetoothGatt = device.connectGatt(this, false, gattCallback)
bluetoothGatt = device.connectGatt(this, false, gattCallback);
這會連線至 BLE 裝置代管的 GATT 伺服器,並傳回
BluetoothGatt
執行個體,
之後就能用於執行 GATT 用戶端作業呼叫端 (Android 應用程式)
是 GATT 用戶端
BluetoothGattCallback
的用途是將結果提供給用戶端,例如
連線狀態,以及任何進一步的 GATT 用戶端作業。
設定繫結服務
在以下範例中,BLE 應用程式提供一項活動
(DeviceControlActivity
) 必須連線至藍牙裝置、顯示裝置資料、
並顯示裝置支援的 GATT 服務和特性。根據地
使用者輸入內容時,這個活動就會與
名為 BluetoothLeService
的 Service
,
透過 BLE API 與 BLE 裝置互動。溝通過程
是透過「繫結服務」執行,
連結到 BluetoothLeService
並呼叫函式
連線至裝置。BluetoothLeService
需要
Binder
實作
為活動提供服務
class BluetoothLeService : Service() {
private val binder = LocalBinder()
override fun onBind(intent: Intent): IBinder? {
return binder
}
inner class LocalBinder : Binder() {
fun getService() : BluetoothLeService {
return this@BluetoothLeService
}
}
}
class BluetoothLeService extends Service {
private Binder binder = new LocalBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
class LocalBinder extends Binder {
public BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
}
活動可透過
bindService()
、
傳入 Intent
即可開始
服務、ServiceConnection
接聽連線和中斷連線事件的實作,以及
指定其他連線選項。
class DeviceControlActivity : AppCompatActivity() {
private var bluetoothService : BluetoothLeService? = null
// Code to manage Service lifecycle.
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(
componentName: ComponentName,
service: IBinder
) {
bluetoothService = (service as LocalBinder).getService()
bluetoothService?.let { bluetooth ->
// call functions on service to check connection and connect to devices
}
}
override fun onServiceDisconnected(componentName: ComponentName) {
bluetoothService = null
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.gatt_services_characteristics)
val gattServiceIntent = Intent(this, BluetoothLeService::class.java)
bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
}
}
class DeviceControlActivity extends AppCompatActivity {
private BluetoothLeService bluetoothService;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bluetoothService = ((LocalBinder) service).getService();
if (bluetoothService != null) {
// call functions on service to check connection and connect to devices
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
bluetoothService = null;
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gatt_services_characteristics);
Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
}
設定 BluetoothAdapter
繫結至服務後,服務必須存取
BluetoothAdapter
。它應該
檢查裝置是否有可用的轉接頭。請參閱設定
藍牙。
BluetoothAdapter
。以下範例會將此設定程式碼納入
initialize()
函式,會傳回表示成功的 Boolean
值。
private const val TAG = "BluetoothLeService"
class BluetoothLeService : Service() {
private var bluetoothAdapter: BluetoothAdapter? = null
fun initialize(): Boolean {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (bluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.")
return false
}
return true
}
...
}
class BluetoothLeService extends Service {
public static final String TAG = "BluetoothLeService";
private BluetoothAdapter bluetoothAdapter;
public boolean initialize() {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
...
}
活動會在其 ServiceConnection
實作中呼叫此函式。
如何處理 initialize()
函式中的假傳回值,取決於您的
應用程式。您可能會看到錯誤訊息,指出
目前的裝置不支援藍牙作業或停用任何功能
需要藍牙才能運作在以下範例中
在活動上呼叫 finish()
返回上一個畫面。
class DeviceControlActivity : AppCompatActivity() {
// Code to manage Service lifecycle.
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(
componentName: ComponentName,
service: IBinder
) {
bluetoothService = (service as LocalBinder).getService()
bluetoothService?.let { bluetooth ->
if (!bluetooth.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth")
finish()
}
// perform device connection
}
}
override fun onServiceDisconnected(componentName: ComponentName) {
bluetoothService = null
}
}
...
}
class DeviceControlsActivity extends AppCompatActivity {
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bluetoothService = ((LocalBinder) service).getService();
if (bluetoothService != null) {
if (!bluetoothService.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
finish();
}
// perform device connection
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
bluetoothService = null;
}
};
...
}
連結裝置
BluetoothLeService
例項初始化後,即可連線至 BLE
裝置。該活動必須將裝置位址傳送給服務,
啟動連線。服務會先呼叫
getRemoteDevice()
敬上
前往 BluetoothAdapter
存取裝置。如果轉接程式找不到
變更位址的裝置時,getRemoteDevice()
會擲回
IllegalArgumentException
。
fun connect(address: String): Boolean {
bluetoothAdapter?.let { adapter ->
try {
val device = adapter.getRemoteDevice(address)
} catch (exception: IllegalArgumentException) {
Log.w(TAG, "Device not found with provided address.")
return false
}
// connect to the GATT server on the device
} ?: run {
Log.w(TAG, "BluetoothAdapter not initialized")
return false
}
}
public boolean connect(final String address) {
if (bluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
try {
final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
} catch (IllegalArgumentException exception) {
Log.w(TAG, "Device not found with provided address.");
return false;
}
// connect to the GATT server on the device
}
DeviceControlActivity
會在服務收到要求後呼叫此 connect()
函式
初始化。活動需要傳入 BLE 裝置的地址。於
以下範例會將裝置位址做為意圖傳遞至活動
外加。
// Code to manage Service lifecycle.
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(
componentName: ComponentName,
service: IBinder
) {
bluetoothService = (service as LocalBinder).getService()
bluetoothService?.let { bluetooth ->
if (!bluetooth.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth")
finish()
}
// perform device connection
bluetooth.connect(deviceAddress)
}
}
override fun onServiceDisconnected(componentName: ComponentName) {
bluetoothService = null
}
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bluetoothService = ((LocalBinder) service).getService();
if (bluetoothService != null) {
if (!bluetoothService.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
finish();
}
// perform device connection
bluetoothService.connect(deviceAddress);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
bluetoothService = null;
}
};
宣告 GATT 回呼
一旦該活動告知服務要連線至哪個裝置與服務
可連線至裝置,服務必須連線至
BLE 裝置。需要「BluetoothGattCallback
」才能接收這個連線
連線狀態、服務探索、特性相關通知
讀取與特性通知
本主題著重於連線狀態通知。請參閱轉移 BLE 另闢蹊徑 服務探索、特性讀取和要求特性 通知。
onConnectionStateChange()
敬上
函式。
在以下範例中,回呼是在 Service
類別中定義,因此在
可搭配
BluetoothDevice
每個 Pod 的 IP 位址
private val bluetoothGattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
// successfully connected to the GATT Server
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// disconnected from the GATT Server
}
}
}
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
// successfully connected to the GATT Server
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// disconnected from the GATT Server
}
}
};
連線至 GATT 服務
宣告 BluetoothGattCallback
後,服務即可使用
connect()
函式中的 BluetoothDevice
物件,用來連線至 GATT
必須在裝置上使用額外服務
connectGatt()
敬上
函式。這需要 Context
物件,即 autoConnect
布林值
標記和 BluetoothGattCallback
。在這個例子中,應用程式
正在連線到 BLE 裝置,因此 false
會傳遞 autoConnect
。
系統會一併新增 BluetoothGatt
屬性。這樣一來,服務就能關閉
連線
要求執行虛擬機器
class BluetoothLeService : Service() {
...
private var bluetoothGatt: BluetoothGatt? = null
...
fun connect(address: String): Boolean {
bluetoothAdapter?.let { adapter ->
try {
val device = adapter.getRemoteDevice(address)
// connect to the GATT server on the device
bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
return true
} catch (exception: IllegalArgumentException) {
Log.w(TAG, "Device not found with provided address. Unable to connect.")
return false
}
} ?: run {
Log.w(TAG, "BluetoothAdapter not initialized")
return false
}
}
}
class BluetoothLeService extends Service {
...
private BluetoothGatt bluetoothGatt;
...
public boolean connect(final String address) {
if (bluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
try {
final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
// connect to the GATT server on the device
bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback);
return true;
} catch (IllegalArgumentException exception) {
Log.w(TAG, "Device not found with provided address. Unable to connect.");
return false;
}
}
}
廣播更新
伺服器與 GATT 伺服器連線或中斷連線時,必須傳送通知給 新狀態的活動有幾種方法可以完成此操作。 以下範例使用廣播傳送 相關資訊
服務會宣告一個函式,以廣播新狀態。這個函式
,在廣播前傳遞至 Intent
物件的動作字串中
傳送至系統
private fun broadcastUpdate(action: String) {
val intent = Intent(action)
sendBroadcast(intent)
}
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
廣播函式設定完成後,
BluetoothGattCallback
:將連線狀態的相關資訊
GATT 伺服器系統會宣告常數和服務目前的連線狀態
在代表 Intent
動作的服務中。
class BluetoothLeService : Service() {
private var connectionState = STATE_DISCONNECTED
private val bluetoothGattCallback: BluetoothGattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
// successfully connected to the GATT Server
connectionState = STATE_CONNECTED
broadcastUpdate(ACTION_GATT_CONNECTED)
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// disconnected from the GATT Server
connectionState = STATE_DISCONNECTED
broadcastUpdate(ACTION_GATT_DISCONNECTED)
}
}
}
...
companion object {
const val ACTION_GATT_CONNECTED =
"com.example.bluetooth.le.ACTION_GATT_CONNECTED"
const val ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"
private const val STATE_DISCONNECTED = 0
private const val STATE_CONNECTED = 2
}
}
class BluetoothLeService extends Service {
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";
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTED = 2;
private int connectionState;
...
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
// successfully connected to the GATT Server
connectionState = STATE_CONNECTED;
broadcastUpdate(ACTION_GATT_CONNECTED);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// disconnected from the GATT Server
connectionState = STATE_DISCONNECTED;
broadcastUpdate(ACTION_GATT_DISCONNECTED);
}
}
};
…
}
監聽活動的最新消息
服務廣播連線更新後,活動
實作 BroadcastReceiver
。
在設定活動時註冊這個接收器,並在
活動離開畫面。透過監聽服務中的事件
活動可以根據目前的
連線狀態。
class DeviceControlActivity : AppCompatActivity() {
...
private val gattUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
BluetoothLeService.ACTION_GATT_CONNECTED -> {
connected = true
updateConnectionState(R.string.connected)
}
BluetoothLeService.ACTION_GATT_DISCONNECTED -> {
connected = false
updateConnectionState(R.string.disconnected)
}
}
}
}
override fun onResume() {
super.onResume()
registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter())
if (bluetoothService != null) {
val result = bluetoothService!!.connect(deviceAddress)
Log.d(DeviceControlsActivity.TAG, "Connect request result=$result")
}
}
override fun onPause() {
super.onPause()
unregisterReceiver(gattUpdateReceiver)
}
private fun makeGattUpdateIntentFilter(): IntentFilter? {
return IntentFilter().apply {
addAction(BluetoothLeService.ACTION_GATT_CONNECTED)
addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED)
}
}
}
class DeviceControlsActivity extends AppCompatActivity {
...
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);
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
connected = false;
updateConnectionState(R.string.disconnected);
}
}
};
@Override
protected void onResume() {
super.onResume();
registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter());
if (bluetoothService != null) {
final boolean result = bluetoothService.connect(deviceAddress);
Log.d(TAG, "Connect request result=" + result);
}
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(gattUpdateReceiver);
}
private static IntentFilter makeGattUpdateIntentFilter() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
return intentFilter;
}
}
在「轉移 BLE 資料」中,
BroadcastReceiver
也可用來將服務探索結果傳達為
以及來自裝置的特徵資料
關閉 GATT 連線
使用藍牙連線時,其中一個重要步驟是關閉
。方法是呼叫 close()
函式,BluetoothGatt
。在以下範例中
保留 BluetoothGatt
的參照。當活動與
服務,系統就會關閉連線,避免裝置電力耗盡。
class BluetoothLeService : Service() {
...
override fun onUnbind(intent: Intent?): Boolean {
close()
return super.onUnbind(intent)
}
private fun close() {
bluetoothGatt?.let { gatt ->
gatt.close()
bluetoothGatt = null
}
}
}
class BluetoothLeService extends Service {
...
@Override
public boolean onUnbind(Intent intent) {
close();
return super.onUnbind(intent);
}
private void close() {
if (bluetoothGatt == null) {
Return;
}
bluetoothGatt.close();
bluetoothGatt = null;
}
}