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為藍牙設備可見時間
startActivity(enable);
Intent searchIntent = new Intent(this, ComminuteActivity.class);
startActivity(searchIntent);
}
首先,需要獲得一個BluetoothAdapter,可以通過getDefaultAdapter()獲得系統默認的藍牙適配器,當然我們也可以自己指定,但這個真心沒有必要,至少我是不需要的。然后我們檢查手機的藍牙是否打開,如果沒有,通過enable()方法打開。接着我們再設置手機藍牙設備的可見,可見時間可以自定義。
完成這些必要的設置后,我們就可以正式開始與藍牙模塊進行通信了:
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);
}
}
要想與任何藍牙模塊進行通信,首先得搜到該設備:
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();
}
}
在這之前,我們得先調用一個方法:
bluetoothAdapter.startDiscovery();
startDiscovery()方法是一個異步方法,它會對其他藍牙設備進行搜索,持續時間為12秒。搜索過程其實是在System Service中進行,我們可以通過cancelDiscovery()方法來停止這個搜索。在系統搜索藍牙設備的過程中,系統可能會發送以下三個廣播:ACTION_DISCOVERY_START(開始搜索),ACTION_DISCOVERY_FINISHED(搜索結束)和ACTION_FOUND(找到設備)。ACTION_FOUND這個才是我們想要的,這個Intent中包含兩個extra fields:EXTRA_DEVICE和EXTRA_CLASS,包含的分別是BluetoothDevice和BluetoothClass,BluetoothDevice中的EXTRA_DEVICE就是我們搜索到的設備對象。 確認搜索到設備后,我們可以從得到的BluetoothDevice對象中獲得設備的名稱和地址。
在android中使用廣播需要我們注冊,這里也不例外:
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); receiver = new BluetoothReceiver(); registerReceiver(receiver, filter);
廣播注冊后需要我們撤銷,這個可以放在這里進行:
@Override
protected void onDestroy() {
unregisterReceiver(receiver);
super.onDestroy();
}
這樣在Activity結束的時候就會自動撤銷該廣播,而不需要我們手動執行。
我這里使用一個ListView來顯示搜索到的藍牙設備,但因為需要只限定一個藍牙設備,所以這里進行了檢查,檢查該設備是否是我們的目標設備,如果是,就添加。當然,為了防止重復添加,有必要增加這么一句:
boolean isSingleDevice = devices.indexOf(device.getName()) == -1;
搜索到該設備后,我們就要對該設備進行連接。
public void connect(final String message) {
Thread thread = new Thread(new Runnable() {
public void run() {
BluetoothSocket tmp = null;
Method method;
try {
method = device.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
tmp = (BluetoothSocket) method.invoke(device, 1);
} catch (Exception e) {
setState(CONNECT_FAILED);
Log.e("TAG", e.toString());
}
socket = tmp;
try {
socket.connect();
isConnect = true;
} catch (Exception e) {
setState(CONNECT_FAILED);
Log.e("TAG", e.toString());
}
連接設備之前需要UUID,所謂的UUID,就是用來進行配對的,全稱是Universally Unique Identifier,是一個128位的字符串ID,用於進行唯一標識。網上的例子,包括谷歌的例子,它們的UUID都是說能用但是我用不了的,都會報出這樣的錯誤:
Service discovery failed
原因可能是作為唯一標識的UUID沒有發揮作用,所以,我就利用反射的原理,讓設備自己提供UUID。
這個錯誤在我們把手機既當做客戶端有當做服務端的時候,同樣也有可能出現,因為作為服務器的時候,我們需要的也是同一個UUID:
mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
作為客戶端是這樣的:
device.createRfcommSocketToServiceRecord(MY_UUID);
當兩個UUID想同時建立Rfcomm的通道時,我們的選擇都是在兩個線程中分別實現,但是忽略了一件最重要的事情:同一個時間只能充當一個角色!所以,解決這個問題的方法就是在我們相連接的設備上也安裝同樣的應用程序,誰先發起連接誰就是客戶端,但我這里是藍牙模塊啊!!怎么能安裝我的應用程序呢!!解決辦法就在下面的通信中。
連接設備之前還有一件事必須確保:
bluetoothAdapter.cancelDiscovery();
這是為了停掉搜索設備,否則連接可能會變得非常慢並且容易失敗。
有關於Socket的編程都需要我們設置一些狀態值來標識通信的狀態,以方便我們調錯,而且連接應該放在一個線程中進行,要讓該線程與我們程序的主線程進行通信,我們需要使用Handle,關於Handle的使用,可以參考我的另一篇博客http://www.cnblogs.com/wenjiang/p/3180324.html,這里不多講。
在使用Socket中,我注意到一個方法:isConnect(),它返回的是布爾值,但是根本就不需要使用到這個方法,Socket的連接如果沒有報錯,說明是已經連接上了。
在谷歌提供的例子中,我們可以看到谷歌的程序員的程序水平很高,一些好的編碼習慣我們可以學習一下,像是在try..catch中才定義的變量,我們應該在try...catch之前聲明一個臨時變量,然后再在try...catch后賦值給我們真正要使用的變量。這種做法的好處就是:如果我們直接就是使用真正的變量,當出現異常的時候,該變量的使用就會出現問題,而且很難進行排查,如果是臨時變量,我么可以通過檢查變量的值來確定是否是賦值時出錯。
谷歌的例子中最大的感想就是滿滿的異常檢查,但也是因為這個,導致它的可讀性不高。java的異常處理機制有時候對於代碼的閱讀真的不是一件舒服的事情,能避免就盡量避免。
如果連接沒有問題,我們就可以和藍牙模塊進行通信:
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是一樣的,但是寫入的時候,需要將字符串轉化為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;
}
當然,這里只是將手機當做客戶端,但是接收藍牙模塊發送過來的信息是沒有必要特意創建服務端的,我們只要一個不斷監聽並讀取對方消息的循環就行。
很簡單的程序就能實現像是藍牙串口助手的功能,由於是項目的代碼,不能貼完整的代碼,但是基本上都在上面了,大家可以參考一下。要想使用藍牙,相應的權限也是必不可少的:
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
這篇博客出自:http://www.cnblogs.com/wenjiang/p/3200138.html
項目可以借鑒,但是打開項目稍微有些問題
代碼放在github上:https://github.com/wenjiang/Bluetooth2.git
在此基礎上,可以用Android studio 來嘗試修改代碼建立藍牙通信!
后續項目建成上傳至github上。
