1. 程式人生 > >Android 藍芽開發之搜尋、配對、連線、通訊大全

Android 藍芽開發之搜尋、配對、連線、通訊大全

        藍芽( Bluetooth®):是一種無線技術標準,可實現固定裝置、移動裝置和樓宇個人域網之間的短距離資料

交換(使用2.4—2.485GHz的ISM波段的UHF無線電波)。藍芽裝置最多可以同時和7個其它藍芽裝置建立連線,進

行通訊,當然並不是每一個藍芽都可以達到最大值。下面,我們從藍芽的基本概念開始,一步一步開始瞭解藍芽。

基本概念: 

         安卓平臺提供對藍芽的通訊棧的支援,允許設別和其他的裝置進行無線傳輸資料。應用程式層通過安卓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 中常用方法如下所示:

boolean()Cancel the current device discovery process.
static boolean(Stringaddress)Validate a String Bluetooth address, such as "00:43:A8:23:10:F0"

Alphabetic characters must be uppercase to be valid.

void(int profile,BluetoothProfileproxy)Close the connection of the profile proxy to the Service.
boolean()Turn off the local Bluetooth adapter—do not use without explicit user action to turn off Bluetooth.
boolean()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.
int(int profile)Get the current connection state of a profile.
boolean
int()Get the current Bluetooth scan mode of the local Bluetooth adapter.
int()Get the current state of the local Bluetooth adapter.
boolean()Return true if the local Bluetooth adapter is currently in the device discovery process.
boolean()Return true if Bluetooth is currently enabled and ready for use.
boolean(Stringname)Set the friendly Bluetooth name of the local Bluetooth adapter.
boolean()Start the remote device discovery process.
boolean(UUID[]serviceUuids,BluetoothAdapter.LeScanCallbackcallback)Starts a scan for Bluetooth LE devices, looking for devices that advertise given services.

BluetoothDevice 中常用方法如下所示:

boolean()Start the bonding (pairing) process with the remote device.
(UUIDuuid)Create an RFCOMMsocket ready to start an insecure outgoing connection to this remote device using SDP lookup of uuid.
(UUIDuuid)Create an RFCOMMready to start a secure outgoing connection to this remote device using SDP lookup of uuid.
int()Describe the kinds of special objects contained in this Parcelable's marshalled representation.
boolean(Objecto)Compares this instance with the specified object and indicates if they are equal.
boolean()Perform a service discovery on the remote device to get the UUIDs supported.
()Returns the hardware address of this BluetoothDevice.
int()Get the bond state of the remote device.
()Get the friendly Bluetooth name of the remote device.
int()Get the Bluetooth device type of the remote device.
()Returns the supported features (UUIDs) of the remote device.
int()Returns an integer hash code for this object.
boolean(byte[] pin)Set the pin during pairing when the pairing method is
()Returns a string representation of this BluetoothDevice.
void(Parcelout, int flags)Flatten this object in to a Parcel.

BluetoothSocket 中常用方法如下所示:

voidclose()Closes the object and release any system resources it holds.
void()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.
boolean()Get the connection status of this socket, ie, whether there is an active connection with remote device.

BluetoothServerSocket 中常用方法如下所示:

(int timeout)Block until a connection is established, with timeout.
()Block until a connection is established.
voidclose()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之類的,所以很容易被猜到並進而建立配對和連線。

這裡自誇一下,這篇部落格還是受到了大家的一些好評。該自動配對方法,博主在魅藍、華為、聯想、紅米以及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()方法是一個可以阻塞的方法。阻塞的意思是,當輸入流中沒有資料

傳來時,該方法被阻塞,程式不會執行下邊的內容,直到有資料