android BluetoothDevice.getName() return null

别来无恙 提交于 2019-11-27 01:37:57

Finally, i found out the solution:

1.For device connected:

Read device name from gatt characteristic org.bluetooth.characteristic.gap.device_name of service org.bluetooth.service.generic_access.

2.For device no connected:

    /**
     * Get device name from ble advertised data
     */
    private LeScanCallback mScanCb = new LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, final int rssi,
            byte[] scanRecord) {
            final BleAdvertisedData badata = BleUtil.parseAdertisedData(scanRecord);
            String deviceName = device.getName();
            if( deviceName == null ){
                deviceName = badata.getName();
            }
    }


////////////////////// Helper Classes: BleUtil and BleAdvertisedData ///////////////
    final public class BleUtil {        
        private final static String TAG=BleUtil.class.getSimpleName();
        public static BleAdvertisedData parseAdertisedData(byte[] advertisedData) {      
            List<UUID> uuids = new ArrayList<UUID>();
            String name = null;
            if( advertisedData == null ){
                return new BleAdvertisedData(uuids, name);
            }

            ByteBuffer buffer = ByteBuffer.wrap(advertisedData).order(ByteOrder.LITTLE_ENDIAN);
            while (buffer.remaining() > 2) {
                byte length = buffer.get();
                if (length == 0) break;

                byte type = buffer.get();
                switch (type) {
                    case 0x02: // Partial list of 16-bit UUIDs
                    case 0x03: // Complete list of 16-bit UUIDs
                        while (length >= 2) {
                            uuids.add(UUID.fromString(String.format(
                                    "%08x-0000-1000-8000-00805f9b34fb", buffer.getShort())));
                            length -= 2;
                        }
                        break;
                    case 0x06: // Partial list of 128-bit UUIDs
                    case 0x07: // Complete list of 128-bit UUIDs
                        while (length >= 16) {
                            long lsb = buffer.getLong();
                            long msb = buffer.getLong();
                            uuids.add(new UUID(msb, lsb));
                            length -= 16;
                         }
                        break;
                    case 0x09:
                        byte[] nameBytes = new byte[length-1];
                        buffer.get(nameBytes);
                        try {
                            name = new String(nameBytes, "utf-8");
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                        break;
                    default:
                        buffer.position(buffer.position() + length - 1);
                        break;
                    }
                }
            return new BleAdvertisedData(uuids, name);
        }
    }


    public class BleAdvertisedData {
        private List<UUID> mUuids;
        private String mName;
        public BleAdvertisedData(List<UUID> uuids, String name){
            mUuids = uuids;
            mName = name;
        }

        public List<UUID> getUuids(){
            return mUuids;
        }

        public String getName(){
            return mName;
        }
    }

BluetoothDevice.getName() may return null if the name could not be determined. This could be due to any number of factors. Regardless, the name is the friendly name of the device, and shouldn't be used to distinguish it from other devices. Instead, use the hardware address through getAddress().

I was trying to display name of my RN4020 Bluetooth module and faced the same issue. Found the problem in Microchip's forum:

If you enabled private service or MLDP, the maximum bytes of device name is 6 bytes, due to the 31 byte advertisement payload limitation.

I had set the device name to 9 characters. Setting the name to 4 bytes fixed the issue.

If you recognize the UUID's of your custom services so you know its your device you can also connect to the device and read its name (if its longer than 6 bytes in my case). This was also suggested in Microchips forum.

http://www.microchip.com/forums/m846328.aspx

I know this is old but this more spec-oriented answer may help answer some cases.

In Bluetooth Low Energy, advertisement and scan-response data is only required to have the Bluetooth Address. Advertisement data is how a client BTLE endpoint discovers a service device. A client can request a scan response and get more data. The device name is optional in this data. However, the BTLE spec requires that all Bluetooth Low Energy endpoints support the Generic Access service which is required to support the Device Name characteristic. Unfortunately, to read that characteristic the Android must first connect and do service discovery. If the advertisement/scan response does not provide the information, I do not believe Android connects to the device to get the name. At least I have never seen any indication of connecting without the app specifically requesting a connection. This is not what you want to be required to do if you want to make a decision to connect.

Fortunately, most BTLE devices I have worked with do provide their name in the advertisement or scan response.

Another possibility is that the device may place the name in the scan response part of the advertisement. Depending upon how one has set up Android's BTLE scanner, one might get only the advertisement data and not the scan response. In this case the name will not be found if the device puts it in the scan response. The default scanner settings, however, are such that a scan response must be received before the scan data is passed up to the app.

I've found that if you query for the device's name immediately after it's picked up at scanning it may return null. To get around this I poll a runnable every second or so on the UI thread a maximum of 3 times (So 3 seconds), and the name is usually resolved by then.

Note, in the snippet provided, the enclosing class implements Runnable, hence why I can pass this into View.postDelayed(Runnable action, long delayMillis)

    private static final int MAX_NAME_CHECKS = 3;
    private static final int NAME_CHECK_PERIOD = 1000;

    int nameChecks;

    @Override
    public void run() {
        resolveName();
    }

    /**
     * Checks for the device name, for a maximum of {@link ViewHolder#MAX_NAME_CHECKS}
     * as the name may not have been resolved at binding.
     */
    private void resolveName() {
        if (device != null) {
            String name = device.getName();
            boolean isEmptyName = TextUtils.isEmpty(name);

            if (isEmptyName) deviceName.setText(R.string.unknown_device);
            else deviceName.setText(name);

            // Check later if device name is resolved
            if (nameChecks++ < MAX_NAME_CHECKS && isEmptyName)
                itemView.postDelayed(this, NAME_CHECK_PERIOD);
        }
    }

On Marshmallow, utilize ScanRecord.getDeviceName() to retrieve the local name embedded in the scan record.

BluetoothDevice.getName() is unreliable if the local name is included in a scan response, rather than in the immediate advertising packet.

    @Override
    public void onScanResult(int callbackType, ScanResult scanResult) {
        super.onScanResult(callbackType, scanResult);

        // Retrieve device name via ScanRecord.
        String deviceName = scanResult.getScanRecord().getDeviceName();
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!