1. 程式人生 > >Android藍牙

Android藍牙

實例 回調 ring 一個 tac 解釋 ioe 獲取 exception

代碼地址如下:
http://www.demodashi.com/demo/12772.html

前言:最近,新換了一家公司,公司的軟件需要通過藍牙與硬件進行通訊,於是趁此機會將Android藍牙詳細的了解了一下。本篇內容是基於普通藍牙。

??Android系統已經為我們提供了操作藍牙的API,我們只要通過這些API便可以操控藍牙,實現打開藍牙設備,搜索周圍藍牙設備,與已連接的設備進行數據傳輸等操作。

??閱讀本文後你將會有一下收獲

  • 知道怎樣打開手機藍牙。
  • 知道怎樣獲取已經進行藍牙配對過的設備。
  • 知道怎樣進行設備之間的連接以及通訊。
  • 知道怎樣設置藍牙設備可進行搜索到以及設置可被搜索的時長

一、藍牙操作

打開手機藍牙

設置藍牙權限

??要在應用中使用藍牙功能,必須聲明藍牙權限 BLUETOOTH。您需要此權限才能執行任何藍牙通信,例如請求連接、接受連接和傳輸數據等。設置權限的代碼如下

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

判斷是否支持藍牙

在打開手機藍牙之前首先判斷手機是否支持藍牙,判斷是否支持藍牙的代碼如下

 BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mBluetoothAdapter == null) {
            Toast.makeText(this,"當前設備不支持藍牙!",Toast.LENGTH_SHORT).show();
        }

解釋一下BluetoothAdapter的作用

BluetoothAdapter代表本地設備藍牙適配器,BluetoothAdapter可讓您執行基本的藍牙任務,如啟動設備發現,查詢綁定(配對)設備列表,使用已知的MAC地址實例化 BluetoothDevice,並創建 BluetoothServerSocket以偵聽來自其他設備的連接請求,並開始掃描藍牙LE設備。

如果設備支持藍牙,則進行打開藍牙的操作

打開藍牙

??調用 BluetoothAdapterisEnabled() 方法來檢查當前是否已啟用藍牙。 如果此方法返回 false,則表示藍牙處於停用狀態。想要啟用藍牙,則需要設置Intent的Action為ACTION_REQUEST_ENABLE

,然後通過startActivityForResult()來啟動藍牙。具體的代碼如下

 if (!mBluetoothAdapter.isEnabled()) {
                Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
            }

這段代碼執行完後,手機則會彈出是否允許開啟藍牙的提示框,如下圖

技術分享圖片

當用戶點擊“拒絕”或則“允許”的時候 Activity 將會在 onActivityResult() 回調中收到結果代碼。

??當成功開啟藍牙時 Activity 將會在 onActivityResult() 回調中收到 RESULT_OK 結果代碼。 如果由於某個錯誤(或用戶響應“拒絕”)而沒有啟用藍牙,則結果代碼為 RESULT_CANCELED。我們便可以重寫 onActivityResult()方法來判斷藍牙是否已經成功開啟。

查詢設備

查詢已經配對的設備

??在搜索設備之前,我們應該先查找已經進行配對的設備,如果目標設備已經進行過配對,則不需要進行設備搜索。因為,執行設備發現對於藍牙適配器而言是一個非常繁重的操作過程,並且會消耗大量資源。可以通過BluetoothAdaptergetBondedDevices()方法來查詢已經配對的設備,具體代碼如下

 private void checkAlreadyConnect() {
     //獲取已經配對的集合
        Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
        if (pairedDevices.size() > 0) {
            for (BluetoothDevice device : pairedDevices) {
                mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
            }
            mArrayAdapter.notifyDataSetChanged();
        }
    }

搜索設備

??要搜索周圍的設備,只需調用BluetoothAdapterstartDiscovery()方法即可。

註:搜索設備是在異步進程中,通常會有12秒的時間來進行查詢掃描,之後對每臺發現的設備進行頁面掃描,以檢索其藍牙名稱。

調用startDiscovery()方法的時候還需要新增如下權限

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

在發現設備後系統會進行ACTION_FOUND的廣播,因此,我們需要一個廣播接收者來接收廣播,以下代碼為發現設備後如何註冊來處理廣播

 // 新建一個 BroadcastReceiver來接收ACTION_FOUND廣播
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            // 發現設備
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                //獲得 BluetoothDevice
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                //向mArrayAdapter中添加設備信息
                mSearchAdapter.add(device.getName() + "\n" + device.getAddress());
                mSearchAdapter.notifyDataSetChanged();
            }
        }
    };
    //設置IntentFilter
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);

註意:執行設備發現對於藍牙適配器而言是一個非常繁重的操作過程,並且會消耗大量資源。 在找到要連接的設備後,確保始終使用cancelDiscovery() 停止發現,然後再嘗試連接。不應該在處於連接狀態時執行發現操作。

啟用可檢測性

??Android 設備默認處於不可檢測到狀態。 用戶可通過系統設置將設備設為在有限的時間內處於可檢測到狀態,或者,應用可請求用戶在不離開應用的同時啟用可檢測性。

??如果您希望將本地設備設為可被其他設備檢測到,請使用 ACTION_REQUEST_DISCOVERABLE 操作 Intent 調用 startActivityForResult(Intent, int)。 這將通過系統設置發出啟用可檢測到模式的請求(無需停止您的應用)。 默認情況下,設備將變為可檢測到並持續 120 秒鐘。 您可以通過添加EXTRA_DISCOVERABLE_DURATION Intent Extra 來定義不同的持續時間。 應用可以設置的最大持續時間為 3600 秒,值為 0 則表示設備始終可檢測到。 任何小於 0 或大於 3600 的值都會自動設為 120 秒。 例如,以下片段會將持續時間設為 300 秒:

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);

進行設備連接

??引用官方的文檔

要在兩臺設備上的應用之間創建連接,必須同時實現服務器端和客戶端機制,因為其中一臺設備必須開放服務器套接字,而另一臺設備必須發起連接(使用服務器設備的 MAC 地址發起連接)。 當服務器和客戶端在同一 RFCOMM 通道上分別擁有已連接的 BluetoothSocket 時,二者將被視為彼此連接。

由上面的引用可知,要想創建兩個應用之間的連接,必須同時實現服務器端和客戶端機制,下面,分別介紹怎樣實現為服務器端和客戶端機制。

實現為服務器

當您需要連接兩臺設備時,其中一臺設備必須通過保持開放的 BluetoothServerSocket 來充當服務器。 服務器套接字的用途是偵聽傳入的連接請求,並在接受一個請求後提供已連接的 BluetoothSocket。 從 BluetoothServerSocket 獲取 BluetoothSocket 後,可以(並且應該)舍棄 BluetoothServerSocket,除非您需要接受更多連接。

下面是作為服務端的代碼

private class AcceptThread extends Thread {
        private final BluetoothServerSocket mmServerSocket;

        public AcceptThread() {
            BluetoothServerSocket tmp = null;
            try {

                tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME,
                        MY_UUID);

            } catch (IOException e) {

            }
            mmServerSocket = tmp;
            mState = STATE_LISTEN;
        }

        public void run() {
            setName("AcceptThread");

            BluetoothSocket socket = null;

            while (mState != STATE_CONNECTED) {
                try {
                    socket = mmServerSocket.accept();
                } catch (IOException e) {
                    break;
                }

                if (socket != null) {
                    synchronized (BluetoothService.this) {
                        switch (mState) {
                            case STATE_LISTEN:
                            case STATE_CONNECTING:
                                connected(socket, socket.getRemoteDevice());
                                break;
                            case STATE_NONE:
                            case STATE_CONNECTED:
                                try {
                                    socket.close();
                                } catch (IOException e) {

                                }
                                break;
                        }
                    }
                }
            }

        }

        public void cancel() {
            try {
                mmServerSocket.close();
            } catch (IOException e) {
            }
        }
    }

這裏解釋一下,為什麽要新開一個線程來作為服務端,因為通過調用 accept()這是一個阻塞調用。

註意:mAdapter.listenUsingRfcommWithServiceRecord(NAME,MY_UUID);中的MY_UUID必須與客服端連接時的UUID一致。

總結一下作為服務端的步驟:

  1. 通過調用 listenUsingRfcommWithServiceRecord(String, UUID) 獲取 BluetoothServerSocket
  2. 通過調用 accept() 開始偵聽連接請求。
  3. 如果不想讓更多的設備連接,則在連接後調用close()關閉。

實現為客戶端

??先看下作為客戶端的代碼

 private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;

        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;

            try {
                tmp = device.createRfcommSocketToServiceRecord(
                        MY_UUID);
            } catch (IOException e) {

            }
            mmSocket = tmp;
            mState = STATE_CONNECTING;
        }

        public void run() {
            setName("ConnectThread");
            mAdapter.cancelDiscovery();
            try {
                mmSocket.connect();
            } catch (IOException e) {
                try {
                    mmSocket.close();
                } catch (IOException e2) {
                }
                return;
            }
            synchronized (BluetoothService.this) {
                mConnectThread = null;
            }

            connected(mmSocket, mmDevice);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {

            }
        }
    }

這裏有一點需要註意,也是官方文檔中強調的

註:在調用 connect() 時,應始終確保設備未在執行設備發現。 如果正在進行發現操作,則會大幅降低連接嘗試的速度,並增加連接失敗的可能性。

因此,在進行連接之前必須調用mAdapter.cancelDiscovery();來關閉查找設備。這裏總結一下作為客戶端的步驟:

  1. 使用 BluetoothDevice,通過調用 createRfcommSocketToServiceRecord(UUID) 獲取 BluetoothSocket。(這裏的UUID必須與服務端的保持一致)
  2. 通過調用 connect() 發起連接。

??如果兩臺設備之前尚未配對,則在連接過程中,Android 框架會自動向用戶顯示配對請求通知或對話框,如下圖所示。

技術分享圖片

進行配對之後就可以進行通信及數據的傳輸了。

數據傳輸

??通過上面的幾步,這時已經可以實現設備之間的連接了。下面說一下設備之間的通信。

??其實在兩臺設備連接成功後,每臺設備都會有一個已連接的 BluetoothSocket。我們則可以利用 BluetoothSocket,來進行數據的傳輸。看代碼

 private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;

        public ConnectedThread(BluetoothSocket socket) {

            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            // 獲取BluetoothSocket的input and output streams
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                Log.e(TAG, "temp sockets not created", e);
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
            mState = STATE_CONNECTED;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectedThread");
            byte[] buffer = new byte[1024];
            int bytes;

            while (mState == STATE_CONNECTED) {
                try {
                    bytes = mmInStream.read(buffer);
                    Log.d(TAG, "已經連接");
                    mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, buffer)
                            .sendToTarget();
                } catch (IOException e) {
                    Log.e(TAG, "disconnected", e);
                    break;
                }
            }
        }

        //寫數據
        public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);
        //發送消息到主線程
              mHandler.obtainMessage(Constants.MESSAGE_WRITE, -1, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                Log.e(TAG, "Exception during write", e);
            }
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }

從上面代碼中可以看到,首先是通過BluetoothSocket來拿到InputStream和OutputStream,然後利用readwrite方法來讀取和寫入數據。

二、項目結構

技術分享圖片

三、結束語

??本文的內容是基於普通藍牙進行描述的,主要講解了怎樣操作藍牙及進行設備間的通訊。不過現在好多都是在BLE藍牙設備間進行通訊了,當然,我也會針對BLE藍牙設備在寫一篇文章,本文就是為後面的BLE藍牙講解做準備的。

轉載請註明出處:www.wizardev.com
Android藍牙

代碼地址如下:
http://www.demodashi.com/demo/12772.html

註:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權

Android藍牙