Hallo allemaal,
Ik ben bezig om een goed werkende Bluetooth app te bouwen in Android, die meerdere Bluetooth apparaten tegelijkertijd bediend. De Android voorbeeld code (BluetoothChatService.class) laat nogal te wensen over qua implementatie, vandaar dat ik e.e.a. zelf aan het ontwerpen/herschrijven ben.
Hieronder de code van een connectThread die er voor zorgt dat je met een ander BT apparaat kunt verbinden. Zodra de verbinding is gelukt - en men een BluetoothSocket heeft verkregen die de verbinding tussen de twee BluetoothDevices regelt - wordt deze thread gestopt en wordt er verder gegaan in een andere thread, namelijk de connectedThread. Deze handelt al het onderlinge verkeer tussen de BT apparaten af.
Ik wil de code van de connectThread herschijven (want spagetti code). De MY_UUID_SECURE en MY_UUID_INSECURE zijn gewoon gegenereerde UUIDs van mijn app, verder niet belangrijk voor het voorbeeld.
De connected(mSocket, mDevice, mSocketType); aanroep in de connectThread leidt naar de volgende code binnen de BluetoothChatService.class:
Nu zie ik in de BluetoothChatService.class dat vanuit de connectThread een aanroep wordt gedaan naar connected() methode in de mainThread (deze wordt dus op de mainThread uitgevoerd!). Deze connected() methode start dan de connectedThread met de eerder verkregen BluetoothSocket, en zo verder.
Ergens vind ik dit behoorlijk kris-kras door elkaar heen lopende code.
Wat ik wil is dat de mainThread altijd de thread is die de volledige regie/controle in handen heeft, ipv de ondersteunende threads (de connectThread moet NIET de connected() method aanroepen in de mainThread bijvoorbeeld).
Ik zat er aan te denken om een verbinding op te bouwen via een Callable task die in een thread wordt uitgevoerd die door een ExecutorService/ThreadPool wordt beheerd/aangestuurd en die vervolgens een Future<BluetoothSocket> object terug geeft zodra de verbinding is opgebouwd. Hierdoor is de connectThread alleen nog maar verantwoordelijk voor het verkrijgen van een BluetoothSocket object en meer niet. Zo zou het ook moeten is mijn idee, want het opbouwen van een verbinding met een ander BluetoothDevice is namelijk een blocking call. Dus deze thread blijft "hangen" totdat een verbinding is opgebouwd, of een exception heeft plaatsgevonden. De huidige connectThread slikt deze exception in en doet er verder niks mee, want de thread stopt zonder resultaat. De mainThread weet dus niet of er een exception heeft plaatsgevonden.
Mijn voorstel voor een andere aanpak mbv een Callable task is als volgt:
Zoals je kan zien is de code enigsinds anders geschreven:
- bij een exception wordt eerst de rommel intern opgeruimd en daarna netjes (dmv een 'gewone' exception) gemeldt aan de aanroepende partij.
- bij een succesvolle verbinding wordt de verkregen BluetoothSocket geretourneerd als resultaat van de call() method.
Voordeel hiervan is dat het opzetten van een verbinding alleen binnen deze task gebeurt, en nergens anders. Er wordt ook geen code aangeroepen die niks met deze taak te maken heeft, zoals in het eerdere voorbeeld.
Vanuit de mainThread kan men dan het volgende doen:
- een nieuwe connectTask aanmaken voor een andere verbinding en deze laten uitvoeren binnen een thread die door de threadpool beheert wordt
- een connectTask annuleren d.m.v. de method aan te roepen cancel() methode.
- controleren of de connectTask inmiddels een BluetoothSocket heeft verkregen door de Future.isDone() methode aan te roepen.
- een time-out te specificeren waar binnen de Future.get() methode dient te reageren om de BluetoothSocket te overhandigen. Dit is een blocking call totdat de time-out periode is overschreden.
Het voordeel zit hem nu in het feit dat de mainThread gewoon door kan blijven gaan met andere zaken en af en toe moet kijken of de verbinding is tot stand gebracht. Daarna kan de mainThread een andere thread de opdracht geven de verbinding te beheren.
Mijn vraag aan jullie: wat vinden jullie van deze oplossing? Beter/Slechter? Heb je je bedenkingen over deze oplossingsrichting? Gaarne jullie feedback, eventueel met aanvullende/verbeterende code voorbeelden.
Alvast bedankt!
Ik ben bezig om een goed werkende Bluetooth app te bouwen in Android, die meerdere Bluetooth apparaten tegelijkertijd bediend. De Android voorbeeld code (BluetoothChatService.class) laat nogal te wensen over qua implementatie, vandaar dat ik e.e.a. zelf aan het ontwerpen/herschrijven ben.
Hieronder de code van een connectThread die er voor zorgt dat je met een ander BT apparaat kunt verbinden. Zodra de verbinding is gelukt - en men een BluetoothSocket heeft verkregen die de verbinding tussen de twee BluetoothDevices regelt - wordt deze thread gestopt en wordt er verder gegaan in een andere thread, namelijk de connectedThread. Deze handelt al het onderlinge verkeer tussen de BT apparaten af.
Ik wil de code van de connectThread herschijven (want spagetti code). De MY_UUID_SECURE en MY_UUID_INSECURE zijn gewoon gegenereerde UUIDs van mijn app, verder niet belangrijk voor het voorbeeld.
Java:
1
2
3
4
5
6
7
8
9
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
| private class ConnectThread extends Thread { private static final String TAG = "ConnectThread"; private final BluetoothAdapter mAdapter; private final BluetoothSocket mSocket; private final BluetoothDevice mDevice; private final String mSocketType; public ConnectThread(BluetoothDevice device, boolean secure) { BluetoothSocket tmp = null; mAdapter = BluetoothAdapter.getDefaultAdapter(); mDevice = device; mSocketType = secure ? "Secure" : "Insecure"; // Get a BluetoothSocket for a connection with the given BluetoothDevice try { if (secure) { tmp = device.createRfcommSocketToServiceRecord( MY_UUID_SECURE); } else { tmp = device.createInsecureRfcommSocketToServiceRecord( MY_UUID_INSECURE); } } catch (IOException e) { Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e); } mSocket = tmp; } @Override public void run() { Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType); setName("ConnectThread" + mSocketType); // Always cancel discovery because it will slow down a connection mAdapter.cancelDiscovery(); // Make a connection to the BluetoothSocket try { // This is a blocking call and will only return on a // successful connection or an exception mSocket.connect(); } catch (IOException e) { // Close the socket try { mSocket.close(); } catch (IOException e2) { Log.e(TAG, "unable to close() " + mSocketType + " socket during connection failure", e2); } return; } // Start the connected thread connected(mSocket, mDevice, mSocketType); } public void cancel() { try { mSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e); } } } |
De connected(mSocket, mDevice, mSocketType); aanroep in de connectThread leidt naar de volgende code binnen de BluetoothChatService.class:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| public synchronized void connected(BluetoothSocket socket, BluetoothDevice device, final String socketType) { if (D) Log.d(TAG, "connected, Socket Type:" + socketType); // Cancel the thread that completed the connection if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} // Cancel any thread currently running a connection if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} // Cancel the accept thread because we only want to connect to one device if (mSecureAcceptThread != null) { mSecureAcceptThread.cancel(); mSecureAcceptThread = null; } if (mInsecureAcceptThread != null) { mInsecureAcceptThread.cancel(); mInsecureAcceptThread = null; } // Start the thread to manage the connection and perform transmissions mConnectedThread = new ConnectedThread(socket, socketType); mConnectedThread.start(); // Send the name of the connected device back to the UI Activity Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME); Bundle bundle = new Bundle(); bundle.putString(BluetoothChat.DEVICE_NAME, device.getName()); msg.setData(bundle); mHandler.sendMessage(msg); setState(STATE_CONNECTED); } |
Nu zie ik in de BluetoothChatService.class dat vanuit de connectThread een aanroep wordt gedaan naar connected() methode in de mainThread (deze wordt dus op de mainThread uitgevoerd!). Deze connected() methode start dan de connectedThread met de eerder verkregen BluetoothSocket, en zo verder.
Ergens vind ik dit behoorlijk kris-kras door elkaar heen lopende code.
Wat ik wil is dat de mainThread altijd de thread is die de volledige regie/controle in handen heeft, ipv de ondersteunende threads (de connectThread moet NIET de connected() method aanroepen in de mainThread bijvoorbeeld).
Ik zat er aan te denken om een verbinding op te bouwen via een Callable task die in een thread wordt uitgevoerd die door een ExecutorService/ThreadPool wordt beheerd/aangestuurd en die vervolgens een Future<BluetoothSocket> object terug geeft zodra de verbinding is opgebouwd. Hierdoor is de connectThread alleen nog maar verantwoordelijk voor het verkrijgen van een BluetoothSocket object en meer niet. Zo zou het ook moeten is mijn idee, want het opbouwen van een verbinding met een ander BluetoothDevice is namelijk een blocking call. Dus deze thread blijft "hangen" totdat een verbinding is opgebouwd, of een exception heeft plaatsgevonden. De huidige connectThread slikt deze exception in en doet er verder niks mee, want de thread stopt zonder resultaat. De mainThread weet dus niet of er een exception heeft plaatsgevonden.
Mijn voorstel voor een andere aanpak mbv een Callable task is als volgt:
Java:
1
2
3
4
5
6
7
8
9
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| private class ConnectTask implements Callable<BluetoothSocket> { private static final String TAG = "ConnectTask"; private final BluetoothAdapter mAdapter; private final BluetoothSocket mSocket; private final BluetoothDevice mDevice; private final String mSocketType; public ConnectTask(BluetoothDevice device, boolean secure) { BluetoothSocket tmp = null; mAdapter = BluetoothAdapter.getDefaultAdapter(); mDevice = device; mSocketType = secure ? "Secure" : "Insecure"; // Get a BluetoothSocket for a connection with the given BluetoothDevice try { if (secure) { tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE); } else { tmp = device .createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE); } } catch (IOException e) { Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e); } mSocket = tmp; } @Override public BluetoothSocket call() throws Exception { // Always cancel discovery because it will slow down a connection mAdapter.cancelDiscovery(); // Make a connection to the BluetoothSocket try { // This is a blocking call and will only return on a // successful connection or an exception mSocket.connect(); } catch (IOException e) { // Close the socket try { mSocket.close(); } catch (IOException e2) { Log.e(TAG, "unable to close() " + mSocketType + " socket during connection failure", e2); } throw new Exception( "Unable to establish a connection with Bluetooth device: " + mDevice.getName() + " at address: " + mDevice.getAddress()); } return mSocket; } public void cancel() { try { mSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e); } } } |
Zoals je kan zien is de code enigsinds anders geschreven:
- bij een exception wordt eerst de rommel intern opgeruimd en daarna netjes (dmv een 'gewone' exception) gemeldt aan de aanroepende partij.
- bij een succesvolle verbinding wordt de verkregen BluetoothSocket geretourneerd als resultaat van de call() method.
Voordeel hiervan is dat het opzetten van een verbinding alleen binnen deze task gebeurt, en nergens anders. Er wordt ook geen code aangeroepen die niks met deze taak te maken heeft, zoals in het eerdere voorbeeld.
Vanuit de mainThread kan men dan het volgende doen:
- een nieuwe connectTask aanmaken voor een andere verbinding en deze laten uitvoeren binnen een thread die door de threadpool beheert wordt
- een connectTask annuleren d.m.v. de method aan te roepen cancel() methode.
- controleren of de connectTask inmiddels een BluetoothSocket heeft verkregen door de Future.isDone() methode aan te roepen.
- een time-out te specificeren waar binnen de Future.get() methode dient te reageren om de BluetoothSocket te overhandigen. Dit is een blocking call totdat de time-out periode is overschreden.
Het voordeel zit hem nu in het feit dat de mainThread gewoon door kan blijven gaan met andere zaken en af en toe moet kijken of de verbinding is tot stand gebracht. Daarna kan de mainThread een andere thread de opdracht geven de verbinding te beheren.
Mijn vraag aan jullie: wat vinden jullie van deze oplossing? Beter/Slechter? Heb je je bedenkingen over deze oplossingsrichting? Gaarne jullie feedback, eventueel met aanvullende/verbeterende code voorbeelden.
Alvast bedankt!