【问题标题】:Bluetooth startDiscovery() is not working on Android 10蓝牙 startDiscovery() 不适用于 Android 10
【发布时间】:2020-08-30 16:24:03
【问题描述】:

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

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

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

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

编辑

正如 Thomas Morris 下面解释的那样,在 Android 10 中,您需要用户打开位置信息。在 Android 9 或更低版本中,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"/>

【问题讨论】:

  • 您在运行应用程序时是否允许权限。您需要进入应用程序设置并启用位置。然后打开定位和蓝牙。您还需要检查您的手机是否为所需的 API 级别。
  • 谢谢,解决方案通常太简单了。该位置从未打开,因此始终仅启用蓝牙。现在我所要做的就是让应用程序打开位置。我请求了位置权限,这在应用程序设置中也是允许的,但我必须手动打开位置。如果我这样做,发现过程就可以正常工作。
  • 我已经为您发布了更详细的答案,请您批准它以使其他用户受益。还添加了一些代码,它应该会给你一个弹出窗口。如果您必须重新安装应用程序,手动操作会很烦人。为什么不使用弹出窗口。
  • 您正在扫描,所以我认为这不是问题,但对于像我这样在互联网上搜索 Android 10 问题的可怜人,我发现如果您在连接 Android 10 之前不扫描,您将获得 Gatt 133。我通过检查是否配对以及是否不进行扫描来解决问题。在测试前禁用/启用 BT 以确认。

标签: android bluetooth android-bluetooth


【解决方案1】:

根据官方Android Documentation,您需要同时拥有 ACCESS_FINE_LOCATIONACCESS_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();
    }
}

上面的代码sn-p会帮你向用户请求上面的权限。

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

【讨论】:

  • 您的回答来得正是时候!谢谢!
【解决方案2】:

从 Android 10 开始,必须启用定位服务,否则将找不到任何设备。

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

【讨论】:

    【解决方案3】:

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

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

    然后

    • 打开设备上的蓝牙
    • 开启设备上的定位功能

    或者一些代码通过弹出窗口自动完成(调用 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();
            }
        }
    

    【讨论】:

    • 我还有一个问题,如果我只要求我可以使用位置(但不能打开),为什么它在 Android 9 或更低版本中可以工作,但在 Android 10 中我必须打开位置在?一个好友的手机安装了Android 9,在他的手机上运行它而无需打开位置。他只需授予使用该位置的权限。
    • 不是 100% 确定,但出于安全原因,位置是一个问题。因此以后的版本需要得到用户的许可。该应用程序不能简单地给它。有点像授予公司存储您的数据的权限,以便您可以使用那里的服务。使用 ble 可以从某人那里获取位置信息。这些法律相对较新,因此在 android 10 而不是 9 上。至少这是我的理解。
    • 不幸的是,这会破坏应用程序的感觉,因为用户应该自动连接到产品。简单的方法是让用户在蓝牙菜单中与设备配对,然后我只在应用程序中搜索配对的设备以找到正确的设备。但是,这与要求用户打开位置一样自动。但是感谢您的帮助!如果您有任何想法,请务必提出来。
    • 为什么用户应该打开GPS?该应用程序只需要蓝牙而不是 GPS!我猜谷歌应该改变它的政策。
    • 这是因为打开蓝牙理论上可以访问位置信息,因此需要获得位置权限,这就是为什么应用程序要求位置而不是GPS但经常引用GPS的原因作为位置。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-20
    • 2013-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多