Статьи

Создайте сканер Bluetooth с Android API Bluetooth

Bluetooth стал очень популярной технологией, особенно на мобильных устройствах. Это технология для обнаружения и передачи данных между соседними устройствами. В наши дни практически каждое современное мобильное устройство обладает возможностями Bluetooth. Если вы хотите создать интерфейс приложения с другим устройством с поддержкой Bluetooth, от телефонов до динамиков, вы должны знать, как использовать Bluetooth API Android.

В этом уроке мы будем создавать приложение, аналогичное встроенному приложению Bluetooth в настройках Android. Он будет включать в себя следующие функции:

  • включить Bluetooth на устройстве
  • отобразить список сопряженных устройств
  • обнаружить и перечислить близлежащие устройства Bluetooth

Мы также рассмотрим основы подключения и отправки данных на другое устройство Bluetooth. Я создал проект для начала, который вы можете скачать с GitHub . На скриншоте ниже показано, как выглядит стартовый проект. Если вы застряли или столкнулись с проблемами, то вы можете взглянуть на готовый проект на GitHub .

Pre App Что дает данный стартовый код

Прежде чем мы сможем включить Bluetooth на устройстве Android, нам нужно запросить необходимые разрешения. Мы делаем это в манифесте приложения. Разрешение BLUETOOTH позволяет нашему приложению подключаться, отключаться и передавать данные с другого устройства Bluetooth. Разрешение BLUETOOTH_ADMIN позволяет нашему приложению обнаруживать новые устройства Bluetooth и изменять настройки Bluetooth устройства.

1
2
3
4
5
<manifest xmlns:android=»http://schemas.android.com/apk/res/android»
   package=»com.tutsplus.matt.bluetoothscanner» >
    
   <uses-permission android:name=»android.permission.BLUETOOTH» />
   <uses-permission android:name=»android.permission.BLUETOOTH_ADMIN» />

Мы будем использовать адаптер Bluetooth для взаимодействия с Bluetooth. Мы создаем экземпляр адаптера в классе ListActivity . Если адаптер null , это означает, что Bluetooth не поддерживается устройством, и приложение не будет работать на текущем устройстве. Мы справляемся с этой ситуацией, показывая пользователю диалоговое окно с предупреждением и закрывая приложение.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
@Override
protected void onCreate(Bundle savedInstanceState) {
    BTAdapter = BluetoothAdapter.getDefaultAdapter();
    // Phone does not support Bluetooth so let the user know and exit.
    if (BTAdapter == null) {
        new AlertDialog.Builder(this)
                .setTitle(«Not compatible»)
                .setMessage(«Your phone does not support Bluetooth»)
                .setPositiveButton(«Exit», new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        System.exit(0);
                    }
                })
                .setIcon(android.R.drawable.ic_dialog_alert)
                .show();
    }
}

Если Bluetooth доступен на устройстве, нам нужно включить его. Чтобы включить Bluetooth, мы запускаем намерение, предоставленное нам Android SDK, BluetoothAdapter.ACTION_REQUEST_ENABLE . Это предоставит пользователю диалоговое окно с запросом разрешения на включение Bluetooth на устройстве. REQUEST_BLUETOOTH — это статическое целое число, которое мы устанавливаем для идентификации запроса активности.

01
02
03
04
05
06
07
08
09
10
11
public class ListActivity extends ActionBarActivity implements DeviceListFragment.OnFragmentInteractionListener {
    public static int REQUEST_BLUETOOTH = 1;
    …
    protected void onCreate(Bundle savedInstanceState) {
    …
        if (!BTAdapter.isEnabled()) {
            Intent enableBT = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBT, REQUEST_BLUETOOTH);
        }
    }
}

На этом этапе мы сканируем сопряженные устройства Bluetooth и отображаем их в виде списка. В контексте мобильного устройства устройство Bluetooth может быть:

  • неизвестный
  • в паре
  • связано

Важно знать разницу между сопряженным и подключенным устройством Bluetooth. Сопряженные устройства знают о существовании друг друга и имеют общий ключ связи , который можно использовать для аутентификации, что приводит к установлению соединения. Устройства автоматически подключаются после установления зашифрованного соединения.

Подключенные устройства совместно используют канал RFCOMM , что позволяет им отправлять и получать данные. Устройство может иметь много сопряженных устройств, но одновременно оно может быть подключено только к одному устройству.

Устройства Bluetooth представлены объектом BluetoothDevice . Список сопряженных устройств можно получить, вызвав метод getBondedDevices() , который возвращает набор объектов BluetoothDevice . Мы вызываем метод getBondedDevices() в DeviceListFragment onCreate() .

1
2
3
4
5
6
7
8
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    Log.d(«DEVICELIST», «Super called for DeviceListFragment onCreate\n»);
    deviceItemList = new ArrayList<DeviceItem>();
 
    Set<BluetoothDevice> pairedDevices = bTAdapter.getBondedDevices();
}

Мы используем getName() и getAddress() для получения дополнительной информации об устройствах Bluetooth. Метод getName() возвращает открытый идентификатор устройства, а метод getAddress() возвращает MAC-адрес устройства , идентификатор, однозначно идентифицирующий устройство.

Теперь, когда у нас есть список сопряженных устройств, мы создаем объект DeviceItem для каждого объекта BluetoothDevice . Затем мы добавляем каждый объект DeviceItem в массив с именем deviceItemList . Мы будем использовать этот массив для отображения списка сопряженных устройств Bluetooth в нашем приложении. Код для отображения списка объектов DeviceItem уже присутствует в стартовом проекте.

1
2
3
4
5
6
if (pairedDevices.size() > 0) {
    for (BluetoothDevice device : pairedDevices) {
        DeviceItem newDevice= new DeviceItem(device.getName(),device.getAddress(),»false»);
        deviceItemList.add(newDevice);
    }
}

Следующим шагом является обнаружение устройств, с которыми устройство еще не сопряжено, неизвестные устройства, и добавление их в список сопряженных устройств. Мы делаем это, когда пользователь нажимает кнопку сканирования. Код для обработки этого находится в DeviceListFragment .

Сначала нам нужно создать BroadcastReceiver и переопределить метод onReceive() . Метод onReceive() вызывается всякий раз, когда обнаруживается устройство Bluetooth.

Метод onReceive() принимает намерение в качестве второго аргумента. Мы можем проверить, с какими намерениями идет трансляция, вызывая getAction() . Если действие — BluetoothDevice.ACTION_FOUND , то мы знаем, что нашли устройство Bluetooth. Когда это происходит, мы создаем объект DeviceItem используя имя устройства и MAC-адрес. Наконец, мы добавляем объект DeviceItem в ArrayAdapter чтобы отобразить его в нашем приложении.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class DeviceListFragment extends Fragment implements AbsListView.OnItemClickListener{
    …
    private final BroadcastReceiver bReciever = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // Create a new device item
                DeviceItem newDevice = new DeviceItem(device.getName(), device.getAddress(), «false»);
                // Add it to our adapter
                mAdapter.add(newDevice);
            }
        }
    };
}

Когда кнопка сканирования включена, нам просто нужно зарегистрировать получатель, который мы только что сделали, и вызвать метод startDiscovery() . Если кнопка сканирования выключена, мы cancelDiscovery() регистрацию получателя и вызываем cancelDiscovery() . Имейте в виду, что открытие занимает много ресурсов. Если ваше приложение подключается к другому устройству Bluetooth, вы всегда должны отменить обнаружение перед подключением.

Мы также ArrayAdapter объект mAdapter , mAdapter , когда начинается обнаружение. Когда мы начинаем сканирование, мы не хотим включать старые устройства, которые могут больше не находиться в пределах досягаемости устройства.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_deviceitem_list, container, false);
    ToggleButton scan = (ToggleButton) view.findViewById(R.id.scan);
    …
    scan.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
            if (isChecked) {
                mAdapter.clear();
                getActivity().registerReceiver(bReciever, filter);
                bTAdapter.startDiscovery();
            } else {
                getActivity().unregisterReceiver(bReciever);
                bTAdapter.cancelDiscovery();
            }
        }
    });
}

Вот и все. Мы закончили наш сканер Bluetooth.

Соединения Bluetooth работают как любое другое соединение. Есть сервер и клиент, которые общаются через сокеты RFCOMM. В Android сокеты RFCOMM представлены в виде объекта BluetoothSocket . К счастью для нас, большая часть технического кода для серверов обрабатывается Android SDK и доступна через Bluetooth API.

Подключиться как клиент просто. Сначала вы получите сокет RFCOMM от нужного устройства BluetoothDevice , вызвав createRfcommSocketToServiceRecord() , передав UUID , 128-битное значение, которое вы создаете. UUID похож на номер порта.

Например, предположим, что вы создаете приложение для чата, которое использует Bluetooth для общения с другими соседними пользователями. Чтобы найти других пользователей для чата, вам нужно поискать другие устройства с установленным приложением чата. Для этого мы будем искать UUID в списке сервисов соседних устройств. Использование UUID для прослушивания и принятия подключений Bluetooth автоматически добавляет этот UUID в список служб телефона или в протокол обнаружения служб.

Как только BluetoothSocket создан, вы вызываете connect() для BluetoothSocket . Это инициализирует соединение с BluetoothDevice через гнездо RFCOMM. Как только наше устройство подключено, мы можем использовать сокет для обмена данными с подключенным устройством. Это похоже на любую стандартную реализацию сервера.

Поддержание соединения Bluetooth стоит дорого, поэтому нам нужно закрыть сокет, когда он нам больше не нужен. Чтобы закрыть сокет, мы вызываем close() на BluetoothSocket .

В следующем фрагменте кода показано, как подключиться к данному BluetoothDevice :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class ConnectThread extends Thread{
    private BluetoothSocket bTSocket;
 
    public boolean connect(BluetoothDevice bTDevice, UUID mUUID) {
        BluetoothSocket temp = null;
        try {
            temp = bTDevice.createRfcommSocketToServiceRecord(mUUID);
        } catch (IOException e) {
            Log.d(«CONNECTTHREAD»,»Could not create RFCOMM socket:» + e.toString());
            return false;
        }
        try {
            bTSocket.connect();
        } catch(IOException e) {
            Log.d(«CONNECTTHREAD»,»Could not connect: » + e.toString());
            try {
                bTSocket.close();
            } catch(IOException close) {
                Log.d(«CONNECTTHREAD», «Could not close connection:» + e.toString());
                return false;
            }
        }
        return true;
    }
 
    public boolean cancel() {
        try {
            bTSocket.close();
        } catch(IOException e) {
            Log.d(«CONNECTTHREAD»,»Could not close connection:» + e.toString());
            return false;
        }
        return true;
    }
}

Подключение в качестве сервера немного сложнее. Во-первых, из вашего BluetoothAdapter вы должны получить BluetoothServerSocket , который будет использоваться для прослушивания соединения. Это используется только для получения общего сокета RFCOMM соединения. Как только соединение установлено, серверный сокет больше не нужен и может быть закрыт с помощью вызова close() .

Мы создаем экземпляр сокета сервера, вызывая listenUsingRfcommWithServiceRecord(String name, UUID mUUID) . Этот метод принимает два параметра: имя типа String и уникальный идентификатор типа UUID . Параметр name — это имя, которое мы даем службе, когда она добавляется в запись SDP (Service Discovery Protocol) телефона. Уникальный идентификатор должен соответствовать UUID, который использует клиент, пытающийся подключиться.

Затем мы вызываем метод accept() для только что полученного BluetoothServerSocket чтобы дождаться соединения. Когда вызов accept() возвращает что-то, что не является null , мы назначаем это нашему BluetoothSocket , который мы можем затем использовать для обмена данными с подключенным устройством.

В следующем фрагменте кода показано, как принять соединение в качестве сервера:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class ServerConnectThread extends Thread{
    private BluetoothSocket bTSocket;
 
    public ServerConnectThread() { }
 
    public void acceptConnect(BluetoothAdapter bTAdapter, UUID mUUID) {
        BluetoothServerSocket temp = null;
        try {
            temp = bTAdapter.listenUsingRfcommWithServiceRecord(«Service_Name», mUUID);
        } catch(IOException e) {
            Log.d(«SERVERCONNECT», «Could not get a BluetoothServerSocket:» + e.toString());
        }
        while(true) {
            try {
                bTSocket = temp.accept();
            } catch (IOException e) {
                Log.d(«SERVERCONNECT», «Could not accept an incoming connection.»);
                break;
            }
            if (bTSocket != null) {
                try {
                    temp.close();
                } catch (IOException e) {
                    Log.d(«SERVERCONNECT», «Could not close ServerSocket:» + e.toString());
                }
                break;
            }
        }
    }
 
    public void closeConnect() {
        try {
            bTSocket.close();
        } catch(IOException e) {
            Log.d(«SERVERCONNECT», «Could not close connection:» + e.toString());
        }
    }
}

Чтение и запись в соединение выполняется с использованием потоков, InputStream и OutputStream . Мы можем получить ссылку на эти потоки, вызвав getInputStream() и getOutputStream() в BluetoothSocket . Для чтения и записи в эти потоки мы вызываем read() и write() соответственно.

Следующий фрагмент кода показывает, как это сделать для одного целого числа:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class ManageConnectThread extends Thread {
 
    public ManageConnectThread() { }
 
    public void sendData(BluetoothSocket socket, int data) throws IOException{
        ByteArrayOutputStream output = new ByteArrayOutputStream(4);
        output.write(data);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(output.toByteArray());
    }
 
    public int receiveData(BluetoothSocket socket) throws IOException{
        byte[] buffer = new byte[4];
        ByteArrayInputStream input = new ByteArrayInputStream(buffer);
        InputStream inputStream = socket.getInputStream();
        inputStream.read(buffer);
        return input.read();
    }
}

Вы можете найти оба примера в готовом проекте на GitHub .

Мы успешно создали собственный сканер Bluetooth и узнали следующее:

  • запросить необходимые разрешения Bluetooth
  • включить Bluetooth на вашем телефоне
  • получить список сопряженных устройств
  • сканировать и отображать список ближайших устройств Bluetooth
  • установить соединение Bluetooth между двумя устройствами
  • отправлять и получать данные через соединение Bluetooth

Не стесняйтесь использовать код в готовом проекте на GitHub и изменять его в своих собственных приложениях.