藍牙( Bluetooth®):是一種無線技術標准,可實現固定設備、移動設備和樓宇個人域網之間的短距離數據
交換(使用2.4—2.485GHz的ISM波段的UHF無線電波)。藍牙設備最多可以同時和7個其它藍牙設備建立連接,進
行通信,當然並不是每一個藍牙都可以達到最大值。下面,我們從藍牙的基本概念開始,一步一步開始了解藍牙。
(尊重勞動成果,轉載請注明出處http://blog.csdn.net/qq_25827845/article/details/52997523)
源碼下載地址:http://download.csdn.net/download/qq_25827845/9757403
基本概念:
安卓平台提供對藍牙的通訊棧的支持,允許設別和其他的設備進行無線傳輸數據。應用程序層通過安卓API來調用藍牙的相關功
能,這些API使程序無線連接到藍牙設備,並擁有P2P或者多端無線連接的特性。
藍牙的功能:
1、掃描其他藍牙設備
2、為可配對的藍牙設備查詢藍牙適配器
3、建立RFCOMM通道
4、通過服務搜索來鏈接其他的設備
5、與其他的設備進行數據傳輸
6、管理多個連接
藍牙建立連接必須要求:
1、打開藍牙
2、查找附近已配對或可用設備
3、連接設備
4、設備間數據交換
常用的藍牙API如下:
BluetoothAdapter | 代表本地藍牙適配器(藍牙無線電)。BluetoothAdapter是所有藍牙交互的入口。使用這個你可以發現其他藍牙設備,查詢已配對的設備列表,使用一個已知的MAC地址來實例化一個BluetoothDevice,以及創建一個BluetoothServerSocket來為監聽與其他設備的通信。
|
BlueDevice | 代表一個遠程藍牙設備,使用這個來請求一個與遠程設備的BluetoothSocket連接,或者查詢關於設備名稱、地址、類和連接狀態等設備信息。 |
BluetoothSocket | 代表一個藍牙socket的接口(和TCP Socket類似)。這是一個連接點,它允許一個應用與其他藍牙設備通過InputStream和OutputStream交換數據。 |
BluetoothServerSocket | 代表一個開放的服務器socket,它監聽接受的請求(與TCP ServerSocket類似)。為了連接兩台Android設備,一個設備必須使用這個類開啟一個服務器socket。當一個遠程藍牙設備開始一個和該設備的連接請求,BluetoothServerSocket將會返回一個已連接的BluetoothSocket,接受該連接。 |
BluetoothAdapter 中常用方法如下所示:
|
Cancel the current device discovery process.
|
|
Validate a String Bluetooth address, such as "00:43:A8:23:10:F0"
Alphabetic characters must be uppercase to be valid. |
|
Close the connection of the profile proxy to the Service.
|
|
Turn off the local Bluetooth adapter—do not use without explicit user action to turn off Bluetooth.
|
|
Turn on the local Bluetooth adapter—do not use without explicit user action to turn on Bluetooth.
|
|
Returns the hardware address of the local Bluetooth adapter.
|
|
|
|
Get a handle to the default local Bluetooth adapter.
|
|
Get the friendly Bluetooth name of the local Bluetooth adapter.
|
|
Get the current connection state of a profile.
|
|
Get the profile proxy object associated with the profile.
|
|
|
|
|
|
Get the current Bluetooth scan mode of the local Bluetooth adapter.
|
|
Get the current state of the local Bluetooth adapter.
|
|
Return true if the local Bluetooth adapter is currently in the device discovery process.
|
|
Return true if Bluetooth is currently enabled and ready for use.
|
|
Create a listening, insecure RFCOMM Bluetooth socket with Service Record.
|
|
Create a listening, secure RFCOMM Bluetooth socket with Service Record.
|
|
Set the friendly Bluetooth name of the local Bluetooth adapter.
|
|
Start the remote device discovery process.
|
|
Starts a scan for Bluetooth LE devices.
|
|
Starts a scan for Bluetooth LE devices, looking for devices that advertise given services.
|
|
Stops an ongoing Bluetooth LE device scan.
|
BluetoothDevice 中常用方法如下所示:
|
Connect to GATT Server hosted by this device.
|
|
Start the bonding (pairing) process with the remote device.
|
|
Create an RFCOMM
BluetoothSocket
socket ready to start an insecure outgoing connection to this remote device using SDP lookup of uuid.
|
|
Create an RFCOMM
BluetoothSocket
ready to start a secure outgoing connection to this remote device using SDP lookup of uuid.
|
|
Describe the kinds of special objects contained in this Parcelable's marshalled representation.
|
|
Compares this instance with the specified object and indicates if they are equal.
|
|
Perform a service discovery on the remote device to get the UUIDs supported.
|
|
Returns the hardware address of this BluetoothDevice.
|
|
Get the Bluetooth class of the remote device.
|
|
Get the bond state of the remote device.
|
|
Get the friendly Bluetooth name of the remote device.
|
|
Get the Bluetooth device type of the remote device.
|
|
Returns the supported features (UUIDs) of the remote device.
|
|
Returns an integer hash code for this object.
|
|
|
|
|
|
Returns a string representation of this BluetoothDevice.
|
|
Flatten this object in to a Parcel.
|
BluetoothSocket 中常用方法如下所示:
|
Closes the object and release any system resources it holds.
|
|
Attempt to connect to a remote device.
|
|
Get the input stream associated with this socket.
|
|
Get the output stream associated with this socket.
|
|
Get the remote device this socket is connecting, or connected, to.
|
|
Get the connection status of this socket, ie, whether there is an active connection with remote device.
|
BluetoothServerSocket 中常用方法如下所示:
|
Block until a connection is established, with timeout.
|
|
Block until a connection is established.
|
|
Immediately close this socket, and release all associated resources.
|
以上四個類貫穿於我們藍牙通信的全過程,包括藍牙搜索、配對、連接以及通信。
使用藍牙需要在配置文件Androidmanifest.xml 中注冊兩種權限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
其中,權限1在得到默認藍牙適配器時需要,即BluetoothAdapter mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter( )
權限2在mBluetoothAdapter.enable( )或者mBluetoothAdapter.disable( ) 時需要使用到。
一、藍牙搜索功能的實現:
1、得到藍牙適配器:
BluetoothAdapter mBluetoothAdapter= BluetoothAdapter.getDefaultAdapter();
若mBluetoothAdapter為 null,則說明當前手機不支持藍牙功能(現在幾乎所有手機都支持了吧。。。)
2、判斷藍牙是否打開:
if (!mBluetoothAdapter.isEnabled()) { //若沒打開則打開藍牙 mBluetoothAdapter.enable(); }
值得注意的是,強制打開藍牙設備的情況有三種:
(1)沒有任何提示,直接打開了藍牙。如Nexus 5 Android 4.4.4 手機。
(2)會彈出提示框,提示安全警告 “ ***應用嘗試開啟藍牙”,可以選擇“拒絕”或“允許”。大多數手機都是這樣的。
(3)強制打開藍牙失敗,並且沒有任何提示。
3、注冊藍牙搜索廣播接收者:
(1)Android 的廣播機制:
Adnroid的廣播機制(以intent對象的形式廣播出去),Android系統廣播的時候不會關心你是否收得到消息、只負責廣播出去,而
且廣播的對象只是在應用程序中注冊了的廣播接收器。我們要做的就是自定義廣播接收器並將其注冊給應用程序,在廣播接收器中
將接收到廣播事件作出相應的處理。如果廣播的事件並不是我們定義的廣播接收器需要的事件類型,一般是會過濾掉不被接收。只
有當廣播事件和我們寫的接收器定義的接收的事件類型一致的時候才會觸發廣播接收器。並且觸發廣播接收器的onReceive方法。當
然我們自定義的廣播接收器需要接受事件的類型是在XML清單文件的<intent-filter>中自己定義聲明的或者自己在程序代碼中定義一
個IntentFilter對象然后通過對象的addAction()方法來自定義接收事件類型。然后我們需要將接收到的事件的處理代碼寫在onReceive
方法中。
(2)注冊分為兩種:靜態注冊和動態注冊。
- 靜態注冊就是在AndroidManifest.xml文件中定義,注冊的廣播接收器必須繼承BroadReceiver
- 動態注冊就是在程序中使用Context.registerReceiver注冊。
我們先演示動態注冊:
//注冊設備被發現時的廣播 IntentFilter filter=new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mReceiver,filter); //注冊一個搜索結束時的廣播 IntentFilter filter2=new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); registerReceiver(mReceiver,filter2);
對應的靜態注冊如下:
<!-- 廣播接收 --> <receiver android:name="包名.類名" > <intent-filter android:priority="1000"> <action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED"/> <action android:name="android.bluetooth.device.action.FOUND" /> </intent-filter> </receiver>
我們如何知道BluetoothAdapter.ACTION_DISCOVERY_FINISHED對應着android.bluetooth.adapter.action.DISCOVERY_FINISHED呢?
這就要看強大的API了。如圖就是一種對應關系:
此處推薦別人上傳的中文API:
4、定義廣播接收:
自定義的廣播接收器對象必須要繼承BroadcastReceiver,然后重寫onReceive方法,處理接收的數據的代碼就寫在這個方法里面。
兩種方法:
- 自定義一個類實現BroadcastReceiver抽象類,並且實現其onReceiver(Context context, Intent intent )方法。
- 直接new BroadcastReceiver()來搞定。
方法1如下:
public class BluetoothReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { ................... } }
方法2如下:
//定義廣播接收 private BroadcastReceiver mReceiver=new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { ....................... } };
5、開始廣播:
通過 mBluetoothAdapter.startDiscovery( ); 來開始廣播。當廣播的事件是我們剛剛注冊的事件時就會觸發廣播接收器,並且觸
發廣播接收器中的onReceiver()方法。
6、解除注冊:
通過 unregisterReceiver(mReceiver); 來解除剛剛的注冊。
至此我們完成了藍牙通信的第一步:藍牙搜索。
下邊給出一個完整Demo實例。
功能為:點擊按鈕將搜索附近的藍牙設備,並且判斷是否與本設備已經配對,分類顯示。
代碼如下:
mainActivity.java
package com.example.administrator.myapplication; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends AppCompatActivity { //定義 private BluetoothAdapter mBluetoothAdapter; private TextView text,text2,text3; private Button botton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text=(TextView) this.findViewById(R.id.textView); //已配對 text2= (TextView) this.findViewById(R.id.textView2); //狀態信息 text3= (TextView) this.findViewById(R.id.textView3); //未配對 botton=(Button) this.findViewById(R.id.button); mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter(); IntentFilter filter=new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mReceiver,filter); IntentFilter filter2=new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); registerReceiver(mReceiver,filter2); botton.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View arg0) { if(!mBluetoothAdapter.isEnabled()) { mBluetoothAdapter.enable(); } mBluetoothAdapter.startDiscovery(); text2.setText("正在搜索..."); } }); } public void onDestroy() { super.onDestroy(); //解除注冊 unregisterReceiver(mReceiver); Log.e("destory","解除注冊"); } //定義廣播接收 private BroadcastReceiver mReceiver=new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { String action=intent.getAction(); Log.e("ywq", action); if(action.equals(BluetoothDevice.ACTION_FOUND)) { BluetoothDevice device=intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if(device.getBondState()==BluetoothDevice.BOND_BONDED) { //顯示已配對設備 text.append("\n"+device.getName()+"==>"+device.getAddress()+"\n"); }else if(device.getBondState()!=BluetoothDevice.BOND_BONDED) { text3.append("\n"+device.getName()+"==>"+device.getAddress()+"\n"); } }else if(action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)){ text2.setText("搜索完成..."); } } }; }
AndroidManifest.xml代碼如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.administrator.myapplication"> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
布局文件activity_main.xml代碼如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.example.administrator.myapplication.MainActivity"> <Button android:text="搜索藍牙" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="19dp" android:id="@+id/button" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" /> <TextView android:text="初始狀態" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/button" android:layout_toRightOf="@+id/button" android:layout_toEndOf="@+id/button" android:layout_marginLeft="45dp" android:layout_marginStart="45dp" android:layout_marginBottom="14dp" android:id="@+id/textView2" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/textView" android:text="已配對藍牙設備如下:" android:layout_marginLeft="12dp" android:layout_marginStart="12dp" android:layout_marginTop="53dp" android:layout_below="@+id/button" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" /> <TextView android:text="未配對藍牙設備如下:" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="107dp" android:id="@+id/textView3" android:layout_below="@+id/textView" android:layout_alignLeft="@+id/textView" android:layout_alignStart="@+id/textView" /> </RelativeLayout>
程序運行結果如下:
二、藍牙自動配對功能實現:
藍牙配對是建立連接的基礎和前提。為什么不配對便無法建立連接?
任何無線通信技術都存在被監聽和破解的可能,藍牙SIG為了保證藍牙通信的安全性,采用認證的方式進行數據交互。同時為
了保證使用的方便性,以配對的形式完成兩個藍牙設備之間的首次通訊認證,經配對之后,隨后的通訊連接就不必每次都要做確
認。所以認證碼的產生是從配對開始的,經過配對,設備之間以PIN碼建立約定的link key用於產生初始認證碼,以用於以后建立的
連接。
所以如果不配對,兩個設備之間便無法建立認證關系,無法進行連接及其之后的操作,所以配對在一定程度上保證了藍牙通信
的安全,當然這個安全保證機制是比較容易被破解的,因為現在很多個人設備沒有人機接口,所以PIN碼都是固定的而且大都設置為
通用的0000或者1234之類的,所以很容易被猜到並進而建立配對和連接。
關於藍牙的自動配對,大家可以參考我的這篇博客:Android藍牙自動配對Demo,親測好使!!!
這里自誇一下,這篇博客還是受到了大家的一些好評。該自動配對方法,博主在魅藍、華為、聯想、紅米以及Nexus手機上都有測
試過,使用的Android系統包括4.0+和5.0+,所以各位可以仔細閱讀該博客。
三、藍牙通信的實現:
本文所述的藍牙通信為:Android 端藍牙設備與其他藍牙設備之間的通信。
下邊講述 Android手機端藍牙與Arduino外接藍牙模塊之間進行通信。
(1)Arduino 端藍牙模塊
藍牙模塊在Arduino 端只是一個串口,將藍牙模塊的Tx、Rx接在Arduino開發板上。
初始化與Android藍牙通信的串口,使用串口.read()來讀取來自手機藍牙的信息;使用串口.println(“XXXXXX”)來向手機
端藍牙發送信息。
Demo代碼如下:
void setup() { Serial.begin(9600); //初始化原有串口 SerialBT.begin(9600); //初始化一個串口用來作為藍牙通信 } void loop() { if(SerialBT.available()){ //如果串口可用,即串口中有數據傳過來 char rece=SerialBT.read(); //rece是來自手機藍牙的信息 Serial.println("已經接收到來自Android藍牙的信息"); //這句話將打印在Arduino自帶的串口監視窗里 if(rece=='A'){ SerialBT.println("這是來自Arduino的信息"); //這句話的內容將顯示在Android手機端 } } }
(2)手機端藍牙模塊:
Demo運行結果如下所示:
Demo代碼如下:
MainActivity.java
package com.ywq; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.UUID; import com.example.alltest.R; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.util.Log; import android.view.Menu; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { //定義組件 TextView statusLabel; Button btnConnect,btnSend,btnQuit; EditText etReceived,etSend; //device var private BluetoothAdapter mBluetoothAdapter = null; private BluetoothSocket btSocket = null; private OutputStream outStream = null; private InputStream inStream = null; //這條是藍牙串口通用的UUID,不要更改 private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); private static String address = "20:16:07:26:18:46"; // <==要連接的目標藍牙設備MAC地址 private ReceiveThread rThread=null; //數據接收線程 //接收到的字符串 String ReceiveData=""; MyHandler handler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //首先調用初始化函數 Init(); InitBluetooth(); handler=new MyHandler(); btnConnect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //判斷藍牙是否打開 if(!mBluetoothAdapter.isEnabled()) { mBluetoothAdapter.enable(); } mBluetoothAdapter.startDiscovery(); //創建連接 new ConnectTask().execute(address); } }); btnQuit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(btSocket!=null) { try { btSocket.close(); btSocket=null; if(rThread!=null) { rThread.join(); } statusLabel.setText("當前連接已斷開"); // etReceived.setText(""); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } }); btnSend.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub new SendInfoTask().execute(etSend.getText().toString()); } }); } public void Init() { statusLabel=(TextView)this.findViewById(R.id.textView1); btnConnect=(Button)this.findViewById(R.id.button1); btnSend=(Button)this.findViewById(R.id.button2); btnQuit=(Button)this.findViewById(R.id.button3); etSend=(EditText)this.findViewById(R.id.editText1); etReceived=(EditText)this.findViewById(R.id.editText2); } public void InitBluetooth() { //得到一個藍牙適配器 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if(mBluetoothAdapter == null) { Toast.makeText(this, "你的手機不支持藍牙", Toast.LENGTH_LONG).show(); finish(); return; } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } //連接藍牙設備的異步任務 class ConnectTask extends AsyncTask<String,String,String> { @Override protected String doInBackground(String... params) { // TODO Auto-generated method stub BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(params[0]); try { btSocket = device.createRfcommSocketToServiceRecord(MY_UUID); btSocket.connect(); Log.e("error", "ON RESUME: BT connection established, data transfer link open."); } catch (IOException e) { try { btSocket.close(); return "Socket 創建失敗"; } catch (IOException e2) { Log .e("error","ON RESUME: Unable to close socket during connection failure", e2); return "Socket 關閉失敗"; } } //取消搜索 mBluetoothAdapter.cancelDiscovery(); try { outStream = btSocket.getOutputStream(); } catch (IOException e) { Log.e("error", "ON RESUME: Output stream creation failed.", e); return "Socket 流創建失敗"; } return "藍牙連接正常,Socket 創建成功"; } @Override //這個方法是在主線程中運行的,所以可以更新界面 protected void onPostExecute(String result) { // TODO Auto-generated method stub //連接成功則啟動監聽 rThread=new ReceiveThread(); rThread.start(); statusLabel.setText(result); super.onPostExecute(result); } } //發送數據到藍牙設備的異步任務 class SendInfoTask extends AsyncTask<String,String,String> { @Override protected void onPostExecute(String result) { // TODO Auto-generated method stub super.onPostExecute(result); statusLabel.setText(result); //將發送框清空 etSend.setText(""); } @Override protected String doInBackground(String... arg0) { // TODO Auto-generated method stub if(btSocket==null) { return "還沒有創建連接"; } if(arg0[0].length()>0)//不是空白串 { //String target=arg0[0]; byte[] msgBuffer = arg0[0].getBytes(); try { // 將msgBuffer中的數據寫到outStream對象中 outStream.write(msgBuffer); } catch (IOException e) { Log.e("error", "ON RESUME: Exception during write.", e); return "發送失敗"; } } return "發送成功"; } } //從藍牙接收信息的線程 class ReceiveThread extends Thread { String buffer=""; @Override public void run() { while(btSocket!=null ) { //定義一個存儲空間buff byte[] buff=new byte[1024]; try { inStream = btSocket.getInputStream(); System.out.println("waitting for instream"); inStream.read(buff); //讀取數據存儲在buff數組中 // System.out.println("buff receive :"+buff.length); processBuffer(buff,1024); //System.out.println("receive content:"+ReceiveData); } catch (IOException e) { e.printStackTrace(); } } } private void processBuffer(byte[] buff,int size) { int length=0; for(int i=0;i<size;i++) { if(buff[i]>'\0') { length++; } else { break; } } // System.out.println("receive fragment size:"+length); byte[] newbuff=new byte[length]; //newbuff字節數組,用於存放真正接收到的數據 for(int j=0;j<length;j++) { newbuff[j]=buff[j]; } ReceiveData=ReceiveData+new String(newbuff); Log.e("Data",ReceiveData); // System.out.println("result :"+ReceiveData); Message msg=Message.obtain(); msg.what=1; handler.sendMessage(msg); //發送消息:系統會自動調用handleMessage( )方法來處理消息 } } //更新界面的Handler類 class MyHandler extends Handler{ @Override public void handleMessage(Message msg) { switch(msg.what){ case 1: etReceived.setText(ReceiveData); break; } } } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); try { if(rThread!=null) { btSocket.close(); btSocket=null; rThread.join(); } this.finish(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
BluetoothReceiver.java
package com.ywq.broadcast; import com.ywq.tools.ClsUtils; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public class BluetoothReceiver extends BroadcastReceiver{ String pin = "1234"; //此處為你要連接的藍牙設備的初始密鑰,一般為1234或0000 public BluetoothReceiver() { } //廣播接收器,當遠程藍牙設備被發現時,回調函數onReceiver()會被執行 @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); //得到action Log.e("action1=", action); BluetoothDevice btDevice=null; //創建一個藍牙device對象 // 從Intent中獲取設備對象 btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if(BluetoothDevice.ACTION_FOUND.equals(action)){ //發現設備 Log.e("發現設備:", "["+btDevice.getName()+"]"+":"+btDevice.getAddress()); if(btDevice.getName().contains("HC-05"))//HC-05設備如果有多個,第一個搜到的那個會被嘗試。 { if (btDevice.getBondState() == BluetoothDevice.BOND_NONE) { Log.e("ywq", "attemp to bond:"+"["+btDevice.getName()+"]"); try { //通過工具類ClsUtils,調用createBond方法 ClsUtils.createBond(btDevice.getClass(), btDevice); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }else Log.e("error", "Is faild"); }else if(action.equals("android.bluetooth.device.action.PAIRING_REQUEST")) //再次得到的action,會等於PAIRING_REQUEST { Log.e("action2=", action); if(btDevice.getName().contains("HC-05")) { Log.e("here", "OKOKOK"); try { //1.確認配對 ClsUtils.setPairingConfirmation(btDevice.getClass(), btDevice, true); //2.終止有序廣播 Log.i("order...", "isOrderedBroadcast:"+isOrderedBroadcast()+",isInitialStickyBroadcast:"+isInitialStickyBroadcast()); abortBroadcast();//如果沒有將廣播終止,則會出現一個一閃而過的配對框。 //3.調用setPin方法進行配對... boolean ret = ClsUtils.setPin(btDevice.getClass(), btDevice, pin); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }else Log.e("提示信息", "這個設備不是目標藍牙設備"); } } }
配對工具類ClsUtils.java如下:
package com.ywq.tools; /************************************ 藍牙配對函數 * **************/ import java.lang.reflect.Method; import java.lang.reflect.Field; import android.bluetooth.BluetoothDevice; import android.util.Log; public class ClsUtils { /** * 與設備配對 參考源碼:platform/packages/apps/Settings.git * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java */ static public boolean createBond(Class btClass, BluetoothDevice btDevice) throws Exception { Method createBondMethod = btClass.getMethod("createBond"); Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice); return returnValue.booleanValue(); } /** * 與設備解除配對 參考源碼:platform/packages/apps/Settings.git * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java */ static public boolean removeBond(Class<?> btClass, BluetoothDevice btDevice) throws Exception { Method removeBondMethod = btClass.getMethod("removeBond"); Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice); return returnValue.booleanValue(); } static public boolean setPin(Class<? extends BluetoothDevice> btClass, BluetoothDevice btDevice, String str) throws Exception { try { Method removeBondMethod = btClass.getDeclaredMethod("setPin", new Class[] {byte[].class}); Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice, new Object[] {str.getBytes()}); Log.e("returnValue", "" + returnValue); } catch (SecurityException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (IllegalArgumentException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return true; } // 取消用戶輸入 static public boolean cancelPairingUserInput(Class<?> btClass, BluetoothDevice device) throws Exception { Method createBondMethod = btClass.getMethod("cancelPairingUserInput"); // cancelBondProcess(btClass, device); Boolean returnValue = (Boolean) createBondMethod.invoke(device); return returnValue.booleanValue(); } // 取消配對 static public boolean cancelBondProcess(Class<?> btClass, BluetoothDevice device) throws Exception { Method createBondMethod = btClass.getMethod("cancelBondProcess"); Boolean returnValue = (Boolean) createBondMethod.invoke(device); return returnValue.booleanValue(); } //確認配對 static public void setPairingConfirmation(Class<?> btClass,BluetoothDevice device,boolean isConfirm)throws Exception { Method setPairingConfirmation = btClass.getDeclaredMethod("setPairingConfirmation",boolean.class); setPairingConfirmation.invoke(device,isConfirm); } /** * * @param clsShow */ static public void printAllInform(Class clsShow) { try { // 取得所有方法 Method[] hideMethod = clsShow.getMethods(); int i = 0; for (; i < hideMethod.length; i++) { Log.e("method name", hideMethod[i].getName() + ";and the i is:" + i); } // 取得所有常量 Field[] allFields = clsShow.getFields(); for (i = 0; i < allFields.length; i++) { Log.e("Field name", allFields[i].getName()); } } catch (SecurityException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (IllegalArgumentException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
配置文件AndroidManifest.xml如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.alltest" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <!-- 藍牙使用權限 --> <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.ywq.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 廣播接收 --> <receiver android:name="com.ywq.broadcast.BluetoothReceiver" > <intent-filter android:priority="1000"> <action android:name="android.bluetooth.device.action.PAIRING_REQUEST"/> <action android:name="android.bluetooth.device.action.FOUND" /> </intent-filter> </receiver> </application> </manifest>
布局文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="連接" /> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="當前沒有連接任何設備" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <EditText android:id="@+id/editText1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="10" > <requestFocus /> </EditText> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="發送" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="接收到的數據" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <EditText android:id="@+id/editText2" android:layout_width="wrap_content" android:layout_height="300dp" android:layout_weight="1" android:ems="10" android:inputType="textMultiLine" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="斷開連接" /> </LinearLayout> </LinearLayout>
程序分析:
程序主要分為:自動配對==>>建立連接==>>開啟線程監聽是否收到信息==>>向Arduino 端發送信息==>>斷開連接
自動配對:通過mBluetoothAdapter.startDiscovery();來實現。我們來看一下API中對該方法的描述:
public boolean startDiscovery ()
開始對遠程設備進行查找的進程
它通常牽涉到一個大概需時12秒的查詢掃描過程,緊跟着是一個對每個獲取到自身藍牙名稱的新設備的頁面掃描。
這是一個異步調用方法:該方法將馬上獲得返回值,注冊ACTION_DISCOVERY_STARTED and
ACTION_DISCOVERY_FINISHED意圖准確地確定該探索是處於開始階段或者完成階段。注冊ACTION_FOUND以活動遠程藍牙設
備 已找到的通知。
設備查找是一個重量級過程。當查找正在進行的時候,用戶不能嘗試對新的遠程藍牙設備進行連接,同時存在的連接將獲得有限制
的帶寬以 及高等待時間。用戶可用cencelDiscovery()類來取消正在執行的查找進程。發現的過程不會由活動來進行管理,但是它會
作為一個系統服務來運 行,因此即使它不能直接請求這樣的一個查詢動作,也必需取消該搜索進程。
設備搜尋只尋找已經被連接的遠程設備。許多藍牙設備默認不會被搜尋到,並且需要進入到一個特殊的模式當中。
如果藍牙狀態不是STATE_ON,這個API將返回false。藍牙打開后,等待ACTION_STATE_CHANGED更新成STATE_ON。
需要BLUETOOTH_ADMIN權限。
返回值
成功返回true,錯誤返回false。
由上面我們可以看出,當調用該方法並且發現設備時,將執行我們自定義的廣播接收類中的onReceiver()會被執行,實現自動配對具體可以參考:
建立連接:使用了一個異步AsyncTask任務。關於AsyncTask的使用,可以參考本博客:
我們首先利用遠程藍牙的mac地址得到了遠程藍牙設備:
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
其次利用UUID得到了一個BluetoothSocket對象:
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
然后調用connect()方法建立了socket連接
最后通過:
mBluetoothAdapter.cancelDiscovery();
來取消了搜索。
開啟線程,監聽輸入:當socket創建成功后,需要監聽輸入。
我們首先通過BluetoothSocket對象得到輸入流。
inStream = btSocket.getInputStream();
通過read()方法來讀取來自Arduino端的信息。其中,read()方法是一個可以阻塞的方法。阻塞的意思是,當輸入流中沒有數據
傳來時,該方法被阻塞,程序不會執行下邊的內容,直到有數據傳來。
如果有數據傳來,則通過Message和Handler來更新UI,實現數據的顯示。
向Arduino發送信息:同樣適用了AsyncTask類來實現, android AsyncTask介紹 。
當我們點擊發送按鈕時,首先判斷socket是否成功創建,成功則使用輸出流發送信息。否則,給出提示。
斷開連接:
首先我們需要通過btSocket.close( )來關閉socket,然后調用線程的join( )來將線程停止。實現了藍牙之間的斷開連接操作。
源碼下載地址:http://download.csdn.net/download/qq_25827845/9757403
至此,我們學習了Android藍牙的搜索、配對、連接、通信,對藍牙開發有了一個較為初步的認識。
如果對你有幫助,記得點贊哦~歡迎大家關注我的博客,可以進群366533258一起交流學習哦~