Android藍(lán)牙應(yīng)用開發(fā)全面總結(jié)


前言

最近在做Android藍(lán)牙這部分內(nèi)容,所以查閱了很多相關(guān)資料,在此總結(jié)一下。


基本概念

  • Bluetooth是一種短距離(10米)的無線通信技術(shù)標(biāo)準(zhǔn),藍(lán)牙協(xié)議分為4層,即核心協(xié)議層、電纜替代協(xié)議層、電話控制協(xié)議層和采納的其它協(xié)議層。這4種協(xié)議中最重要的是核心協(xié)議。藍(lán)牙的核心協(xié)議包括基帶、鏈路管理、邏輯鏈路控制和適應(yīng)協(xié)議四部分。其中鏈路管理(LMP)負(fù)責(zé)藍(lán)牙組件間連接的建立。邏輯鏈路控制與適應(yīng)協(xié)議(L2CAP)位于基帶協(xié)議層上,屬于數(shù)據(jù)鏈路層,是一個為高層傳輸和應(yīng)用層協(xié)議屏蔽基帶協(xié)議的適配協(xié)議。

  • 安卓平臺提供對藍(lán)牙的通訊棧的支持,允許設(shè)別和其他的設(shè)備進(jìn)行無線傳輸數(shù)據(jù)。應(yīng)用程序?qū)油ㄟ^安卓API來調(diào)用藍(lán)牙的相關(guān)功能,這些API使程序無線連接到藍(lán)牙設(shè)備,并擁有P2P或者多端無線連接的特性。


  • 藍(lán)牙的功能:
  1. 掃描其他藍(lán)牙設(shè)備
  2. 為可配對的藍(lán)牙設(shè)備查詢藍(lán)牙適配器
  3. 建立RFCOMM通道(其實就是尼瑪?shù)恼J(rèn)證)
  4. 通過服務(wù)搜索來鏈接其他的設(shè)備
  5. 與其他的設(shè)備進(jìn)行數(shù)據(jù)傳輸
  6. 管理多個連接
  • 藍(lán)牙建立連接必須要求:
  1. 打開藍(lán)牙
  2. 查找附近已配對或可用設(shè)備
  3. 連接設(shè)備
  4. 設(shè)備間數(shù)據(jù)交互

藍(lán)牙API


代碼分布

packages/apps/Bluetooth/

  • 藍(lán)牙應(yīng)用,主要是關(guān)于藍(lán)牙應(yīng)用協(xié)議的表現(xiàn)代碼,包括opp、hfp、hdp、a2dp、pan等等

frameworks/base/core/Java/android/server/

  • 4.2以后這個目錄雖然還有,但里面代碼已經(jīng)轉(zhuǎn)移到應(yīng)用層了,就是前面那個目錄,所以4.2.2上的藍(lán)牙這里可以忽略。

framework/base/core/java/android/bluetooth

  • 這個目錄里的代碼更像一個橋梁,里面有供java層使用一些類,也有對應(yīng)的aidl文件聯(lián)系C、C++部分的代碼,還是挺重要的。

kernel\drivers\bluetoothBluetooth

  • 具體協(xié)議實現(xiàn)。包括hci,hid,rfcomm,sco,SDP等協(xié)議

kernel\net\bluetooth Linux kernel

  • 對各種接口的Bluetoothdevice的驅(qū)動。例如:USB接口,串口等,上面kernel這兩個目錄有可能看不到的,但一定會有的。

external\bluetooth\bluedroid

  • 官方藍(lán)牙協(xié)議棧

system\bluetoothBluetooth

  • 適配層代碼,和framework那個作用類似,是串聯(lián)framework與協(xié)議棧的工具。

關(guān)鍵類

/frameworks/base/core/java/android/bluetooth/

  • BluetoothAdapter 代表本地藍(lán)牙適配器(藍(lán)牙發(fā)射器),是所有藍(lán)牙交互的入口。通過它可以搜索其它藍(lán)牙設(shè)備,查詢已經(jīng)配對的設(shè)備列表,通過已知的MAC地址創(chuàng)建BluetoothDevice,創(chuàng)建BluetoothServerSocket監(jiān)聽來自其它設(shè)備的通信。
  • BluetoothDevice 代表了一個遠(yuǎn)端的藍(lán)牙設(shè)備, 使用它請求遠(yuǎn)端藍(lán)牙設(shè)備連接或者獲取 遠(yuǎn)端藍(lán)牙設(shè)備的名稱、地址、種類和綁定狀態(tài)。 (其信息是封裝在 bluetoothsocket 中) 。
  • BluetoothSocket 代表了一個藍(lán)牙套接字的接口(類似于 tcp 中的套接字) ,他是應(yīng)用程 序通過輸入、輸出流與其他藍(lán)牙設(shè)備通信的連接點。
  • BluetoothServerSocket 代表打開服務(wù)連接來監(jiān)聽可能到來的連接請求 (屬于 server 端) , 為了連接兩個藍(lán)牙設(shè)備必須有一個設(shè)備作為服務(wù)器打開一個服務(wù)套接字。 當(dāng)遠(yuǎn)端設(shè)備發(fā)起連 接連接請求的時候,并且已經(jīng)連接到了的時候,Blueboothserversocket 類將會返回一個 bluetoothsocket。
  • BluetoothClass 描述了一個設(shè)備的特性(profile)或該設(shè)備上的藍(lán)牙大致可以提供哪些服務(wù)(service),但不可信。比如,設(shè)備是一個電話、計算機(jī)或手持設(shè)備;Blueboothserversocket 設(shè)備可以提供audio/telephony服務(wù)等??梢杂盟鼇磉M(jìn)行一些UI上的提示。
  • BluetoothProfile 藍(lán)牙協(xié)議
  • BluetoothHeadset 提供手機(jī)使用藍(lán)牙耳機(jī)的支持。這既包括藍(lán)牙耳機(jī)和免提(V1.5)模式。
  • BluetoothA2dp 定義高品質(zhì)的音頻,可以從一個設(shè)備傳輸?shù)搅硪粋€藍(lán)牙連接。 “A2DP的”代表高級音頻分配模式。
  • BluetoothHealth 代表了醫(yī)療設(shè)備配置代理控制的藍(lán)牙服務(wù)
  • BluetoothHealthCallback 一個抽象類,使用實現(xiàn)BluetoothHealth回調(diào)。你必須擴(kuò)展這個類并實現(xiàn)回調(diào)方法接收更新應(yīng)用程序的注冊狀態(tài)和藍(lán)牙通道狀態(tài)的變化。
  • BluetoothHealthAppConfiguration 代表一個應(yīng)用程序的配置,藍(lán)牙醫(yī)療第三方應(yīng)用注冊與遠(yuǎn)程藍(lán)牙醫(yī)療設(shè)備交流。
  • BluetoothProfile.ServiceListener 當(dāng)他們已經(jīng)連接到或從服務(wù)斷開時通知BluetoothProfile IPX的客戶時一個接口(即運(yùn)行一個特定的配置文件,內(nèi)部服務(wù))。

\packages\apps\Settings\src\com\android\settings\bluetooth

  • BluetoothEnabler 界面上藍(lán)牙開啟、關(guān)閉的開關(guān)就是它了,
  • BluetoothSettings 主界面,用于管理配對和連接設(shè)備
  • LocalBluetoothManager 提供了藍(lán)牙API上的簡單調(diào)用接口,這里只是開始。
  • CachedBluetoothDevice 描述藍(lán)牙設(shè)備的類,對BluetoothDevice的再封裝
  • BluetoothPairingDialog 那個配對提示的對話框

/packages/apps/Phone/src/com/android/phone/

  • BluetoothPhoneService 在phone的目錄肯定和電話相關(guān)了,藍(lán)牙接聽掛斷電話會用到這個

/packages/apps/Bluetooth/src/com/android/bluetooth/

說到這里不能不說4.2藍(lán)牙的目錄變了,在4.1及以前的代碼中packages層的代碼只有opp協(xié)議相關(guān)應(yīng)用的代碼,也就是文件傳輸那部分,而4.2的代碼應(yīng)用層的代碼則豐富了許多,按具體的藍(lán)牙應(yīng)用協(xié)議來區(qū)別,分為以下文件夾(這里一并對藍(lán)牙一些名詞作個簡單解釋)

  • btservice 這個前面AdapterService.java的描述大家應(yīng)該能猜到一些,關(guān)于藍(lán)牙基本操作的目錄,一切由此開始。

    • AdapterService (4.2后才有的代碼)藍(lán)牙打開、關(guān)閉、掃描、配對都會走到這里,其實更準(zhǔn)確的說它替代了4.1之前的BluetoothService.java,原來的工作就由這個類來完成了。
  • a2dp (Advanced Audio Distribution Profile)高級音頻傳輸模式,藍(lán)牙立體聲,和藍(lán)牙耳機(jī)聽歌有關(guān)那些。

  • avrcp 音頻/視頻遠(yuǎn)程控制配置文件,是用來聽歌時暫停,上下歌曲選擇的。

  • hdp (Health Device Profile)藍(lán)牙醫(yī)療設(shè)備模式,可以創(chuàng)建支持藍(lán)牙的醫(yī)療設(shè)備,使用藍(lán)牙通信的應(yīng)用,例如心率監(jiān)視器,血液,溫度計和秤。

  • hfp (Hands-free Profile)讓藍(lán)牙設(shè)備可以控制電話,如接聽、掛斷、拒接、語音撥號等,拒接、語音撥號要視藍(lán)牙耳機(jī)及電話是否支持。

  • pbap (Phonebook Access Profile)電話號碼簿訪問協(xié)議

  • hid (The Human Interface Device)人機(jī)交互接口,藍(lán)牙鼠標(biāo)鍵盤什么的就是這個了。該協(xié)議改編自USB HID Protocol。

  • opp (Object Push Profile)對象存儲規(guī)范,最為常見的,文件的傳輸都是使用此協(xié)議。

  • pan (Personal Area Network)描述了兩個或更多個藍(lán)牙設(shè)備如何構(gòu)成一個即時網(wǎng)絡(luò),和網(wǎng)絡(luò)有關(guān)還有串行端口功能(SPP),撥號網(wǎng)絡(luò)功能(DUN)

android 4.2的藍(lán)牙應(yīng)用層部分代碼更豐富了,雖然有些目錄還沒具體代碼,不過說不準(zhǔn)哪個版本更新就有了,就像4.0添加了hdp醫(yī)療那部分一樣。另外原本在framework的JNI代碼也被移到packages/apps/bluetooth當(dāng)中。


主要方法

  • BluetoothAdapter (藍(lán)牙本地適配器)

    • getDefaultAdapter() 得到本地藍(lán)牙適配器
    • setName(String name) 設(shè)置藍(lán)牙名稱
    • disable() 關(guān)閉藍(lán)牙
    • enable() 打開藍(lán)牙
    • isEnabled() 判斷藍(lán)牙是否打開
    • getName() 得到本地藍(lán)牙的名稱
    • getAddress() 得到本地藍(lán)牙適配器的地址
    • getBondedDevices() 得到已經(jīng)綁定的藍(lán)牙的設(shè)備
    • getRemoteDevice(byte[] address) 得到遠(yuǎn)程藍(lán)牙設(shè)備
    • getRemoteDevice(String address) 得到遠(yuǎn)程藍(lán)牙設(shè)備
    • startDiscovery() 開始搜多附近藍(lán)牙
    • cancelDiscovery() 停止當(dāng)前搜索藍(lán)牙的 Task
    • listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid) 創(chuàng)建 BluetoothServerSocket
  • BluetoothDevice (藍(lán)牙設(shè)備)

    • createBond() 藍(lán)牙配對 (低版本不支持,>=api19)
    • createRfcommSocketToServiceRecord(UUID uuid) 創(chuàng)建 BluetoothSocket
    • getBondState() 得到配對的狀態(tài)
    • getAddress() 得到遠(yuǎn)程藍(lán)牙適配器的地址
    • getName() 得到遠(yuǎn)程藍(lán)牙的名稱
  • BluetoothServerSocket (數(shù)據(jù)傳輸服務(wù)端)
    這個類一共只有三個方法兩個重載的。兩個重載的區(qū)別在于后面的方法指定了過時時間,需要注意的是,執(zhí)行這兩個方法的時候,直到接收到了客戶端的請求(或是過期之后),都會阻塞線程,應(yīng)該放在新線程里運(yùn)行!

    • close() 關(guān)閉
    • connect() 連接
    • isConnected() 判斷當(dāng)前的連接狀態(tài)
    • accept() 接收請求
    • accept(int timeout) 接收請求
  • BluetoothSocket (數(shù)據(jù)傳輸客戶端)

    • close() 關(guān)閉
    • connect() 連接
    • getInptuStream() 獲取輸入流
    • getOutputStream() 獲取輸出流
    • getRemoteDevice() 獲取遠(yuǎn)程設(shè)備,這里指的是獲取bluetoothSocket指定連接的那個遠(yuǎn)程藍(lán)牙設(shè)備

藍(lán)牙操作


打開和關(guān)閉藍(lán)牙

開啟藍(lán)牙有兩種方法:

一、直接調(diào)用系統(tǒng)對話框啟動藍(lán)牙:

AndroidManifest.xml文件中添加需要的權(quán)限,高版本也不需要動態(tài)授權(quán):

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

然后,在代碼中執(zhí)行:

startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 1);

如果不想讓用戶看到對話框,那么我們還可以選擇第二種方法,進(jìn)行靜默開啟藍(lán)牙。

二、靜默開啟,不會有方法一的對話框:

照樣在AndroidManifest.xml文件中添加需要的權(quán)限:

<!-- 已適配Android6.0 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />  
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />  
<uses-feature
    android:name="android.hardware.bluetooth_le"
    android:required="true" />

由于藍(lán)牙所需要的權(quán)限包含Dangerous Permissions,所以我們需要在Java代碼中進(jìn)行動態(tài)授權(quán)處理:

private static final int REQUEST_BLUETOOTH_PERMISSION=10;

private void requestBluetoothPermission(){
    //判斷系統(tǒng)版本
    if (Build.VERSION.SDK_INT >= 23) {
        //檢測當(dāng)前app是否擁有某個權(quán)限
        int checkCallPhonePermission = ContextCompat.checkSelfPermission(this, 
                Manifest.permission.ACCESS_COARSE_LOCATION);
        //判斷這個權(quán)限是否已經(jīng)授權(quán)過
        if(checkCallPhonePermission != PackageManager.PERMISSION_GRANTED){
            //判斷是否需要 向用戶解釋,為什么要申請該權(quán)限
            if(ActivityCompat.shouldShowRequestPermissionRationale(this, 
                    Manifest.permission.ACCESS_COARSE_LOCATION))
                Toast.makeText(this,"Need bluetooth permission.", 
                        Toast.LENGTH_SHORT).show();
            ActivityCompat.requestPermissions(this ,new String[]
                    {Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_BLUETOOTH_PERMISSION);
            return;
        }else{
        }
    } else {
    }
}

接下來我們就可以靜默開啟藍(lán)牙了:

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.enable(); //開啟

關(guān)閉藍(lán)牙

if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
    mBluetoothAdapter.disable();
}

搜索藍(lán)牙設(shè)備

搜索分為主動搜索和被動搜索:

一、被動搜索

if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) 
{
    Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    // 設(shè)置被發(fā)現(xiàn)時間,最大值是3600秒,0表示設(shè)備總是可以被發(fā)現(xiàn)的(小于0或者大于3600則會被自動設(shè)置為120秒)
    discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);
    activity.startActivity(discoverableIntent);
}

二、主動搜索

創(chuàng)建BluetoothAdapter對象

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

我們先獲取并顯示一下已經(jīng)配對的藍(lán)牙設(shè)備列表

/*
 * 已配對設(shè)備列表
 */
private ListView mBoundDevicesLv;
/**
 * 顯示已配對的設(shè)備列表
 */
private void showBoundDevices() {
    List<Map<String, String>> mBoundDevicesList = new ArrayList<>();
    Set<BluetoothDevice> boundDeviceSet = mBluetoothAdapter.getBondedDevices();
    for (BluetoothDevice boundDevices : boundDeviceSet) {
        Map<String, String> mBoundDevicesMap = new HashMap<>();
        mBoundDevicesMap.put("name", boundDevices.getName());
        mBoundDevicesMap.put("address", boundDevices.getAddress());
        mBoundDevicesList.add(mBoundDevicesMap);
    }
    SimpleAdapter mSimpleAdapter = new SimpleAdapter(MainActivity.this, mBoundDevicesList,
            android.R.layout.simple_list_item_2,
            new String[]{"name", "address"},
            new int[]{android.R.id.text1, android.R.id.text2});
    mBoundDevicesLv.setAdapter(mSimpleAdapter);
}

開始搜索

if (mBluetoothAdapter == null) {
    LogUtil.e(TAG, "設(shè)備不支持藍(lán)牙");
}
// 打開藍(lán)牙       
if (!mBluetoothAdapter.isEnabled()) {
    BluetoothAdapter.enable();
    mBluetoothAdapter.cancelDiscovery();
}
// 尋找藍(lán)牙設(shè)備,android會將查找到的設(shè)備以廣播形式發(fā)出去       
while (!mBluetoothAdapter.startDiscovery()) {
    LogUtil.e(TAG, "嘗試失敗");
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }   
}

定義搜索結(jié)果的廣播接收器

// 設(shè)置廣播信息過濾
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);//每搜索到一個設(shè)備就會發(fā)送一個該廣播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//當(dāng)全部搜索完后發(fā)送該廣播
filter.setPriority(Integer.MAX_VALUE);//設(shè)置優(yōu)先級
registerReceiver(receiver, filter);// 注冊藍(lán)牙搜索廣播接收者,接收并處理搜索結(jié)果

搜索藍(lán)牙設(shè)備的廣播接收器如下:

/**
 * 搜索出的設(shè)備集合
 */
private List<Map<String, String>> devices = new ArrayList<>();
/**
 * 發(fā)現(xiàn)的設(shè)備列表
 */
private ListView mDevicesLv;
/**
 * 定義廣播接收器
 */
private final BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            ToastUtil.showToast(MainActivity.this, "Showing Devices");
                // 從Intent中獲取設(shè)備對象
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // 定義一個裝載藍(lán)牙設(shè)備名字和地址的Map
                Map<String, String> deviceMap = new HashMap<>();
                // 過濾已配對的和重復(fù)的藍(lán)牙設(shè)備
                if ((device.getBondState() != BluetoothDevice.BOND_BONDED) && isSingleDevice(device)) {
                    deviceMap.put("name", device.getName() == null ? "null" : device.getName());
                    deviceMap.put("address", device.getAddress());
                    devices.add(deviceMap);
                }
                // 顯示發(fā)現(xiàn)的藍(lán)牙設(shè)備列表
                mDevicesLv.setVisibility(View.VISIBLE);
                // 加載設(shè)備
                showDevices();
        } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
            //已搜素完成
        }
    }
};定義服務(wù)端線程類:

/**
 * 判斷此設(shè)備是否存在
 */
private boolean isSingleDevice(BluetoothDevice device) {
    if (devices == null) {
        return true;
    }
    for (Map<String, String> mDeviceMap : devices) {
        if ((device.getAddress()).equals(mDeviceMap.get("address"))) {
            return false;
        }
    }
    return true;
}

/**
 * 顯示搜索到的設(shè)備列表
 */
private void showDevices() {
    SimpleAdapter mSimpleAdapter = new SimpleAdapter(MainActivity.this, devices,
            android.R.layout.simple_list_item_2,
            new String[]{"name", "address"},
            new int[]{android.R.id.text1, android.R.id.text2});
    mDevicesLv.setAdapter(mSimpleAdapter);
}

藍(lán)牙配對

當(dāng)我們搜索到了藍(lán)牙的之后,就需要配對,因為只有在配對之后才能連接。

在上面的搜索到的設(shè)備列表的點擊事件中,進(jìn)行配對。

BluetoothDevice device = (BluetoothDevice) adapter.getItem(i);
if (device.getBondState() == BluetoothDevice.BOND_BONDED) {//是否已配對
    connect(device);
} else {
    try {
        Method boned=device.getClass().getMethod("createBond");
        boolean isok= (boolean) boned.invoke(device);
        if(isOk) {
            connect(device);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }         
}

這里需要說明的是,這個配對Android在API19之后對外提供了createBond()這個方法。但是在API19以前并沒有這個方法,所以用反射兼容性比較好。


藍(lán)牙的UUID

在進(jìn)行藍(lán)牙連接之前,先介紹一下一個關(guān)鍵的東西:兩個藍(lán)牙設(shè)備進(jìn)行連接時需要使用同一個UUID。但很多讀者可能發(fā)現(xiàn),有很多型號的手機(jī)(可能是非Android系統(tǒng)的手機(jī))之間使用了不同的程序也可以使用藍(lán)牙進(jìn)行通訊。從表面上看,它們之間幾乎不可能使用同一個UUID。

UUID的格式如下:

xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

UUID的格式被分成5段,其中中間3段的字符數(shù)相同,都是4,第1段是8個字符,最后一段是12個字符。所以UUID實際上是一個8-4-4-4-12的字符串。

實際上,UUID和TCP的端口一樣,也有一些默認(rèn)的值。例如,將藍(lán)牙模擬成串口的服務(wù)就使用了一個標(biāo)準(zhǔn)的UUID:

00001101-0000-1000-8000-00805F9B34FB

除此之外,還有很多標(biāo)準(zhǔn)的UUID,如下面就是兩個標(biāo)準(zhǔn)的UUID:

信息同步服務(wù):00001104-0000-1000-8000-00805F9B34FB
文件傳輸服務(wù):00001106-0000-1000-8000-00805F9B34FB


藍(lán)牙設(shè)備間的數(shù)據(jù)傳輸

藍(lán)牙傳輸數(shù)據(jù)與Socket類似。在網(wǎng)絡(luò)中使用Socket和ServerSocket控制客戶端和服務(wù)端的數(shù)據(jù)讀寫。而藍(lán)牙通訊也由客戶端和服務(wù)端Socket來完成。藍(lán)牙客戶端Socket是BluetoothSocket,藍(lán)牙服務(wù)端Socket是BluetoothServerSocket。這兩個類都在android.bluetooth包中。

無論是BluetoothSocket,還是BluetoothServerSocket,都需要一個UUID(全局唯一標(biāo)識符,Universally Unique Identifier),UUID相當(dāng)于Socket的端口,而藍(lán)牙地址相當(dāng)于Socket的IP。

下面,我們開始進(jìn)行模擬一個藍(lán)牙數(shù)據(jù)的傳輸:

一、首先來看客戶端:

定義全局常量變量:

private ListView mDevicesLv;
private BluetoothAdapter mBluetoothAdapter;
private List<Map<String, String>> devices = new ArrayList<>();
//隨便定義一個UUID
private final UUID MY_UUID = UUID.fromString("abcd1234-ab12-ab12-ab12-abcdef123456");
private BluetoothSocket clientSocket;
private BluetoothDevice device;  
private OutputStream os;//輸出流

接下來我們設(shè)置設(shè)備列表的點擊事件

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    Map<String, String> s = devices.get(i);
    String address = s.get("address");//把地址解析出來
    //主動連接藍(lán)牙服務(wù)端
    try {
        // 如果當(dāng)前正在搜索,則取消搜索。
        if (mBluetoothAdapter.isDiscovering()) {
            mBluetoothAdapter.cancelDiscovery();
        }
        try {
            if (device == null) {
                //獲得遠(yuǎn)程設(shè)備
                device = mBluetoothAdapter.getRemoteDevice(address);
            }
            if (clientSocket == null) {
                //創(chuàng)建客戶端藍(lán)牙Socket
                clientSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
                //開始連接藍(lán)牙,如果沒有配對則彈出對話框提示我們進(jìn)行配對
                clientSocket.connect();
                //獲得輸出流(客戶端指向服務(wù)端輸出文本)
                os = clientSocket.getOutputStream();
            }
        } catch (Exception e) {
        }
        if (os != null) {
            //往服務(wù)端寫信息
            os.write("藍(lán)牙信息來了".getBytes("utf-8"));
        }
    } catch (Exception e) {
    }
}

二、接下來看服務(wù)端:

服務(wù)端使用的是另一部手機(jī),接受上面手機(jī)通過藍(lán)牙發(fā)送過來的信息并顯示。

定義全局常量變量:

private BluetoothAdapter mBluetoothAdapter;
private AcceptThread acceptThread;
// 和客戶端相同的UUID
private final UUID MY_UUID = UUID.fromString("abcd1234-ab12-ab12-ab12-abcdef123456");
private final String NAME = "Bluetooth_Socket";
private BluetoothServerSocket serverSocket;
private BluetoothSocket socket;
private InputStream is;//輸入流

定義服務(wù)端線程類:

private Handler handler = new Handler() {
    public void handleMessage(Message msg) {
        Toast.makeText(getApplicationContext(), String.valueOf(msg.obj),
                Toast.LENGTH_LONG).show();
        super.handleMessage(msg);
    }
};

// 服務(wù)端監(jiān)聽客戶端的線程類
private class AcceptThread extends Thread {
    public AcceptThread() {
        try {
            serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
        } catch (Exception e) {
        }
    }
    public void run() {
        try {
            socket = serverSocket.accept();
            is = socket.getInputStream();
            while(true) {
                byte[] buffer =new byte[1024];
                int count = is.read(buffer);
                Message msg = new Message();
                msg.obj = new String(buffer, 0, count, "utf-8");
                handler.sendMessage(msg);
            }
        }
        catch (Exception e) {
        }
    }
}

在onCreate方法中初始化線程類并開啟:

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
acceptThread = new AcceptThread();
acceptThread.start();

注意,使用socket.getInputStream接收到的數(shù)據(jù)是字節(jié)流,這樣的數(shù)據(jù)是沒法分析的,所以很多情況需要一個byte轉(zhuǎn)十六進(jìn)制String的函數(shù):

public static String bytesToHex(byte[] bytes) { 
    char[] hexChars = new char[bytes.length * 2]; 
    for ( int j = 0; j < bytes.length; j++ ) {     
        int v = bytes[j] & 0xFF;     
        hexChars[j * 2] = hexArray[v >>> 4];     
        hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 
    } 
    return new String(hexChars);
}

藍(lán)牙協(xié)議


藍(lán)牙協(xié)議簡介

從Android 3.0開始,Bluetooth API就包含了對Bluetooth profiles的支持。

Bluetooth profile是基于藍(lán)牙的設(shè)備之間通信的無線接口規(guī)范。

你在你的類里可以完成BluetoothProfile接口來支持某一Bluetooth profile。

Android Bluetooth API完成了下面的Bluetooth profile:

  1. Headset profile提供了移動電話上的Bluetooth耳機(jī)支持。Android提供了BluetoothHeadset類,它是一個協(xié)議,用來通過IPC(interprocess communication)控制Bluetooth Headset Service。BluetoothHeadset既包含Bluetooth Headset profile也包含Hands-Free profile,還包括對AT命令的支持。
  2. HFP (Hands-free Profile),免提模式,讓藍(lán)牙設(shè)備可以控制電話,如接聽、掛斷、拒接、語音撥號等,拒接、語音撥號要視藍(lán)牙耳機(jī)及電話是否支持。
  3. HDP(Health Device Profile.),藍(lán)牙醫(yī)療設(shè)備模式,可以創(chuàng)建支持藍(lán)牙的醫(yī)療設(shè)備,使用藍(lán)牙通信的應(yīng)用,例如心率監(jiān)視器,血液,溫度計和秤。
  4. AVRCP,音頻/視頻遠(yuǎn)程控制配置文件,是用來聽歌時暫停,上下歌曲選擇的。
  5. A2DP(Advanced Audio Distribution Profile),高級音頻傳輸模式。Android提供了BluetoothA2dp類,這是一個通過IPC來控制Bluetooth A2DP的協(xié)議。
  6. HID (The Human Interface Device),人機(jī)交互接口,藍(lán)牙鼠標(biāo)鍵盤什么的就是這個了。該協(xié)議改編自USB HID Protocol。
  7. OPP (Object Push Profile),對象存儲規(guī)范,最為常見的,文件的傳輸都是使用此協(xié)議。
  8. PAN (Personal Area Network),描述了兩個或更多個藍(lán)牙設(shè)備如何構(gòu)成一個即時網(wǎng)絡(luò),和網(wǎng)絡(luò)有關(guān)還有串行端口功能(SPP),撥號網(wǎng)絡(luò)功能(DUN)。
  9. PBAP (Phonebook Access Profile),電話號碼簿訪問協(xié)議。

藍(lán)牙協(xié)議的使用

下面是使用profile的基本步驟:

  1. 獲取默認(rèn)的Bluetooth適配器。
  2. 使用getProfileProxy()來建立一個與profile相關(guān)的profile協(xié)議對象的連接。在下面的例子中,profile協(xié)議對象是BluetoothHeadset的一個實例。
  3. 設(shè)置BluetoothProfile.ServiceListener。該listener通知BluetoothProfile IPC客戶端,當(dāng)客戶端連接或斷連服務(wù)器的時候
  4. 在BluetoothProfile.ServiceListener的onServiceConnected()內(nèi),得到一個profile協(xié)議對象的句柄。
  5. 一旦擁有了profile協(xié)議對象,就可以用它來監(jiān)控連接的狀態(tài),完成于該profile相關(guān)的其他操作。

例如,下面的代碼片段顯示如何連接到一個BluetoothHeadset協(xié)議對象,用來控制Headset profile:

BluetoothHeadset mBluetoothHeadset;
// 獲取默認(rèn)的Bluetooth適配器
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 連接Headset profile
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener(){
    public void onServiceConnected(int profile, BluetoothProfile proxy) {
        if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset = (BluetoothHeadset) proxy;
        }
    }
    public void onServiceDisconnected(int profile) {
        if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset = null;
        }
    }
};

// ... 使用 mBluetoothHeadset

// 使用之后,關(guān)閉Proxy
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset)

以上,就先分析到這兒吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容