主頁 > 移動端開發 > Android筆記---藍牙開發BT和BLE

Android筆記---藍牙開發BT和BLE

2021-06-12 08:46:40 移動端開發

目錄

  • 前言
    • 一般開發步驟
    • 相關API介紹
      • 一、通用API
        • 1.BluetoothAdapter
        • 2.BluetoothDevice
      • 二、經典藍牙(BT)API
        • 1.BluetoothSocket
        • 2.BluetoothServerSocket
        • 3.BluetoothClass
        • 4.BluetoothProfile
      • 三、低功耗藍牙(BLE)API
        • 1.BluetoothGatt
        • 2.BluetoothGattCallback
        • 3.BluetoothGattService
        • 4.BluetoothGattCharacteristic
        • 5.BluetoothGattDescriptor
  • 1.加入藍牙權限
    • (1)android.permission.BLUETOOTH
    • (2)android.permission.BLUETOOTH_ADMIN
  • 2.是否支持藍牙
  • 3.是否打開藍牙
    • (1)判斷藍牙是否打開
    • (2)友好提示用戶打開藍牙
    • (3)監聽用戶是否打開藍牙的操作回呼
  • 4.掃描藍牙
    • (1)經典藍牙搜索
      • ① 新建藍牙廣播接收器
      • ② 動態注冊藍牙廣播接收器
      • ② 開啟掃描藍牙
    • (2)低功耗藍牙搜索
  • 5.藍牙連接與斷開
    • (1)經典藍牙(BT)
      • ① createRfcommSocketToServiceRecord獲取BluetoothSocket
      • ② 反射獲取BluetoothSocket
      • ③ 連接藍牙
      • ④ 靜默設定配對碼
      • ⑤ 斷開連接
    • (2)低功耗藍牙(BLE)
      • 連接之前
        • GATT層次結構
        • 各成員作用
      • ① 設定自定義回呼
      • ② 啟動藍牙連接
      • ③ 發現GATT服務
      • ④ 配置通信
      • ⑤ 斷開連接
  • 6.通信
    • (1)經典藍牙通信
      • ① 獲取輸入輸出流
      • ② 發送資料
      • ③ 讀取資料
    • (2)低功耗藍牙通信
      • ① 發送資料
      • ② 讀取資料
  • 7.補充
        • 參考

前言

在做藍牙技術開發前,首先可以了解一下藍牙模塊分類:
藍牙分類
現今藍牙模塊開發分為經典藍牙和低功耗藍牙開發,目前市面上最火的物聯網技術大多選擇低功耗藍牙模塊,效率高且功耗低,缺點是只支持小資料量的資料傳輸,在Android 4.3 (API 18)后開始引入;而對于資料量較大的傳輸,如音視頻等開發,則需要使用經典藍牙模塊,在開發前一定要先區分藍牙模塊型別,因為兩種藍牙模塊的開發是不同的,在本篇文章兩種藍牙模塊的開發步驟都會介紹到,

一般開發步驟

藍牙模塊開發步驟如下:
在這里插入圖片描述

兩種藍牙模塊的開發步驟大致一樣,僅藍牙連接和藍牙通信時有所區分,

相關API介紹

一、通用API

1.BluetoothAdapter

本地藍牙配接器,用于一些藍牙的基本操作,比如判斷藍牙是否開啟、搜索藍牙設備等,

2.BluetoothDevice

藍牙設備物件,包含一些藍牙設備的屬性,比如設備名稱、mac地址等,

二、經典藍牙(BT)API

1.BluetoothSocket

表示藍牙socket的介面(與TCP Socket類似, 關于socket的概念請自行查閱計算機網路的相關內容),該類的物件作為應用中資料傳輸的連接點,

2.BluetoothServerSocket

表示服務器socket,用來監聽未來的請求(和TCP ServerSocket類似),為了能使兩個藍牙設備進行連接,一個設備必須使用該類開啟服務器socket,當遠程的藍牙設備請求該服務端設備時,如果連接被接受,BluetoothServerSocket將會回傳一個已連接的BluetoothSocket類物件,

3.BluetoothClass

描述藍牙設備的主要特征,BluetoothClass的類物件是一個只讀的藍牙設備的屬性集,盡管該類物件并不能可靠地描述BluetoothProfile的所有內容以及該設備支持的所有服務資訊,但是該類物件仍然有助于對該設備的型別進行提示,

4.BluetoothProfile

表示藍牙規范,藍牙規范是兩個基于藍牙設備通信的標準,

三、低功耗藍牙(BLE)API

1.BluetoothGatt

藍牙通用屬性協議,定義了BLE通訊的基本規則,是BluetoothProfile的實作類,Gatt是Generic Attribute Profile的縮寫,用于連接設備、搜索服務等操作,

2.BluetoothGattCallback

藍牙設備連接成功后,用于回呼一些操作的結果,必須連接成功后才會回呼,

3.BluetoothGattService

藍牙設備提供的服務,是藍牙設備特征的集合,

4.BluetoothGattCharacteristic

藍牙設備特征,是構建GATT服務的基本資料單元,

5.BluetoothGattDescriptor

藍牙設備特征描述符,是對特征的額外描述,

1.加入藍牙權限

(1)android.permission.BLUETOOTH

為了能夠在你開發的應用設備中使用藍牙功能,必須宣告藍牙的權限"BLUETOOTH",在進行藍牙的通信,例如請求連接,接受連接以及交換資料中,需要用到該權限

(2)android.permission.BLUETOOTH_ADMIN

如果你的應用程式需要實體化藍牙設備的搜索或者對藍牙的設定進行操作,那么必須宣告BLUETOOTH_ADMIN權限,大多數應用需要該權限對本地的藍牙設備進行搜索,該權限的其他能力并不應當被使用,除非你的應用是一個電源管理的應用,需要對藍牙的設定進行修改

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

這里需要注意的是,Google在Android 6.0之后,為了更好的保護用戶的資料安全,所有需要訪問硬體唯一識別符號的地方都需要申請位置權限,而且搜索周圍的藍牙設備,需要手機提供位置服務,否則呼叫藍牙掃描將搜索不到任何結果,

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> 
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

在AndroidManifest.xml中加入位置權限,位置權限屬于Dangerous級別的權限,別忘了在代碼里邊還需要動態申請,

2.是否支持藍牙

		//獲取藍牙配接器 若配接器為空則當前手機不支持藍牙
		bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
        if (bluetoothAdapter == null) {
            Toast.makeText(this, "當前手機設備不支持藍牙", Toast.LENGTH_SHORT).show()
        }

3.是否打開藍牙

(1)判斷藍牙是否打開

			//手機設備支持藍牙,判斷藍牙是否已開啟
            if (bluetoothAdapter!!.isEnabled) {
                Toast.makeText(this, "手機藍牙已開啟", Toast.LENGTH_SHORT).show()
                //可以進行搜索藍牙的操作
                searchBtDevice()
            } else {
                //藍牙沒有打開,去打開藍牙,推薦使用第二種打開藍牙方式
                //第一種方式:直接打開手機藍牙,沒有任何提示,一般不采用這種方法
                //                bluetoothAdapter.enable();  //BLUETOOTH_ADMIN權限
                //第二種方式:友好提示用戶打開藍牙
                val enableBtIntent = Intent(ACTION_REQUEST_ENABLE)
                startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
            }

(2)友好提示用戶打開藍牙

                //友好提示用戶打開藍牙
                val enableBtIntent = Intent(ACTION_REQUEST_ENABLE)
                startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)

在這里插入圖片描述

(3)監聽用戶是否打開藍牙的操作回呼

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_ENABLE_BT) {
            if (resultCode == Activity.RESULT_OK) {
                // 藍牙已打開
                //可以開始搜索藍牙 獲取藍牙串列
                searchBtDevice()
            } else {
                // 藍牙未打開 一般操作是彈出確認彈框提示用戶繼續打開藍牙
                DialogUtil.getInstance().showConfirmDialog(this, "提示", "藍牙沒有開啟,是否打開藍牙?", "打開",
                    "取消", object : DialogUtil.DialogConfirmListener<Any> {
                        override fun cancel() {
                        	//如果用戶選擇取消的話  可以關閉當前頁面或者提示藍牙未打開
                            finish()
                        }

                        override fun confirm(data: Any?) {
                            //繼續友好提示用戶打開藍牙
                            val enableBtIntent = Intent(ACTION_REQUEST_ENABLE)
                            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
                        }
                    })
            }
        }
    }

4.掃描藍牙

藍牙掃描共有兩種方式,一種是適于經典藍牙開發模式的呼叫藍牙配接器 bluetoothAdapter.startDiscovery()搜索藍牙,通過注冊藍牙廣播接收器獲取掃描到的藍牙資訊,這種搜索方法不管是經典藍牙還是低功耗藍牙都能全部掃描出來;另一種方法則是針對低功耗藍牙進行搜索掃描,只有低功耗藍牙模式的藍牙資訊才會被掃描出來,

(1)經典藍牙搜索

① 新建藍牙廣播接收器

public class BroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "BluetoothReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        LogUtil.logNormalMsg("----藍牙廣播接收器----------action----------" + action);
        //開啟搜索
        if (TextUtils.equals(action, BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
            if (onDeviceSearchListener != null) {
                onDeviceSearchListener.onDiscoveryStart();  //開啟搜索回呼
            }
            findConnectedBluetooth();
        } else if (TextUtils.equals(action, BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
            //完成搜素
            if (onDeviceSearchListener != null) {
                //完成搜素回呼
                onDeviceSearchListener.onDiscoveryStop();
            }

        } else if (TextUtils.equals(action, BluetoothDevice.ACTION_FOUND)) {
            //3.0搜索到設備
            //藍牙設備
            BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            //信號強度
            int rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);

            LogUtil.logNormalMsg("TAG", "掃描到設備:" + bluetoothDevice.getName() + "-->" + bluetoothDevice.getAddress());
            if (onDeviceSearchListener != null) {
                //3.0搜素到設備回呼
                onDeviceSearchListener.onDeviceFound(bluetoothDevice, rssi);
            }
        } else if (TextUtils.equals(action, BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
            //藍牙已斷開
            //藍牙設備
            BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            LogUtil.logNormalMsg("--------------藍牙已斷開---------------");
            if (onDeviceSearchListener != null) {
                //判斷是否是當前藍牙連接斷開了
                if (bluetoothDevice.getAddress().equals(Content.bluetoothMac)){
                    onDeviceSearchListener.onDeviceDisconnect();
                }
            }
        } else if (TextUtils.equals(action, BluetoothDevice.ACTION_ACL_CONNECTED)) {
            //藍牙已連接
            LogUtil.logNormalMsg("--------------藍牙已連接---------------");
            if (onDeviceSearchListener != null) {
                onDeviceSearchListener.onDeviceConnect();
            }
        } else if (TextUtils.equals(action, BluetoothDevice.ACTION_PAIRING_REQUEST)) {
            //經典藍牙的配對請求 可以用于攔截配對框,在代碼里設定配對碼
        }
    }
    /**
     * 藍牙設備搜索監聽者
     * 1、開啟搜索
     * 2、完成搜索
     * 3、搜索到設備
     */
    public interface OnDeviceSearchListener {
        void onDiscoveryStart();   //開啟搜索

        void onDiscoveryStop();    //完成搜索

        void onDeviceFound(BluetoothDevice bluetoothDevice, int rssi);  //搜索到設備

        //設備斷開連接
        void onDeviceDisconnect();

        //設備連接成功
        void onDeviceConnect();
    }

    private OnDeviceSearchListener onDeviceSearchListener;

    public void setOnDeviceSearchListener(OnDeviceSearchListener onDeviceSearchListener) {
        this.onDeviceSearchListener = onDeviceSearchListener;
    }
}

② 動態注冊藍牙廣播接收器

    /**
     * 注冊藍牙廣播接收
     */
    private fun initBtBroadcast() {
        //注冊廣播接收
        btBroadcastReceiver = BtBroadcastReceiver()
        //這里需要當前的Activity實作(implements)一下藍牙設備搜索監聽者OnDeviceSearchListener 
        btBroadcastReceiver!!.setOnDeviceSearchListener(this)
        val intentFilter = IntentFilter()
        //將優先級調高
        intentFilter.priority = 1001
        //添加Action
        intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED) //開始掃描
        intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)//掃描結束
        intentFilter.addAction(BluetoothDevice.ACTION_FOUND)//搜索到設備
        intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)//斷開連接
        intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)//連接
        intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)//藍牙配對請求
        registerReceiver(btBroadcastReceiver, intentFilter)

    }

② 開啟掃描藍牙

 /**
     * 開始搜索藍牙
     */
    private fun searchBtDevice() {
        if (bluetoothAdapter!!.isDiscovering) {
            //當前正在搜索設備...停止當前搜索
            bluetoothAdapter!!.cancelDiscovery()
        }
        //開始搜索
        bluetoothAdapter!!.startDiscovery()
    }

開啟掃描后在藍牙廣播接收器中可以監聽到手機掃描到的藍牙,然后通過藍牙設備搜索監聽者OnDeviceSearchListener方法回呼中拿到藍牙設備資訊,

    override fun onDeviceFound(device: BluetoothDevice?, rssi: Int) {
        //每掃描到藍牙資訊都會回呼該方法 這里可以拿到藍牙設備資訊和信號強度
    }

到這一步的話,開發者可以將掃描到的藍牙設備資訊串列展示,點擊串列即可做藍牙連接的操作,
在這里插入圖片描述

(2)低功耗藍牙搜索

通過startLeScan()方法開啟掃描,掃描結果直接在BluetoothAdapter.LeScanCallback或者ScanCallback 中回呼處理,不用經典藍牙模式需要注冊廣播才能獲取掃描到的藍牙資訊,這里注意不同版本呼叫startScan方法的邏輯不一樣,目前大多數android版本都在5.0以上,基本上選用后者的掃描方法即可

//1. Android 4.3以上,Android 5.0以下
mBluetoothAdapter.startLeScan(BluetoothAdapter.LeScanCallback LeScanCallback);
//2. Android 5.0以上,掃描的結果在mScanCallback中進行處理
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
mBluetoothLeScanner.startScan(ScanCallback mScanCallback);

回呼處理:

public abstract class ScanCallback {
    /**
     * Callback when a BLE advertisement has been found.
     *
     * @param callbackType Determines how this callback was triggered. Could be one of {@link
     * ScanSettings#CALLBACK_TYPE_ALL_MATCHES}, {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH} or
     * {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST}
     * @param result A Bluetooth LE scan result.
     */
    public void onScanResult(int callbackType, ScanResult result) {
    	//這里可以處理藍牙掃描結果
    	BluetoothDevice bluetoothDevice =  result.getDevice();
    }

    /**
     * Callback when batch results are delivered.
     *
     * @param results List of scan results that are previously scanned.
     */
    public void onBatchScanResults(List<ScanResult> results) {
    }

    /**
     * Callback when scan could not be started.
     *
     * @param errorCode Error code (one of SCAN_FAILED_*) for scan failure.
     */
    public void onScanFailed(int errorCode) {
    }
}

如果只是涉及到低功耗藍牙的技術開發,選擇第二種方法掃描藍牙就可以了,比較簡便,反之選擇第一種方法,

5.藍牙連接與斷開

(1)經典藍牙(BT)

APP與經典藍牙設備連接一般通過BluetoothDevice物件的createRfcommSocketToServiceRecord方法獲取BluetoothSocket,類似Socket編程,呼叫connect()方法開始連接,連接成功以后通過BluetoothSocket的getInputStream()和getOutputStream()方法獲取輸入流和輸出流,進而與藍牙進行通信,createRfcommSocketToServiceRecord方法需要傳入UUID引數,一般通過需要連接的藍牙設備引數得知,沒法獲取UUID也沒關系,可以通過反射機制得到BluetoothSocket物件,再進行連接,這里需要注意藍牙連接是一個耗時的程序,不能在主執行緒中執行,

① createRfcommSocketToServiceRecord獲取BluetoothSocket

		//1、獲取BluetoothSocket
        try {
            //建立安全的藍牙連接,會彈出配對框
            mSocket = mDevice.createRfcommSocketToServiceRecord(UUID.fromString(uuid));
        } catch (IOException e) {
            e.printStackTrace();
            LogUtil.logNormalMsg(TAG, "獲取BluetoothSocket例外!" + e.getMessage());
        }

② 反射獲取BluetoothSocket

		//1、獲取BluetoothSocket
        try {
            //通過反射獲取Socket
            mSocket = (BluetoothSocket) mDevice.getClass().
                     getMethod("createRfcommSocket", new Class[]{int.class})
                     .invoke(mDevice, 1);
        } catch (IOException e) {
            e.printStackTrace();
            LogUtil.logNormalMsg(TAG, "獲取BluetoothSocket例外!" + e.getMessage());
        }

③ 連接藍牙

獲取BluetoothSocket后就可以與藍牙設備連接了,呼叫connect()連接方法后一般手機就會彈出藍牙配對框,有一些藍牙設備不會出現彈框因為藍牙模塊沒有設定配對碼,這里只針對有配對碼的藍牙模塊,手動輸入配對碼后就成功連接了,一般藍牙開發設備的配對碼是0000或者1234,在考慮用戶體驗的時候,一般不讓用戶自己輸入配對碼,這一步操作顯得繁瑣多余,APP開發時一般都考慮在連接時靜默設定藍牙的配對碼,在用戶無感知的情況下進行連接和配對操作,做這一步操作就需要開發者攔截藍牙配對的Action (BluetoothDevice.ACTION_PAIRING_REQUEST),在代碼中進行配對處理,在BroadcastReceiver 藍牙廣播接收器中增加對藍牙配對請求的攔截處理,

		mSocket.connect();

④ 靜默設定配對碼

		if (TextUtils.equals(action, BluetoothDevice.ACTION_PAIRING_REQUEST)) {
            BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            //藍牙配對
            Log.i(TAG, "##### 是配對請求的請求 ######");
            Bundle extras = intent.getExtras();
            Log.i(TAG, "-->" + extras.toString());
            Object device = extras.get("android.bluetooth.device.extra.DEVICE");
            Object pairKey = BaseData.bluetoothPassword;
            Log.i(TAG, "device-->" + String.valueOf(device));
            Log.i(TAG, "pairkey-->" + String.valueOf(pairKey));
            try {
                //中斷配對廣播傳遞 如果沒有將廣播終止,則會出現一個一閃而過的配對框,
                abortBroadcast();
                //呼叫setPin方法進行配對...
                setPin(btDevice.getClass(), btDevice, String.valueOf(pairKey));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

記得在動態注冊藍牙廣播接收器的時候添加一下藍牙配對請求的Action

//藍牙配對請求Action
intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)

設定配對碼

	/**
     * 設定藍牙配對碼
     * @param btClass btClass
     * @param btDevice BluetoothDevice
     * @param str str
     * @return boolean
     */
    public boolean setPin(Class btClass, BluetoothDevice btDevice, String str) {
        boolean flag = false;
        try {
            Class[] arrayOfClass = new Class[1];
            arrayOfClass[0] = byte[].class;
            Method removeBondMethod = btClass.getDeclaredMethod("setPin", arrayOfClass);
            Object[] arrayOfObject = new Object[1];
            arrayOfObject[0] = str.getBytes();
            flag = (Boolean) removeBondMethod.invoke(btDevice, arrayOfObject);
            LogUtil.logNormalMsg(TAG, "setPin result: " + flag);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (flag) {
            LogUtil.logNormalMsg("-------設定藍牙配對碼成功");
        } else {
            LogUtil.logNormalMsg("-------設定藍牙配對碼失敗");
        }
        return flag;
    }

這里在貼一下我自己藍牙連接的代碼:

public class ConnectThread {
    private static final String TAG = "ConnectThread";
    private final BluetoothAdapter mBluetoothAdapter;
    private BluetoothSocket mmSocket;
    private final BluetoothDevice mDevice;

    public ConnectThread(BluetoothAdapter bluetoothAdapter, BluetoothDevice bluetoothDevice, String uuid) {
        this.mBluetoothAdapter = bluetoothAdapter;
        this.mDevice = bluetoothDevice;

        //使用一個臨時變數,等會賦值給mmSocket
        //因為mmSocket是靜態的
        BluetoothSocket tmp = null;

        if (mmSocket != null) {
            LogUtil.logNormalMsg(TAG, "ConnectThread-->mmSocket != null先去釋放");
            try {
                mmSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        LogUtil.logNormalMsg(TAG, "ConnectThread-->mmSocket != null已釋放");

        //1、獲取BluetoothSocket
        try {
            //建立安全的藍牙連接,會彈出配對框
            tmp = mDevice.createRfcommSocketToServiceRecord(UUID.fromString(uuid));
        } catch (IOException e) {
            e.printStackTrace();
            LogUtil.logNormalMsg(TAG, "ConnectThread-->獲取BluetoothSocket例外!" + e.getMessage());
        }

        mmSocket = tmp;
        if (mmSocket != null) {
            LogUtil.logNormalMsg(TAG, "ConnectThread-->已獲取BluetoothSocket");
        }

    }

    public void connectBluetooth() {
        connect();
    }

    private void connect() {
        //連接之前先取消發現設備,否則會大幅降低連接嘗試的速度,并增加連接失敗的可能性
        if (mBluetoothAdapter == null) {
            LogUtil.logNormalMsg(TAG, "ConnectThread:run-->mBluetoothAdapter == null");
            return;
        }
        //取消發現設備
        if (mBluetoothAdapter.isDiscovering()) {
            mBluetoothAdapter.cancelDiscovery();
        }

        if (mmSocket == null) {
            LogUtil.logNormalMsg(TAG, "ConnectThread:run-->mmSocket == null");
            return;
        }
        boolean firstFlag = false;
        //2、通過socket去連接設備
        try {
            LogUtil.logNormalMsg(TAG, "ConnectThread:run-->去連接...");
            if (onBluetoothConnectListener != null) {
                onBluetoothConnectListener.onStartConn();  //開始去連接回呼
            }

            mmSocket.connect();
            firstFlag = true;
        } catch (Exception e) {
            LogUtil.logNormalMsg(TAG, "ConnectThread:run-->第一次連接例外!" + e.getMessage());
            boolean flag = false;
            try {
                LogUtil.logNormalMsg(TAG, "ConnectThread:run-->嘗試第二次通過反射獲取Socket連接");
                mmSocket = (BluetoothSocket) mDevice.getClass().
                        getMethod("createRfcommSocket", new Class[]{int.class})
                        .invoke(mDevice, 1);
                mmSocket.connect();
                flag = true;
                Log.e(TAG, "mmSocket.isConnected():" + mmSocket.isConnected());

            } catch (Exception e2) {
                LogUtil.logNormalMsg("mmSocket連接失敗:" + e.getMessage());
                e2.printStackTrace();
                LogUtil.logNormalMsg(TAG, "ConnectThread:run-->連接例外2!" + e2.getMessage());

                if (onBluetoothConnectListener != null) {
                    onBluetoothConnectListener.onConnFailure("連接例外:" + e.getMessage());
                }
                //釋放
                cancel();
            } finally {
                if (flag) {
                    if (onBluetoothConnectListener != null) {
                        //連接成功回呼
                        onBluetoothConnectListener.onConnSuccess(mmSocket);
                        LogUtil.logNormalMsg(TAG, "ConnectThread:run-->連接成功");
                    }
                } else {
                    if (onBluetoothConnectListener != null) {
                        //連接失敗回呼
                        onBluetoothConnectListener.onConnFailure("");
                    }
                }
            }
        } finally {
            if (firstFlag) {
                if (onBluetoothConnectListener != null) {
                    //連接成功回呼
                    onBluetoothConnectListener.onConnSuccess(mmSocket);
                    LogUtil.logNormalMsg(TAG, "ConnectThread:run-->連接成功");
                }
            }
        }
    }



    /**
     * 釋放
     */
    public void cancel() {
        try {
            if (mmSocket != null && mmSocket.isConnected()) {
                LogUtil.logNormalMsg(TAG, "ConnectThread:cancel-->mmSocket.isConnected() = " + mmSocket.isConnected());
                mmSocket.close();
                mmSocket = null;
                return;
            }
            if (mmSocket != null) {
                mmSocket.close();
                mmSocket = null;
            }
            LogUtil.logNormalMsg(TAG, "ConnectThread:cancel-->關閉已連接的套接字釋放資源");
        } catch (IOException e) {
            LogUtil.logNormalMsg(TAG, "ConnectThread:cancel-->關閉已連接的套接字釋放資源例外!" + e.getMessage());
        }
    }

    private OnBluetoothConnectListener onBluetoothConnectListener;

    public void setOnBluetoothConnectListener(OnBluetoothConnectListener onBluetoothConnectListener) {
        this.onBluetoothConnectListener = onBluetoothConnectListener;
    }

    //連接狀態監聽者
    public interface OnBluetoothConnectListener {
        void onStartConn();  //開始連接

        void onConnSuccess(BluetoothSocket bluetoothSocket);  //連接成功

        void onConnFailure(String errorMsg);  //連接失敗
    }

}

藍牙連接成功后,就可通過BluetoothSocket 的輸入輸出流和藍牙設備進行通信了,
需要注意的是,靜默設定藍牙配對碼一般需要和藍牙設備廠家協商一個固定的配對碼,不然藍牙配對碼一直改變的話會導致APP連接不上藍牙而且沒有任何錯誤提示,

⑤ 斷開連接

斷開經典藍牙連接其實就是關閉BluetoothSocket和輸入輸出流,這里注意需要先把輸入輸出流關閉再將BluetoothSocket關閉,

	try {
            if(mInStream != null){
                mInStream.close();  //關閉輸入流
            }
            if(mOutStream != null){
                mOutStream.close();  //關閉輸出流
            }
            if(mSocket != null){
                mSocket.close();   //關閉socket
            }
            mInStream = null;
            mOutStream = null;
            mmSocket = null;
            LogUtil.logNormalMsg(TAG,"成功斷開連接");
        } catch (Exception e) {
            e.printStackTrace();
            // 任何一部分報錯,都將強制關閉socket連接
            mInStream = null;
            mOutStream = null;
            mmSocket = null;
            LogUtil.logNormalMsg(TAG, "斷開連接例外!" + e.getMessage());
        }

(2)低功耗藍牙(BLE)

低功耗藍牙的連接稍微復雜一點:
在這里插入圖片描述

連接之前

在連接藍牙這之前需要對GATT了解一下,方便加深我們對低功耗藍牙開發技術的理解,

GATT層次結構

在這里插入圖片描述
該層次結構的頂層是一個 profile(概要檔案),profile 由滿足 use case(用例)所需的一個或多個 services(服務)組成,services(服務)由對其他服務的 characteristics(特征)或 references(參考)組成,每個 characteristics(特征)都包含一個值,并且可以包含關于該值的可選資訊,service(服務)和 characteristic(特性)以及 characteristic(特性)的組成部分(即、值和 descriptors(描述符))包含 profile data,并且都存盤在 server(服務器)上的 Attributes(屬性)中,

各成員作用

1、Profile

Profile規格規定了交換組態檔資料的架構,此架構定義了組態檔所用的基本元素,例如服務和特征,該層級的最高層是組態檔(profile),組態檔由實作用例所需的一個或多個服務組成,服務由特征或有關其它服務的參考組成,每一個特征包括一個值,還可能包括有關該值的可選資訊,服務、特征以及特征的組件(即特征值和特征描述符)構成了組態檔資料,并全部存盤在服務器的屬性中,沒錯,上面這是摘抄的,說簡單點就是協議規范,各個藍牙兼容都靠這個規范,使用標準的Profile就不需要過多關注它了,

2、Service

服務分管不同的資料處理,例如有電池電量的,有心率的,它不參與具體的資料互動,承擔的是功能磁區的職責,每個服務都有一個UUID進行區分,

3、Characteristic

特征是直接參與資料互動的成員之一,特征也有一個UUID進行標識,通過UUID獲取到特征,修改特征里面的Value達到通訊目的,這樣的通訊模式和觀察者模式相似,當Value有改變的時候會通知監聽這個特征的觀察者,告訴它資料有改變,觀察者去獲取資料就完成了一次通訊,具體的實作往后詳述,

4、Descriptor

對特征值的描述,是用來定義特征值的已定義屬性,例如:描述符可以指定可讀的描述,特征值的可接受范圍或者特征值特定的度量單位,這個東西用的不是很多,假如中心設備收不到資料,那么就看看是不是Descriptor沒有設定可通知,

5、Advertising

廣播,外圍設備讓自身可以被發現的手段,只有發出廣播了,才能通過Ble掃描到,一旦設備連接上,廣播就會停止,

低功耗藍牙通過BluetoothGatt進行連接和管理,通過BluetoothDevice的connectGatt()方法可以得到BluetoothGatt物件,其中需要傳入三個引數context背景關系,autoConnect是否自動連接,callback連接回呼,關鍵在于第三個引數BluetoothGattCallback,因為開發者需要在BluetoothGattCallback回呼中發現服務和配置通信管道,同時可以在其中監聽下位機(藍牙模塊)回復的訊息,這里需要注意的是,獲取到GATT中的服務Service、特征Characteristic和特征值描述Descriptor都需要指定UUID,這些UUID一般從藍牙模塊廠家獲取,獲取通過代碼方式遍歷所有的UUID實驗出正確的UUID,后續會講到,

connectGatt()方法原始碼:

	/**
     * Connect to GATT Server hosted by this device. Caller acts as GATT client.
     * The callback is used to deliver results to Caller, such as connection status as well
     * as any further GATT client operations.
     * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
     * GATT client operations.
     *
     * @param callback GATT callback handler that will receive asynchronous callbacks.
     * @param autoConnect Whether to directly connect to the remote device (false) or to
     * automatically connect as soon as the remote device becomes available (true).
     * @throws IllegalArgumentException if callback is null
     */
    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
            BluetoothGattCallback callback) {
        return (connectGatt(context, autoConnect, callback, TRANSPORT_AUTO));
    }

① 設定自定義回呼

/**
     * 低功耗藍牙連接回呼
     * 接收藍牙資料
     */
    inner class MyBluetoothGattCallback : BluetoothGattCallback() {
        override fun onConnectionStateChange(
            gatt: BluetoothGatt,
            status: Int,
            newState: Int
        ) {
            super.onConnectionStateChange(gatt, status, newState)
            Content.isConnectBluetoothNow = false
            //連接成功  開始發現服務
            when {
                newState == BluetoothAdapter.STATE_CONNECTED -> {
                	//到這一步 gatt已經連接成功了  可以呼叫發現gatt的服務
                    mGatt?.discoverServices()
                }
                newState == BluetoothAdapter.STATE_DISCONNECTED -> {
                    //連接斷開
                }
                status == 133 -> {
                    //連接失敗
                }
            }
        }

        override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
            super.onServicesDiscovered(gatt, status)
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //到這一步 已經發現了gatt的所有服務 可以通過gatt.services獲取
				
				//如果我們知道gatt相關的所有UUID 則可以開始配置通信管道
				//獲取寫入的特征值
				writeCharact = this.bluetoothGattService?.getCharacteristic(writeUUID)
				//獲取通知監聽的特征值 相當于讀取監聽  開啟監聽后可實時監聽下位機訊息
				notifyCharact = this.bluetoothGattService?.getCharacteristic(notifyUUID)
				//設定開啟監聽 實時監聽下位機訊息
				//1.設定特征值通知
            	mGatt?.setCharacteristicNotification(notifyCharact , true)
            	//2.獲取descriptor
            	val descriptor: BluetoothGattDescriptor = notifyCharact ?.getDescriptor(descriptorUUID)!!
            	descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
            	//3.設定descriptor 這里設定結果會在onDescriptorWrite方法中回呼 
            	mGatt?.writeDescriptor(descriptor)
            	//至此低功耗藍牙連接和配置完成
            }
        }

        override fun onCharacteristicRead(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            status: Int
        ) {
            super.onCharacteristicRead(gatt, characteristic, status)
        }

        override fun onCharacteristicWrite(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            status: Int
        ) {
            super.onCharacteristicWrite(gatt, characteristic, status)
            val value = characteristic.value
            val data = Util.bytesToAscii(value)
            //發送資料成功后會回呼該方法
            LogUtil.logNormalMsg("onCharacteristicWrite", "發送成功:$data")
        }

        override fun onCharacteristicChanged(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic
        ) {
            super.onCharacteristicChanged(gatt, characteristic)
            // value為設備發送的資料,根據資料協議進行決議
            //接收處理資料
            handBluetoothData(characteristic.value)
        }

        override fun onDescriptorRead(
            gatt: BluetoothGatt,
            descriptor: BluetoothGattDescriptor,
            status: Int
        ) {
            super.onDescriptorRead(gatt, descriptor, status)
        }

        override fun onDescriptorWrite(
            gatt: BluetoothGatt,
            descriptor: BluetoothGattDescriptor,
            status: Int
        ) {
            super.onDescriptorWrite(gatt, descriptor, status)
            if (status == BluetoothGatt.GATT_SUCCESS) {
                LogUtil.logNormalMsg("LockBluetoothService", "設定Descriptor成功,可以發送資料了")
                //一些藍牙模塊需要在連接成功后設定mtu 根據開發需要配置
                mGatt?.requestMtu(103)
            }
        }
    }

② 啟動藍牙連接

啟動藍牙藍牙連接后,通過connectGatt方法得到BluetoothGatt物件,連接結果可在上一步自定義的BluetoothGattCallback中回呼處理,

mGatt = bluetoothDevice1!!.connectGatt(appContext, false, bluetoothGattCallback)

③ 發現GATT服務

這一步操作需在連接成功后執行,發現服務后才可配置藍牙通信

mGatt?.discoverServices()	

④ 配置通信

獲取BluetoothGattService服務和用于讀寫的BluetoothGattCharacteristic特征值,配置藍牙通信,

				//獲取寫入的特征值
				writeCharact = this.bluetoothGattService?.getCharacteristic(writeUUID)
				//獲取通知監聽的特征值 相當于讀取監聽  開啟監聽后可實時監聽下位機訊息
				notifyCharact = this.bluetoothGattService?.getCharacteristic(notifyUUID)
				//設定開啟監聽 實時監聽下位機訊息
				//1.設定特征值通知
            	mGatt?.setCharacteristicNotification(notifyCharact , true)
            	//2.獲取descriptor
            	val descriptor: BluetoothGattDescriptor = notifyCharact ?.getDescriptor(descriptorUUID)!!
            	descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
            	//3.設定descriptor 這里設定結果會在onDescriptorWrite方法中回呼 
            	mGatt?.writeDescriptor(descriptor)
            	//至此低功耗藍牙連接和配置完成 可以進行通信了

一般藍牙開發廠商都會告知相關的UUID,如果不知道藍牙模塊的各UUID,可以通過遍歷Gatt的所有服務查找,直至找到正確的UUID,

    /**
     * 查找低功耗藍牙uuid
     */
    private fun findUUID(gatt: BluetoothGatt) {
        LogUtil.logNormalMsg("gatt.getServices().size():" + gatt.services.size)
        if (gatt.services != null && gatt.services.size > 0) {
        	//遍歷所有服務
            for (i in gatt.services.indices) {
                val service = gatt.services[i]
                LogUtil.logNormalMsg("---------------------------------------------")
               	//輸出當前服務的UUID
                LogUtil.logNormalMsg("service.getUuid=" + service.uuid)
                //判斷當前服務是否有特征值
                if (service.characteristics != null && service.characteristics.size > 0) {					//遍歷當前所有服務的特征值
                    for (j in service.characteristics.indices) {
                        val characteristic =
                            service.characteristics[j]
                        //輸出當前特征值的UUID
                        LogUtil.logNormalMsg("characteristic.getUuid=" + characteristic.uuid)
                        //獲取特征值屬性
                        val charaProp = characteristic.properties
                        //是否支持讀取
                        if (charaProp or BluetoothGattCharacteristic.PROPERTY_READ > 0) {
                            LogUtil.logNormalMsg("-------type:PROPERTY_READ")
                        }
                        //是否支持寫入
                        if (charaProp or BluetoothGattCharacteristic.PROPERTY_WRITE > 0) {
                            LogUtil.logNormalMsg("-------type:PROPERTY_WRITE")
                        }
                        //是否支持開啟監聽
                        if (charaProp or BluetoothGattCharacteristic.PROPERTY_NOTIFY > 0) {
                            LogUtil.logNormalMsg("-------type:PROPERTY_NOTIFY")
                        }
                        if (characteristic.descriptors != null && characteristic.descriptors.size > 0) {
                            for (descriptor in characteristic.descriptors) {
                                LogUtil.logNormalMsg("descriptor.getUuid()=" + descriptor.uuid)
                            }
                        }
                    }
                }
            }
        }
    }

這里注意要找全面一點的服務,有些服務下沒有特征,有些特征不支持讀寫,有些特征下沒有描述,無法開啟讀取監聽,
在這里插入圖片描述

⑤ 斷開連接

	mGatt?.let {
            //斷開BluetoothGatt連接
            it.disconnect()
            //關閉BluetoothGatt
            it.close()
        }

6.通信

(1)經典藍牙通信

根據5中提到的連接邏輯,可以得到BluetoothSocket,類似Socket編程,通過BluetoothSocket的到輸入輸出流與藍牙模塊進行資料通信,這里建議另起執行緒進行通信操作,需要實時監聽InputStream輸入流里邊是否有資料可讀取,

① 獲取輸入輸出流

		//獲取 InputStream 和 OutputStream
        try {
            mInStream = socket.getInputStream();
            mOutStream = socket.getOutputStream();

        } catch (IOException e) {
        	e.printStackTrace();
            LogUtil.logNormalMsg(TAG,"獲取InputStream 和 OutputStream例外!");
        }

② 發送資料

	//發送資料
    public boolean write(byte[] bytes){
        try {
            if(mOutStream == null){
                LogUtil.logNormalMsg(TAG, "mmOutStream == null");
                return false;
            }
            //發送資料
            mOutStream.write(bytes);
            mOutStream.flush();
            Log.d(TAG, "寫入成功:"+ bytes2HexString(bytes, bytes.length));
            return true;

        } catch (IOException e) {
            LogUtil.logNormalMsg("發送資料出錯:"+e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

③ 讀取資料

	//執行緒的run方法
 	@Override
    public void run(){
        //最大快取區 存放流
        byte[] buffer = new byte[1024 * 2];  //buffer store for the stream
        //從流的read()方法中讀取的位元組數
        int bytes = 0;  //bytes returned from read()
        //持續監聽輸入流直到發生例外
        while(!isStop){
            try {
                if(mInStream == null){
                    LogUtil.logNormalMsg(TAG,"ConnectedThread:run-->輸入流mmInStream == null");
                    break;
                }
                //先判斷是否有資料,有資料再讀取
                if(mInStream.available() != 0){
                    //從(mmInStream)輸入流中(讀取內容)讀取的一定數量位元組數,并將它們存盤到緩沖區buffer陣列中,bytes為實際讀取的位元組數
                    bytes = mInStream.read(buffer);
                    LogUtil.logNormalMsg(TAG,"讀取資料長度:"+bytes);
                    //存放實際讀取的資料內容
                    byte[] b = Arrays.copyOf(buffer,bytes);
                    //處理藍牙資料
                    handBluetoothData(b);
                }
                Thread.sleep(150);
            } catch (Exception e) {
                LogUtil.logNormalMsg(TAG,"接收訊息例外!" + e.getMessage());
                //關閉流和socket
                boolean isClose = cancel();
                if(isClose){
                    LogUtil.logNormalMsg(TAG,"接收訊息例外,成功斷開連接!");
                }
                break;
            }
        }
        //關閉流和socket
        boolean isClose = cancel();
        if(isClose){
            Log.d(TAG,"接收訊息結束,斷開連接!");
        }
    }

(2)低功耗藍牙通信

低功耗藍牙通信較為簡單一些,直接通過寫入的特征值Characteristic就可發送資料,而讀取資料在BluetoothGattCallback的
onCharacteristicChanged回呼方法中監聽讀取藍牙設備發送過來的資料,

① 發送資料

	fun sendMsgToBluetooth(msg: ByteArray): Boolean {
        if (writeCharact != null) {
        	//設定特征值的value
            writeCharact?.value = msg
            //設定特征值的寫入型別 這里根據需要選擇型別  詳細可閱讀原始碼
            writeCharact?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
            //通過gatt寫入資料
            return mGatt!!.writeCharacteristic(writeCharact)
        }
        return false
    }

② 讀取資料

 		override fun onCharacteristicChanged(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic
        ) {
            super.onCharacteristicChanged(gatt, characteristic)
            //接收處理資料
            handBluetoothData(characteristic.value)
        }

7.補充

(1)在多次除錯中發現,在經典藍牙模式開發中,有兩種情況會掃描不到藍牙設備:

  1. APP內斷開藍牙連接,再次掃描藍牙或是重啟APP再次掃描;
  2. 再啟動APP時,藍牙設備已與手機APP配對連接;
    出現這種情況不要慌,只要把與手機藍牙連接的藍牙設備找到并加入到掃描的藍牙串列中,重新點擊藍牙連接就可以解決,
    搜索已與手機連接的藍牙:
	/**
     * 搜索已連接的藍牙
     */
    private void findConnectedBluetooth() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        Class<BluetoothAdapter> bluetoothAdapterClass = BluetoothAdapter.class;//得到BluetoothAdapter的Class物件
        try {//得到連接狀態的方法
            Method method = bluetoothAdapterClass.getDeclaredMethod("getConnectionState", (Class[]) null);
            //打開權限
            method.setAccessible(true);
            int state = (int) method.invoke(adapter, (Object[]) null);
            if(state == BluetoothAdapter.STATE_CONNECTED){
                LogUtil.logNormalMsg("BLUETOOTH","BluetoothAdapter.STATE_CONNECTED");
                Set<BluetoothDevice> devices = adapter.getBondedDevices();
                LogUtil.logNormalMsg("BLUETOOTH","devices:"+devices.size());
                for(BluetoothDevice device : devices){
                    Method isConnectedMethod = BluetoothDevice.class.getDeclaredMethod("isConnected", (Class[]) null);
                    method.setAccessible(true);
                    boolean isConnected = (boolean) isConnectedMethod.invoke(device, (Object[]) null);
                    if(isConnected){
                        //找到已連接的藍牙設備 進行下一步處理
                        if (device != null) {
                          onDeviceSearchListener.onDeviceFound(device, 50);
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

(2)低功耗藍牙GATT的默認MTU支持發送的資料量為23位元組(byte), 除去GATT的opcode一個位元組以及GATT的handle 2個位元組之后,剩下的20個位元組便是留給GATT的了,GATT默認支持最大512位元組的資料通信,可以通過設定MTU的大小來改變,一般在藍牙連接成功后設定,這里推薦在onDescriptorWrite回呼方法中配置:

		override fun onDescriptorWrite(
            gatt: BluetoothGatt,
            descriptor: BluetoothGattDescriptor,
            status: Int
        ) {
            super.onDescriptorWrite(gatt, descriptor, status)
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //藍牙連接配置通信成功  可以設定mtu了
                mGatt?.requestMtu(103)
            }
        }

設定MTU是否的結果在onMtuChanged中可以得到

        override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
            super.onMtuChanged(gatt, mtu, status)
             if (status == BluetoothGatt.GATT_SUCCESS) { 
             	//MTU設定成功 回傳當前所支持的mtu大小
				this.supportedMTU = mtu
   			 }
        }

參考

安卓BLE藍牙開發詳解
Android 從開發角度來看經典藍牙和低功耗(BLE)藍牙的區別
Android連接經典藍牙
Android 藍牙BLE開發詳解

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/286800.html

標籤:其他

上一篇:Android11.0 平板默認橫屏且兼容重力傳感器方案

下一篇:Android Webview 使用

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more