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ù)鏈路層,是一個(gè)為高層傳輸和應(yīng)用層協(xié)議屏蔽基帶協(xié)議的適配協(xié)議影锈。

  • 安卓平臺(tái)提供對(duì)藍(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. 為可配對(duì)的藍(lán)牙設(shè)備查詢藍(lán)牙適配器
  3. 建立RFCOMM通道(其實(shí)就是尼瑪?shù)恼J(rèn)證)
  4. 通過服務(wù)搜索來鏈接其他的設(shè)備
  5. 與其他的設(shè)備進(jìn)行數(shù)據(jù)傳輸
  6. 管理多個(gè)連接
  • 藍(lán)牙建立連接必須要求:
  1. 打開藍(lán)牙
  2. 查找附近已配對(duì)或可用設(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以后這個(gè)目錄雖然還有,但里面代碼已經(jīng)轉(zhuǎn)移到應(yīng)用層了咨堤,就是前面那個(gè)目錄菇篡,所以4.2.2上的藍(lán)牙這里可以忽略。

framework/base/core/java/android/bluetooth

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

kernel\drivers\bluetoothBluetooth

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

kernel\net\bluetooth Linux kernel

  • 對(duì)各種接口的Bluetoothdevice的驅(qū)動(dòng)咪鲜。例如:USB接口狐赡,串口等,上面kernel這兩個(gè)目錄有可能看不到的疟丙,但一定會(huì)有的颖侄。

external\bluetooth\bluedroid

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

system\bluetoothBluetooth

  • 適配層代碼,和framework那個(gè)作用類似享郊,是串聯(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)配對(duì)的設(shè)備列表,通過已知的MAC地址創(chuàng)建BluetoothDevice,創(chuàng)建BluetoothServerSocket監(jiān)聽來自其它設(shè)備的通信炊琉。
  • BluetoothDevice 代表了一個(gè)遠(yuǎn)端的藍(lán)牙設(shè)備, 使用它請(qǐng)求遠(yuǎn)端藍(lán)牙設(shè)備連接或者獲取 遠(yuǎn)端藍(lán)牙設(shè)備的名稱展蒂、地址、種類和綁定狀態(tài)苔咪。 (其信息是封裝在 bluetoothsocket 中) 锰悼。
  • BluetoothSocket 代表了一個(gè)藍(lán)牙套接字的接口(類似于 tcp 中的套接字) ,他是應(yīng)用程 序通過輸入、輸出流與其他藍(lán)牙設(shè)備通信的連接點(diǎn)团赏。
  • BluetoothServerSocket 代表打開服務(wù)連接來監(jiān)聽可能到來的連接請(qǐng)求 (屬于 server 端) , 為了連接兩個(gè)藍(lán)牙設(shè)備必須有一個(gè)設(shè)備作為服務(wù)器打開一個(gè)服務(wù)套接字箕般。 當(dāng)遠(yuǎn)端設(shè)備發(fā)起連 接連接請(qǐng)求的時(shí)候,并且已經(jīng)連接到了的時(shí)候,Blueboothserversocket 類將會(huì)返回一個(gè) bluetoothsocket。
  • BluetoothClass 描述了一個(gè)設(shè)備的特性(profile)或該設(shè)備上的藍(lán)牙大致可以提供哪些服務(wù)(service),但不可信舔清。比如,設(shè)備是一個(gè)電話丝里、計(jì)算機(jī)或手持設(shè)備;Blueboothserversocket 設(shè)備可以提供audio/telephony服務(wù)等√遐耍可以用它來進(jìn)行一些UI上的提示丙者。
  • BluetoothProfile 藍(lán)牙協(xié)議
  • BluetoothHeadset 提供手機(jī)使用藍(lán)牙耳機(jī)的支持。這既包括藍(lán)牙耳機(jī)和免提(V1.5)模式营密。
  • BluetoothA2dp 定義高品質(zhì)的音頻,可以從一個(gè)設(shè)備傳輸?shù)搅硪粋€(gè)藍(lán)牙連接。 “A2DP的”代表高級(jí)音頻分配模式目锭。
  • BluetoothHealth 代表了醫(yī)療設(shè)備配置代理控制的藍(lán)牙服務(wù)
  • BluetoothHealthCallback 一個(gè)抽象類,使用實(shí)現(xiàn)BluetoothHealth回調(diào)评汰。你必須擴(kuò)展這個(gè)類并實(shí)現(xiàn)回調(diào)方法接收更新應(yīng)用程序的注冊狀態(tài)和藍(lán)牙通道狀態(tài)的變化。
  • BluetoothHealthAppConfiguration 代表一個(gè)應(yīng)用程序的配置,藍(lán)牙醫(yī)療第三方應(yīng)用注冊與遠(yuǎn)程藍(lán)牙醫(yī)療設(shè)備交流痢虹。
  • BluetoothProfile.ServiceListener 當(dāng)他們已經(jīng)連接到或從服務(wù)斷開時(shí)通知BluetoothProfile IPX的客戶時(shí)一個(gè)接口(即運(yùn)行一個(gè)特定的配置文件,內(nèi)部服務(wù))被去。

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

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

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

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

/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ū)別耍攘,分為以下文件夾(這里一并對(duì)藍(lán)牙一些名詞作個(gè)簡單解釋)

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

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

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

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

  • hfp (Hands-free Profile)讓藍(lán)牙設(shè)備可以控制電話五嫂,如接聽、掛斷肯尺、拒接沃缘、語音撥號(hào)等,拒接则吟、語音撥號(hào)要視藍(lán)牙耳機(jī)及電話是否支持槐臀。

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

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

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

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

android 4.2的藍(lán)牙應(yīng)用層部分代碼更豐富了欢顷,雖然有些目錄還沒具體代碼槽棍,不過說不準(zhǔn)哪個(gè)版本更新就有了,就像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)牙配對(duì) (低版本不支持,>=api19)
    • createRfcommSocketToServiceRecord(UUID uuid) 創(chuàng)建 BluetoothSocket
    • getBondState() 得到配對(duì)的狀態(tài)
    • getAddress() 得到遠(yuǎn)程藍(lán)牙適配器的地址
    • getName() 得到遠(yuǎn)程藍(lán)牙的名稱
  • BluetoothServerSocket (數(shù)據(jù)傳輸服務(wù)端)
    這個(gè)類一共只有三個(gè)方法兩個(gè)重載的布持。兩個(gè)重載的區(qū)別在于后面的方法指定了過時(shí)時(shí)間豌拙,需要注意的是,執(zhí)行這兩個(gè)方法的時(shí)候题暖,直到接收到了客戶端的請(qǐng)求(或是過期之后)按傅,都會(huì)阻塞線程,應(yīng)該放在新線程里運(yùn)行胧卤!

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

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

藍(lán)牙操作


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

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

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

AndroidManifest.xml文件中添加需要的權(quán)限枝誊,高版本也不需要?jiǎng)討B(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);

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

二、靜默開啟祠够,不會(huì)有方法一的對(duì)話框:

照樣在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)行動(dòng)態(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是否擁有某個(gè)權(quán)限
        int checkCallPhonePermission = ContextCompat.checkSelfPermission(this, 
                Manifest.permission.ACCESS_COARSE_LOCATION);
        //判斷這個(gè)權(quán)限是否已經(jīng)授權(quán)過
        if(checkCallPhonePermission != PackageManager.PERMISSION_GRANTED){
            //判斷是否需要 向用戶解釋,為什么要申請(qǐng)?jiān)摍?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è)備

搜索分為主動(dòng)搜索和被動(dòng)搜索:

一古瓤、被動(dòng)搜索

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

二、主動(dòng)搜索

創(chuàng)建BluetoothAdapter對(duì)象

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

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

/*
 * 已配對(duì)設(shè)備列表
 */
private ListView mBoundDevicesLv;
/**
 * 顯示已配對(duì)的設(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會(huì)將查找到的設(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);//每搜索到一個(gè)設(shè)備就會(huì)發(fā)送一個(gè)該廣播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//當(dāng)全部搜索完后發(fā)送該廣播
filter.setPriority(Integer.MAX_VALUE);//設(shè)置優(yōu)先級(jí)
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è)備對(duì)象
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // 定義一個(gè)裝載藍(lán)牙設(shè)備名字和地址的Map
                Map<String, String> deviceMap = new HashMap<>();
                // 過濾已配對(duì)的和重復(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)牙配對(duì)

當(dāng)我們搜索到了藍(lán)牙的之后,就需要配對(duì)叽奥,因?yàn)橹挥性谂鋵?duì)之后才能連接。

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

BluetoothDevice device = (BluetoothDevice) adapter.getItem(i);
if (device.getBondState() == BluetoothDevice.BOND_BONDED) {//是否已配對(duì)
    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();
    }         
}

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


藍(lán)牙的UUID

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

UUID的格式如下:

xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

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

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

00001101-0000-1000-8000-00805F9B34FB

除此之外俯渤,還有很多標(biāo)準(zhǔn)的UUID呆细,如下面就是兩個(gè)標(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臀叙。這兩個(gè)類都在android.bluetooth包中略水。

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

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

一、首先來看客戶端:

定義全局常量變量:

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

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

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    Map<String, String> s = devices.get(i);
    String address = s.get("address");//把地址解析出來
    //主動(dòng)連接藍(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)牙,如果沒有配對(duì)則彈出對(duì)話框提示我們進(jìn)行配對(duì)
                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ù)是沒法分析的暖侨,所以很多情況需要一個(gè)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就包含了對(duì)Bluetooth profiles的支持。

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

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

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

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

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

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

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

例如,下面的代碼片段顯示如何連接到一個(gè)BluetoothHeadset協(xié)議對(duì)象,用來控制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)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載研侣,如需轉(zhuǎn)載請(qǐng)通過簡信或評(píng)論聯(lián)系作者谱邪。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市庶诡,隨后出現(xiàn)的幾起案子惦银,更是在濱河造成了極大的恐慌,老刑警劉巖末誓,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扯俱,死亡現(xiàn)場離奇詭異,居然都是意外死亡喇澡,警方通過查閱死者的電腦和手機(jī)迅栅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撩幽,“玉大人库继,你說我怎么就攤上這事〈茏恚” “怎么了宪萄?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長榨惰。 經(jīng)常有香客問我拜英,道長,這世上最難降的妖魔是什么琅催? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任居凶,我火速辦了婚禮虫给,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侠碧。我一直安慰自己抹估,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布弄兜。 她就那樣靜靜地躺著药蜻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪替饿。 梳的紋絲不亂的頭發(fā)上语泽,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音视卢,去河邊找鬼踱卵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛据过,可吹牛的內(nèi)容都是我干的惋砂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼绳锅,長吁一口氣:“原來是場噩夢啊……” “哼班利!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起榨呆,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤罗标,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后积蜻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闯割,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年竿拆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宙拉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丙笋,死狀恐怖谢澈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情御板,我是刑警寧澤锥忿,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站怠肋,受9級(jí)特大地震影響敬鬓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一钉答、第九天 我趴在偏房一處隱蔽的房頂上張望础芍。 院中可真熱鬧,春花似錦数尿、人聲如沸仑性。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虏缸。三九已至,卻和暖如春嫩实,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窥岩。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國打工甲献, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颂翼。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓晃洒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親朦乏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子球及,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容