前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Harmony Ble 蓝牙App (一)扫描

Harmony Ble 蓝牙App (一)扫描

作者头像
晨曦_LLW
发布于 2023-11-21 01:55:17
发布于 2023-11-21 01:55:17
67000
代码可运行
举报
运行总次数:0
代码可运行

前言

  关于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中,里面的代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
  "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中,例如扫描蓝牙时我们需要定位权限。可以在里面增加如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    "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,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <ListContainer
        ohos:id="$+id:lc_device"
        ohos:height="match_parent"
        ohos:width="match_parent"/>

</DirectionalLayout>

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_slice_scan);
    }

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MainAbility extends Ability {
    @Override
    public 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中添加如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        "metaData": {
          "customizeData": [
            {
              "extra": "",
              "name": "hwc-theme",
              "value": "androidhwext:style/Theme.Emui.Light.NoTitleBar"
            }
          ]
        },

添加位置如下所示:

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
  "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中的代码,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns: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">

    <DirectionalLayout
        ohos:height="50vp"
        ohos:width="match_parent"
        ohos:alignment="vertical_center"
        ohos:background_element="$color:blue"
        ohos:orientation="horizontal"
        ohos:start_padding="12vp">

        <Text
            ohos: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"/>

        <Text
            ohos: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>

    <ListContainer
        ohos:id="$+id:lc_device"
        ohos:height="match_parent"
        ohos:width="match_parent"/>

</DirectionalLayout>

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

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @Override
    public 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接口,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface ScanCallback {

    void onScanResult(BleScanResult result);

    default void onGroupScanResultsEvent(List<BleScanResult> results){}

    default void onScanFailed(String failed){}
}
② 扫描类

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 {

        @Override
        public void scanResultEvent(BleScanResult bleScanResult) {
            if (scanCallback != null) {
                scanCallback.onScanResult(bleScanResult);
            }
        }

        @Override
        public void scanFailedEvent(int resultCode) {
            if (scanCallback != null) {
                scanCallback.onScanFailed(String.valueOf(resultCode));
            }
        }

        @Override
        public 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中的代码,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,修改后再改动里面的代码,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class BleApp extends AbilityPackage {
    private static BleCore bleCore;

    @Override
    public void onInitialize() {
        super.onInitialize();

        bleCore = BleCore.getInstance(getContext());
    }

    public static BleCore getBleCore() {
        return bleCore;
    }
}
① Slice的生命周期

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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类中的代码,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ScanSlice extends AbilitySlice {

    private final String TAG = ScanSlice.class.getSimpleName();

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_slice_scan);
        LogUtils.LogD(TAG, "onStart");
    }


    @Override
    public void onActive() {
        LogUtils.LogD(TAG, "onActive");
    }

    @Override
    protected void onInactive() {
        LogUtils.LogD(TAG, "onInactive");
    }

    @Override
    public void onForeground(Intent intent) {
        LogUtils.LogD(TAG, "onForeground");
    }

    @Override
    protected void onBackground() {
        LogUtils.LogD(TAG, "onBackground");
    }

    @Override
    protected void onStop() {
        LogUtils.LogD(TAG, "onStop");
    }
}

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

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

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

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

这两种回到应用的方式日志一样,然后我们按返回键回到桌面,看看日志:

那么现在你对于Slice的生命周期就比较了解了,下面我们进行代码的编写。

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	private final BluetoothHost mBluetoothHost;

在构造方法中实例化

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public BleCore(Context context) {
        ...
        // 获取蓝牙本机管理对象
        mBluetoothHost = BluetoothHost.getDefaultHost(context);
    }

然后我们再写两个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public boolean isEnableBt() {
        return mBluetoothHost.getBtState() == BluetoothHost.STATE_ON;
    }

    public void enableBt() {
        mBluetoothHost.enableBt();
    }

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ScanSlice extends AbilitySlice {

    private final String TAG = ScanSlice.class.getSimpleName();
    private BleCore bleCore;

    private Text txScanStatus;
    private ListContainer lcDevice;

    @Override
    public 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 -> {
            
        });
    }

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @Override
    public void onActive() {
        // 判断是否打开蓝牙
        if (!bleCore.isEnableBt()) {
            //打开蓝牙
            bleCore.enableBt();
            return;
        }
    }

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @Override
    public 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中增加如下方法代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    private void startScan() {
        bleCore.startScan();
        txScanStatus.setText("停止");
    }

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @Override
    public void onStart(Intent intent) {
        ...
        bleCore.setPhyScanCallback(this);
        //点击监听
        txScanStatus.setClickedListener(component -> {
            if (bleCore.isScanning()) {
                stopScan();//扫描开关停止扫描
            } else {
                startScan();//开始扫描
            }
        });
    }

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @Override
    public void onScanResult(BleScanResult result) {
        LogUtils.LogD(TAG, result.getPeripheralDevice().getDeviceAddr());
    }

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @Override
    public void onActive() {
        ...
        // 是否在扫描中
        if (!bleCore.isScanning()) {
            startScan();
        }
    }

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

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

六、显示设备

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

① 自定义蓝牙类

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns: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">

    <Image
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:end_margin="16vp"
        ohos:image_src="$graphic:ic_bluetooth"
        ohos:start_margin="16vp"/>

    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:orientation="vertical"
        ohos:weight="1">

        <Text
            ohos:id="$+id:device_name"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="设备名称"
            ohos:text_size="16fp"/>

        <Text
            ohos: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>

    <Text
        ohos: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,代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?xml version="1.0" encoding="UTF-8"?>

<vector
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="48vp"
    ohos:width="48vp"
    ohos:viewportHeight="1024"
    ohos:viewportWidth="1024">

    <path
        ohos: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类,代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;
    }

    @Override
    public int getCount() {
        return deviceList == null ? 0 : deviceList.size();
    }

    @Override
    public Object getItem(int position) {
        if (deviceList != null && position >= 0 && position < deviceList.size()) {
            return deviceList.get(position);
        }
        return null;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public 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中使用,首先我们创建几个变量,代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    private final List<BleDevice> mList = new ArrayList<>();
    private ScanDeviceProvider provider;

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @Override
    public void onStart(Intent intent) {
        ...

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

        });
    }

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    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;
    }

    @Override
    public 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线程同步刷新提供者,再修改一个开始扫描和停止扫描的方法代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-11-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Harmony Ble蓝牙App(二)连接与发现服务
  在上一篇中我们进行扫描设备的处理,本文中进行连接和发现服务的数据处理,运行效果图如下所示:
晨曦_LLW
2023/11/24
4190
Harmony Ble蓝牙App(二)连接与发现服务
Android 低功耗蓝牙开发(扫描、连接、数据交互)Kotlin版
  写这篇文章是因为有读者想看看Kotlin中怎么操作低功耗蓝牙,再加上我也想写一些关于Kotlin的内容,对于低功耗蓝牙的Java版的,我写了两篇,一个是扫描、连接,另一篇就是数据交互,而这篇Kotlin文章我会减少讲解的环节,更多的注重业务逻辑和UI以及Kotlin的语法。
晨曦_LLW
2021/12/30
1.8K0
Android 低功耗蓝牙开发(扫描、连接、数据交互)Kotlin版
Android 低功耗蓝牙开发(扫描、连接)
  之间我写过蓝牙开发的文章,只不过是针对于经典蓝牙,可以理解为普通蓝牙,连接的对象是经典蓝牙,列如手机蓝牙、蓝牙耳机等设备。而也有读者说在学习低功耗蓝牙,因此就有了这篇文章,一方面是为了丰富蓝牙的使用,一方面也是为了帮助看我文章的读者,我会讲的很细,很多人也说我在记流水账,不过这不重要,重要的是你从流水账里学到了什么。
晨曦_LLW
2021/07/23
2.9K0
鸿蒙 PageSlider 滑动组件基础用法【鸿蒙专题9】
各位同学大家好 有断时间没有给大家更新文章了具体多久我也记得了。最近还在学习鸿蒙开发, 学到了PageSlider 滑动组件类似安卓里面 viewpager 可以实现 屏幕左右滑动的效果 ,那么废话不多说我们正式开始
徐建国
2022/03/30
1K0
鸿蒙 PageSlider 滑动组件基础用法【鸿蒙专题9】
HarmonyOS实战—服务卡片初体验
最近看到很多博客网站上出现了HarmonyOS的征文活动,看到那些精美的奖品让我也安耐不住开了,当然奖品的诱惑当然是抵挡不住我对技术的狂热追求,对于开发者而言技术没有顶峰没有终点。那么今天给大家做一个卡片服务开发的经验分享,如果有什么地方说的不对的请各位开发者进行指正,如果有什么问题也可以发私信或者直接在帖子中留言,我也会及时回复大家。
爱吃土豆丝的打工人
2021/11/10
1.2K0
HarmonyOS实战—服务卡片初体验
自学鸿蒙应用开发(33)- 在布局中使用自定义UI组件
组建容器类ArcProgressBarContainer负责协调每个ArcProgressBar的描画动作。
面向对象思考
2021/03/16
9220
自学HarmonyOS应用开发(75)- 显示拍摄结果
照片拍摄完成接下来的动作就是确认拍照结果。我们的实现方法是对前面文章中用到的文件浏览器功能进行扩展,从而实现一个简单的照片浏览器功能。
面向对象思考
2021/11/25
5270
自学HarmonyOS应用开发(75)- 显示拍摄结果
Android 12 蓝牙适配
  在我的申请下,公司终于购买了一台基于Android12.0的手机,然后我就开心的拿去安装测试了,发现程序崩溃了,于是我这里就写下来,Android12.0的蓝牙适配方法。
晨曦_LLW
2022/04/27
2.2K0
Android 12 蓝牙适配
Harmony Ble蓝牙App(四)描述符
  上一篇中了解了特性和属性,同时显示设备蓝牙服务下的特性和属性,本文中就需要来使用这些特性和属性来完成一些功能。
晨曦_LLW
2024/05/25
3380
Harmony Ble蓝牙App(四)描述符
自学鸿蒙应用开发(18)- Ability内部画面迁移
布局代码中第59行~第97行的用于生成分别向组件Slice,列表Slice和标签页Slice进行迁移的按钮。程序执行时的画面表示如下:
面向对象思考
2021/01/14
3790
自学鸿蒙应用开发(18)- Ability内部画面迁移
自学鸿蒙应用开发(17)- TabList和Tab
在layout目录下创建TabList布局,将其命名为ability_tablist.xml。
面向对象思考
2021/01/13
5400
自学鸿蒙应用开发(5)- button组件
代码中按钮id被指定为hello_button,这个信息会在下面的响应代码中用到。
面向对象思考
2020/12/31
8800
自学鸿蒙应用开发(5)- button组件
【鸿蒙 HarmonyOS】UI 组件 ( Text 组件 )
id 属性 : ohos:id="$+id:text_helloworld" , 用于作为当前组件的唯一标识 , 在单个布局文件中不允许 id 标识重复 ;
韩曙亮
2023/03/28
9960
【鸿蒙 HarmonyOS】UI 组件 ( Text 组件 )
“开辟鸿蒙 ” - 鸿蒙系统应用开发尝鲜
第一次知道“鸿蒙”这个词,是在《红楼梦》的开篇引子。鸿蒙,在古典文化中,表示宇宙形成前的混沌状态,意为开天辟地之前。
极客人
2020/10/09
9110
HarmonyOS实战——ToastDialog组件基本使用
1. ToastDialog 简介 [在这里插入图片描述] ToastDialog 是 CommonDialog 的子类,他们的用法几乎是一致的,只不过 ToastDialog 有自己的特性 ToastDialog 的组成如下:标题、提示内容、选择按钮 [在这里插入图片描述] 一般只会用中间的提示内容,因为 ToastDialog 出现的意义就是用来作消息提示的 ToastDialog 弹框有自己的展示时间,默认展示 2 秒钟,时间到了之后弹框就会自动消失 2. ToastDialog 案例 案例:点击按钮
兮动人
2021/09/13
7420
HarmonyOS实战——ToastDialog组件基本使用
【鸿蒙 HarmonyOS】界面跳转 ( AbilitySlice 之间的界面跳转 | AbilitySlice 之间的值传递 )
代码示例 GitHub 地址 : https://github.com/han1202012/HarmonyHelloWorld
韩曙亮
2023/03/28
8500
【鸿蒙 HarmonyOS】界面跳转 ( AbilitySlice 之间的界面跳转 | AbilitySlice 之间的值传递 )
【鸿蒙 HarmonyOS】界面跳转 ( Page Ability 的 action 标识 | Page Ability 之间的界面跳转及传递数据 | 鸿蒙工程下创建 Module | 代码示例 )
Page Ability 界面跳转 , 其中 Page Ability 是通过 action 字符串进行标记的 , 每个 Page Ability 对应的 action 字符串在 src/main/ 下的 config.json 中配置 ,
韩曙亮
2023/03/28
1.4K0
【鸿蒙 HarmonyOS】界面跳转 ( Page Ability 的 action 标识 | Page Ability 之间的界面跳转及传递数据 | 鸿蒙工程下创建 Module | 代码示例 )
自学鸿蒙应用开发(13)- ProgressBar
下面代码中的第18行获取ProgressBar组件后,在第19行根据TimePicker的状态更新Progress的形式,然后在第26行TimerPicker的响应处理中,同样是根据TimePicker的状态更新ProgressBar的值。
面向对象思考
2021/01/13
5270
HarmonyOS实战—实现长按事件
1. 长按事件 长按事件使用的次数不是很多,但在有些特殊的情况下还是要用到的。 比如:复制一段文字的时候就是长按操作 长按事件和单、双击事件也非常类似 接口名:LongClickedListener
兮动人
2021/08/22
1.2K0
HarmonyOS实战—实现长按事件
【鸿蒙 HarmonyOS】UI 组件 ( 多选按钮 | Checkbox 组件 )
调用 Checkbox 对象的 setCheckedStateChangedListener 方法设置 选中 / 取消选中 的 AbsButton.CheckedStateChangedListener 监听器 , 当用户 选中 / 取消选中 时 , 会回调上述监听器的 onCheckedChanged 方法 , 其中第二个参数 boolean b , b 为 true 多选按钮选中 , false 取消选中 ;
韩曙亮
2023/03/28
1.8K0
【鸿蒙 HarmonyOS】UI 组件 ( 多选按钮 | Checkbox 组件 )
推荐阅读
相关推荐
Harmony Ble蓝牙App(二)连接与发现服务
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档