Harmony Ble 蓝牙App (一)扫描

Harmony Ble 蓝牙App (一)扫描

  • 前言
  • 正文
    • 一、创建工程
    • 二、工程配置
      • ① 权限配置
      • ② Debug配置
      • ③ UI配置
    • 三、扫描
      • ① 扫描接口
      • ② 扫描类
    • 四、业务处理
      • ① Slice的生命周期
      • ② 蓝牙开关和动态权限请求
    • 五、扫描设备
    • 六、显示设备
      • ① 自定义蓝牙类
      • ② 提供者
      • ③ 显示设备
    • 七、源码

前言

  关于Android的低功耗蓝牙,我做了很多介绍了,那么对于Harmony来说这一块我没有做过介绍,而实际中我确实做过一个Harmony的BLE项目,所以这里分享一些内容出来。

正文

  在Harmony中进行Ble的蓝牙开发实际上和Android中类似,但是又有一些不同,因为Harmony的SDK还在不断的完善。而这里我们使用的是API 6进行项目开发,使用的语言是Java,至于为什么使用API 6而不是最新的API 9,因为我买不起遥遥领先,所以只能用API 6的HUAWEI P30进行真机测试。蓝牙这种APP一定是要使用真机测试的,你用虚拟机是不行的,话不多说,我们开始吧。

在这里插入图片描述

一、创建工程

  下面开始创建工程。

在这里插入图片描述

选择Empty Ability,点击Next。我们创建一个名为HarmonyBle的项目,语言为Java。

在这里插入图片描述

点击Finish完成创建。

在这里插入图片描述

默认的工程就是这个样子的,是不是很像Android创建的工程呢?

二、工程配置

① 权限配置

  Harmony中同样有权限这个概念,也需要配置静态权限和动态权限,只不过配置静态权限的地方不一样。Harmony是在config.json中,里面的代码如下:

{"app": {"bundleName": "com.llw.ble","vendor": "example","version": {"code": 1000000,"name": "1.0.0"}},"deviceConfig": {},"module": {"package": "com.llw.ble","name": ".MyApplication","mainAbility": "com.llw.ble.MainAbility","deviceType": ["phone","tablet","tv","wearable","car"],"distro": {"deliveryWithInstall": true,"moduleName": "entry","moduleType": "entry","installationFree": false},"abilities": [{"skills": [{"entities": ["entity.system.home"],"actions": ["action.system.home"]}],"name": "com.llw.ble.MainAbility","description": "$string:mainability_description","icon": "$media:icon","label": "$string:entry_MainAbility","launchType": "standard","orientation": "unspecified","visible": true,"type": "page"}]}
}

  你阅读一下,你就会发现,这和Android的AndroidManifest.xml配置文件好像差不多啊。只不过一个用的是json,一个用的是xml。

  所以我们配置权限也是在config.json中,例如扫描蓝牙时我们需要定位权限。可以在里面增加如下代码:

    "reqPermissions": [{"name": "ohos.permission.LOCATION"},{"name": "ohos.permission.USE_BLUETOOTH"},{"name": "ohos.permission.DISCOVER_BLUETOOTH"},{"name": "ohos.permission.MANAGE_BLUETOOTH"}]

如下图所示,注意json中标点符号。

在这里插入图片描述

② Debug配置

  然后我们就应该要来写代码了,不过在此之前,我们先了解一下Ability和Slice的区别,Ability就像一个画框,而Slice就像一个画布。我们可以在一个画框里面加载多个画布,就好像多个页面之前的跳转,我们可以用Slice来进行,下面我们增加一个扫描的Slice,我们复制一下MainAbilitySlice,再粘贴一下,出现的弹窗中改名字

在这里插入图片描述
  为什么要通过这种方式来创建Java文件呢?因为DevEco Studio我创建不了Java文件,可能是这个版本的DS没有这个选项亦或是我没有找到。

  下面我们需要创建对应layout文件,再resources/base/layout下创建一个slice_scan.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:height="match_parent"ohos:width="match_parent"ohos:alignment="center"ohos:orientation="vertical"><ListContainerohos:id="$+id:lc_device"ohos:height="match_parent"ohos:width="match_parent"/></DirectionalLayout>

  然后我们再修改ScanSlice中的内容,让它加载我们刚写好的slice_scan.xml。修改onStart()方法,代码如下所示:

    @Overridepublic void onStart(Intent intent) {super.onStart(intent);super.setUIContent(ResourceTable.Layout_slice_scan);}

现在App打开之后默认会运行MainAbility,我们看一下这个里面。

public class MainAbility extends Ability {@Overridepublic void onStart(Intent intent) {super.onStart(intent);super.setMainRoute(MainAbilitySlice.class.getName());}
}

  可以看到,在setMainRoute()方法中,默认加载的是MainAbilitySlice,将它改为我们刚写好的ScanSlice,代码:super.setMainRoute(ScanSlice.class.getName());

然后我们先运行一下看看,通过USB链接到鸿蒙手机上。

在这里插入图片描述

这里会提示报错,浏览一下错误信息。

在这里插入图片描述

  这里是说我们需要配置Signing。点击Run下面或者右侧弹窗的Open signing configs,会打开一个配置窗口,如下图所示:

在这里插入图片描述

我们点击Signing Configs选项,需要你进行登录,如下图所示:

在这里插入图片描述

  这里就需要你登录华为的帐号了,我们当前在本地运行所以是Debug模式,旁边有一个Release表示发布版本,它里面配置的东西和Debug模式一致,区别在于Debug模式下的配置信息只要我们登录之后,DevEco Studio会帮助我们自动生成,而Release中的信息则需要开发者去华为开发者官网上去创建应用并申请配置文件和证书,比较麻烦,但是如果你要上架应用则必须做这一步,在国内,华为应用市场上架应用是最严格的。华为的你搞得定,其他的都是小趴菜,不值一提。

  下面我们先登录,会打开一个网页,登录成功之后,你会看到这样的页面。

在这里插入图片描述

  然后我们回到DS,就会自动配置Debug模式下的证书和配置文件,如下图所示:

在这里插入图片描述

  点击OK,会在DS中进行一个配置,配置好之后你可以在工程目录下的build.gradle中看到debug的相关信息,如下图所示。

在这里插入图片描述

然后我们再运行一下看看,这一次毫无疑问是可以运行成功的。如下图所示:

在这里插入图片描述

③ UI配置

可以看到默认的标题栏就如同Android默认的ActionBar,丑的很特别,我们去掉它,在config.json中添加如下代码:

        "metaData": {"customizeData": [{"extra": "","name": "hwc-theme","value": "androidhwext:style/Theme.Emui.Light.NoTitleBar"}]},

添加位置如下所示:

在这里插入图片描述

下面我们再运行一下看看。

在这里插入图片描述
是不是Unbelievable! 同样为了标题好看,我们在element下创建一个color.json,里面的代码如下所示:

{"color": [{"name": "white","value": "#FFF"},{"name": "black","value": "#000"},{"name": "blue","value": "#FFA7D3FF"},{"name": "bg_color","value": "#F8F8F8"},{"name": "gray","value": "#989898"}]
}

我们再修改一下scan_slice.xml中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:height="match_parent"ohos:width="match_parent"ohos:alignment="center"ohos:background_element="$color:bg_color"ohos:orientation="vertical"><DirectionalLayoutohos:height="50vp"ohos:width="match_parent"ohos:alignment="vertical_center"ohos:background_element="$color:blue"ohos:orientation="horizontal"ohos:start_padding="12vp"><Textohos:id="$+id:title"ohos:height="match_content"ohos:width="match_content"ohos:text="选择设备"ohos:text_color="#FFF"ohos:text_font="HwChinese-medium"ohos:text_size="18fp"ohos:weight="1"/><Textohos:id="$+id:tx_scan_status"ohos:height="match_content"ohos:width="match_content"ohos:end_margin="6vp"ohos:padding="8vp"ohos:text="搜索"ohos:text_color="#FFF"ohos:text_size="14fp"/></DirectionalLayout><ListContainerohos:id="$+id:lc_device"ohos:height="match_parent"ohos:width="match_parent"/></DirectionalLayout>

这个DirectionalLayout布局就是线性布局,我们可以点击右侧导航栏的Previewer进行布局预览,如下图所示。

在这里插入图片描述

右上角的T图标,点击之后可以查看当前布局的层级。

在这里插入图片描述

  这里说明一下,有时候在通过资源使用颜色值的时候会无法生效,所以就会直接使用#FFF,在代码里也是如此,这应该属于编译器的Bug。

标题栏就写好了,还有状态栏我们没有改,状态栏我们在MainAbility中进行修改,代码如下所示:

    @Overridepublic void onStart(Intent intent) {Window window = WindowManager.getInstance().getTopWindow().get();window.setStatusBarColor(Color.getIntColor("#A7D3FF"));super.onStart(intent);super.setMainRoute(ScanSlice.class.getName());}

还是修改onStart()方法,然后我们运行一下看看。

在这里插入图片描述
好了,下面我们来写扫描需要的内容代码。

三、扫描

  首先我们在com.llw.ble包下新建一个core包,core包下创建一个BleCore类,这里面就是控制Ble蓝牙相关的一切,比如扫描,连接,读写数据等操作,我们先不写代码。下面在core包下创建一个scan包。

① 扫描接口

scan包下新建一个ScanCallback接口,代码如下:

public interface ScanCallback {void onScanResult(BleScanResult result);default void onGroupScanResultsEvent(List<BleScanResult> results){}default void onScanFailed(String failed){}
}

② 扫描类

然后在scan包下新建一个BleScan类,代码如下所示:

public class BleScan {private final BleCentralManager centralManager;private boolean isScanning = false;private ScanCallback scanCallback;// 创建扫描过滤器然后开始扫描private List<BleScanFilter> filters;private static volatile BleScan mInstance;//初始化public static BleScan getInstance(Context context) {if (mInstance == null) {synchronized (BleScan.class) {if (mInstance == null) {mInstance = new BleScan(context);}}}return mInstance;}public BleScan(Context context) {BleScanCallback centralManagerCallback = new BleScanCallback();centralManager = new BleCentralManager(context, centralManagerCallback);}/*** 当前是否正在扫描* @return true 扫描中,false 未扫描*/public boolean isScanning() {return isScanning;}/*** 设置过滤信息* @param filters 蓝牙扫描过滤列表*/public void setFilters(List<BleScanFilter> filters) {this.filters = filters;}/*** 设置扫描回调,页面需要实现才能获取扫描到的设备* @param scanCallback 扫描回调*/public void setScanCallback(ScanCallback scanCallback) {this.scanCallback = scanCallback;}/*** 开始扫描*/public void startScan() {if (centralManager == null) {localScanFailed("Bluetooth not turned on.");return;}centralManager.startScan(filters);isScanning = true;}/*** 停止扫描*/public void stopScan() {if (!isScanning) {localScanFailed("Not currently scanning, your stop has no effect.");return;}centralManager.stopScan();isScanning = false;}/*** 实现扫描回调*/public class BleScanCallback implements BleCentralManagerCallback {@Overridepublic void scanResultEvent(BleScanResult bleScanResult) {if (scanCallback != null) {scanCallback.onScanResult(bleScanResult);}}@Overridepublic void scanFailedEvent(int resultCode) {if (scanCallback != null) {scanCallback.onScanFailed(String.valueOf(resultCode));}}@Overridepublic void groupScanResultsEvent(final List<BleScanResult> scanResults) {// 对扫描结果进行处理if (scanCallback != null) {scanCallback.onGroupScanResultsEvent(scanResults);}}}/*** 本地扫描失败处理* @param failed 错误信息*/private void localScanFailed(String failed) {if (scanCallback != null) {scanCallback.onScanFailed(failed);}}
}

  这里面采用单例模式,在初始化之后直接调用,然后再实现扫描回调接口,返回扫描信息,有开始、停止扫描和是否正在扫描方法。这个类你可以直接用,也可以再封装到BleCore中,这里我们封装到BleCore中,修改BleCore中的代码,如下所示:

public class BleCore  {private static volatile BleCore mInstance;private final BleScan bleScan;public BleCore(Context context) {//蓝牙扫描bleScan = BleScan.getInstance(context);}public static BleCore getInstance(Context context) {if (mInstance == null) {synchronized (BleCore.class) {if (mInstance == null) {mInstance = new BleCore(context);}}}return mInstance;}public void setPhyScanCallback(ScanCallback scanCallback) {bleScan.setScanCallback(scanCallback);}public boolean isScanning() {return bleScan.isScanning();}public void startScan() {bleScan.startScan();}public void stopScan() {bleScan.stopScan();}
}

四、业务处理

  这里的业务处理主要是两个,第一个是蓝牙开关监听,第二个动态权限申请。

再进行业务处理之前,我们先修改一下MyApplication类的名字,修改为BleApp,修改后再改动里面的代码,如下所示:

public class BleApp extends AbilityPackage {private static BleCore bleCore;@Overridepublic void onInitialize() {super.onInitialize();bleCore = BleCore.getInstance(getContext());}public static BleCore getBleCore() {return bleCore;}
}

① Slice的生命周期

  首先我们来看一下Slice的生命周期,这个就比较重要,下面我们首先在com.llw.ble下创建一个utils包,utils包下创建一个LogUtils类,代码如下所示:

public class LogUtils {static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "HarmonyBle");private static HiLogLabel logLabel;public static void setLogLabel(HiLogLabel logLabel) {LogUtils.logLabel = logLabel;}public static void Log(String content) {HiLog.info(LABEL, content);}public static void LogI(String TAG, String content) {HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG);HiLog.info(label, content);}public static void LogD(String TAG, String content) {HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG);HiLog.debug(label, content);}public static void LogE(String TAG, String content) {HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG);HiLog.error(label, content);}public static void LogW(String TAG, String content) {HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00201, TAG);HiLog.warn(label, content);}}

  这是因为Harmony中打印日志比较麻烦,所以写一个工具类,封装一下,下面我们修改一下ScanSlice类中的代码,如下所示:

public class ScanSlice extends AbilitySlice {private final String TAG = ScanSlice.class.getSimpleName();@Overridepublic void onStart(Intent intent) {super.onStart(intent);super.setUIContent(ResourceTable.Layout_slice_scan);LogUtils.LogD(TAG, "onStart");}@Overridepublic void onActive() {LogUtils.LogD(TAG, "onActive");}@Overrideprotected void onInactive() {LogUtils.LogD(TAG, "onInactive");}@Overridepublic void onForeground(Intent intent) {LogUtils.LogD(TAG, "onForeground");}@Overrideprotected void onBackground() {LogUtils.LogD(TAG, "onBackground");}@Overrideprotected void onStop() {LogUtils.LogD(TAG, "onStop");}
}

然后我们运行一下看看,检查控制台日志:

在这里插入图片描述

然后我们通过Home键回到桌面,看看日志:

在这里插入图片描述

然后我们点击桌面上的图标回到应用中,看看日志:

在这里插入图片描述

再回到桌面,然后我们通过后台的运行程序进入应用,看看日志:

在这里插入图片描述
这两种回到应用的方式日志一样,然后我们按返回键回到桌面,看看日志:

在这里插入图片描述
那么现在你对于Slice的生命周期就比较了解了,下面我们进行代码的编写。

② 蓝牙开关和动态权限请求

  首先处理蓝牙相关的,在BleCore中添加如下代码:

	private final BluetoothHost mBluetoothHost;

在构造方法中实例化

    public BleCore(Context context) {...// 获取蓝牙本机管理对象mBluetoothHost = BluetoothHost.getDefaultHost(context);}

然后我们再写两个方法:

    public boolean isEnableBt() {return mBluetoothHost.getBtState() == BluetoothHost.STATE_ON;}public void enableBt() {mBluetoothHost.enableBt();}

  用于判断是否打开蓝牙和打开蓝牙,回到ScanSlice中我们需要使用BleCore来处理蓝牙相关的工作,代码如下所示:

public class ScanSlice extends AbilitySlice {private final String TAG = ScanSlice.class.getSimpleName();private BleCore bleCore;private Text txScanStatus;private ListContainer lcDevice;@Overridepublic void onStart(Intent intent) {super.onStart(intent);super.setUIContent(ResourceTable.Layout_slice_scan);bleCore = BleApp.getBleCore();txScanStatus = (Text) findComponentById(ResourceTable.Id_tx_scan_status);lcDevice = (ListContainer) findComponentById(ResourceTable.Id_lc_device);//点击监听txScanStatus.setClickedListener(component -> {});}@Overridepublic void onActive() {// 判断是否打开蓝牙if (!bleCore.isEnableBt()) {//打开蓝牙bleCore.enableBt();return;}}
}

  首先在onStart()中对BleCore进行实例化,findComponentById就如同findViewById,然后在onActive()中调用刚才我们所写的方法。

    @Overridepublic void onActive() {// 判断是否打开蓝牙if (!bleCore.isEnableBt()) {//打开蓝牙bleCore.enableBt();return;}}

然后是定位权限的处理,同样在onActive()中,增加代码如下所示:

    @Overridepublic void onActive() {// 判断是否打开蓝牙...// 是否获取定位权限String locationPermission = "ohos.permission.LOCATION";if (verifySelfPermission(locationPermission) != IBundleManager.PERMISSION_GRANTED) {requestPermissionsFromUser(new String[]{locationPermission}, 100);return;}}

这里首先我们定义一个权限,然后判断是否授予,没有授予则进行请求,下面运行一下看看:

在这里插入图片描述

  那么我们就完成了蓝牙打开和定位权限动态申请,你可以在运行一次,你会发现,你还需要请求权限的,因为DS默认安装时不会保留应用的数据,而蓝牙打开了属于系统层面的,所以你可以不用再打开蓝牙,而需要重新请求定位权限,为了避免这一点,我们点击Run→ Edit Configurations...

在这里插入图片描述

在弹出的窗口上勾选Keep Application Data

在这里插入图片描述

点击OK,再运行即可。

五、扫描设备

  接下来我们进行扫描的处理,在ScanSlice中增加如下方法代码:

    private void startScan() {bleCore.startScan();txScanStatus.setText("停止");}private void stopScan() {bleCore.stopScan();txScanStatus.setText("搜索");}

  这里就是扫描和停止方法,同时修改一下Text文本,在onStart()中首先实现扫描回调监听,然后处理再处理txScanStatus文本的点击事件,代码如下所示:

    @Overridepublic void onStart(Intent intent) {...bleCore.setPhyScanCallback(this);//点击监听txScanStatus.setClickedListener(component -> {if (bleCore.isScanning()) {stopScan();//扫描开关停止扫描} else {startScan();//开始扫描}});}

这里this会报错,鼠标放在上面,Alt + Enter,出现弹窗。

在这里插入图片描述

选择最后一个,就会给你实现ScanCallback中的onScanResult()方法,代码如下所示:

    @Overridepublic void onScanResult(BleScanResult result) {LogUtils.LogD(TAG, result.getPeripheralDevice().getDeviceAddr());}

我们在里面打印一下扫描到的设备Mac地址,最后我们在onActive()中增加如下所示代码:

    @Overridepublic void onActive() {...// 是否在扫描中if (!bleCore.isScanning()) {startScan();}}

下面运行一下,看看控制台日志:

在这里插入图片描述

扫描出来了,只不过目前还看不到,所以我们要渲染一下,让它可以看到。

六、显示设备

要显示设备,首先我们需要写一个Bean。

① 自定义蓝牙类

在core包下新建一个BleDevice类,里面的代码如下所示:

public class BleDevice {private String realName = "Unknown device"; //蓝牙设备真实名称private String macAddress;                  //地址private int rssi;                           //信号强度private BlePeripheralDevice device;         //设备public BleDevice(BleScanResult scanResult) {this.device = scanResult.getPeripheralDevice();this.macAddress = device.getDeviceAddr();String name = device.getDeviceName().get();if (name != null || !name.isEmpty()) {this.realName = name;}this.rssi = scanResult.getRssi();}public String getRealName() {return realName;}public void setRealName(String realName) {this.realName = realName;}public String getMacAddress() {return macAddress;}public void setMacAddress(String macAddress) {this.macAddress = macAddress;}public int getRssi() {return rssi;}public void setRssi(int rssi) {this.rssi = rssi;}public BlePeripheralDevice getDevice() {return device;}public void setDevice(BlePeripheralDevice device) {this.device = device;}
}

  这个Bean没有什么好说的,下面要做的就是列表Item的渲染,在Android中我们使用的是适配器Adapter,而在Harmony中使用的是提供者Provider

② 提供者

同样我们先写布局,在layout下新建一个item_scan_device.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:height="match_content"ohos:width="match_parent"ohos:alignment="vertical_center"ohos:background_element="#FFF"ohos:bottom_padding="8vp"ohos:orientation="horizontal"ohos:right_padding="16vp"ohos:top_margin="1vp"ohos:top_padding="8vp"><Imageohos:height="match_content"ohos:width="match_content"ohos:end_margin="16vp"ohos:image_src="$graphic:ic_bluetooth"ohos:start_margin="16vp"/><DirectionalLayoutohos:height="match_content"ohos:width="match_content"ohos:orientation="vertical"ohos:weight="1"><Textohos:id="$+id:device_name"ohos:height="match_content"ohos:width="match_content"ohos:text="设备名称"ohos:text_size="16fp"/><Textohos:id="$+id:device_address"ohos:height="match_content"ohos:width="match_content"ohos:text="设备地址"ohos:text_color="$color:gray"ohos:text_size="14fp"ohos:top_margin="4vp"/></DirectionalLayout><Textohos:id="$+id:rssi"ohos:height="match_content"ohos:width="match_content"ohos:align_parent_end="true"ohos:text_color="#000000"ohos:text_size="10fp"/></DirectionalLayout>

  几个主要内容,设备名称、Mac地址、Rssi信号强度,然后这里有一个图标,在graphic下创建一个ic_bluetooth.xml,代码如下所示:

<?xml version="1.0" encoding="UTF-8"?><vectorxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:height="48vp"ohos:width="48vp"ohos:viewportHeight="1024"ohos:viewportWidth="1024"><pathohos:fillColor="#A7D3FF"ohos:pathData="M53.31,512a458.69,458.69 0,1 1,917.38 0A458.69,458.69 0,0 1,53.31 512zM584.96,301.82a356.16,356.16 0,0 0,-39.81 -26.69c-12.1,-6.34 -32,-13.89 -52.74,-3.01 -20.48,10.82 -25.86,31.23 -27.78,44.67 -1.92,13.18 -1.92,30.21 -1.92,48.45v77.18l-57.92,-49.6a32,32 0,0 0,-41.6 48.64L445.44,512 363.2,582.4a32,32 0,1 0,41.6 48.64l57.92,-49.6v77.18c0,18.24 0,35.33 1.92,48.51 1.92,13.44 7.23,33.86 27.78,44.61 20.74,10.88 40.64,3.33 52.74,-2.94a356.48,356.48 0,0 0,39.81 -26.69l39.42,-28.8c10.62,-7.74 21.31,-15.55 29.06,-23.1 8.64,-8.58 18.56,-21.57 18.56,-40.06 0,-18.56 -9.92,-31.55 -18.56,-40.06 -7.68,-7.55 -18.43,-15.36 -29.06,-23.17L548.99,512l75.39,-54.98c10.62,-7.74 21.31,-15.55 29.06,-23.17 8.64,-8.51 18.56,-21.5 18.56,-40 0,-18.56 -9.92,-31.55 -18.56,-40.06 -7.68,-7.62 -18.43,-15.36 -29.06,-23.17l-39.42,-28.8zM526.72,367.36v64.77c0,7.36 0,11.01 2.37,12.16 2.3,1.28 5.25,-0.9 11.2,-5.25l44.8,-32.7 8.32,-6.08c3.97,-2.94 5.95,-4.42 5.95,-6.53 0,-2.18 -1.98,-3.65 -5.95,-6.53l-8.32,-6.14 -36.1,-26.3a3344.06,3344.06 0,0 0,-9.34 -6.78c-5.44,-3.97 -8.19,-5.95 -10.5,-4.8 -2.37,1.15 -2.37,4.54 -2.37,11.33v12.86zM526.72,656.45L526.72,591.74c0,-7.36 0,-11.01 2.37,-12.16 2.3,-1.22 5.25,0.96 11.2,5.25l44.8,32.7 8.32,6.14c3.97,2.88 5.95,4.35 5.95,6.53 0,2.11 -1.98,3.58 -5.95,6.53l-8.32,6.08 -36.1,26.37 -9.34,6.78c-5.44,3.97 -8.19,5.95 -10.5,4.74 -2.37,-1.15 -2.37,-4.48 -2.37,-11.33v-12.8z"></path>
</vector>

  下面我们写提供者,在com.llw.ble下创建一个provider包,包下创建一个ScanDeviceProvider类,代码如下所示:

public class ScanDeviceProvider extends BaseItemProvider {private final List<BleDevice> deviceList;private final AbilitySlice slice;public ScanDeviceProvider(List<BleDevice> list, AbilitySlice slice) {this.deviceList = list;this.slice = slice;}@Overridepublic int getCount() {return deviceList == null ? 0 : deviceList.size();}@Overridepublic Object getItem(int position) {if (deviceList != null && position >= 0 && position < deviceList.size()) {return deviceList.get(position);}return null;}@Overridepublic long getItemId(int position) {return position;}@Overridepublic Component getComponent(int position, Component component, ComponentContainer componentContainer) {final Component cpt;ScanDeviceHolder holder;BleDevice device = deviceList.get(position);if (component == null) {cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_scan_device, null, false);holder = new ScanDeviceHolder(cpt);//将获取到的子组件信息绑定到列表项的实例中cpt.setTag(holder);} else {cpt = component;// 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。holder = (ScanDeviceHolder) cpt.getTag();}holder.deviceName.setText(device.getRealName());holder.deviceAddress.setText(device.getMacAddress());holder.rssi.setText(String.format(Locale.getDefault(), "%d dBm", device.getRssi()));return cpt;}/*** 用于保存列表项的子组件信息*/public static class ScanDeviceHolder {Text deviceName;Text deviceAddress;Text rssi;public ScanDeviceHolder(Component component) {deviceName = (Text) component.findComponentById(ResourceTable.Id_device_name);deviceAddress = (Text) component.findComponentById(ResourceTable.Id_device_address);rssi = (Text) component.findComponentById(ResourceTable.Id_rssi);}}
}

  通过提供者的代码,可以看到它和适配器的写法差不多,不同的是你得注意getComponent()方法中的处理,另外提供者默认提供了Item的点击方法,所以我们不用再自己去写了。

③ 显示设备

  我们回到ScanSlice中使用,首先我们创建几个变量,代码如下所示:

    private final List<BleDevice> mList = new ArrayList<>();private ScanDeviceProvider provider;

然后在onStart()方法中进行初始化:

    @Overridepublic void onStart(Intent intent) {...provider = new ScanDeviceProvider(mList, this);lcDevice.setItemProvider(provider);//列表item点击监听lcDevice.setItemClickedListener((listContainer, component, position, id) -> {});}

这里设置了列表提供者,然后添加item点击监听,最后我们在扫描回调中渲染数据,修改代码如下所示:

private int findIndex(BleDevice bleDevice, List<BleDevice> deviceList) {int index = 0;for (final BleDevice devi : deviceList) {if (bleDevice.getMacAddress().equals(devi.getDevice().getDeviceAddr())) return index;index += 1;}return -1;}@Overridepublic void onScanResult(BleScanResult result) {BleDevice bleDevice = new BleDevice(result);int index = findIndex(bleDevice, mList);if (index == -1) {//添加新设备mList.add(bleDevice);} else {//更新已有设备的rssi和时间戳mList.get(index).setRssi(bleDevice.getRssi());}getUITaskDispatcher().syncDispatch(() -> provider.notifyDataChanged());}

这里添加一个findIndex()方法,用于添加设备和更新设备,最终通过UI线程同步刷新提供者,再修改一个开始扫描和停止扫描的方法代码:

    private void startScan() {mList.clear();provider.notifyDataChanged();bleCore.startScan();txScanStatus.setText("停止");LogUtils.LogD(TAG,"开始扫描设备!");}private void stopScan() {bleCore.stopScan();txScanStatus.setText("搜索");LogUtils.LogD(TAG,"已经停止扫描设备!");}

运行一下看看:

在这里插入图片描述

七、源码

如果对你有所帮助的话,不妨 Star 或 Fork,山高水长,后会有期~

源码地址:HarmonyBle-Java

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/202110.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu(Linux)的基本操作

基本操作三步走 1、输入vim code.c点击i&#xff08;出现insert&#xff09;表示可以编辑代码编辑代码之后按下esc&#xff08;退出编辑模式&#xff09;按下shift:&#xff08;冒号&#xff09;wq&#xff08;退出文件&#xff09;2、输入gcc code.c&#xff08;进行编译代码…

中国互联网成功出海,国产操作系统抛弃安卓,打破谷歌垄断

近期国内诸多互联网公司都在大举招募人手&#xff0c;为国产操作系统开发应用&#xff0c;恰在此时中国的互联网也成功走向海外市场&#xff0c;或许这次国产操作系统真的要成功了&#xff0c;谷歌垄断智能手机操作系统十多年的局面将因此被打破。 两个多月前&#xff0c;某为就…

MySQL之BETWEEN AND包含范围查询总结

一、时间范围 查询参数格式与数据库类型相对应时&#xff0c;between and包含头尾&#xff0c;否则依情况 当数据库字段中存储的是yyyy-MM-dd格式&#xff0c;即date类型&#xff1a; 用between and查询&#xff0c; 参数yyyy-MM-dd格式时&#xff0c;包含头尾&#xff0c;相当…

带记忆的超级GPT智能体,能做饭、煮咖啡、整理家务!

随着AI技术的快速迭代&#xff0c;Alexa、Siri、小度、天猫精灵等语音助手得到了广泛应用。但在自然语言理解和完成复杂任务方面仍然有限。 相比文本的标准格式&#xff0c;语音充满复杂性和多样性&#xff08;例如&#xff0c;地方话&#xff09;,传统方法很难适应不同用户的…

都被“锟斤拷”毒害过,那么究竟是为什么会出现这些奇怪的字符?

不管是在工作中还是生活中&#xff0c;都被“锟斤拷”毒害过&#xff0c;比如这样&#xff1a; 或者这样&#xff1a; 还有这样&#xff1a; 那么究竟是为什么会出现这些奇怪的字符&#xff1f; ASCII编码 在计算机底层都是用0和1进行存储的&#xff0c;ASCII编码将所有的字母…

高通OTA升级方案介绍

高通OTA升级方案介绍 1. 高通LE OTA1.1 背景1.2 Recovery系统 2. SDX12 OTA方案3 OTA包的加密 3UK Penetration Test对于OTA升级也有严格的安全要求&#xff0c;下面是几条用例要求&#xff1a; Firmware: A sufficiently strong signing key MUST be in use. Signing keys MUS…

Ubuntu18 Opencv3.4.12 viz 3D显示安装、编译、使用、移植

Opencv3.*主模块默认包括两个3D库 calib3d用于相机校准和三维重建 &#xff0c;viz用于三维图像显示&#xff0c;其中viz是cmake选配。 参考&#xff1a; https://docs.opencv.org/3.4.12/index.html 下载linux版本的源码 sources。 查看cmake apt list --installed | grep…

力扣hot100 两数之和 哈希表

&#x1f468;‍&#x1f3eb; 力扣 两数之和 &#x1f60b; 思路 在一个数组中如何快速找到某一个数的互补数&#xff1a;哈希表 O(1)实现⭐ AC code class Solution {public int[] twoSum(int[] nums, int target){HashMap<Integer, Integer> map new HashMap<&g…

PC端页面进去先出现加载效果

自定义指令v-loading&#xff0c;只需要绑定Boolean即可 v-loading“loading” <el-table :data"list" border style"width: 100%" v-loading"loading"><el-table-column align"center" label"序号" width"5…

中部A股第一城,长沙如何赢商?

文|智能相对论 作者|范柔丝 长沙的马路&#xff0c;都很有故事。 一条解放西路&#xff0c;是全国人民都争相打卡的娱乐地标&#xff1b;一条太平街&#xff0c;既承载了历史的厚重又演绎着现代的鲜活...... 但如果来到河西的桐梓坡路&#xff0c;风景会变得截然不同。 沿…

微信小程序蓝牙连接 uniApp蓝牙连接设备

蓝牙列表期待效果 代码 <template><view class"bluetooth-list"><view class"align-items option" style"justify-content: space-between;" v-for"item in bluetoothList" :key"item.deviceId"><vie…

汽车智能座舱/智能驾驶SOC -2

第二篇&#xff08;笔记&#xff09;。 未来智能汽车电子电气将会是集中式架构&#xff08;车载数据中心&#xff09;虚拟化技术&#xff08;提供车载数据中心灵活性和安全性&#xff09;这个几乎是毋庸置疑的了。国际大厂也否纷纷布局超算芯片和车载数据中心平台。但是演进需…

网络安全等级保护收费标准?

不同省份价格会略有不同&#xff0c;二级等保一般不低于5万元;三级等保不低于9万元&#xff0c;个别省份也可能7万也能办理&#xff0c;根据企业实际情况和省市选定的代理机构确定。 等级保护二级? 第二级等保是指信息系统受到破坏后&#xff0c;会对公民、法人和其他组织的合…

win10底部任务栏无响应?试试这些方法!

win10的任务栏是一个关键的用户界面元素&#xff0c;允许您轻松访问应用程序和系统功能。然而&#xff0c;有时您可能会遇到win10底部任务栏无响应的问题&#xff0c;这会妨碍您的工作流程。本篇文章将介绍解决win 10底部任务栏无响应的问题的三种方法&#xff0c;每种方法都会…

《数据仓库入门实践》

前言&#xff1a; 1、问什么要写这篇博客&#xff1f; 随着自己在数仓岗位工作的年限增加&#xff0c;对数仓的理解和认知也在发生着变化 所有用这篇博客来记录工作中用到的知识点与经验 2、这篇博客主要记录了哪些内容&#xff1f; 在日常工作中&#xff0c;发现刚接触不久数仓…

脸爱云一脸通智慧管理平台未授权访问

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一、漏洞概述 脸爱云一脸通智慧管理平台存在严重漏洞&#xff0c;允许…

【机器学习基础】K-Means聚类算法

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;机器学习 欢迎订阅&#xff01;相对完整的机器学习基础教学&#xff01; ⭐特别提醒&#xff1a;针对机器学习&#xff0c;特别开始专栏&#xff1a;机器学习python实战…

深度学习图像修复算法 - opencv python 机器视觉 计算机竞赛

文章目录 0 前言2 什么是图像内容填充修复3 原理分析3.1 第一步&#xff1a;将图像理解为一个概率分布的样本3.2 补全图像 3.3 快速生成假图像3.4 生成对抗网络(Generative Adversarial Net, GAN) 的架构3.5 使用G(z)生成伪图像 4 在Tensorflow上构建DCGANs最后 0 前言 &#…

SpringCloud - 新版淘汰 Ribbon,在 OpenFeign 中整合 LoadBalancer 负载均衡

目录 一、LoadBalancer 负载均衡 1.1、前言 1.2、LoadBalancer 负载均衡底层实现原理 二、整合 OpenFeign LoadBalancer 2.1、所需依赖 2.2、具体实现 2.3、自定义负载均衡策略 一、LoadBalancer 负载均衡 1.1、前言 在 2020 年以前的 SpringCloud 采用 Ribbon 作为负载…

【Flask使用】全知识md文档,4大部分60页第3篇:Flask模板使用和案例

本文的主要内容&#xff1a;flask视图&路由、虚拟环境安装、路由各种定义、状态保持、cookie、session、模板基本使用、过滤器&自定义过滤器、模板代码复用&#xff1a;宏、继承/包含、模板中特有变量和函数、Flask-WTF 表单、CSRF、数据库操作、ORM、Flask-SQLAlchemy…