Android 藍牙開發(一)藍牙通信

1 藍牙基本操作

隨着可穿戴設備的流行,研究藍牙是必不可少的一門技術了。

總結了下藍牙開發使用的一些東西分享一下。

藍牙權限

首先需要AndroidManifest.xml文件中添加操作藍牙的權限。

<uses-permissionandroid:name="android.permission.BLUETOOTH" />

允許程序連接到已配對的藍牙設備。

<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />

允許程序發現和配對藍牙設備。


BluetoothAdapter

操作藍牙主要用到的類 BluetoothAdapter類,使用時導包
import android.bluetooth.BluetoothAdapter;
源碼具體位置frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java

BluetoothAdapter 代表本地設備的藍牙適配器。該BluetoothAdapter可以執行基本的藍牙任務,例如啓
動設備發現,查詢配對的設備列表,使用已知的MAC地址實例化一個BluetoothDevice類,並創建一個
BluetoothServerSocket監聽來自其他設備的連接請求。

獲取藍牙適配器

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
開啓藍牙

 if(!mBluetoothAdapter.isEnabled()){
//彈出對話框提示用戶是後打開
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler, REQUEST_ENABLE);
      //不做提示,直接打開,不建議用下面的方法,有的手機會有問題。
      // mBluetoothAdapter.enable();
}

獲取本地藍牙信息

//獲取本機藍牙名稱
String name = mBluetoothAdapter.getName();
//獲取本機藍牙地址
String address = mBluetoothAdapter.getAddress();
Log.d(TAG,"bluetooth name ="+name+" address ="+address);
//獲取已配對藍牙設備
Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();
Log.d(TAG, "bonded device size ="+devices.size());
for(BluetoothDevice bonddevice:devices){
	Log.d(TAG, "bonded device name ="+bonddevice.getName()+" address"+bonddevice.getAddress());
}

搜索設備

mBluetoothAdapter.startDiscovery();

停止搜索

mBluetoothAdapter.cancelDiscovery();

搜索藍牙設備,該過程是異步的,通過下面註冊廣播接受者,可以監聽是否搜到設備。

IntentFilter filter = new IntentFilter();
//發現設備
filter.addAction(BluetoothDevice.ACTION_FOUND);
//設備連接狀態改變
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
//藍牙設備狀態改變
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothReceiver, filter);

監聽掃描結果

通過廣播接收者查看掃描到的藍牙設備,每掃描到一個設備,系統都會發送此廣播(BluetoothDevice.ACTION_FOUNDE)。其中參數intent可以獲取藍牙設備BluetoothDevice

該demo中是連接指定名稱的藍牙設備,BLUETOOTH_NAME爲"Galaxy Nexus",如果掃描不到,記得改這個藍牙名稱。

private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver(){
		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();
			Log.d(TAG,"mBluetoothReceiver action ="+action);
			if(BluetoothDevice.ACTION_FOUND.equals(action)){//每掃描到一個設備,系統都會發送此廣播。
				//獲取藍牙設備
				BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
				if(scanDevice == null || scanDevice.getName() == null) return;
				Log.d(TAG, "name="+scanDevice.getName()+"address="+scanDevice.getAddress());
				//藍牙設備名稱
				String name = scanDevice.getName();
				if(name != null && name.equals(BLUETOOTH_NAME)){
					mBluetoothAdapter.cancelDiscovery();
					//取消掃描
					mProgressDialog.setTitle(getResources().getString(R.string.progress_connecting));					//連接到設備。
					mBlthChatUtil.connect(scanDevice);
				}
			}else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){

			}
		}
		
	};

設置藍牙可見性

有時候掃描不到某設備,這是因爲該設備對外不可見或者距離遠,需要設備該藍牙可見,這樣該才能被搜索到。

可見時間默認值爲120s,最多可設置300。

if (mBluetoothAdapter.isEnabled()) {
	if (mBluetoothAdapter.getScanMode() != 
			BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
		Intent discoverableIntent = new Intent(
				BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
		discoverableIntent.putExtra(
				BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);
		startActivity(discoverableIntent);
	}
}

2 服務端

android 藍牙之間可以通過SDP協議建立連接進行通信,通信方式類似於平常使用socket。

首先創建BluetoothServerSocket ,BluetoothAdapter中提供了兩種創建BluetoothServerSocket 方式,如下圖所示爲創建安全的RFCOMM Bluetooth socket,該連接是安全的需要進行配對。而通過listenUsingInsecureRfcommWithServiceRecord創建的RFCOMM Bluetooth socket是不安全的,連接時不需要進行配對。

其中的uuid需要服務器端和客戶端進行統一。

private class AcceptThread extends Thread {
        // 本地服務器套接字
        private final BluetoothServerSocket mServerSocket;
        public AcceptThread() {       	
            BluetoothServerSocket tmp = null;
            // 創建一個新的偵聽服務器套接字
            try {
                tmp = mAdapter.listenUsingRfcommWithServiceRecord(
                		SERVICE_NAME, SERVICE_UUID);
            	//tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(SERVICE_NAME, SERVICE_UUID);
            } catch (IOException e) {
                Log.e(TAG, "listen() failed", e);
            }
            mServerSocket = tmp;
        }

        public void run() {
            BluetoothSocket socket = null;
            // 循環,直到連接成功
            while (mState != STATE_CONNECTED) {
                try {
                    // 這是一個阻塞調用 返回成功的連接
                    // mServerSocket.close()在另一個線程中調用,可以中止該阻塞
                    socket = mServerSocket.accept();
                } catch (IOException e) {
                    Log.e(TAG, "accept() failed", e);
                    break;
                }
                // 如果連接被接受
                if (socket != null) {
                    synchronized (BluetoothChatUtil.this) {
                        switch (mState) {
                        case STATE_LISTEN:
                        case STATE_CONNECTING:
                            // 正常情況。啓動ConnectedThread。
                            connected(socket, socket.getRemoteDevice());
                            break;
                        case STATE_NONE:
                        case STATE_CONNECTED:
                            // 沒有準備或已連接。新連接終止。
                            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 {
                mServerSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of server failed", e);
            }
        }
    }
mServerSocket通過accept()等待客戶端的連接(阻塞),直到連接成功或失敗。


3 客戶端

客戶端主要用來創建RFCOMM socket,並連接服務端。

先掃描周圍的藍牙設備,如果掃描到指定設備則進行連接。mBlthChatUtil.connect(scanDevice)連接到設備,

連接過程主要在ConnectThread線程中進行,先創建socket,方式有兩種,

如下代碼中是安全的(createRfcommSocketToServiceRecord)。另一種不安全連接對應的函數是createInsecureRfcommSocketToServiceRecord

private class ConnectThread extends Thread {
        private BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;
        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;
            // 得到一個bluetoothsocket
            try {
            	mmSocket = device.createRfcommSocketToServiceRecord
            			(SERVICE_UUID);
            } catch (IOException e) {
                Log.e(TAG, "create() failed", e);
                mmSocket = null;
            }
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectThread");
            try { 
                // socket 連接,該調用會阻塞,直到連接成功或失敗
                mmSocket.connect();
            } catch (IOException e) {
                connectionFailed();
                try {//關閉這個socket
                    mmSocket.close();
                } catch (IOException e2) {
                    e2.printStackTrace();
                }
                return;
            }
            // 啓動連接線程
            connected(mmSocket, mmDevice);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }
接着客戶端socket主動連接服務端。連接過程中會自動進行配對,需要雙方同意纔可以連接成功。


4 數據傳輸

客戶端與服務端連接成功後都會調用connected(mmSocket, mmDevice),創建一個ConnectedThread線程()。

該線程主要用來接收和發送數據。客戶端和服務端處理方式一樣。該線程通過socket獲得輸入輸出流。

private  InputStream mmInStream = socket.getInputStream();

private  OutputStream mmOutStream =socket.getOutputStream();

發送數據

public void write(byte[] buffer) {
    try {
        mmOutStream.write(buffer);
        // 分享發送的信息到Activity
        mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer)
                .sendToTarget();
    } catch (IOException e) {
        Log.e(TAG, "Exception during write", e);
    }
}
接收數據

線程循環進行接收數據。

public void run() {
    // 監聽輸入流
    while (true) {
        try {
            byte[] buffer = new byte[1024];
            // 讀取輸入流
            int bytes = mmInStream.read(buffer);
            // 發送獲得的字節的ui activity
            Message msg = mHandler.obtainMessage(MESSAGE_READ);
            Bundle bundle = new Bundle();
            bundle.putByteArray(READ_MSG, buffer);
            msg.setData(bundle);
            mHandler.sendMessage(msg);          
        } catch (IOException e) {
            Log.e(TAG, "disconnected", e);
                connectionLost();
                break;
            }
        }
   }

demo下載:http://www.demodashi.com/demo/10676.html

歡迎掃一掃關注我的微信公衆號,定期推送優質技術文章


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章