蓝牙 startDiscovery() 在 Android 10 上不工作

Bluetooth startDiscovery() is not working on Android 10

我遇到了一个问题。我尝试了 Stack Overflow 和其他网站的一些提示。

我想编写一个应用程序,搜索周围所有的蓝牙设备,如果找到匹配的设备(MAC-地址作为参考),应用程序将开始连接。

因此我编写了一个测试应用程序来测试发现功能。但不幸的是,在我的 Android 10 设备上启动发现过程存在一个大问题。我有一台带有 Android 4.1.2 (SDK 16) 的旧三星 S3 Mini,我的代码运行良好。 在 Android 10 设备上 startDiscovery() returns false,不同于 Android 4 设备 returns true。他们在 android 开发人员页面上说,如果发生错误,则返回值为 false。 BroadcastReceiver 应该可以正常工作,因为 Android 9 mobile phone 上的应用检测到蓝牙搜索已在设置中启动。这只是 startDiscovery() 函数,这是问题所在(在我看来)。

在开始发现过程之前,我正在检查所有权限和蓝牙状态。但我认为,它不可能是书面代码,因为它在旧设备上运行得很好。也许我缺少一些新设备的东西。

编辑

正如 Thomas Morris 在下面解释的那样,在 Android 10 中,您需要用户打开的位置。在Android9以下,Thomas Morris的回答是正确的,因为在29以下的所有SDK中,只需要权限,不需要启用位置服务。

是否有解决方案来避免要求用户自己打开位置?

这是我的 MainActivity:

public class MainActivity extends AppCompatActivity {
final String TAG = "MainActivity";
BluetoothAdapter bluetoothAdapter;
int status = 0;     //0 = start discovering, 1 = cancel discovering

public static final int REQUEST_ACCESS_COARSE_LOCATION = 1;
public static final int REQUEST_ENABLE_BLUETOOTH = 11;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_FOUND));
    registerReceiver(receiver, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED));
    registerReceiver(receiver, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED));

    bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

    checkBluetoothState();

    final Button test = findViewById(R.id.testbutton);
    test.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if(status == 0) {
                if(bluetoothAdapter != null && bluetoothAdapter.isEnabled()) {
                    if (checkCoarseLocationPermission()) {
                        Boolean result = bluetoothAdapter.startDiscovery(); //start discovering and show result of function
                        Toast.makeText(getApplicationContext(), "Start discovery result: " + result, Toast.LENGTH_SHORT).show();
                        Log.d(TAG, "Start discovery: " + result);
                        test.setText("Stop");
                        status = 1;
                    }
                }else{
                    checkBluetoothState();
                }
            }else{
                Log.d(TAG,"Stop");
                status = 0;
                bluetoothAdapter.cancelDiscovery();
                test.setText("Start");
            }
        }
    });

    checkCoarseLocationPermission();
}

private boolean checkCoarseLocationPermission() {
    //checks all needed permissions
    if(ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED){
        ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_ACCESS_COARSE_LOCATION);
        return false;
    }else{
        return true;
    }

}

private void checkBluetoothState() {
    //checks if bluetooth is available and if it´s enabled or not
    if(bluetoothAdapter == null){
        Toast.makeText(getApplicationContext(), "Bluetooth not available", Toast.LENGTH_SHORT).show();
    }else{
        if(bluetoothAdapter.isEnabled()){
            if(bluetoothAdapter.isDiscovering()){
                Toast.makeText(getApplicationContext(), "Device is discovering...", Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(getApplicationContext(), "Bluetooth is enabled", Toast.LENGTH_SHORT).show();
            }
        }else{
            Toast.makeText(getApplicationContext(), "You need to enabled bluetooth", Toast.LENGTH_SHORT).show();
            Intent enabledIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enabledIntent, REQUEST_ENABLE_BLUETOOTH);
        }
    }
}

// Create a BroadcastReceiver for ACTION_FOUND.
private final BroadcastReceiver receiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Discovery has found a device. Get the BluetoothDevice
            // object and its info from the Intent.
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            String deviceName = device.getName();
            String deviceHardwareAddress = device.getAddress(); // MAC address
            Log.d(TAG,"Device found: " + deviceName + "|" + deviceHardwareAddress);
            Toast.makeText(getApplicationContext(), "FOUND: " + deviceName + "|" + deviceHardwareAddress, Toast.LENGTH_SHORT).show();
        }

        if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
            //report user
            Log.d(TAG,"Started");
            Toast.makeText(getApplicationContext(), "STARTED", Toast.LENGTH_SHORT).show();
        }

        if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
            //change button back to "Start"
            status = 0;
            final Button test = findViewById(R.id.testbutton);
            test.setText("Start");
            //report user
            Log.d(TAG,"Finished");
            Toast.makeText(getApplicationContext(), "FINISHED", Toast.LENGTH_SHORT).show();
        }

        if(BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)){
            final int extra = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,-1);
            if(extra == (BluetoothAdapter.STATE_ON)) {
                if (bluetoothAdapter.isDiscovering()) {
                    bluetoothAdapter.cancelDiscovery();
                }
                Boolean b = bluetoothAdapter.startDiscovery();
                Toast.makeText(getApplicationContext(), "Start discovery" + b, Toast.LENGTH_SHORT).show();
            }
        }
    }
};


@Override
protected void onDestroy() {
    super.onDestroy();
    if (bluetoothAdapter.isDiscovering()){
        bluetoothAdapter.cancelDiscovery();
    }

    unregisterReceiver(receiver);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
    super.onActivityResult(requestCode,resultCode,data);

    if(requestCode == REQUEST_ENABLE_BLUETOOTH){
        checkBluetoothState();
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){
    super.onRequestPermissionsResult(requestCode,permissions,grantResults);

    switch (requestCode){
        case REQUEST_ACCESS_COARSE_LOCATION:
            if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                Toast.makeText(getApplicationContext(),"Permission granted",Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(getApplicationContext(),"Permission denied",Toast.LENGTH_SHORT).show();
            }
    }
}



}

这是我的清单:

<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>

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

在应用程序上启用位置权限。为此,请访问:

  • Android phone 设置
  • 应用和通知
  • 查看所有应用程序
  • 找到您的应用程序并select它
  • 权限
  • 允许位置滑动

然后

  • 打开设备上的蓝牙
  • 打开设备上的位置

或者一些代码通过弹出窗口自动执行此操作(调用 oncreate 方法)

public void checkPermission() {
        if (Build.VERSION.SDK_INT >= 23) {
            if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {

            } else {
                ActivityCompat.requestPermissions(this, new String[]{
                        Manifest.permission.ACCESS_FINE_LOCATION,
                        Manifest.permission.ACCESS_COARSE_LOCATION,}, 1);
            }
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
        } else {
            checkPermission();
        }
    }

开始Android10 必须启用位置服务,否则将找不到任何设备。

我用 Android 10 在华为 P30 上测试了 BluetoothAdapter.startDiscovery() 并且这个方法总是 return false 但实际上发现已经开始(授予位置权限和位置服务启用)。所以我不检查 startDiscovery() 方法的结果。

根据官方 Android Documentation 你需要同时拥有 ACCESS_FINE_LOCATION ACCESS_BACKGROUND_LOCATION 开始发现蓝牙设备的权限。

 /**
 * From Android 10 onwards it needs Access Location to search Bluetooth Devices
 */

private void checkForLocationPermission(){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && checkSelfPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            discoverDevices();
        } else {
            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_BACKGROUND_LOCATION,}, 1);
        }
    }

}

/**
 * Request Access Location while using the App, because bluetooth need location to start discovering devices
 * @param requestCode
 * @param permissions
 * @param grantResults
 */

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
        discoverDevices();
    } else {
        checkForLocationPermission();
    }
}

以上代码片段将帮助您向用户请求上述权限。

P.S:此外,这些也需要在 Android Manifest 中指定。