撰寫時間︰2012/04/15 16:06
更新時間︰2012/04/14 10:34
文章更新次數︰1
一、前言
Bluetooth藍芽裝置在手機界已經存在很久了,也早就成為連低階Android手機都有的基本配備。
官方也提供了一個利用藍芽連線互相對話的範例程式,
讓我們能快速地了解藍芽在Android中的使用方式。
二、文章開始
首先,
我們先建立一個觀念︰
藍芽一定分成2個端點,
分別為被動的Server端和主動的Client端。
底下是BluetoothChat的Sample Code程式流程︰
=========現在在BluetoothChat.java底下==============
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
BluetoothServerSocket是一個開放式的server socket,用來監聽任何傳進來的請求(原理類似TCP ServerSocket)。為了讓2隻Android devices能夠連線,其中一隻裝置必須開啟server socket。當遠端的藍芽裝置向手上這隻裝備請求連線後,這隻裝置上的BluetoothServerSocket會回傳一個accepted的BluetoothSocket給呼叫那一方。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.因此我們知道,上面程式碼中
BluetoothSocket socket = mmServerSocket.accept();就是應證了BluetoothServerSocket會吐BluetoothSocket出來這件事。
回到一開始呼叫AcceptThread.start()的那個時間點,
也就是說,
程式在一啟動時,
都先要求使用者開啟藍芽,
然後隨時準備接收別臺藍芽裝置會傳送連線請求的事件。
我們取到了BluetoothSocket後,
看看這個BluetoothSocket能做些什麼。
首先,
在官方技術文件提到︰
BluetoothSocket represents the interface for a Bluetooth socket (similar to a TCP
BluetoothSocket是一個Bluetooth socket的接口(原理類似TCP Socket)。這個連結點允許APP透過InputStream和OutpusStream互相交換資料。Socket
). This is the connection point that allows
an application to exchange data with another Bluetooth device via InputStream
and OutputStream.因此我們得知,
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。
你好 可否提供完整範例檔 學生專題用
ReplyDelete你好!
ReplyDelete我使用
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();時,出現了以下的兩個錯誤:
1.android.bluetooth.BluetoothAdapter- Call requires API level 5 (current min is 3):
2.android.bluetooth.BluetoothAdapter#getDefaultAdapter
請問這是甚麼意思?跟編譯版本有關嗎?
C^3你修改AndroidManifest.xml
ReplyDelete然後Clean project試試。
C^3你修改AndroidManifest.xml
ReplyDelete""
然後Clean project試試。
C^3你修改AndroidManifest.xml
ReplyDeleteuses-sdk minSdkVersion="10"
然後Clean project試試。
成功了,感謝!
ReplyDelete另外還有幾個問題:
Android藍芽傳輸只能轉換成byte嗎?
可以直接傳字串或字元嗎?
因為我要將指令傳給ARM的STM32F217晶片做處理,Android是用byte來傳值讓我很困擾。
不會吧..用byte超正常的
Delete您好 小鰻大大 可以請問您一個問題 指導小弟嘛
ReplyDelete就是在IAB 這個機制中
是手機Client 這邊 要發送訊息給後台server
再由後台Server跟Google 溝通
還是 直接由 Client 跟 google 溝通呢
另外 就是 假如說 購買一個物件 購買成功之後
是我們自己在開通那個物件 還是 我們必須要將物件上傳到 google 再由他們丟回給我們呢
這是小的問題 希望大大能指導我一下 謝謝
您好 請問我有方法讓只要有安裝相同程式的使用者
ReplyDelete他們之間的藍芽連結是不需要再額外詢問的嗎?
請問一下
ReplyDelete關於學習android的路程
直接看範例 實在有許多不知道從哪裡跑出來的變數 或是引用
是否要從書上學習比較了解
@ 林則宇 最好的方法是看能不能找到網路上有sample code,這樣會最快幫到你
ReplyDelete官方的範例打開後,連完藍芽應用程式發生錯誤
ReplyDelete這個跟API版本有關係?
請問一下,我用bluetoothchat 範例,因為看起來跟你發布的code類似,所以請問看看,因為我手機跟其他人手機可以用這範例app互傳訊息,但是我手機配對其他藍芽模組可以配對,但是用這程式在連接那邊一直無法連接,請問這跟app code 有關係,還是藍芽模組有關係,如果可以就回我信箱 chiuka12@gmail.com ,謝謝!!如果需要我在把程式寄給你,謝謝
ReplyDelete想要問問 小鰻大大你是用Eclipse
ReplyDelete程式做的嗎? 因為要做專題但是以前沒接觸過這東西 但我想做的東西 就是從8051接收到訊號後 傳到手機顯示當前狀態 就這樣。
請問一下,要如何讓bluetooth在手機背景程式抓資料呢?
ReplyDeletegmail:brian.chunhao@gmail.com
This comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
ReplyDelete請問一下,要如何讓bluetooth在手機背景程式抓來自其他裝置的資料(get byte)呢?
ReplyDelete您好~
ReplyDelete可以提供範例檔嗎?
專題需要用到
謝謝您~
iva98741@gmail.com
可以提供完整CODE 嗎 包刮 LAYOUT的部分謝謝 專題需要
ReplyDeleteg062567512@gmail.com
hi 請問能 提供範例檔讓我學習用嗎 ...
ReplyDeletekaka784804@hotmail.com
This comment has been removed by the author.
ReplyDelete可否提供完整檔案練習用
ReplyDeleteu0251069@gmail.com
請問能提供完整檔案給我練習嗎?
ReplyDelete謝謝~
a22658802@gmail.com
您好!
ReplyDelete請問可以提供完整範例檔嗎?
專題需要用到
不好意思
謝謝您
h930650806@gmail.com
您好:
ReplyDelete我目前正在研究Android wear 間藍芽的傳輸 (使用motorola 穿戴式裝置)
請問方便提供完整的範例檔嗎?
非常謝謝您!!!
qzwx74@gmail.com
你好:
ReplyDelete我根據版大的教學,寫出了一藍芽傳輸程式,但是只能跟BLUETOOTH V2.0 連線,
V4.0 會出現 Unable to connect device ,請問要如何針對V4.0連線部分進行更改,能否給些建議,謝謝。
您好,
ReplyDelete請問是否方便提供完整範例檔給我學習嗎?
感謝!
Howard1014@gmail.com
你好!
ReplyDelete請問可以提供完整的範例檔,給我學習嗎?
謝謝您
jhengko1116@gmail.com
您好 !
ReplyDelete請問可以提供 完整的code給我參考嗎
專題卡住了 感謝您
40125115@gm.nfu.edu.tw
你好,請問可以提供完整範例檔給我參考嗎,學生專題用,謝謝。
ReplyDeleteap126126@gmail.com
您好 !
ReplyDelete請問可以提供 完整的code給我參考嗎
麻煩您了!!
a5030755662000@yahoo.com.tw
你好!
ReplyDelete請問可以提供完整的範例檔,給我學習嗎?
謝謝您
james0716boy@gmail.com
您好:
ReplyDelete我目前正在研究Android wear 間藍芽的傳輸
請問方便提供完整的範例檔嗎?
非常謝謝您!!!
changhua21015@gmail.com
我目前正在做 Android & Arduino 間藍芽的傳輸 的相關專題
ReplyDelete請問方便提供完整的範例檔嗎?
真的非常謝謝您
letsgo911488@gmail.com
非常詳細
ReplyDelete不知道能不能做到多人同時藍芽連接呢?
你好~
ReplyDelete最近正在學習跟android 藍芽相關的問題
能否方便提供完整範例檔呢?
非常感謝你
L0972805195@gmail.com
嗨
ReplyDelete我剛從android studio 開始學起
有點迷迷糊糊藍芽方面那些該放在哪些地方
可以提供source code讓我參考嗎
可以的話感謝你
這是我的mail
aboy0507@gmail.com
嗨
ReplyDelete我剛從android studio 開始學起
有點迷迷糊糊藍芽方面那些該放在哪些地方
可以提供source code讓我參考嗎
可以的話感謝你
這是我的mail
aboy0507@gmail.com
你好
ReplyDelete因為學校專題需要有手機APP藍牙連接的部分
不過才剛碰Android Studio而已
請問可以提供code參考一下嗎
這是我的Email
star881113@gmail.com
可以的話謝謝
也感謝你的各種教學!
Unknown said...
ReplyDelete你好
因為學校專題需要有手機APP藍牙連接的部分
但輸入上述程式碼obtainMessage一直有誤
不太知道為什麼QQQQQ
請問可以提供code參考一下嗎
這是我的Email
lafukanim@gmail.com
謝謝!
This comment has been removed by the author.
ReplyDelete學校專題有要用到藍芽的部分 我傳值跟連接的部分不太熟悉
ReplyDelete請問可以提供CODE參考嗎?
這是我的Email
dp114029@gmail.com
你好
ReplyDelete因為學校專題需要有手機APP藍牙連接的部分
請問可以提供code參考一下嗎
這是我的Email
asd10004022@gmail.com
感謝
您好....正在研究藍芽的傳輸
ReplyDelete請問可以提供CODE參考嗎?
這是我的Email
wenjyj@gmail.com
你好!
ReplyDelete想問大師關於從STM32傳送指令到android的部分?
現在卡在這一關能是否有一些範例可以參考?謝謝
專題需要謝謝😭🙏
cherrie8505@gmail.com
您好!
ReplyDelete第一次接觸Android Studio, 是否可以提供Source code
讓我學習,目前有藍牙傳輸上的需求,所以時間上不允許我慢慢
看書學,老實說我是想拿您的code來改看看,看是否能改成我
所須要的部份,順便研究一下怎寫,謝謝您
kevin651212@gmail.com
你好!
ReplyDelete請問可以提供完整的範例檔,給我學習嗎?
謝謝您
bosen0621@gmail.com
您好
ReplyDelete因為學校專題需要有手機APP藍牙連接的部分
請問可以提供code參考一下嗎
這是我的Email
chouchachoung@gmail.com
感謝
This comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
ReplyDelete您好
ReplyDelete因為學校專題需要有手機APP連接藍牙的部分
請問可以提供完整的範例檔,給我學習嗎?
這我的電子郵件
as9004600@gmail.com
您好
ReplyDelete目前對藍芽連接還是有許多問題,可以提供完整範例檔,給我學習嗎?非常感謝
這我的電子郵件
ilove2426you@gmail.com
您好
ReplyDelete因為學校專題需要用此部分做專題
請問可以提供完整的範例檔,給我學習嗎?
這我的電子郵件
rtckyaaaa@gmail.com