LXF163:Android: Общение с другими
Olkol (обсуждение | вклад) (Новая страница: «Категория:Постоянные рубрики Категория:Android '''Cвяжем ваши устройства через Bluetooth''…») |
Olkol (обсуждение | вклад) (→Связываем Activity вместе) |
||
(не показаны 3 промежуточные версии 1 участника) | |||
Строка 7: | Строка 7: | ||
''Джульетта Кемп рассказывает, как Bluetooth помогает общаться с другими устройствами, и проясняет сервис обмена сообщениями.'' | ''Джульетта Кемп рассказывает, как Bluetooth помогает общаться с другими устройствами, и проясняет сервис обмена сообщениями.'' | ||
− | [[Файл:LXF160.code_android.expert.png |left |100px |thumb|'''Наш эксперт'''Джульетта Кемп пишет статьи для различных изданий и работает системным администратором. Во время написания этой статьи ей пришлось немало пожонглировать с телефоном.]] | + | [[Файл:LXF160.code_android.expert.png |left |100px |thumb|'''Наш эксперт''' Джульетта Кемп пишет статьи для различных изданий и работает системным администратором. Во время написания этой статьи ей пришлось немало пожонглировать с телефоном.]] |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
Сегодня Bluetooth можно найти на любых устройствах, и с его помощью легко обмениваться данными между устройствами. На этом уроке мы напишем сервис обмена сообщениями по Bluetooth и узнаем, как обнаружить другие устройства (спаренные или нет), запустить сервер Bluetooth, отправить запрос на подключение со стороны клиента и передать данные между двумя устройствами. Отметим, что этот проект делался под Android 10 (2.3.3), но сработается и с более поздними версиями. Полный код урока можно найти на LXFDVD, но помните, что он не будет выполняться «как есть» – для компиляции в вашей локальной среде проекты Android нужно настроить либо вручную, либо в Eclipse. | Сегодня Bluetooth можно найти на любых устройствах, и с его помощью легко обмениваться данными между устройствами. На этом уроке мы напишем сервис обмена сообщениями по Bluetooth и узнаем, как обнаружить другие устройства (спаренные или нет), запустить сервер Bluetooth, отправить запрос на подключение со стороны клиента и передать данные между двумя устройствами. Отметим, что этот проект делался под Android 10 (2.3.3), но сработается и с более поздними версиями. Полный код урока можно найти на LXFDVD, но помните, что он не будет выполняться «как есть» – для компиляции в вашей локальной среде проекты Android нужно настроить либо вручную, либо в Eclipse. | ||
− | Устанавливаем соединение по Bluetooth | + | ===Устанавливаем соединение по Bluetooth=== |
Изменения, которые надо проделать в файле AndroidManifest.xml, описаны на нашем DVD. Для обмена данными по Bluetooth понадобятся и клиент, и сервер. Сервер слушает подключения, открыв сокет BluetoothServerSocket; клиент создает BluetoothSocket, открывает канал связи с сервером RFCOMM и отправляет запрос на соединение. Сервер принимает соединение и открывает свой BluetoothSocket. Когда оба сокета BluetoothSocket на одном и том же канале RFCOMM будут открыты, клиент и сервер подключатся друг к другу и смогут обмениваться данными. | Изменения, которые надо проделать в файле AndroidManifest.xml, описаны на нашем DVD. Для обмена данными по Bluetooth понадобятся и клиент, и сервер. Сервер слушает подключения, открыв сокет BluetoothServerSocket; клиент создает BluetoothSocket, открывает канал связи с сервером RFCOMM и отправляет запрос на соединение. Сервер принимает соединение и открывает свой BluetoothSocket. Когда оба сокета BluetoothSocket на одном и том же канале RFCOMM будут открыты, клиент и сервер подключатся друг к другу и смогут обмениваться данными. | ||
Строка 69: | Строка 62: | ||
acceptThread.start(); | acceptThread.start(); | ||
+ | |||
+ | Метод start() завершает все существующие нити, пытающиеся подключиться или уже отправляющие данные (поэтому подхватить старую сессию не получится), и, если надо, запускает новую нить сервера для приема соединений. Найдите на LXFDVD код вспомогательного метода setState(), который определяет текущее состояние сервиса и возвращает информацию в главное Activity. | ||
+ | |||
+ | ==Подключаемся== | ||
+ | |||
+ | Код нити сервера AcceptThread слишком длинен, чтобы привести его здесь целиком, так что ищите его тоже на LXFDVD. Для создания слушающего сокета мы пользуемся методом API listenUsingRfcommWithServiceRecord(). Ему передаются UUID и имя сервиса, которое добавится в запись сервиса RFCOMM. UUID уникально идентифицирует сервис и должен быть одинаковым для клиента и сервера, подключающихся друг к другу. Существуют онлайн-сервисы для генерации UUID. | ||
+ | |||
+ | В методе run() мы проверяем, что уже ни к кому не подключены (если да, то не принимаем другие соединения), и если нет, слушаем сокет. Вызов serverSocket.accept() — блокирующий; это означает, что он завершится только при успешном подключении либо при наличии исключения. Он не завершается по таймауту. Поэтому так важно запускать его в отдельной нити, чтобы не влиять ни на что другое. | ||
+ | |||
+ | Если у нас есть принятое соединение (т. е. переменная socket имеет значение), мы действуем в зависимости от текущего состояния сервиса. Если сервис подключается (“connecting”), мы идем дальше и запускаем нить подключения в методе connected() основного класса, который мы вскоре рассмотрим. Если сервис уже подключен, мы избавляемся от сокета, так как поддерживаем только одно соединение в один момент времени. | ||
+ | |||
+ | ===Действия в качестве клиента=== | ||
+ | |||
+ | Класс AcceptThread реализует действия программы в качестве сервера; а как насчет клиента? Для этого у нас есть метод connect() и внутренний класс ConnectThread. | ||
+ | |||
+ | public synchronized void connect(BluetoothDevice device) { | ||
+ | |||
+ | if (state == STATE_CONNECTING) { | ||
+ | |||
+ | if (connectThread != null) {connectThread.cancel(); connectThread = null;} | ||
+ | |||
+ | } | ||
+ | |||
+ | if (dataThread != null) {dataThread.cancel(); dataThread = null;} | ||
+ | |||
+ | connectThread = new ConnectThread(device); | ||
+ | |||
+ | connectThread.start(); | ||
+ | |||
+ | setState(STATE_CONNECTING); | ||
+ | |||
+ | } | ||
+ | |||
+ | Если мы уже подключаемся, завершим все существующие нити подключений. Мы также завершаем все установленные подключения, а затем создаем и запускаем новую нить ConnectThread: | ||
+ | |||
+ | private class ConnectThread extends Thread { | ||
+ | |||
+ | private final BluetoothSocket socket; | ||
+ | |||
+ | private final BluetoothDevice device; | ||
+ | |||
+ | public ConnectThread(BluetoothDevice d) { | ||
+ | |||
+ | device = d; | ||
+ | |||
+ | BluetoothSocket tmp = null; | ||
+ | |||
+ | try { | ||
+ | |||
+ | tmp = device.createRfcommSocketToServiceRecord(MY_UUID); | ||
+ | |||
+ | } catch (IOException e) { | ||
+ | |||
+ | Log.e(TAG, “create() failed”, e); | ||
+ | |||
+ | } | ||
+ | |||
+ | socket = tmp; | ||
+ | |||
+ | } | ||
+ | |||
+ | public void run() { | ||
+ | |||
+ | setName(“ConnectThread”); | ||
+ | |||
+ | adapter.cancelDiscovery(); | ||
+ | |||
+ | try { | ||
+ | |||
+ | socket.connect(); | ||
+ | |||
+ | } catch (IOException e) { | ||
+ | |||
+ | try { | ||
+ | |||
+ | socket.close(); | ||
+ | |||
+ | } catch (IOException e2) { | ||
+ | |||
+ | Log.e(TAG, “unable to close() socket during connection failure”, e2); | ||
+ | |||
+ | } | ||
+ | |||
+ | connectionFailed(); | ||
+ | |||
+ | return; | ||
+ | |||
+ | } | ||
+ | |||
+ | synchronized (BluetoothIMService.this) { | ||
+ | |||
+ | connectThread = null; | ||
+ | |||
+ | } | ||
+ | |||
+ | connected(socket, device); | ||
+ | |||
+ | } | ||
+ | |||
+ | public void cancel() { | ||
+ | |||
+ | try { | ||
+ | |||
+ | {{Врезка|right|Заголовок=Скорая помощь |Ширина=10%|Содержание= | ||
+ | Эмулятор не поддерживает Bluetooth; поэтому для проверки придется воспользоваться настоящим устройством, или настроить виртуальную машину и воспользоваться ею.}} | ||
+ | socket.close(); | ||
+ | |||
+ | } catch (IOException e) { | ||
+ | |||
+ | Log.e(TAG, “close() of connect socket failed”, e); | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | Класс socket является финальным и инициализируется только один раз, и конструктор для создания временного сокета BluetoothSocket RFCOMM для сервиса, идентифицируемого UUID, использует метод createRfcommSocketToServiceRecord(). | ||
+ | |||
+ | Метод socket.connect() тоже является блокирующим, поэтому он тоже должен быть в отдельной нити, как и здесь. Если мы получаем исключение, то подключение невозможно, и мы закрываем сокет и завершаем метод. Если подключение возможно, мы вызываем метод connected() внешнего класса. | ||
+ | |||
+ | ===Передача данных=== | ||
+ | |||
+ | На данный момент, независимо от того, являемся ли мы сервером (с нитью AcceptThread) или клиентом (с нитью ConnectThread), у нас есть подключенный сокет, и с ним нужно кое-что сделать. Вот метод connected() класса BluetoothIMService: | ||
+ | |||
+ | public synchronized void connected(BluetoothSocket socket, | ||
+ | |||
+ | BluetoothDevice device) { | ||
+ | |||
+ | if (connectThread != null) { connectThread.cancel(); connectThread = null; } | ||
+ | |||
+ | if (dataThread != null) { dataThread.cancel(); dataThread = null; } | ||
+ | |||
+ | if (acceptThread != null) { acceptThread.cancel(); acceptThread = null; } | ||
+ | |||
+ | dataThread = new DataTransferThread(socket); | ||
+ | |||
+ | dataThread.start(); | ||
+ | |||
+ | Message msg = handler.obtainMessage(BluetoothIM.MESSAGE_DEVICE_NAME); | ||
+ | |||
+ | Bundle bundle = new Bundle(); | ||
+ | |||
+ | bundle.putString(BluetoothIM.DEVICE_NAME, device.getName()); | ||
+ | |||
+ | msg.setData(bundle); | ||
+ | |||
+ | handler.sendMessage(msg); | ||
+ | |||
+ | setState(STATE_CONNECTED); | ||
+ | |||
+ | } | ||
+ | |||
+ | Мы снова завершаем все существующие нити, а после этого создаем и запускаем нить передачи и приема сообщений DataTransferThread. Мы также сообщаем главному Занятию имя устройства, к которому подключаемся. Наконец, есть нить DataTransferThread. Ее код опять же слишком длинен, и у нас на него здесь не хватит места (а у вас вряд ли хватит терпения на перепечатку его из журнала), поэтому берите его с DVD. Конструктор задает значение сокета, потоков InputStream и OutputStream и подключает потоки с сокету. | ||
+ | |||
+ | {{Врезка|left|Заголовок= Синхронизация|Ширина=60%|Содержание=Возможно, вы заметили, что все методы сервиса BluetoothIMService отмечены ключевым словом synchronized. Это важно потому, что к этим методам будут обращаться несколько нитей. | ||
+ | |||
+ | Ключевое слово synchronized означает, что только одна нить может получить доступ к методу в один момент времени – всем остальным нитям придется подождать. В нашем случае это позволяет избежать ситуации, когда AcceptThread получает соединение в тот же момент, когда ConnectThread инициирует его. | ||
+ | |||
+ | Если бы методы connect() не были синхронизированы, то две нити выполняли бы его одновременно и попытались бы завершить одна другую. | ||
+ | |||
+ | Если ключевым словом synchronized пометить метод, то первая нить, обратившаяся к нему, получает управление, а другие приостанавливаются на время выполнения метода. Но метод write() этого класса не синхронизирован, и пользователь может отправлять сообщение, пока предыдущее еще пишется. | ||
+ | |||
+ | В этом методе с помощью внутреннего блока синхронизации получается текущая копия нити, запись в него производится вне этого блока. | ||
+ | }} | ||
+ | |||
+ | Метод run() слушает InputStream и передает главному Занятию принятые сообщения через обработчик. Метод write() этой нити будет вызван методом write() внешнего класса BluetoothIMService: | ||
+ | |||
+ | public void write(byte[] out) { | ||
+ | |||
+ | DataTransferThread r; | ||
+ | |||
+ | synchronized (this) { | ||
+ | |||
+ | if (state != STATE_CONNECTED) return; | ||
+ | |||
+ | r = dataThread; | ||
+ | |||
+ | } | ||
+ | |||
+ | r.write(out); | ||
+ | |||
+ | } | ||
+ | |||
+ | Здесь также есть пара методов обработки подключений (для утерянных или неудачных подключений) – см. код на DVD для подробностей. | ||
+ | |||
+ | ===Связываем Activity вместе=== | ||
+ | |||
+ | Теперь у нас есть список доступных устройств и сервис, который может подключиться к устройствам и обработать передачу данных. Следующая задача – инициировать соединение с конкретным устройством. | ||
+ | |||
+ | Для начала запустим нить AcceptThread при запуске Activity на случай, если есть другое устройство, которое ждет, пока наши устройства станут доступными. Добавьте следующий код в метод onStart(): | ||
+ | |||
+ | if (!bluetoothAdapter.isEnabled()) { | ||
+ | |||
+ | // О работе с Bluetooth, когда он не включен, см. врезку | ||
+ | |||
+ | } else { | ||
+ | |||
+ | if (imService == null) { | ||
+ | |||
+ | imService = new BluetoothIMService(this, handler); | ||
+ | |||
+ | } | ||
+ | |||
+ | ensureDiscoverable(); | ||
+ | |||
+ | } | ||
+ | |||
+ | Метод ensureDiscoverable() (см. код на DVD) делает устройство доступным для обнаружения на пять минут. Эту возможность следует активировать из меню, а не автоматически. | ||
+ | |||
+ | Добавьте к списку устройств метод onClickListener, чтобы по щелчку на устройстве пользователь мог подключиться к нему: | ||
+ | |||
+ | // Добавьте подобные строки к обоим методам сканирования | ||
+ | |||
+ | pairedDevicesListView.setOnItemClickListener(listOnClickListener); | ||
+ | |||
+ | // .... | ||
+ | |||
+ | private AdapterView.OnItemClickListener | ||
+ | |||
+ | listOnClickListener = | ||
+ | |||
+ | new AdapterView.OnItemClickListener() { | ||
+ | |||
+ | public void onItemClick(AdapterView<?> parent, View view, int position, long id) { | ||
+ | |||
+ | String device[] = ((String)parent.getItemAtPosition(position)).split(“\\n”); | ||
+ | |||
+ | BluetoothDevice chosenDevice = bluetoothAdapter.getRemoteDevice(device[1]); | ||
+ | |||
+ | unregisterReceiver(receiver); | ||
+ | |||
+ | isReceiverRegistered = false; | ||
+ | |||
+ | setupIM(); | ||
+ | |||
+ | imService.connect(chosenDevice); | ||
+ | |||
+ | } | ||
+ | |||
+ | }; | ||
+ | |||
+ | Этот код удаляет регистрацию BroadcastReceiver (т. е. после обнаружения устройства, к которому нужно подключиться, мы перестаем искать новые), задает параметры обмена сообщениями и вызывает BluetoothIMService для подключения к выбранному устройству. | ||
+ | |||
+ | ===Отправка и прием сообщений=== | ||
+ | |||
+ | Для показа сообщений нужно изменить расположение компонентов. Для переданных и принятых сообщений используем список List, для ввода сообщения – текстовое поле EditText, для отправки сообщения – кнопку. Метод setupIM() выглядит следующим образом (XML-код см. на DVD): | ||
+ | |||
+ | private void setupIM() { | ||
+ | |||
+ | setContentView(R.layout.messaging); | ||
+ | |||
+ | conversationArrayAdapter = new ArrayAdapter<String>(this, R.layout.message); | ||
+ | |||
+ | conversationListView = (ListView) findViewById(R.id.conversation); | ||
+ | |||
+ | conversationListView.setAdapter(conversationArrayAdapter); | ||
+ | |||
+ | messageOutEditText = (EditText) findViewById(R.id.send_edittext); | ||
+ | |||
+ | messageOutEditText.setOnEditorActionListener(writeListener); | ||
+ | |||
+ | sendButton = (Button) findViewById(R.id.button_send); | ||
+ | |||
+ | sendButton.setOnClickListener(new OnClickListener() { | ||
+ | |||
+ | public void onClick(View v) { | ||
+ | |||
+ | TextView view = (TextView) findViewById(R.id.send_edittext); | ||
+ | |||
+ | String message = view.getText().toString(); | ||
+ | |||
+ | sendMessage(message); | ||
+ | |||
+ | } | ||
+ | |||
+ | }); | ||
+ | |||
+ | if (imService == null) { | ||
+ | |||
+ | imService = new BluetoothIMService(this, handler); | ||
+ | |||
+ | } | ||
+ | |||
+ | outMessageBuffer = new StringBuffer(“”); | ||
+ | |||
+ | } | ||
+ | |||
+ | ArrayAdapter взаимодействует с ListView; кнопка берет содержимое EditText и отправляет его в качестве сообщения. Наконец, при необходимости инициализируется сервис обмена сообщениями, а также инициализируется буфер для исходящих сообщений. Метод sendMessage() вызывает сервис обмена сообщениями для передачи данных: | ||
+ | |||
+ | private void sendMessage(String message) { | ||
+ | |||
+ | if (imService.getState() != BluetoothIMService.STATE_CONNECTED) { | ||
+ | |||
+ | Toast.makeText(this, R.string.notConnected, Toast.LENGTH_SHORT).show(); | ||
+ | |||
+ | return; | ||
+ | |||
+ | } | ||
+ | |||
+ | if (message.length() > 0) { | ||
+ | |||
+ | byte[] send = message.getBytes(); | ||
+ | |||
+ | imService.write(send); | ||
+ | |||
+ | outMessageBuffer.setLength(0); | ||
+ | |||
+ | messageOutEditText.setText(outMessageBuffer); | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | Мы проверяем, что сервис подключен и что есть сообщение для отправки, затем передаем его сервису обмена сообщениями и обнуляем буфер сообщения и поле ввода EditText. Как насчет входящих сообщений? В классе BluetoothIMService нить DataTransfer слушает входящие данные и отправляет сообщение при их получении. Обработчик работает с сообщениями и Runnables, позволяя запланировать будущие действия или добавить действия в очередь для выполнения в другой нити. | ||
+ | |||
+ | private final Handler handler = new Handler() { | ||
+ | |||
+ | public void handleMessage(Message msg) { | ||
+ | |||
+ | switch (msg.what) { | ||
+ | |||
+ | case MESSAGE_STATE_CHANGE: | ||
+ | |||
+ | switch (msg.arg1) { | ||
+ | |||
+ | case BluetoothIMService.STATE_CONNECTED: | ||
+ | |||
+ | setupIM(); | ||
+ | |||
+ | conversationArrayAdapter.clear(); | ||
+ | |||
+ | break; | ||
+ | |||
+ | case BluetoothIMService.STATE_CONNECTING: | ||
+ | |||
+ | case BluetoothIMService.STATE_LISTEN: | ||
+ | |||
+ | case BluetoothIMService.STATE_NONE: | ||
+ | |||
+ | break; | ||
+ | |||
+ | } | ||
+ | |||
+ | break; | ||
+ | |||
+ | case MESSAGE_WRITE: | ||
+ | |||
+ | byte[] writeBuf = (byte[]) msg.obj; | ||
+ | |||
+ | String writeMessage = new String(writeBuf); | ||
+ | |||
+ | conversationArrayAdapter.add( | ||
+ | |||
+ | getResources().getString(R.string.thisDevice) + | ||
+ | |||
+ | getResources().getString(R.string.idDivider) + writeMessage); | ||
+ | |||
+ | break; | ||
+ | |||
+ | case MESSAGE_READ: | ||
+ | |||
+ | byte[] readBuf = (byte[]) msg.obj; | ||
+ | |||
+ | String readMessage = new String(readBuf, 0, msg.arg1); | ||
+ | |||
+ | conversationArrayAdapter.add(connectedDeviceName + | ||
+ | |||
+ | getResources().getString(R.string.idDivider) + readMessage); | ||
+ | |||
+ | break; | ||
+ | |||
+ | case MESSAGE_DEVICE_NAME: | ||
+ | |||
+ | connectedDeviceName = msg.getData().getString(DEVICE_NAME); | ||
+ | |||
+ | Toast.makeText(getApplicationContext(), | ||
+ | |||
+ | getResources().getString(R.string.connectionSucceededDisplay) + connectedDeviceName, Toast.LENGTH_SHORT).show(); | ||
+ | |||
+ | break; | ||
+ | |||
+ | case MESSAGE_TOAST: | ||
+ | |||
+ | Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST), | ||
+ | |||
+ | Toast.LENGTH_SHORT).show(); | ||
+ | |||
+ | break; | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | }; | ||
+ | |||
+ | Если сервис обмена сообщениями подключается или подключен, мы задаем IM display и сбрасываем его. Если было написано сообщение, мы добавляем его в ArrayAdapter, который отображает разговор. Если сообщение было прочитано, мы делаем то же самое, добавляя имя устройства, которое отправило его. А при получении имени устройства мы записываем его в переменную и отображаем сообщение Toast. | ||
+ | |||
+ | На этом все! Скомпилируйте, запустите и протестируйте программу на нескольких телефонах. Многое в ней можно улучшить – например, сделать несколько вкладок и одновременно обрабатывать несколько подключений. Также можно адаптировать код, сделав его однонаправленным, а не двунаправленным – для трансляции сообщений по Bluetooth на другие устройства. | ||
+ | {{Врезка|left|Заголовок= Если Bluetooth не включен…|Ширина=98%|Содержание=На данный момент, если Bluetooth не поддерживается и не включен, то приложение «упадет» при попытке обращения к BluetoothAdapter. Вместо этого можно дать пользователю возможность включить Bluetooth в начале работы программы. | ||
+ | |||
+ | Добавьте следующие строки в BluetoothIM.onStart(): | ||
+ | |||
+ | if (!bluetoothAdapter.isEnabled()) { | ||
+ | |||
+ | Intent btIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); | ||
+ | |||
+ | startActivityForResult(btIntent, REQUEST_ENABLE_BT); | ||
+ | |||
+ | } else { // the next lines are already there | ||
+ | |||
+ | if (imService == null) { | ||
+ | |||
+ | imService = new BluetoothIMService | ||
+ | (this, handler); | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | Затем нам понадобится метод onResume() для обработки ситуации, при которой мы приостанавливаем Занятие, чтобы включить Bluetooth: | ||
+ | |||
+ | public synchronized void onResume() { | ||
+ | |||
+ | super.onResume(); | ||
+ | |||
+ | if (imService != null) { | ||
+ | |||
+ | if (imService.getState() == BluetoothIMService.STATE_NONE) { | ||
+ | |||
+ | imService.start(); | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | Если мы не запустили сервис, сейчас мы запустим его явно (обратите внимание, что при приостановке onStart() вызывается до onResume(), так что если сервиса обмена сообщениями нет, он будет создан). }} | ||
+ | |||
+ | | |
Текущая версия на 01:54, 19 октября 2018
|
|
|
Cвяжем ваши устройства через Bluetooth
Содержание |
[править] Общение с другими
Джульетта Кемп рассказывает, как Bluetooth помогает общаться с другими устройствами, и проясняет сервис обмена сообщениями.
Сегодня Bluetooth можно найти на любых устройствах, и с его помощью легко обмениваться данными между устройствами. На этом уроке мы напишем сервис обмена сообщениями по Bluetooth и узнаем, как обнаружить другие устройства (спаренные или нет), запустить сервер Bluetooth, отправить запрос на подключение со стороны клиента и передать данные между двумя устройствами. Отметим, что этот проект делался под Android 10 (2.3.3), но сработается и с более поздними версиями. Полный код урока можно найти на LXFDVD, но помните, что он не будет выполняться «как есть» – для компиляции в вашей локальной среде проекты Android нужно настроить либо вручную, либо в Eclipse.
[править] Устанавливаем соединение по Bluetooth
Изменения, которые надо проделать в файле AndroidManifest.xml, описаны на нашем DVD. Для обмена данными по Bluetooth понадобятся и клиент, и сервер. Сервер слушает подключения, открыв сокет BluetoothServerSocket; клиент создает BluetoothSocket, открывает канал связи с сервером RFCOMM и отправляет запрос на соединение. Сервер принимает соединение и открывает свой BluetoothSocket. Когда оба сокета BluetoothSocket на одном и том же канале RFCOMM будут открыты, клиент и сервер подключатся друг к другу и смогут обмениваться данными.
После установки соединения сервер должен закрыть свой сокет BluetoothServerSocket, если вам не нужно поддерживать несколько соединений одновременно.
Итак, есть два варианта создания подключения:
» написать отдельные программы клиента и сервера;
» написать программу, способную вести себя и как клиент, и как сервер, и запрашивать или принимать подключение.
Второй вариант используется во многих приложениях Bluetooth; его мы и выберем. Если у вас нет двух устройств для проверки соединения, можно развернуть сервер Java на ноутбуке или ПК. По установке соединения и сервер, и клиент будут передавать и принимать данные. Поэтому наш класс BluetoothIMService будет выполнять три отдельных действия:
» слушать попытки соединения («сервер»);
» отправлять запрос на соединения («клиент»);
» обрабатывать передачу данных после установки соединения.
Эти действия будут реализованы в отдельных нитях, и мы сможем слушать соединения, не прерывая прочую активность приложений и системы.
Мы создаем и запускаем сервис следующим образом:
public class BluetoothIMService {
// Различные поля; подробности см. в коде на DVD
public BluetoothIMService(Context context, Handler h) {
adapter = BluetoothAdapter.getDefaultAdapter();
handler = h;
state = STATE_NONE;
}
public synchronized void start() {
if (connectThread != null) {connectThread.cancel(); connectThread = null;}
if (dataThread != null) {dataThread.cancel(); dataThread = null;}
setState(STATE_LISTEN);
if (acceptThread == null) {
acceptThread = new AcceptThread(true);
acceptThread.start();
Метод start() завершает все существующие нити, пытающиеся подключиться или уже отправляющие данные (поэтому подхватить старую сессию не получится), и, если надо, запускает новую нить сервера для приема соединений. Найдите на LXFDVD код вспомогательного метода setState(), который определяет текущее состояние сервиса и возвращает информацию в главное Activity.
[править] Подключаемся
Код нити сервера AcceptThread слишком длинен, чтобы привести его здесь целиком, так что ищите его тоже на LXFDVD. Для создания слушающего сокета мы пользуемся методом API listenUsingRfcommWithServiceRecord(). Ему передаются UUID и имя сервиса, которое добавится в запись сервиса RFCOMM. UUID уникально идентифицирует сервис и должен быть одинаковым для клиента и сервера, подключающихся друг к другу. Существуют онлайн-сервисы для генерации UUID.
В методе run() мы проверяем, что уже ни к кому не подключены (если да, то не принимаем другие соединения), и если нет, слушаем сокет. Вызов serverSocket.accept() — блокирующий; это означает, что он завершится только при успешном подключении либо при наличии исключения. Он не завершается по таймауту. Поэтому так важно запускать его в отдельной нити, чтобы не влиять ни на что другое.
Если у нас есть принятое соединение (т. е. переменная socket имеет значение), мы действуем в зависимости от текущего состояния сервиса. Если сервис подключается (“connecting”), мы идем дальше и запускаем нить подключения в методе connected() основного класса, который мы вскоре рассмотрим. Если сервис уже подключен, мы избавляемся от сокета, так как поддерживаем только одно соединение в один момент времени.
[править] Действия в качестве клиента
Класс AcceptThread реализует действия программы в качестве сервера; а как насчет клиента? Для этого у нас есть метод connect() и внутренний класс ConnectThread.
public synchronized void connect(BluetoothDevice device) {
if (state == STATE_CONNECTING) {
if (connectThread != null) {connectThread.cancel(); connectThread = null;}
}
if (dataThread != null) {dataThread.cancel(); dataThread = null;}
connectThread = new ConnectThread(device);
connectThread.start();
setState(STATE_CONNECTING);
}
Если мы уже подключаемся, завершим все существующие нити подключений. Мы также завершаем все установленные подключения, а затем создаем и запускаем новую нить ConnectThread:
private class ConnectThread extends Thread {
private final BluetoothSocket socket;
private final BluetoothDevice device;
public ConnectThread(BluetoothDevice d) {
device = d;
BluetoothSocket tmp = null;
try {
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
Log.e(TAG, “create() failed”, e);
}
socket = tmp;
}
public void run() {
setName(“ConnectThread”);
adapter.cancelDiscovery();
try {
socket.connect();
} catch (IOException e) {
try {
socket.close();
} catch (IOException e2) {
Log.e(TAG, “unable to close() socket during connection failure”, e2);
}
connectionFailed();
return;
}
synchronized (BluetoothIMService.this) {
connectThread = null;
}
connected(socket, device);
}
public void cancel() {
try {
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
socket.close();
} catch (IOException e) {
Log.e(TAG, “close() of connect socket failed”, e);
}
}
}
Класс socket является финальным и инициализируется только один раз, и конструктор для создания временного сокета BluetoothSocket RFCOMM для сервиса, идентифицируемого UUID, использует метод createRfcommSocketToServiceRecord().
Метод socket.connect() тоже является блокирующим, поэтому он тоже должен быть в отдельной нити, как и здесь. Если мы получаем исключение, то подключение невозможно, и мы закрываем сокет и завершаем метод. Если подключение возможно, мы вызываем метод connected() внешнего класса.
[править] Передача данных
На данный момент, независимо от того, являемся ли мы сервером (с нитью AcceptThread) или клиентом (с нитью ConnectThread), у нас есть подключенный сокет, и с ним нужно кое-что сделать. Вот метод connected() класса BluetoothIMService:
public synchronized void connected(BluetoothSocket socket,
BluetoothDevice device) {
if (connectThread != null) { connectThread.cancel(); connectThread = null; }
if (dataThread != null) { dataThread.cancel(); dataThread = null; }
if (acceptThread != null) { acceptThread.cancel(); acceptThread = null; }
dataThread = new DataTransferThread(socket);
dataThread.start();
Message msg = handler.obtainMessage(BluetoothIM.MESSAGE_DEVICE_NAME);
Bundle bundle = new Bundle();
bundle.putString(BluetoothIM.DEVICE_NAME, device.getName());
msg.setData(bundle);
handler.sendMessage(msg);
setState(STATE_CONNECTED);
}
Мы снова завершаем все существующие нити, а после этого создаем и запускаем нить передачи и приема сообщений DataTransferThread. Мы также сообщаем главному Занятию имя устройства, к которому подключаемся. Наконец, есть нить DataTransferThread. Ее код опять же слишком длинен, и у нас на него здесь не хватит места (а у вас вряд ли хватит терпения на перепечатку его из журнала), поэтому берите его с DVD. Конструктор задает значение сокета, потоков InputStream и OutputStream и подключает потоки с сокету.
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
Метод run() слушает InputStream и передает главному Занятию принятые сообщения через обработчик. Метод write() этой нити будет вызван методом write() внешнего класса BluetoothIMService:
public void write(byte[] out) {
DataTransferThread r;
synchronized (this) {
if (state != STATE_CONNECTED) return;
r = dataThread;
}
r.write(out);
}
Здесь также есть пара методов обработки подключений (для утерянных или неудачных подключений) – см. код на DVD для подробностей.
[править] Связываем Activity вместе
Теперь у нас есть список доступных устройств и сервис, который может подключиться к устройствам и обработать передачу данных. Следующая задача – инициировать соединение с конкретным устройством.
Для начала запустим нить AcceptThread при запуске Activity на случай, если есть другое устройство, которое ждет, пока наши устройства станут доступными. Добавьте следующий код в метод onStart():
if (!bluetoothAdapter.isEnabled()) {
// О работе с Bluetooth, когда он не включен, см. врезку
} else {
if (imService == null) {
imService = new BluetoothIMService(this, handler);
}
ensureDiscoverable();
}
Метод ensureDiscoverable() (см. код на DVD) делает устройство доступным для обнаружения на пять минут. Эту возможность следует активировать из меню, а не автоматически.
Добавьте к списку устройств метод onClickListener, чтобы по щелчку на устройстве пользователь мог подключиться к нему:
// Добавьте подобные строки к обоим методам сканирования
pairedDevicesListView.setOnItemClickListener(listOnClickListener);
// ....
private AdapterView.OnItemClickListener
listOnClickListener =
new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String device[] = ((String)parent.getItemAtPosition(position)).split(“\\n”);
BluetoothDevice chosenDevice = bluetoothAdapter.getRemoteDevice(device[1]);
unregisterReceiver(receiver);
isReceiverRegistered = false;
setupIM();
imService.connect(chosenDevice);
}
};
Этот код удаляет регистрацию BroadcastReceiver (т. е. после обнаружения устройства, к которому нужно подключиться, мы перестаем искать новые), задает параметры обмена сообщениями и вызывает BluetoothIMService для подключения к выбранному устройству.
[править] Отправка и прием сообщений
Для показа сообщений нужно изменить расположение компонентов. Для переданных и принятых сообщений используем список List, для ввода сообщения – текстовое поле EditText, для отправки сообщения – кнопку. Метод setupIM() выглядит следующим образом (XML-код см. на DVD):
private void setupIM() {
setContentView(R.layout.messaging);
conversationArrayAdapter = new ArrayAdapter<String>(this, R.layout.message);
conversationListView = (ListView) findViewById(R.id.conversation);
conversationListView.setAdapter(conversationArrayAdapter);
messageOutEditText = (EditText) findViewById(R.id.send_edittext);
messageOutEditText.setOnEditorActionListener(writeListener);
sendButton = (Button) findViewById(R.id.button_send);
sendButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
TextView view = (TextView) findViewById(R.id.send_edittext);
String message = view.getText().toString();
sendMessage(message);
}
});
if (imService == null) {
imService = new BluetoothIMService(this, handler);
}
outMessageBuffer = new StringBuffer(“”);
}
ArrayAdapter взаимодействует с ListView; кнопка берет содержимое EditText и отправляет его в качестве сообщения. Наконец, при необходимости инициализируется сервис обмена сообщениями, а также инициализируется буфер для исходящих сообщений. Метод sendMessage() вызывает сервис обмена сообщениями для передачи данных:
private void sendMessage(String message) {
if (imService.getState() != BluetoothIMService.STATE_CONNECTED) {
Toast.makeText(this, R.string.notConnected, Toast.LENGTH_SHORT).show();
return;
}
if (message.length() > 0) {
byte[] send = message.getBytes();
imService.write(send);
outMessageBuffer.setLength(0);
messageOutEditText.setText(outMessageBuffer);
}
}
Мы проверяем, что сервис подключен и что есть сообщение для отправки, затем передаем его сервису обмена сообщениями и обнуляем буфер сообщения и поле ввода EditText. Как насчет входящих сообщений? В классе BluetoothIMService нить DataTransfer слушает входящие данные и отправляет сообщение при их получении. Обработчик работает с сообщениями и Runnables, позволяя запланировать будущие действия или добавить действия в очередь для выполнения в другой нити.
private final Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_STATE_CHANGE:
switch (msg.arg1) {
case BluetoothIMService.STATE_CONNECTED:
setupIM();
conversationArrayAdapter.clear();
break;
case BluetoothIMService.STATE_CONNECTING:
case BluetoothIMService.STATE_LISTEN:
case BluetoothIMService.STATE_NONE:
break;
}
break;
case MESSAGE_WRITE:
byte[] writeBuf = (byte[]) msg.obj;
String writeMessage = new String(writeBuf);
conversationArrayAdapter.add(
getResources().getString(R.string.thisDevice) +
getResources().getString(R.string.idDivider) + writeMessage);
break;
case MESSAGE_READ:
byte[] readBuf = (byte[]) msg.obj;
String readMessage = new String(readBuf, 0, msg.arg1);
conversationArrayAdapter.add(connectedDeviceName +
getResources().getString(R.string.idDivider) + readMessage);
break;
case MESSAGE_DEVICE_NAME:
connectedDeviceName = msg.getData().getString(DEVICE_NAME);
Toast.makeText(getApplicationContext(),
getResources().getString(R.string.connectionSucceededDisplay) + connectedDeviceName, Toast.LENGTH_SHORT).show();
break;
case MESSAGE_TOAST:
Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),
Toast.LENGTH_SHORT).show();
break;
}
}
};
Если сервис обмена сообщениями подключается или подключен, мы задаем IM display и сбрасываем его. Если было написано сообщение, мы добавляем его в ArrayAdapter, который отображает разговор. Если сообщение было прочитано, мы делаем то же самое, добавляя имя устройства, которое отправило его. А при получении имени устройства мы записываем его в переменную и отображаем сообщение Toast.
На этом все! Скомпилируйте, запустите и протестируйте программу на нескольких телефонах. Многое в ней можно улучшить – например, сделать несколько вкладок и одновременно обрабатывать несколько подключений. Также можно адаптировать код, сделав его однонаправленным, а не двунаправленным – для трансляции сообщений по Bluetooth на другие устройства.
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
|