Приложение Harmony Ble Bluetooth (1) Сканировать
Приложение Harmony Ble Bluetooth (1) Сканировать

Предисловие

Я много рассказывал о Bluetooth с низким энергопотреблением в Android, но не рассказывал об этом аспекте Harmony. На самом деле я работал над проектом Harmony BLE, поэтому поделюсь здесь некоторым контентом.

текст

 Разработка Bluetooth для Ble в Harmony на самом деле аналогична разработке в Android, но есть некоторые различия, поскольку SDK Harmony все еще совершенствуется. Здесь мы используем API 6 для разработки проектов, а используемый язык — Java. Что касается того, почему мы используем API 6 вместо последней версии API 9, потому что я не могу позволить себе быть далеко впереди, поэтому я могу использовать только HUAWEI P30. с API 6 для реальных испытаний машин. Такие приложения, как Bluetooth, необходимо тестировать на реальной машине. Использовать виртуальную машину нельзя. Без лишних слов, давайте начнем.

1. Создать проект

 Приступаем к созданию проекта.

выбиратьEmpty Ability,НажмитеNext。Мы создаем файл с именемHarmonyBleПроект,Язык — Java.

НажмитеFinishЗавершить создание。

Проект по умолчанию выглядит так. Очень ли он похож на проект, созданный Android?

2. Конфигурация проекта

① Конфигурация разрешений

 Гармония также имеет концепцию Разрешения.,Также необходимо Конфигурациястатический Разрешенияи динамика Разрешения,только Конфигурациястатический Разрешенияместа разные。Harmonyнаходится вconfig.jsonсередина,Код внутри выглядит следующим образом:

Язык кода:javascript
копировать
{
  "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середина,Например, при сканировании Bluetooth нам нужно найти Разрешения. Вы можете добавить внутрь следующий код:

Язык кода:javascript
копировать
    "reqPermissions": [
      {
        "name": "ohos.permission.LOCATION"
      },
      {
        "name": "ohos.permission.USE_BLUETOOTH"
      },
      {
        "name": "ohos.permission.DISCOVER_BLUETOOTH"
      },
      {
        "name": "ohos.permission.MANAGE_BLUETOOTH"
      }
    ]

Как показано на рисунке ниже, обратите внимание на знаки препинания в json.

② Отладочная конфигурация

  Затем нам следует написать код, но перед этим мы сначала поймем разницу между «Ability» и «Slice». Способность похожа на рамку для изображения, а Slice — на холст. Мы можем загружать несколько холстов в кадр, как при переходе между несколькими страницами. Затем мы добавляем отсканированный фрагмент. Копируем MainAbilitySlice и вставляем его снова. Появляется всплывающее окно «Изменить имя».

 Почему нам нужно создавать файлы Java таким способом? Поскольку я не могу создавать файлы Java в DevEco Studio, возможно, в этой версии DS нет этой опции или я ее не нашел.

 Далее нам нужно создать соответствующий файл макета, а затем создать срез_scan.xml в разделе resources/base/layout. Код выглядит следующим образом:

Язык кода:javascript
копировать
<?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 и позволяем ему загрузить файл среза_scan.xml, который мы только что написали. Измените метод onStart(), код выглядит следующим образом:

Язык кода:javascript
копировать
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_slice_scan);
    }

Теперь приложение будет запускать MainAbility по умолчанию после его открытия. Давайте посмотрим на это.

Язык кода:javascript
копировать
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());

Тогда давайте сначала запустим его и подключим к телефону Hongmeng через USB.

Здесь появится сообщение об ошибке. Просмотрите сообщение об ошибке.

  Это значит, что нам нужно КонфигурацияSigning。НажмитеRunподили者右侧弹窗изOpen signing configs,Откроется окно Конфигурация.,Как показано ниже:

我们НажмитеSigning ConfigsПараметры,Требуется войти в систему,Как показано ниже:

Здесь вам необходимо войти в свою учетную запись Huawei. В настоящее время мы работаем локально, поэтому рядом с ним находится релиз, чтобы указать версию выпуска. Конфигурации в нем такие же, как и в режиме отладки. Разница в том, что информацию о конфигурации в режиме отладки необходимо вводить только после входа в систему. Studio поможет нам автоматически сгенерировать его, тогда как информация в Release требует, чтобы разработчики зашли на официальный сайт разработчика Huawei, чтобы создать приложение и подать заявку на файл конфигурации и сертификат. Это более хлопотно, но если вы хотите разместить приложение. полке, вы должны сделать этот шаг. В Китае рынок приложений Huawei является самым строгим, когда дело доходит до листинга приложений. С Huawei можно справиться, но остальные просто несерьезны и не заслуживают упоминания.

Давайте сначала войдем в систему, и откроется веб-страница. После успешного входа вы увидите вот такую ​​страницу.

 Затем возвращаемся в DS и сертификат и файл конфигурации в режиме Debug будут автоматически настроены, как показано на рисунке ниже:

 Нажмите ОК.,Сделаю конфигурацию в DS.,Конфигурация好之后你可以существовать工程目录下изbuild.gradleсередина看到debugиз相关信息,Как показано ниже.

Тогда давайте запустим его еще раз и посмотрим. На этот раз нет никаких сомнений в том, что он может работать успешно. Как показано ниже:

③ Конфигурация пользовательского интерфейса

可以看到默认из标题栏Точно так же, какAndroid默认изActionBar,Уродливый очень особенный,давай удалим это,Добавьте следующий код в config.json:

Язык кода:javascript
копировать
        "metaData": {
          "customizeData": [
            {
              "extra": "",
              "name": "hwc-theme",
              "value": "androidhwext:style/Theme.Emui.Light.NoTitleBar"
            }
          ]
        },

Добавьте местоположение следующим образом:

Давайте запустим его еще раз и посмотрим.

Да или нетUnbelievable! И ради красивого названия,мыelementСоздайте его подcolor.json,Код внутри выглядит следующим образом:

Язык кода:javascript
копировать
{
  "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
копировать
<?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,То же самое верно и в коде,Это должна быть ошибка компилятора.

Строка заголовка написана, а строку состояния мы не изменили. Мы изменили строку состояния в MainAbility. Код выглядит следующим образом:

Язык кода:javascript
копировать
    @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()метод,Тогда давайте запустим и посмотрим.

Хорошо, давайте напишем код контента, необходимый для сканирования.

3. Сканировать

  首先мыcom.llw.bleСоздать новый под пакетомcoreСумка,coreСумка Создайте его подBleCoreдобрый,Здесь есть все, что связано с управлением BleBluetooth.,Например, сканирование,соединять,Такие операции, как чтение и запись данных.,我们先不写код。подсуществоватьcoreСумка Создайте его подscanСумка。

① Интерфейс сканирования

scanСоздать новый под пакетомScanCallbackинтерфейс,Код выглядит следующим образом:

Язык кода:javascript
копировать
public interface ScanCallback {

    void onScanResult(BleScanResult result);

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

    default void onScanFailed(String failed){}
}
② Тип сканирования

затем вscanСоздать новый под пакетомBleScanдобрый,Код выглядит следующим образом:

Язык кода:javascript
копировать
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 Сканирование, ложь Не сканируется
     */
    public boolean isScanning() {
        return isScanning;
    }

    /**
     * Установить информацию о фильтре
     * @param filters Список фильтров сканирования Bluetooth
     */
    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
копировать
public class BleCore  {

    private static volatile BleCore mInstance;
    private final BleScan bleScan;

    public BleCore(Context context) {
        //Сканирование Bluetooth
        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();
    }
}

4. Бизнес-процессинг

 Здесь есть два основных бизнес-процесса: первый — мониторинг переключателя Bluetooth, а второй — применение динамических разрешений.

Прежде чем приступить к бизнес-обработке, мы сначала изменяем имя класса MyApplication на BleApp. После модификации мы меняем код внутри, как показано ниже:

Язык кода:javascript
копировать
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;
    }
}
① Жизненный цикл среза

  Сначала давайте взглянем на Жизненный. цикл среза,Это важнее,под我们首先существоватьcom.llw.bleСоздайте его подutilsСумка,utilsСумка Создайте его подLogUtilsдобрый,Код выглядит следующим образом:

Язык кода:javascript
копировать
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
копировать
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. Давайте напишем код ниже.

② Переключатель Bluetooth и динамический запрос разрешения.

ˆСначала решите проблемы, связанные с Bluetooth.,существоватьBleCoreсередина添加如下код:

Язык кода:javascript
копировать
	private final BluetoothHost mBluetoothHost;

создается в конструкторе

Язык кода:javascript
копировать
    public BleCore(Context context) {
        ...
        // Получите собственный объект управления Bluetooth.
        mBluetoothHost = BluetoothHost.getDefaultHost(context);
    }

Затем пишем еще два метода:

Язык кода:javascript
копировать
    public boolean isEnableBt() {
        return mBluetoothHost.getBtState() == BluetoothHost.STATE_ON;
    }

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

  используется для Определите, включен ли Bluetoothи открытьBluetooth,вернуться вScanSliceсередина我们需要使用BleCoreсправитьсяBluetooth相关из工作,Код выглядит следующим образом:

Язык кода:javascript
копировать
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() {
        // Определите, включен ли Bluetooth
        if (!bleCore.isEnableBt()) {
            //Включаем Bluetooth
            bleCore.enableBt();
            return;
        }
    }
}

  首先существоватьonStart()середина对BleCoreсоздавать экземпляр,findComponentByIdТочно так же, какfindViewById,затем вonActive()середина调用刚才我们所写изметод。

Язык кода:javascript
копировать
    @Override
    public void onActive() {
        // Определите, включен ли Bluetooth
        if (!bleCore.isEnableBt()) {
            //Включаем Bluetooth
            bleCore.enableBt();
            return;
        }
    }

Затем происходит обработка разрешений на позиционирование. Также в onActive() добавьте следующий код:

Язык кода:javascript
копировать
    @Override
    public void onActive() {
        // Определите, включен ли Bluetooth
        ...
        // Получить ли позиционирование Разрешения
        String locationPermission = "ohos.permission.LOCATION";
        if (verifySelfPermission(locationPermission) != IBundleManager.PERMISSION_GRANTED) {
            requestPermissionsFromUser(new String[]{locationPermission}, 100);
            return;
        }
    }

Здесь мы сначала определяем разрешение, а затем определяем, следует ли его предоставить. Если нет, выполните запрос ниже, чтобы увидеть:

Затем мы завершили открытие и позиционирование динамического приложения «Разрешения» Bluetooth.,Вы можете запустить его один раз,ты найдешь,Вам также необходимо запросить Разрешения,Потому что DS не сохраняет данные приложения при установке по умолчанию.,И Bluetooth открывает системный уровень,Так что вам больше не придется включать Bluetooth,И нужно повторно запросить позиционирование,чтобы избежать этого,我们НажмитеRun→ Edit Configurations...

существовать弹出из窗口上勾选Keep Application Data

Нажмите «ОК» и запустите еще раз.

5. Сканирующее оборудование

 Далее мы приступаем к процессу сканирования и добавляем в ScanSlice следующий код метода:

Язык кода:javascript
копировать
    private void startScan() {
        bleCore.startScan();
        txScanStatus.setText("Стоп");
    }

    private void stopScan() {
        bleCore.stopScan();
        txScanStatus.setText("Поиск");
    }

Вот методы сканирования и остановки,Измените текстовый текст одновременно,существоватьonStart()середина首先实现Сканировать обратный вызовмонитор,Потом обрабатывать и обрабатывать.txScanStatus文本из Нажмите事件,Код выглядит следующим образом:

Язык кода:javascript
копировать
    @Override
    public void onStart(Intent intent) {
        ...
        bleCore.setPhyScanCallback(this);
        //Наблюдение за кликами
        txScanStatus.setClickedListener(component -> {
            if (bleCore.isScanning()) {
                stopScan();//Переключатель сканирования Остановить сканирование
            } else {
                startScan();//Начать сканирование
            }
        });
    }

здесьthisСообщим об ошибке,Наведите на него мышь,Alt + Входим, появляется всплывающее окно.

Выберите последний,сбудется для тебяScanCallbackсерединаизonScanResult()метод,Код выглядит следующим образом:

Язык кода:javascript
копировать
    @Override
    public void onScanResult(BleScanResult result) {
        LogUtils.LogD(TAG, result.getPeripheralDevice().getDeviceAddr());
    }

мы里面打印一下扫描到изоборудованиеMac-адрес,最后мыonActive()середина增加如下所示код:

Язык кода:javascript
копировать
    @Override
    public void onActive() {
        ...
        // Это сканирование?
        if (!bleCore.isScanning()) {
            startScan();
        }
    }

Запустите его ниже и посмотрите журнал консоли:

Он отсканирован, но пока не виден, поэтому нам нужно отрендерить его так, чтобы его можно было увидеть.

6. Дисплейное оборудование

Чтобы отобразить устройство, сначала нам нужно написать Bean.

① Пользовательский класс Bluetooth

Создайте новый класс BleDevice в базовом пакете. Код внутри выглядит следующим образом:

Язык кода:javascript
копировать
public class BleDevice {

    private String realName = "Unknown device"; //Настоящее имя устройства Bluetooth
    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;
    }
}

Об этом Бобе нечего сказать,Следующее, что нужно сделать, это отобразить элемент списка.,существоватьAndroidсередина我们使用из是适配器Adapter,而существоватьHarmonyсередина使用из是поставщикProvider

② Поставщик

Аналогично, давайте сначала напишем макет,существоватьlayoutСоздайте новый подitem_scan_device.xml,Код выглядит следующим образом:

Язык кода:javascript
копировать
<?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
копировать
<?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 и создаем класс ScanDeviceProvider в этом пакете. Код выглядит следующим образом:

Язык кода:javascript
копировать
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()методсерединаиз处理,Кроме того, поставщик по умолчанию предоставляет метод щелчка по товару.,Так что нам больше не придется писать это самим.

③ Дисплейное оборудование

 Вернёмся к ScanSlice. Сначала создадим несколько переменных. Код выглядит следующим образом:

Язык кода:javascript
копировать
    private final List<BleDevice> mList = new ArrayList<>();
    private ScanDeviceProvider provider;

Затем инициализируйте его в методе onStart():

Язык кода:javascript
копировать
    @Override
    public void onStart(Intent intent) {
        ...

        provider = new ScanDeviceProvider(mList, this);
        lcDevice.setItemProvider(provider);
        //отслеживание кликов по элементам списка
        lcDevice.setItemClickedListener((listContainer, component, position, id) -> {

        });
    }

Здесь мы настраиваем поставщик списка, затем добавляем прослушиватель кликов по элементу и, наконец, визуализируем данные в обратном вызове сканирования. Модифицированный код выглядит следующим образом:

Язык кода:javascript
копировать
    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()метод,Используется для добавления устройств и обновления устройств.,Наконец, синхронно обновите поставщика через поток пользовательского интерфейса.,Изменить другую Начать сканированиеи Остановить сканированиеизметодкод:

Язык кода:javascript
копировать
    private void startScan() {
        mList.clear();
        provider.notifyDataChanged();
        bleCore.startScan();
        txScanStatus.setText("Стоп");
        LogUtils.LogD(TAG,"Начать сканированиеоборудование!");
    }

    private void stopScan() {
        bleCore.stopScan();
        txScanStatus.setText("Поиск");
        LogUtils.LogD(TAG,"уже Остановить сканированиеоборудование!");
    }

Запустите его и увидите:

7. Исходный код

Если это тебе поможет, можешь также попробовать Стар или Форк. Горы высокие, а реки длинные~ Увидимся позже~

Адрес исходного кода:HarmonyBle-Java

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose