Android藍芽使用詳解(普通藍芽)

wizardev發表於2018-04-01

前言:最近,新換了一家公司,公司的軟體需要通過藍芽與硬體進行通訊,於是趁此機會將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);
            }
複製程式碼

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

Android藍芽使用詳解(普通藍芽)

當使用者點選“拒絕”或則“允許”的時候 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 框架會自動向使用者顯示配對請求通知或對話方塊,如下圖所示。

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藍芽講解做準備的。

  文中都是擷取的主要程式碼,要獲取全部原始碼請點選這裡

ps: 歷史文章中有乾貨哦!

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

歡迎關注我的公眾號
掃碼關注公眾號,回覆“獲取資料”有驚喜

相關文章