Bluetooth(藍芽)

Bluetooth(藍芽)

一、前言

Bluetooth藍芽裝置在手機界已經存在很久了,

也早就成為連低階Android手機都有的基本配備。

官方也提供了一個利用藍芽連線互相對話的範例程式

讓我們能快速地了解藍芽在Android中的使用方式。

二、文章開始

首先,

我們先建立一個觀念︰

藍芽一定分成2個端點,

分別為被動的Server端主動的Client端

底下是BluetoothChat的Sample Code程式流程︰

=========現在在BluetoothChat.java底下==============

1.在onCreate()時,呼叫

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

一起來看看官方文件裡怎麼說BluetoothAdapter

BluetoothAdapter represents the local Bluetooth adapter (Bluetooth radio).

The BluetoothAdapter is the entry-point for all Bluetooth interaction.

Using this, you can discover other Bluetooth devices, query a list of bonded (paired) devices, instantiate a BluetoothDevice using a known MAC address, and create a BluetoothServerSocket to listen for communications from other devices.

BluetoothAdapter是區域藍芽接口(藍芽廣播)。BluetoothAdapter也是所有藍芽交易互動的啟始點。用這個接口,我們可以偵 測區域內有哪些其它的藍芽裝置、查詢已配對過的藍芽列表、用已知的MAC地址建立一個BluetoothDevice實體、建立一個 BluetoothServerSocket來監聽是否有其它藍芽裝置傳來的通訊…等。

2-1.得到一個BluetoothAdapter實體之後,

在onStart()裡,

如果沒有啟動藍芽,則要求使用者開啟藍芽。

指令是︰

if (!mBluetoothAdapter.isEnabled()) {

Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

startActivityForResult(enableIntent, REQUEST_ENABLE_BT);

// Otherwise, setup the chat session

}else {

if (mChatService == null) setupChat();

}

2-2.透過setupChat()建立起基本的對話視窗和BluetoothChatService背景服務,並把主Thread的Handler傳給Service以供日後傳回message。

mChatService = new BluetoothChatService(this, mHandler);

3.在onResume()裡,也做一樣的事,如果檢查沒有開啟藍芽BluetoothChatService背景服務,則再次開始該服務。

if (mChatService.getState() == BluetoothChatService.STATE_NONE) {

// Start the Bluetooth chat services

mChatService.start();

}

=========現在進入BluetoothChatService.java裡==============

程式碼才剛開出來,

馬上就看到這個Service的程式架構中,塞了3個執行緒,

分別為︰

(1)AcceptThread

(2)ConnectThread

(3)ConnectedThread

馬上來看看它們在Service裡,分別擔任什麼樣的任務︰

// Cancel any thread attempting to make a connection

if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}

// Cancel any thread currently running a connection

if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

// Start the thread to listen on a BluetoothServerSocket

if (mAcceptThread == null) {

mAcceptThread = new AcceptThread();

mAcceptThread.start();

}

1.在onStart()中,

檢查如果ConnectThread和ConnectedThread存在,則將他們關掉。

2.啟動一個AcceptThread(現在的流程是在藍芽開啟中的狀態,開啟了一個AcceptThread待命)。

這個AcceptThread存在的目的,是因為程式先假設每臺裝置都有可能想要跟它做藍芽連線。

來看一下這個程式一啟動後就執行的AcceptThread裡面做了些什麼︰

/**

* This thread runs while listening for incoming connections. It behaves

* like a server-side client. It runs until a connection is accepted

* (or until cancelled).

*/

private class AcceptThread extends Thread {

// The local server socket

private final BluetoothServerSocket mmServerSocket;

public AcceptThread() {

BluetoothServerSocket tmp = null;

// Create a new listening server socket

try {

tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);

} catch (IOException e) {

Log.e(TAG, "listen() failed", e);

}

mmServerSocket = tmp;

}

public void run() {

if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);

setName("AcceptThread");

BluetoothSocket socket = null;

// Listen to the server socket if we're not connected

while (mState != STATE_CONNECTED) {

try {

// This is a blocking call and will only return on a

// successful connection or an exception

socket = mmServerSocket.accept();

} catch (IOException e) {

Log.e(TAG, "accept() failed", e);

break;

}

// If a connection was accepted

if (socket != null) {

synchronized (BluetoothChatService.this) {

switch (mState) {

case STATE_LISTEN:

case STATE_CONNECTING:

// Situation normal. Start the connected thread.

connected(socket, socket.getRemoteDevice());

break;

case STATE_NONE:

case STATE_CONNECTED:

// Either not ready or already connected. Terminate new socket.

try {

socket.close();

} catch (IOException e) {

Log.e(TAG, "Could not close unwanted socket", e);

}

break;

}

}

}

}

if (D) Log.i(TAG, "END mAcceptThread");

}

public void cancel() {

if (D) Log.d(TAG, "cancel " + this);

try {

mmServerSocket.close();

} catch (IOException e) {

Log.e(TAG, "close() of server failed", e);

}

}

}

我們看到了在AcceptThread裡面,埋入了一個BluetoothServerSocket。

看一下Bluetooth Android官方對它的解釋︰

BluetoothServerSocket represents an open server socket that listens for incoming requests (similar to a TCP ServerSocket). In order to connect two Android devices, one device must open a server socket with this class. When a remote Bluetooth device makes a connection request to the this device, the BluetoothServerSocket will return a connected BluetoothSocket when the connection is accepted.

BluetoothServerSocket是一個開放式的server socket,用來監聽任何傳進來的請求(原理類似TCP ServerSocket)。為了讓2隻Android devices能夠連線,其中一隻裝置必須開啟server socket。當遠端的藍芽裝置向手上這隻裝備請求連線後,這隻裝置上的BluetoothServerSocket會回傳一個accepted的 BluetoothSocket給呼叫那一方。

因此我們知道,上面程式碼中

BluetoothSocket socket = mmServerSocket.accept();

就是應證了BluetoothServerSocket會吐BluetoothSocket出來這件事。

回到一開始呼叫AcceptThread.start()的那個時間點,

也就是說,

程式在一啟動時,

都先要求使用者開啟藍芽,

然後隨時準備接收別臺藍芽裝置會傳送連線請求的事件。

我們取到了BluetoothSocket後,

看看這個BluetoothSocket能做些什麼。

首先,

在官方技術文件提到︰

BluetoothSocket represents the interface for a Bluetooth socket (similar to a TCP Socket). This is the connection point that allows an application to exchange data with another Bluetooth device via InputStream and OutputStream.

BluetoothSocket是一個Bluetooth socket的接口(原理類似TCP Socket)。這個連結點允許APP透過InputStream和OutpusStream互相交換資料。

因此我們得知,

BluetoothSocket可以讓我們做到資料交換的功能。

因為在Service onStart()呼叫AcceptThread.start()後,

馬上將藍芽狀態設定成setState(STATE_LISTEN);

因此,在switch迴圈中,

程式執行了connected()函式。

這段程式碼如下︰

case STATE_LISTEN:

case STATE_CONNECTING:

// Situation normal. Start the connected thread.

connected(socket, socket.getRemoteDevice());

break;

馬上來看看connected()函式做了哪些事

/**

* Start the ConnectedThread to begin managing a Bluetooth connection

* @param socket The BluetoothSocket on which the connection was made

* @param device The BluetoothDevice that has been connected

*/

public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {

if (D) Log.d(TAG, "connected");

// Cancel the thread that completed the connection

if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}

// Cancel any thread currently running a connection

if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

// Cancel the accept thread because we only want to connect to one device

if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}

// Start the thread to manage the connection and perform transmissions

mConnectedThread = new ConnectedThread(socket);

mConnectedThread.start();

// Send the name of the connected device back to the UI Activity

Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);

Bundle bundle = new Bundle();

bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());

msg.setData(bundle);

mHandler.sendMessage(msg);

setState(STATE_CONNECTED);

}

為了避免重覆連線,

先檢查有沒有已存在的ConectThread、ConnectedThrad和AcceptThread。

如果有,一律先關掉。

然後,啟動ConnectedThread,

並將MESSAGE_DEVICE_NAME用handler(mHandler,還記得我們前面有提到在BluetoothChat.java傳了一個主Thread的Handler給Service嗎?)傳訊的方式,

將Client端的裝置資料傳回BluetoothChat.java,

讓Server端知道是誰在跟它做連結。

前面提到,

一旦取得了BluetoothSocket之後,

就可以開始執行互相傳遞資料的工作了 。

這個被啟動的ConnectedThread就是在做資料互傳的監聽工作

我們看看ConnectedThread做了些什麼

/**

* This thread runs during a connection with a remote device.

* It handles all incoming and outgoing transmissions.

*/

private class ConnectedThread extends Thread {

private final BluetoothSocket mmSocket;

private final InputStream mmInStream;

private final OutputStream mmOutStream;

public ConnectedThread(BluetoothSocket socket) {

Log.d(TAG, "create ConnectedThread");

mmSocket = socket;

InputStream tmpIn = null;

OutputStream tmpOut = null;

// Get the 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;

}

public void run() {

Log.i(TAG, "BEGIN mConnectedThread");

byte[] buffer = new byte[1024];

int bytes;

// Keep listening to the InputStream while connected

while (true) {

try {

// Read from the InputStream

bytes = mmInStream.read(buffer);

// Send the obtained bytes to the UI Activity

mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)

.sendToTarget();

} catch (IOException e) {

Log.e(TAG, "disconnected", e);

connectionLost();

break;

}

}

}

/**

* Write to the connected OutStream.

* @param buffer The bytes to write

*/

public void write(byte[] buffer) {

try {

mmOutStream.write(buffer);

// Share the sent message back to the UI Activity

mHandler.obtainMessage(BluetoothChat.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);

}

}

}

是的!

有看到嗎?

ConnectedThread正在用BluetoothSocket取得InputStream和OutputStream,

並透過旗下的write()和read()在做2隻藍芽裝置的溝通!

現在剩下ConnectThread還沒有去理解了,

查了一下ConnectThread被start的時間是發生在一開始對話頁面的menu鍵中!

原來ConnectThread的目的是要主動連接其它已開啟藍芽的裝置。

當使用者點擊Connect a device時,

會啟動ConnectThread,

開始尋找附近的藍芽裝置,

並對對方發出連線訊號,

對方監聽到你的配對要求後,

對方手機裡原本程式就開啟中的AcceptThread便答應你的請求,

然後開啟ConnectedThread,

並利用連結成功後得到的BluetoothSocket和你做藍芽傳輸溝通。

主動連線端是Client端,被動接收端是Server端,

就好像精子與受精卵…

三、總結

在這裡我把整個程式流程重覆敍述一次︰

在連線的一開始,兩隻手機的程式一開始都先建立一個AcceptThread

(因為誰都不知道誰最後會成為被動接收的Server端,誰又是主動的Client端),

然後都跟RFCOMM頻道索取這隻app專屬的BluetoothServerSocket實體。

Server方做了些什麼︰

用BluetoothServerSocket這個實體去等待Client端用ConnectThread發出的請求連線事件

連線若成功會得到這次藍芽溝通專用的BluetoothSocket。

Client方做了些什麼︰

Client端執行ConnectThread

1.Client端在與Server方連線(Connect a deivce)之前,

會先取得到Server端的身份證MAC address,

並用該address得到Server端的BluetoothDevice實體。

2.Client端藉由自己的MY_UUID和Server端的BluetoothDevice實體,

從RFCOMM頻道拿到這次藍芽溝通專用的BluetoothSocket。

兩方在這個時候都拿到這次藍芽溝通專用的BluetootheSocket

也都在此時知道了對方的BluetoothDevice實體(知道對方的身份)。

這時候雙方都同時開啟ConnectedThread,

彼此利用BluetoothSocket互相做資料傳輸。

註︰資料傳輸利用 InputStream和OutputStream。

轉載:小鰻的Android學習筆記