android藍牙通訊開發(fā)---與藍牙模塊進行通信

轉(zhuǎn)自android藍牙開發(fā)---與藍牙模塊進行通信

近半個月來一直在搞android藍牙這方面,主要是項目需要與藍牙模塊進行通信呼盆。開頭的進展很順利擂达,但因為藍牙模塊不在我這里,所以只能用手機測試瞒津。一開頭就發(fā)現(xiàn)手機的藍牙不能用,為了證明這點括尸,我刷了四次不同不同系統(tǒng)的官方包巷蚪,正式宣布手機的藍牙報銷了,于是和朋友換手機濒翻。在測試的過程中也是非常痛苦屁柏,放假了啦膜,同學(xué)都幾乎回家了,剩下的同學(xué)中竟然80%都是用非android手機淌喻!我和我的小伙伴都嚇呆了I摇!就算借來了手機裸删,測試過程中老是有人打電話過來八拱,嚴重影響我的開發(fā)!涯塔!于是肌稻,我果斷催促對方快點把藍牙模塊寄過來,等模塊寄過來后匕荸,半個小時內(nèi)就搞定了5贰!
于是榛搔,我得到了很好的教訓(xùn):請確保項目中的最關(guān)鍵因素是否在我們的掌握中诺凡。像是藍牙模塊這種東西,應(yīng)該今早催促對方拿過來才是践惑,而不是自己一個人在那邊瞎搞腹泌。
嘮叨話就先到這里,正篇正式開始尔觉。

搜索設(shè)備

android藍牙這方面還是很好搞的真屯,因為大家的方式都是差不多的。先說說如何開啟藍牙設(shè)備和設(shè)置可見時間:

private void search() {
    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    if (!adapter.isEnabled()) {
        adapter.enable();
    }
    Intent enable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    enable.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 3600); //3600為藍牙設(shè)備可見時間 startActivity(enable); 
    Intent searchIntent = new Intent(this, ComminuteActivity.class);
    startActivity(searchIntent);
}

首先穷娱,需要獲得一個BluetoothAdapter绑蔫,可以通過getDefaultAdapter()獲得系統(tǒng)默認的藍牙適配器,當然我們也可以自己指定泵额,但這個真心沒有必要配深,至少我是不需要的。然后我們檢查手機的藍牙是否打開嫁盲,如果沒有篓叶,通過enable()方法打開。接著我們再設(shè)置手機藍牙設(shè)備的可見羞秤,可見時間可以自定義缸托。 完成這些必要的設(shè)置后,我們就可以正式開始與藍牙模塊進行通信了:

public class ComminuteActivity extends Activity {
    private BluetoothReceiver receiver;
    private BluetoothAdapter bluetoothAdapter;
    private List < String > devices;
    private List < BluetoothDevice > deviceList;
    private Bluetooth client;
    private final String lockName = "BOLUTEK";
    private String message = "000001";
    private ListView listView;
    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.search_layout);
        listView = (ListView) this.findViewById(R.id.list);
        deviceList = new ArrayList < BluetoothDevice > ();
        devices = new ArrayList < String > ();
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        bluetoothAdapter.startDiscovery();
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        receiver = new BluetoothReceiver();
        registerReceiver(receiver, filter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override public void onItemClick(AdapterView < ? > parent, View view, int position, long id) {
                setContentView(R.layout.connect_layout);
                BluetoothDevice device = deviceList.get(position);
                client = new Bluetooth(device, handler);
                try {
                    client.connect(message);
                } catch (Exception e) {
                    Log.e("TAG", e.toString());
                }
            }
        });
    }
    @Override protected void onDestroy() {
        unregisterReceiver(receiver);
        super.onDestroy();
    }
    private final Handler handler = new Handler() {
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {
                case Bluetooth.CONNECT_FAILED:
                    Toast.makeText(ComminuteActivity.this, "連接失敗", Toast.LENGTH_LONG).show();
                    try {
                        client.connect(message);
                    } catch (Exception e) {
                        Log.e("TAG", e.toString());
                    }
                    break;
                case Bluetooth.CONNECT_SUCCESS:
                    Toast.makeText(ComminuteActivity.this, "連接成功", Toast.LENGTH_LONG).show();
                    break;
                case Bluetooth.READ_FAILED:
                    Toast.makeText(ComminuteActivity.this, "讀取失敗", Toast.LENGTH_LONG).show();
                    break;
                case Bluetooth.WRITE_FAILED:
                    Toast.makeText(ComminuteActivity.this, "寫入失敗", Toast.LENGTH_LONG).show();
                    break;
                case Bluetooth.DATA:
                    Toast.makeText(ComminuteActivity.this, msg.arg1 + "", Toast.LENGTH_LONG).show();
                    break;
            }
        }
    };
    private class BluetoothReceiver extends BroadcastReceiver {
        @Override public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (isLock(device)) {
                    devices.add(device.getName());
                }
                deviceList.add(device);
            }
            showDevices();
        }
    }
    private boolean isLock(BluetoothDevice device) {
        boolean isLockName = (device.getName()).equals(lockName);
        boolean isSingleDevice = devices.indexOf(device.getName()) == -1;
        return isLockName && isSingleDevice;
    }
    private void showDevices() {
        ArrayAdapter < String > adapter = new ArrayAdapter < String > (this, android.R.layout.simple_list_item_1, devices);
        listView.setAdapter(adapter);
    }
}

要想與任何藍牙模塊進行通信瘾蛋,首先得搜到該設(shè)備:

private class BluetoothReceiver extends BroadcastReceiver {
    @Override public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            if (isLock(device)) {
                devices.add(device.getName());
            }
            deviceList.add(device);
        }
        showDevices();
    }
}

在這之前俐镐,我們得先調(diào)用一個方法:
bluetoothAdapter.startDiscovery();

startDiscovery()方法是一個異步方法,它會對其他藍牙設(shè)備進行搜索哺哼,持續(xù)時間為12秒佩抹。搜索過程其實是在System Service中進行叼风,我們可以通過cancelDiscovery()方法來停止這個搜索。在系統(tǒng)搜索藍牙設(shè)備的過程中棍苹,系統(tǒng)可能會發(fā)送以下三個廣播:ACTION_DISCOVERY_START(開始搜索)无宿,ACTION_DISCOVERY_FINISHED(搜索結(jié)束)和ACTION_FOUND(找到設(shè)備)。ACTION_FOUND這個才是我們想要的枢里,這個Intent中包含兩個extra fields:EXTRA_DEVICE和EXTRA_CLASS孽鸡,包含的分別是BluetoothDevice和BluetoothClass,BluetoothDevice中的EXTRA_DEVICE就是我們搜索到的設(shè)備對象栏豺。 確認搜索到設(shè)備后彬碱,我們可以從得到的BluetoothDevice對象中獲得設(shè)備的名稱和地址。
在android中使用廣播需要我們注冊冰悠,這里也不例外:

IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
 receiver = new BluetoothReceiver(); registerReceiver(receiver, filter);

廣播注冊后需要我們撤銷,這個可以放在這里進行:

 @Override 
protected void onDestroy() {
   unregisterReceiver(receiver); super.onDestroy(); 
}```

這樣在Activity結(jié)束的時候就會自動撤銷該廣播配乱,而不需要我們手動執(zhí)行溉卓。    我這里使用一個ListView來顯示搜索到的藍牙設(shè)備,但因為需要只限定一個藍牙設(shè)備搬泥,所以這里進行了檢查桑寨,檢查該設(shè)備是否是我們的目標設(shè)備,如果是忿檩,就添加尉尾。當然考润,為了防止重復(fù)添加胰柑,有必要增加這么一句:
boolean isSingleDevice = devices.indexOf(device.getName()) == -1;

###配對
掃描到設(shè)備后 未配對`_device.createBond()` 創(chuàng)建配對,是否配對成功通過廣播接收践险,代碼如下
    BluetoothDevice _device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        toast(getString(R.string.BlueToothBondStart));
        if (_device.createBond()) {
            /*停止掃描*/
            BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
            /*監(jiān)聽配對成功的回調(diào)*/
            initBondBroadCast();
        }
    }

    private void initBondBroadCast() {
        ActivityDeviceList context = ActivityDeviceList.this;
        if (mReceiver != null) {
            context.unregisterReceiver(mReceiver);
            mReceiver = null;
        }
        mReceiver = new MyBlueToothBroadcastReceiver(context);
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        context.registerReceiver(mReceiver, filter);
    }
    }
###連接設(shè)備
搜索到該設(shè)備后班套,我們就要對該設(shè)備進行連接肢藐。

/**
* 開啟服務(wù) 連接藍牙
*
* @param address
*/

public void startBlueToothService(String address) {
    BluetoothDevice _device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
      /*測試uuid*/
       /* try {
            Method getUuidsMethod = BluetoothAdapter.class.getDeclaredMethod("getUuids", null);
            ParcelUuid[] uuids = (ParcelUuid[]) getUuidsMethod.invoke(BluetoothAdapter.getDefaultAdapter(), null);
            for (ParcelUuid uuid : uuids) {
                log("UUID: " + uuid.getUuid().toString());
            }
            *//* 輸出結(jié)果
            UUID: 0000111f-0000-1000-8000-00805f9b34fb
            UUID: 00001112-0000-1000-8000-00805f9b34fb
            UUID: 0000110a-0000-1000-8000-00805f9b34fb*//*
        } catch (Exception e) {
            e.printStackTrace();
        }*/
    try {
        UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
        BluetoothSocket _bluetoothSocket = _device.createRfcommSocketToServiceRecord(uuid);
        try {
            SocketListener socketListener = new SocketListener();
            readThread = new BlueToothSocketThread(_bluetoothSocket, socketListener);
            readThread.start();
        } catch (Exception e) {
            e.printStackTrace();
            toast(getString(R.string.BlueToothConnectFail));
        }


    } catch (IOException e) {
        toast("連接失敗入");
    }
}

連接設(shè)備之前需要UUID,所謂的UUID吱韭,就是用來進行配對的吆豹,全稱是Universally Unique Identifier,是一個128位的字符串ID理盆,用于進行唯一標識痘煤。網(wǎng)上的例子,包括谷歌的例子猿规,它們的UUID都是說能用但是我用不了的衷快,都會報出這樣的錯誤:
      Service discovery failed 
      原因可能是作為唯一標識的UUID沒有發(fā)揮作用,所以姨俩,我就利用反射的原理烦磁,讓設(shè)備自己提供UUID养匈。
      這個錯誤在我們把手機既當做客戶端有當做服務(wù)端的時候,同樣也有可能出現(xiàn)都伪,因為作為服務(wù)器的時候呕乎,我們需要的也是同一個UUID:

`mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);`

作為客戶端是這樣的:
`device.createRfcommSocketToServiceRecord(MY_UUID);`

當兩個UUID想同時建立Rfcomm的通道時,我們的選擇都是在兩個線程中分別實現(xiàn)陨晶,但是忽略了一件最重要的事情:同一個時間只能充當一個角色猬仁!所以,解決這個問題的方法就是在我們相連接的設(shè)備上也安裝同樣的應(yīng)用程序先誉,誰先發(fā)起連接誰就是客戶端湿刽,但我這里是藍牙模塊啊:侄诈闺!怎么能安裝我的應(yīng)用程序呢!铃芦!解決辦法就在下面的通信中雅镊。
      連接設(shè)備之前還有一件事必須確保:
`bluetoothAdapter.cancelDiscovery();`

 這是為了停掉搜索設(shè)備,否則連接可能會變得非常慢并且容易失敗刃滓。      有關(guān)于Socket的編程都需要我們設(shè)置一些狀態(tài)值來標識通信的狀態(tài)仁烹,以方便我們調(diào)錯,而且連接應(yīng)該放在一個線程中進行咧虎,要讓該線程與我們程序的主線程進行通信卓缰,我們需要使用Handle,關(guān)于Handle的使用砰诵,可以參考我的另一篇博客[http://www.cnblogs.com/wenjiang/p/3180324.html](http://www.cnblogs.com/wenjiang/p/3180324.html)征唬,
這里不多講。
      在使用Socket中茁彭,我注意到一個方法:isConnect()鳍鸵,它返回的是布爾值,但是根本就不需要使用到這個方法尉间,Socket的連接如果沒有報錯偿乖,說明是已經(jīng)連接上了。
      在谷歌提供的例子中哲嘲,我們可以看到谷歌的程序員的程序水平很高贪薪,一些好的編碼習(xí)慣我們可以學(xué)習(xí)一下,像是在try..catch中才定義的變量眠副,我們應(yīng)該在try...catch之前聲明一個臨時變量画切,然后再在try...catch后賦值給我們真正要使用的變量。這種做法的好處就是:如果我們直接就是使用真正的變量囱怕,當出現(xiàn)異常的時候霍弹,該變量的使用就會出現(xiàn)問題毫别,而且很難進行排查,如果是臨時變量典格,我么可以通過檢查變量的值來確定是否是賦值時出錯岛宦。
     谷歌的例子中最大的感想就是滿滿的異常檢查,但也是因為這個耍缴,導(dǎo)致它的可讀性不高砾肺。java的異常處理機制有時候?qū)τ诖a的閱讀真的不是一件舒服的事情,能避免就盡量避免防嗡。
     如果連接沒有問題变汪,我們就可以和藍牙模塊進行通信:

if (isConnect) {
try {
OutputStream outStream = socket.getOutputStream();
outStream.write(getHexBytes(message));
} catch (IOException e) {
setState(WRITE_FAILED);
Log.e("TAG", e.toString());
}
try {
InputStream inputStream = socket.getInputStream();
int data;
while (true) {
try {
data = inputStream.read();
Message msg = handler.obtainMessage();
msg.what = DATA;
msg.arg1 = data;
handler.sendMessage(msg);
} catch (IOException e) {
setState(READ_FAILED);
Log.e("TAG", e.toString());
break;
}
}
} catch (IOException e) {
setState(WRITE_FAILED);
Log.e("TAG", e.toString());
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
Log.e("TAG", e.toString());
}
}
}
}


 這里包括寫入和讀取,用法和基本的Socket是一樣的蚁趁,但是寫入的時候裙盾,需要將字符串轉(zhuǎn)化為16進制:

private byte[] getHexBytes(String message) {
int len = message.length() / 2;
char[] chars = message.toCharArray();
String[] hexStr = new String[len];
byte[] bytes = new byte[len];
for (int i = 0, j = 0; j < len; i += 2, j++) {
hexStr[j] = "" + chars[i] + chars[i + 1];
bytes[j] = (byte) Integer.parseInt(hexStr[j], 16);
}
return bytes;
}


   當然,這里只是將手機當做客戶端他嫡,但是接收藍牙模塊發(fā)送過來的信息是沒有必要特意創(chuàng)建服務(wù)端的番官,我們只要一個不斷監(jiān)聽并讀取對方消息的循環(huán)就行。       很簡單的程序就能實現(xiàn)像是藍牙串口助手的功能涮瞻,由于是項目的代碼鲤拿,不能貼完整的代碼假褪,但是基本上都在上面了署咽,大家可以參考一下。要想使用藍牙生音,相應(yīng)的權(quán)限也是必不可少的:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

###作為服務(wù)端

UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
String name = "Server";
BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothServerSocket serverSocket = defaultAdapter.listenUsingRfcommWithServiceRecord(name, uuid);
BluetoothSocket bluetoothSocket = serverSocket.accept();
bluetoothSocket.getOutputStream().write(tag);

在項目開發(fā)中可以用兩個手機一個做服務(wù)端一個做客戶端 調(diào)試宁否,完全沒問題的
android sample 存放路徑
android-sdk\samples\android-23\connectivity\bluetooth
android-sdk\samples\android-23\connectivity\BluetoothLeGatt
[代碼放在github上](https://github.com/wenjiang/Bluetooth2.git),
[github android 藍牙項目](
https://github.com/search?l=Java&o=desc&q=bluetooth&s=stars&type=Repositories&utf8=%E2%9C%93)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缀遍,一起剝皮案震驚了整個濱河市慕匠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌域醇,老刑警劉巖台谊,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異譬挚,居然都是意外死亡锅铅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門减宣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盐须,“玉大人,你說我怎么就攤上這事漆腌≡舻耍” “怎么了阶冈?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長塑径。 經(jīng)常有香客問我女坑,道長,這世上最難降的妖魔是什么晓勇? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任堂飞,我火速辦了婚禮,結(jié)果婚禮上绑咱,老公的妹妹穿的比我還像新娘绰筛。我一直安慰自己,他們只是感情好描融,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布铝噩。 她就那樣靜靜地躺著,像睡著了一般窿克。 火紅的嫁衣襯著肌膚如雪骏庸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天年叮,我揣著相機與錄音具被,去河邊找鬼。 笑死只损,一個胖子當著我的面吹牛一姿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跃惫,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼叮叹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了爆存?” 一聲冷哼從身側(cè)響起蛉顽,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎先较,沒想到半個月后携冤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡闲勺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年曾棕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霉翔。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡睁蕾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情子眶,我是刑警寧澤瀑凝,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站臭杰,受9級特大地震影響粤咪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜渴杆,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一寥枝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧磁奖,春花似錦囊拜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至身诺,卻和暖如春蜜托,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背霉赡。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工橄务, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人穴亏。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓蜂挪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迫肖。 傳聞我的和親對象是個殘疾皇子锅劝,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評論 25 707
  • 藍牙 注:本文翻譯自https://developer.android.com/guide/topics/conn...
    RxCode閱讀 8,626評論 11 99
  • 公司的項目最近需要用到藍牙開發(fā)的相關(guān)內(nèi)容攒驰,因此特地查閱了Google官方文檔的內(nèi)容并進行二次整理蟆湖,希望能對需要學(xué)習(xí)...
    Chuckiefan閱讀 32,417評論 44 123
  • 初識低功耗藍牙 Android 4.3(API Level 18)開始引入Bluetooth Low Energy...
    JBD閱讀 112,484評論 46 342
  • 1. 不怕麻煩 2. 有哪幾種方案,哪種方案有利于客戶和公司玻粪,然后選擇哪一種 3. 說話之前先想好怎么說隅津,別急,有...
    irisxiang閱讀 200評論 0 0