Android 設計-4 藍牙
開始設計 BluetoothService
上一篇我們主要是把 Bluetooth 設備找出來,然後啟動它。為了程式的包裝,所以我們用了一點技巧,直接在 onStart() 執行,對於 Android 來說它強調 Activity / Fragment 的生命週期,所以你不可不知,你的程式該擺在哪個位置。
我們的 BluetoothEnable 沒有 extented Android 的類別,同樣的 BluetoothService 也沒有。首先我們還是取材自 BluetoothChatService,請自行下載 Android 的 BluetoothChat 範例,我直接口頭說明。
簡單分析一下,BluetoothChatService,它建立三個 Thread 分別是 ConnectThread,ConnectedThread,AcceptThread,然後一大堆的 synchronized 綁來綁去。對 Java 菜鳥來說可能搞到最後還是搞不清楚,何時用 Thread vs. Runnable,何時用 synchronized? 我也搞不清楚?!
然後在為了避免與 UI 顯示衝突,藉由 handle 傳送資料。通常在寫 windows 的 API 上很常見,阿貝已經有 N 年沒寫 Windows API 了,這是年輕人的宿命,阿貝老了。
Java 為了 JVM 創造出一些多工 (multiTasking) 的方法,其中大家最常用的就是 Thread 及 Runnable,兩個差別在哪裡,簡單來說一個是 Abstract Class,一個是 Interface Class。哪裡用 Abstract,何處用 Interface? 說實在,見仁見智,因為一個物件的定義,沒有一定的規律,你可以把 車輛 Vehicle 定義成 Abstract 然後保留空間給 車廠名稱 FactoryName,依此類推。你也可以當作 Interface 然後保留車輛顏色 CarColor, 給車種。都是看你當時的系統設計原理而定。不要告訴我,什麼是 Interface 什麼是 Abstract 你不懂,那你就只是個程式員,要加油了。
至於多工?那就跳過吧,我們要從計算機概論開始了解,就很累了,有興趣,自己學吧。
Thread
什麼是 Thread?有人翻 執行序,有人翻 線程,有點接近,也就是 一段程序(procedure),有起頭跟結尾,程式人員要寫好,不要無止境。有人說從 Main 開始就是一個 Thread,你可以說對,也可以說不對,基本上 Java 裡面定義了 Thread ,就表示這一段程序要脫離 main 鐵路的主幹, 獨立分叉出來, 最後是否接軌回去也是程式設計人員自己決定。
我們寫一個小小的測試:
public class ThreadTest {
public static void main(String[] args) {
func();
}
/**
條件:
1.有加 synchronized
2.沒加 synchronized
3.採用 run()
4.改採 start()
*/
private static /* synchronized */ void func() {
for (int i=0; i < 3; i++) {
String s = "[Thread " + i + "]";
TestThread tt = new TestThread(s);
tt.run();
// tt.start();
}
}
} // main ThreadTest
class TestThread extends Thread {
String name;
public TestThread(String name) {
this.name = name;
}
/**
條件:
1.採用 synchronized (TestThread.class)
2.改用 synchronized (this)
*/
public void run() {
// synchronized ( /*this */ TestThread.class){
try{
testA(name);
testB(name);
} catch (Exception e) {
System.out.println(e.getMessage());
}
//}
}
public void testA(String name) throws Exception {
for(int i=0;i<4;i++) {
System.out.print(name+" A: " + i + " > ");
Thread.sleep(100);
}
}
public void testB(String name) throws Exception{
for(int i=0;i<4;i++) {
System.out.print(name+" B: " + i + " > ");
Thread.sleep(100);
}
}
} // class TestThread
改寫 BluetoothService
所以 ConnectThread,ConnectedThread,AcceptThread 的作用在哪裡?依照作者的想法,從建立連接點開始就採用 Thread,這樣對 Android 傳資料時只要專注在 Handler,任何狀況 STAT都往 message裡面丟,一勞永逸。但是又為了避免傳輸過程中,Data 被後面的 Thread 蓋過,所以就四處加上 synchronized,因為在 Thread 裡,誰先誰後已經不可得知了;所以誰先站住茅坑,就可以先上廁所。
簡單來說,ConnectThread 先佔第一順位,先啟動藍牙,接著 ConnectedThread 接上 Java 裡面的 ioStream,最後派上主角 AcceptThread 開始相互傳輸藍牙資料。
我們要做的是 Obd 的傳輸,當然不需要那麼囉唆,所以第一第二步驟就可以用一般的搞定,而 AcceptThread 保留 Thread但是不可以像 BluetoothChat 一樣不同步,因為需要一個口令一個動作。
Interface 主導 BluetoothService
先前講過,Abstract 跟 Interface 青菜蘿蔔隨人高興,設計錯誤,自行負責。在這裡我們採用 Interface 來主導,為什呢?因為在物件導向的定義裡,我們遵循物件導向技術主要的 封裝、繼承與多型 三大基本觀念,此次採行封裝的概念來運作。
回去前幾章看看,有目錄的那一張圖表。
就這簡單。
public interface Gateway
{
/**
*
* bluetooth connection state code
*/
int STATE_IDLE = 0;
int STATE_STREAM_CONNECTED = 1;
int STATE_IO_ERROR = 2;
int SEND_READ_DATA = 4;
int SEND_WRITE_DATA = 5;
int STATE_LINK_STREAM_ERROR = 6;
int STATE_LINK_SOCKET_ERROR = 7;
int STATE_CREATE_SOCKET_ERROR = 8;
int EXTRASTATE_RECEIVE_ERROR = 1;
int EXTRASTATE_WRITE_ERROR = 2;
/**
*
* @return true:success false:failure
*/
boolean linkSocket();
void closeSocket();
/**
*
* @param bytes
*/
void send(byte[] bytes);
/**
*
* @return bluetooth state
*/
int getStateCode();
}
public abstract class ObdStream {
private Gateway gateway;
protected ObdStream(Gateway gateway) {
this.gateway = gateway;
}
public boolean socketConnect(){
return gateway.linkSocket();
}
public void socketSend(String s){
gateway.send(s.getBytes());
}
public int getState(){
return gateway.getStateCode();
}
. . . .
如何使用呢? 把Gateway打包丟到TroubleCode(),再把 obd 打包,裝到 startConnectBluetooth(),也就這樣而已。
obd = new TroubleCode(
getActivity(),
(Gateway) new BluetoothService(handler, new ObdConfig(getActivity()))
);
startConnectBluetooth(obd);
obd.initObd();
obd.socketSend(obd.getCode());
...
留言
張貼留言