Android -傳統藍牙通信聊天


概述

Android 傳統藍牙的使用,包括開關藍牙、搜索設備、藍牙連接、通信等。

詳細

原文地址:

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

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

一、准備工作

 

開發環境:

jdk1.8

Eclipse Luna Service Release 1 (4.4.1)

運行環境:

華為榮耀6(Android4.4)、華為p9(Android7.0)

實現功能:

  • Android 藍牙開發 (開關藍牙、搜索設備、藍牙配對、連接、通信、斷開連接等)。

二、代碼結構

代碼包里面,有兩個部分,一個是源碼,一個是V7支持包。

屏幕快照 2017-07-20 下午5.54.54.png

三、程序實現-藍牙通信

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;  
            }  
        }  
}

 

四、運行效果

1、運行,右鍵項目:Run as -》Android Application (備注:Eclipse需要配置Android開發環境)

2、運行效果如下:

 

客戶端

1500550538898087682.jpeg

 

服務端

Screenshot_2017-07-20-19-26-57.png

注:本文著作權歸作者,由demo大師發表,拒絕轉載,轉載需要作者授權


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM