ble startscan後にScanCallbackが動作しない(API22->API30)
Discussion
解決したいこと
API22で動作していたBLE通信がAPI30で動作しなくなりました。
Android公式サイトで指示している通り、
AndroidManifest.xmlのpermissionにACCESS_FINE_LACATION追加しても
動作しません。
解決方法やデバッグの注意点を教えてください。
発生している問題・エラー
startscan後、ScanCallbackに入らない。
該当するソースコード
<<<>>>
<?xml version="1.0" encoding="utf-8"?>
package="com.example.myapplication">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />-->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<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>
<activity android:name=".ConnectNodeActivity1"/>
</application>
<<<>>>
package com.example.myapplication;
import android.annotation.SuppressLint;
import android.app.Activity;
//import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
//import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static com.example.myapplication.R.id.button_XXX_list_scan;
import static com.example.myapplication.R.id.layout_device_list;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class ConnectNodeActivity1 extends Activity {
// ---- フィールド定義(定数)
private static final String TAG = "XXXXXXXXXXXXXXXXXXXX"; //Logのタグ
// TODO 使用する connectBlue BLE モジュールのサービス(Serial port service)のUUID を定義
// 使用する connectBlue BLE モジュールのサービス(Serial port service)のUUID
static final String SERVICE_UUID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
// TODO connectBlue BLE モジュールのキャラクタリスティック(Serial port FIFO characteristic)のUUID を定義
// connectBlue BLE モジュールのキャラクタリスティック(Serial port FIFO characteristic)のUUID
static final String CHARACTERISTIC_UUID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
// ---- フィールド定義(View 関連) ----
// private ProgressDialog mProgressDialog; // スキャン中に表示するプログレスダイアログ
private LinearLayout mScanListLinearLayout;// スキャンしたペリフェラルデバイス一覧を表示するView
private TextView mDeviceText; // 接続を試みるペリフェラルデバイス名を表示するView
// ---- フィールド定義(BLE 関連) ----
private BluetoothAdapter mBluetoothAdapter; // Bluetoothアダプタのインスタンス
private BluetoothLeScanner mBluetoothLeScanner;// (Android 5.0以降で使用する)BLEスキャナのインスタンス
private final List<BluetoothDevice> mDeviceList = new ArrayList<>();// スキャンしたデバイスを保存するリスト
private static final int REQUEST_ENABLE_BT = 1; // Bluetoothの許可を求めるインテントで呼び出し元を識別するためのフラグ
private BluetoothGatt mBluetoothGatt; // GATTプロファイルのインスタンス
private TextView mDeviceValue; // 出力値を表示するView
// ---- フィールド定義(その他) ----
// ==== アクティビティのライフサイクル ====
/**
* onCreate(ConnectNodeActivityアクティビティ起動時に実行)
* 内容:Viewの初期設定,OnClickListenerの登録,BLEの初期設定
*
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ---- Viewの初期設定 ----
setContentView(R.layout.activity_connectnode);
mScanListLinearLayout = findViewById(layout_device_list);
mDeviceText = findViewById(R.id.connectedNode_name);
mDeviceValue = findViewById(R.id.hvoutput_value);
// (端末がBLEに対応しているかどうか確認する)
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, "この端末はBLEに対応していません。", Toast.LENGTH_LONG).show();
finish();
}
// (BluetoothManager の取得)
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
// (BluetoothAdapter の取得)
mBluetoothAdapter = bluetoothManager.getAdapter();
// (端末の Bluetooth 機能が無効化されている場合は許可するようユーザに促す)
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
} else {
//(Android 5.0以上なら)BluetoothLeScannerも取得
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
}
}
// ---- OnClickListener の登録 ----
// (スキャンボタン)
Button scanButton = findViewById(button_XXX_list_scan);
scanButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
// (今回は接続相手を1つに限定するため)接続中のペリフェラルを切断
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
mBluetoothGatt = null;
}
// デバイス名およびデバイス別の設定表示を初期化
mDeviceText.setText("---");
// スキャン中は2度押し出来ないようにする
view.setVisibility(View.GONE);
// スキャンを開始する
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Android 5.0以上
// 消費電力よりも検出しやすさを優先したスキャン設定に変更
ScanSettings.Builder builder = new ScanSettings.Builder();
builder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
ScanSettings scanSettings = builder.build();
// スキャン動作を開始する
mBluetoothLeScanner.startScan(null, scanSettings, mScanCallback);
// キャンセルボタンの表示
addCancelBtn();
}
}
});
}
/**
* onStop(アクティビティが非表示となった時に実行)
* 内容:接続中のペリフェラルがあれば切断
*/
@Override
protected void onStop() {
super.onStop();
// 接続中のペリフェラルがあれば切断する
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
mBluetoothGatt = null;
}
}
/**
* startActivityForResultで遷移したアクティビティからの戻り時に呼び出されるメソッド
*
* @param requestCode 遷移時に指定したリクエスト
* @param resultCode 呼び出し先から返された結果
* @param data
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 自分のリクエストでなければ何もしない
if (requestCode != REQUEST_ENABLE_BT) {
return;
}
// ユーザがBluetoothを有効にしなかった場合はアプリケーションを終了する
if (resultCode == Activity.RESULT_CANCELED) {
Toast.makeText(this, "端末のBluetooth設定を有効にしてください。", Toast.LENGTH_LONG).show();
finish();
} else {
// 取得できていない可能性のあるデータを再取得しておく
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
mBluetoothLeScanner.startScan(mScanCallback);
}
}
}
// --- フィールド定義(コールバックインスタンス) ----
// デバイススキャン後のコールバックインスタンス(Android 5.0以降のデバイス用)
private ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanFailed(int errorCode) {
Log.d(TAG, "onScanFailed: ");
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
Log.d(TAG, "onBatchScanResults: ");
}
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType,result);
// TODO 位置情報対応後(API26以降必須)に動作しなくなった(API22→API30)
// スキャンしたデバイスの情報をリストに追加する
addScannedDevice(result.getDevice());
}
};
// GATTプロファイル接続後のコールバックインスタンス
private final BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {
/**
* 接続状態が変化したときに実行されるコールバックメソッド
*
* @param gatt 接続状態の変化を検出したGATT情報
* @param status 接続or切断操作結果(GATT_SUCCESS:成功)
* @param newState 操作後の接続状態(STATE_DISCONNECTED:切断,STATE_CONNECTED:接続)
*/
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
// 接続に成功したら接続情報を保存する
mBluetoothGatt = gatt;
// 画面表示を変更(UIスレッド上で実行する)
runOnUiThread(() -> {
String deviceName = mBluetoothGatt.getDevice().getName();
// 接続デバイス名
TextView textView = findViewById(R.id.connectedNode_name);
textView.setText(deviceName);
// デバイス設定表示
if (deviceName.endsWith("V")) { //Device側 default: XXX-V
setContentView(R.layout.layout_settings);
}
});
} else {
// 切断を検出した場合はリソースを解放して終了する
gatt.close();
}
}
/**
* サービス検索完了時に実行されるコールバックメソッド
*
* @param gatt サービス検索を行ったGATT情報
* @param status 操作結果(GATT_SUCCESS:成功)
*/
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
// 検索に失敗した場合は処理を中止して、再度検索を開始
if (status != BluetoothGatt.GATT_SUCCESS) {
gatt.discoverServices();
return;
}
// 検索したサービスの一覧をログに出力
List<BluetoothGattService> serviceList = gatt.getServices();
for (BluetoothGattService lst : serviceList) {
Log.d(TAG, "onServicesDiscovered: " + lst.getUuid().toString());
}
// UUIDを指定して使用したいサービスのインスタンスを取得
// TODO 今回利用するサービスのインスタンスを UUIDを指定して取得
// TODO 取得したインスタンスを bluetoothGattService に保持
BluetoothGattService bluetoothGattService = gatt.getService(UUID.fromString(SERVICE_UUID));
// UUIDを指定して使用したいキャラクタリスティックのインスタンスを取得
// TODO 今回利用するキャラクタリスティックのインスタンスを UUIDを指定
// TODO 取得します。インスタンスは bluetoothGattCharacteristic に保持
bluetoothGattService.getCharacteristic(UUID.fromString(CHARACTERISTIC_UUID));
}
/**
* キャラクタリスティックへの書き込みに対して
* ペリフェラルからレスポンスを受信した場合のコールバックメソッド
*
* @param gatt レスポンスを返してきたペリフェラルのGATT情報
* @param characteristic 書き込んだ値
* @param status GATT_SUCCESS(書き込み成功)
*/
// TODO onCharacteristicWrite をオーバーライド
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
final BluetoothGattCharacteristic characteristic,
int status) {
// エラーコードの場合は書き込み失敗
// TODO 書き込み失敗を検出
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
// 書き込み成功をトースト表示する(UIスレッドで実行)
// TODO 書き込んだキャラクタリスティックの値を取得
final String value = new String(characteristic.getValue());
// TODO 書き込み成功をトースト表示する(UIスレッドで実行)
runOnUiThread(() -> Toast.makeText(getApplicationContext(),
"[ " + value + " ]が書き込まれました。",
Toast.LENGTH_SHORT).show());
}
/**
* キャラクタリスティックの値更新通知を受信したときのコールバックメソッド
*
* @param gatt 通知を検出したGATT情報
* @param characteristic 更新されたキャラクタリスティック
*/
// TODO onCharacteristicChanged をオーバーライド
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic
characteristic) {
// センサー値の表示値を更新する ※UIスレッドで実行すること
// TODO キャラクタリスティックの値を取得します。
final String value = new String(characteristic.getValue());
runOnUiThread(() -> mDeviceValue.setText(value));
}
};
// ==== クラスローカルメソッド ====
/**
* スキャン中に表示するプログレスダイアログの表示処理はNG→API26以降は非推奨(削除)
* 但し、スキャンボタンを押した後は、キャンセルボタンを表示して、スキャン停止できるようにしておく。
*/
private void addCancelBtn() {
Button newBtnA = new Button(this);
newBtnA.setText("キャンセル");
mScanListLinearLayout.addView(newBtnA);
newBtnA.setOnClickListener(view -> {
// キャンセルボタンが押されたら、スキャンを停止する
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Android 5.0以上
mBluetoothLeScanner.stopScan(mScanCallback);
newBtnA.setVisibility(View.GONE);
findViewById(button_XXX_list_scan).setVisibility(View.VISIBLE);
}
});
}
/**
* スキャンしたデバイスの情報を画面とリストに追加する
*
* @param device スキャンしたデバイス
*/
private void addScannedDevice(@NonNull BluetoothDevice device) {
// (デバイス名が取得できていない場合は追加しない)
if (device.getName() == null) {
return;
}
// (アドレスが登録済みであれば追加しない)
for (BluetoothDevice dv : mDeviceList) {
if ((dv.getAddress().equals(device.getAddress()))) {
return;
}
}
// (デバイス名が "XXXX" から始まっていないものは追加しない)
if (!(device.getName().startsWith("XXXX"))) {
return;
}
// TODO スキャンしたデバイスの情報をリストに追加してボタンを追加
mDeviceList.add(device);
Button newBtn = new Button(this);
newBtn.setText(device.getName());
mScanListLinearLayout.addView(newBtn);
TextView textView = findViewById(R.id.connectedNode_name);
textView.setText("");
newBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// (今回は接続相手を1つに限定するため)接続中のペリフェラルを切断
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
mBluetoothGatt = null;
}
// ボタンのデバイス名のペリフェラルに接続
String deviceName = ((Button) view).getText().toString();
BluetoothDevice device = searchDevice(deviceName);
if (device != null) {
device.connectGatt(getApplicationContext(), false, mBluetoothGattCallback);
// mBluetoothGatt = device.connectGatt(getApplicationContext(), false, mBluetoothGattCallback);
// TODO xml設定画面を表示する
setContentView(R.layout.layout_settings);
}
}
});
}
/**
* スキャン済みデバイスのリストから指定したデバイス名の情報を取得する
*
* @param deviceName 取得したいデバイスのデバイス名
* @return 取得したデバイス
*/
private BluetoothDevice searchDevice(String deviceName) {
for (BluetoothDevice device : mDeviceList) {
if (device.getName().equals(deviceName)) {
return device;
}
}
return null;
}
}
自分で試したこと
スマホのBluetoothを無効にして、プログラムを実行し、Bluetoothを有効にした場合のみ、
ScanCallbackに入り、onScanFailedがでるようになりました。
先行きが不透明です。
よろしくお願いします。