android藍牙開發---與藍牙模塊進行通訊

     近半個月來一直在搞android藍牙這方面,主要是項目須要與藍牙模塊進行通訊。開頭的進展很順利,但由於藍牙模塊不在我這裏,因此只能用手機測試。一開頭就發現手機的藍牙不能用,爲了證實這點,我刷了四次不一樣不一樣系統的官方包,正式宣佈手機的藍牙報銷了,因而和朋友換手機。在測試的過程當中也是很是痛苦,放假了,同窗都幾乎回家了,剩下的同窗中居然80%都是用非android手機!我和個人小夥伴都嚇呆了!!就算借來了手機,測試過程當中總是有人打電話過來,嚴重影響個人開發!!因而,我果斷催促對方快點把藍牙模塊寄過來,等模塊寄過來後,半個小時內就搞定了!!html

     因而,我獲得了很好的教訓:請確保項目中的最關鍵因素是否在咱們的掌握中。像是藍牙模塊這種東西,應該今早催促對方拿過來纔是,而不是本身一我的在那邊瞎搞。java

      嘮叨話就先到這裏,正篇正式開始。android

      android藍牙這方面仍是很好搞的,由於你們的方式都是差很少的。先說說如何開啓藍牙設備和設置可見時間:git

 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);
    }
}

      要想與任何藍牙模塊進行通訊,首先得搜到該設備:github

 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);

     廣播註冊後須要咱們撤銷,這個能夠放在這裏進行:socket

   @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"/>

       代碼放在github上:https://github.com/wenjiang/Bluetooth2.git,用的是Eclipse,由於是三年前的項目了。