前言
在上一篇中我们完成了连接和发现服务两个动作,那么再发现服务之后要做什么呢?发现服务只是让你知道设备有什么服务,可以做什么事情。
正文
本篇要做的是显示服务下的特性,首先我们了解一下特性的基本知识。在蓝牙低功耗(BLE)中,特性(Characteristic)
是蓝牙设备提供的一种数据单元,用于描述设备的某个属性或功能。特性包含了一系列的属性和值,可以用于读取、写入和通知数据。
BLE特性相关的关键概念和说明:
UUID(Universally Unique Identifier)
:每个特性都会有一个唯一的UUID,用于标识该特性。值(Value)
:特性包含一个值,可以是字节数组、字符串或其他数据类型。该值代表特性的当前状态或数据内容。属性(Properties)
:特性具有一组属性,包括读、写、通知等。属性决定了可以对特性进行哪些操作。读(Read)
:允许外部设备从特性中读取当前的值。写(Write)
:允许外部设备向特性写入一个新的值。通知(Notify)
:当特性的值发生变化时,可以通过通知方式将新的值发送给订阅该特性的外部设备。描述符(Descriptor)
:特性可以附带一个或多个描述符,用于提供关于特性的额外信息或配置。
使用BLE特性,可以实现各种功能和数据交互,例如传感器数据的读取、设备状态的监控、远程控制等。特性的读写和通知操作可以通过与蓝牙设备的交互来实现。需要注意的是,BLE特性的操作和功能是由设备的厂商定义的,并在设备的GATT(Generic Attribute Profile)配置文件中进行描述。
首先理清一下思路,我们现在知道服务下面有特性,特性下面有一些属性值,其中属性(Properties)
尤为重要,因为它决定了你的特性可以进行那些操作。用一个图来说明服务,特性,属性之间的关系。
一、获取属性列表
下面我们先获取最下面的属性,这是一个列表,属性值的处理有一些不同,首先我们在BleUtils中增加一个函数,代码如下所示:
/**
* 获取属性
*/
public static List<String> getProperties(int property) {
List<String> properties = new ArrayList<>();
for (int i = 0; i < 8; i++) {
switch (property & (1 << i)) {
case 0x01:
properties.add("Broadcast");
break;
case 0x02:
properties.add("Read");
break;
case 0x04:
properties.add("Write No Response");
break;
case 0x08:
properties.add("Write");
break;
case 0x10:
properties.add("Notify");
break;
case 0x20:
properties.add("Indicate");
break;
case 0x40:
properties.add("Authenticated Signed Writes");
break;
case 0x80:
properties.add("Extended Properties");
break;
}
}
return properties;
}
这里是通过位运算进行计算属性的值,首先是循环遍历,先左移再按位与,得到最终的值,根据值得到属性描述,这些描述就是具体的功能操作。会返回一个属性列表,有了列表我们就可以写一个适配器了。
二、属性提供者
首先我们在layout下创建一个item_property.xml
,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<Text
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:id="$+id:tx_property"
ohos:height="match_content"
ohos:width="match_content"
ohos:end_margin="8vp"
ohos:text="property"
ohos:text_color="$color:blue"
ohos:text_size="14fp"/>
因为是String类型,所以我们就直接用一个Text显示即可,下面我们写提供者,在provider
包下新建一个PropertyProvider
类,代码如下所示:
public class PropertyProvider extends BaseItemProvider {
private final List<String> propertyList;
private final AbilitySlice slice;
public PropertyProvider(List<String> list, AbilitySlice slice) {
this.propertyList = list;
this.slice = slice;
}
@Override
public int getCount() {
return propertyList == null ? 0 : propertyList.size();
}
@Override
public Object getItem(int position) {
if (propertyList != null && position >= 0 && position < propertyList.size()) {
return propertyList.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;
ServiceHolder holder;
if (component == null) {
cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_property, null, false);
holder = new ServiceHolder(cpt);
//将获取到的子组件信息绑定到列表项的实例中
cpt.setTag(holder);
} else {
cpt = component;
// 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。
holder = (ServiceHolder) cpt.getTag();
}
holder.txProperty.setText(propertyList.get(position));
return cpt;
}
/**
* 用于保存列表项的子组件信息
*/
public static class ServiceHolder {
Text txProperty;
public ServiceHolder(Component component) {
txProperty = (Text) component.findComponentById(ResourceTable.Id_tx_property);
}
}
}
这里进行了属性的点击监听,我们可以回调到特性适配器中去处理,下面我们要处理的就是特性了。
三、获取特性名称
首先是特性名称,同样是根据UUID,同样是那个PDF文档,在BleUtils中增加一个getCharacteristicsName()
函数,代码有点多,如下所示:
/**
* 获取特性名称
* @param uuid UUID
*/
public static String getCharacteristicsName(UUID uuid) {
String targetUuid = getShortUUID(uuid);
switch (targetUuid) {
case "0x2A00":
return "Device Name";
case "0x2A01":
return "Appearance";
case "0x2A02":
return "Peripheral Privacy Flag";
case "0x2A03":
return "Reconnection Address";
case "0x2A04":
return "Peripheral Preferred Connection Parameters";
case "0x2A05":
return "Service Changed";
case "0x2A06":
return "Alert Level";
case "0x2A07":
return "Tx Power Level";
case "0x2A08":
return "Date Time";
case "0x2A09":
return "Day of Week";
case "0x2A0A":
return "Day Date Time";
case "0x2A0C":
return "Exact Time 256";
case "0x2A0D":
return "DST Offset";
case "0x2A0E":
return "Time Zone";
case "0x2A0F":
return "Local Time Information";
case "0x2A11":
return "Time with DST";
case "0x2A12":
return "Time Accuracy";
case "0x2A13":
return "Time Source";
case "0x2A14":
return "Reference Time Information";
case "0x2A16":
return "Time Update Control Point";
case "0x2A17":
return "Time Update State";
case "0x2A18":
return "Glucose Measurement";
case "0x2A19":
return "Battery Level";
case "0x2A1C":
return "Temperature Measurement";
case "0x2A1D":
return "Temperature Type";
case "0x2A1E":
return "Intermediate Temperature";
case "0x2A21":
return "Measurement Interval";
case "0x2A22":
return "Boot Keyboard Input Report";
case "0x2A23":
return "System ID";
case "0x2A24":
return "Model Number String";
case "0x2A25":
return "Serial Number String";
case "0x2A26":
return "Firmware Revision String";
case "0x2A27":
return "Hardware Revision String";
case "0x2A28":
return "Software Revision String";
case "0x2A29":
return "Manufacturer Name String";
case "0x2A2A":
return "IEEE 11073-20601 Regulatory Certification Data List";
case "0x2A2B":
return "Current Time";
case "0x2A2C":
return "Magnetic Declination";
case "0x2A31":
return "Scan Refresh";
case "0x2A32":
return "Boot Keyboard Output Report";
case "0x2A33":
return "Boot Mouse Input Report";
case "0x2A34":
return "Glucose Measurement Context";
case "0x2A35":
return "Blood Pressure Measurement";
case "0x2A36":
return "Intermediate Cuff Pressure";
case "0x2A37":
return "Heart Rate Measurement";
case "0x2A38":
return "Body Sensor Location";
case "0x2A39":
return "Heart Rate Control Point";
case "0x2A3F":
return "Alert Status";
case "0x2A40":
return "Ringer Control Point";
case "0x2A41":
return "Ringer Setting";
case "0x2A42":
return "Alert Category ID Bit Mask";
case "0x2A43":
return "Alert Category ID";
case "0x2A44":
return "Alert Notification Control Point";
case "0x2A45":
return "Unread Alert Status";
case "0x2A46":
return "New Alert";
case "0x2A47":
return "Supported New Alert Category";
case "0x2A48":
return "Supported Unread Alert Category";
case "0x2A49":
return "Blood Pressure Feature";
case "0x2A4A":
return "HID Information";
case "0x2A4B":
return "Report Map";
case "0x2A4C":
return "HID Control Point";
case "0x2A4D":
return "Report";
case "0x2A4E":
return "Protocol Mode";
case "0x2A4F":
return "Scan Interval Window";
case "0x2A50":
return "PnP ID";
case "0x2A51":
return "Glucose Feature";
case "0x2A52":
return "Record Access Control Point";
case "0x2A53":
return "RSC Measurement";
case "0x2A54":
return "RSC Feature";
case "0x2A55":
return "SC Control Point";
case "0x2A5A":
return "Aggregate";
case "0x2A5B":
return "CSC Measurement";
case "0x2A5C":
return "CSC Feature";
case "0x2A5D":
return "Sensor Location";
case "0x2A5E":
return "PLX Spot-Check Measurement";
case "0x2A5F":
return "PLX Continuous Measurement";
case "0x2A60":
return "PLX Features";
case "0x2A63":
return "Cycling Power Measurement";
case "0x2A64":
return "Cycling Power Vector";
case "0x2A65":
return "Cycling Power Feature";
case "0x2A66":
return "Cycling Power Control Point";
case "0x2A67":
return "Location and Speed";
case "0x2A68":
return "Navigation";
case "0x2A69":
return "Position Quality";
case "0x2A6A":
return "LN Feature";
case "0x2A6B":
return "LN Control Point";
case "0x2A6C":
return "Elevation";
case "0x2A6D":
return "Pressure";
case "0x2A6E":
return "Temperature";
case "0x2A6F":
return "Humidity";
case "0x2A70":
return "True Wind Speed";
case "0x2A71":
return "True Wind Direction";
case "0x2A72":
return "Apparent Wind Speed";
case "0x2A73":
return "Apparent Wind Direction";
case "0x2A74":
return "Gust Factor";
case "0x2A75":
return "Pollen Concentration";
case "0x2A76":
return "UV Index";
case "0x2A77":
return "Irradiance";
case "0x2A78":
return "Rainfall";
case "0x2A79":
return "Wind Chill";
case "0x2A7A":
return "Heat Index";
case "0x2A7B":
return "Dew Point";
case "0x2A7D":
return "Descriptor Value Changed";
case "0x2A7E":
return "Aerobic Heart Rate Lower Limit";
case "0x2A7F":
return "Aerobic Threshold";
case "0x2A80":
return "Age";
case "0x2A81":
return "Anaerobic Heart Rate Lower Limit";
case "0x2A82":
return "Anaerobic Heart Rate Upper Limit";
case "0x2A83":
return "Anaerobic Threshold";
case "0x2A84":
return "Aerobic Heart Rate Upper Limit";
case "0x2A85":
return "Date of Birth";
case "0x2A86":
return "Date of Threshold Assessment";
case "0x2A87":
return "Email Address";
case "0x2A88":
return "Fat Burn Heart Rate Lower Limit";
case "0x2A89":
return "Fat Burn Heart Rate Upper Limit";
case "0x2A8A":
return "First Name";
case "0x2A8B":
return "Five Zone Heart Rate Limits";
case "0x2A8C":
return "Gender";
case "0x2A8D":
return "Heart Rate Max";
case "0x2A8E":
return "Height";
case "0x2A8F":
return "Hip Circumference";
case "0x2A90":
return "Last Name";
case "0x2A91":
return "Maximum Recommended Heart Rate";
case "0x2A92":
return "Resting Heart Rate";
case "0x2A93":
return "Sport Type for Aerobic and Anaerobic Thresholds";
case "0x2A94":
return "Three Zone Heart Rate Limits";
case "0x2A95":
return "Two Zone Heart Rate Limits";
case "0x2A96":
return "VO2 Max";
case "0x2A97":
return "Waist Circumference";
case "0x2A98":
return "Weight";
case "0x2A99":
return "Database Change Increment";
case "0x2A9A":
return "User Index";
case "0x2A9B":
return "Body Composition Feature";
case "0x2A9C":
return "Body Composition Measurement";
case "0x2A9D":
return "Weight Measurement";
case "0x2A9E":
return "Weight Scale Feature";
case "0x2A9F":
return "User Control Point";
case "0x2AA0":
return "Magnetic Flux Density - 2D";
case "0x2AA1":
return "Magnetic Flux Density - 3D";
case "0x2AA2":
return "Language";
case "0x2AA3":
return "Barometric Pressure Trend";
case "0x2AA4":
return "Bond Management Control Point";
case "0x2AA5":
return "Bond Management Feature";
case "0x2AA6":
return "Central Address Resolution";
case "0x2AA7":
return "CGM Measurement";
case "0x2AA8":
return "CGM Feature";
case "0x2AA9":
return "CGM Status";
case "0x2AAA":
return "CGM Session Start Time";
case "0x2AAB":
return "CGM Session Run Time";
case "0x2AAC":
return "CGM Specific Ops Control Point";
case "0x2AAD":
return "Indoor Positioning Configuration";
case "0x2AAE":
return "Latitude";
case "0x2AAF":
return "Longitude";
case "0x2AB0":
return "Local North Coordinate";
case "0x2AB1":
return "Local East Coordinate";
case "0x2AB2":
return "Floor Number";
case "0x2AB3":
return "Altitude";
case "0x2AB4":
return "Uncertainty";
case "0x2AB5":
return "Location Name";
case "0x2AB6":
return "URI";
case "0x2AB7":
return "HTTP Headers";
case "0x2AB8":
return "HTTP Status Code";
case "0x2AB9":
return "HTTP Entity Body";
case "0x2ABA":
return "HTTP Control Point";
case "0x2ABB":
return "HTTPS Security";
case "0x2ABC":
return "TDS Control Point";
case "0x2ABD":
return "OTS Feature";
case "0x2ABE":
return "Object Name";
case "0x2ABF":
return "Object Type";
case "0x2AC0":
return "Object Size";
case "0x2AC1":
return "Object First -Created";
case "0x2AC2":
return "Object Last - Modified";
case "0x2AC3":
return "Object ID";
case "0x2AC4":
return "Object Properties";
case "0x2AC5":
return "Object Action Control Point";
case "0x2AC6":
return "Object List Control Point";
case "0x2AC7":
return "Object List Filter";
case "0x2AC8":
return "Object Changed";
case "0x2AC9":
return "Resolvable Private Address Only";
case "0x2ACC":
return "Fitness Machine Feature";
case "0x2ACD":
return "Treadmill Data";
case "0x2ACE":
return "Cross Trainer Data";
case "0x2ACF":
return "Step Climber Data";
case "0x2AD0":
return "Stair Climber Data";
case "0x2AD1":
return "Rower Data";
case "0x2AD2":
return "Indoor Bike Data";
case "0x2AD3":
return "Training Status";
case "0x2AD4":
return "Supported Speed Range";
case "0x2AD5":
return "Supported Inclination Range";
case "0x2AD6":
return "Supported Resistance Level Range";
case "0x2AD7":
return "Supported Heart Rate Range";
case "0x2AD8":
return "Supported Power Range";
case "0x2AD9":
return "Fitness Machine Control Point";
case "0x2ADA":
return "Fitness Machine Status";
case "0x2ADB":
return "Mesh Provisioning Data In";
case "0x2ADC":
return "Mesh Provisioning Data Out";
case "0x2ADD":
return "Mesh Proxy Data In";
case "0x2ADE":
return "Mesh Proxy Data Out";
case "0x2AE0":
return "Average Current";
case "0x2AE1":
return "Average Voltage";
case "0x2AE2":
return "Boolean";
case "0x2AE3":
return "Chromatic Distance from Planckian";
case "0x2AE4":
return "Chromaticity Coordinates";
case "0x2AE5":
return "Chromaticity in CCT and Duv Values";
case "0x2AE6":
return "Chromaticity Tolerance";
case "0x2AE7":
return "CIE 13.3 - 1995 Color Rendering Index";
case "0x2AE8":
return "Coefficient";
case "0x2AE9":
return "Correlated Color Temperature";
case "0x2AEA":
return "Count 16";
case "0x2AEB":
return "Count 24";
case "0x2AEC":
return "Country Code";
case "0x2AED":
return "Date UTC";
case "0x2AEE":
return "Electric Current";
case "0x2AEF":
return "Electric Current Range";
case "0x2AF0":
return "Electric Current Specification";
case "0x2AF1":
return "Electric Current Statistics";
case "0x2AF2":
return "Energy";
case "0x2AF3":
return "Energy in a Period of Day";
case "0x2AF4":
return "Event Statistics";
case "0x2AF5":
return "Fixed String 16";
case "0x2AF6":
return "Fixed String 24";
case "0x2AF7":
return "Fixed String 36";
case "0x2AF8":
return "Fixed String 8";
case "0x2AF9":
return "Generic Level";
case "0x2AFA":
return "Global Trade Item Number";
case "0x2AFB":
return "Illuminance";
case "0x2AFC":
return "Luminous Efficacy";
case "0x2AFD":
return "Luminous Energy";
case "0x2AFE":
return "Luminous Exposure";
case "0x2AFF":
return "Luminous Flux";
case "0x2B00":
return "Luminous Flux Range";
case "0x2B01":
return "Luminous Intensity";
case "0x2B02":
return "Mass Flow";
case "0x2B03":
return "Perceived Lightness";
case "0x2B04":
return "Percentage 8";
case "0x2B05":
return "Power";
case "0x2B06":
return "Power Specification";
case "0x2B07":
return "Relative Runtime in a Current Range";
case "0x2B08":
return "Relative Runtime in a Generic Level Range";
case "0x2B09":
return "Relative Value in a Voltage Range";
case "0x2B0A":
return "Relative Value in an Illuminance Range";
case "0x2B0B":
return "Relative Value in a Period of Day";
case "0x2B0C":
return "Relative Value in a Temperature Range";
case "0x2B0D":
return "Temperature 8";
case "0x2B0E":
return "Temperature 8 in a Period of Day";
case "0x2B0F":
return "Temperature 8 Statistics";
case "0x2B10":
return "Temperature Range";
case "0x2B11":
return "Temperature Statistics";
case "0x2B12":
return "Time Decihour 8";
case "0x2B13":
return "Time Exponential 8";
case "0x2B14":
return "Time Hour 24";
case "0x2B15":
return "Time Millisecond 24";
case "0x2B16":
return "Time Second 16";
case "0x2B17":
return "Time Second 8";
case "0x2B18":
return "Voltage";
case "0x2B19":
return "Voltage Specification";
case "0x2B1A":
return "Voltage Statistics";
case "0x2B1B":
return "Volume Flow";
case "0x2B1C":
return "Chromaticity Coordinate";
case "0x2B1D":
return "RC Feature";
case "0x2B1E":
return "RC Settings";
case "0x2B1F":
return "Reconnection Configuration Control Point";
case "0x2B20":
return "IDD Status Changed";
case "0x2B21":
return "IDD Status";
case "0x2B22":
return "IDD Annunciation Status";
case "0x2B23":
return "IDD Features";
case "0x2B24":
return "IDD Status Reader Control Point";
case "0x2B25":
return "IDD Command Control Point";
case "0x2B26":
return "IDD Command Data";
case "0x2B27":
return "IDD Record Access Control Point";
case "0x2B28":
return "IDD History Data";
case "0x2B29":
return "Client Supported Features";
case "0x2B2A":
return "Database Hash";
case "0x2B2B":
return "BSS Control Point";
case "0x2B2C":
return "BSS Response";
case "0x2B2D":
return "Emergency ID";
case "0x2B2E":
return "Emergency Text";
case "0x2B2F":
return "ACS Status";
case "0x2B30":
return "ACS Data In";
case "0x2B31":
return "ACS Data Out Notify";
case "0x2B32":
return "ACS Data Out Indicate";
case "0x2B33":
return "ACS Control Point";
case "0x2B34":
return "Enhanced Blood Pressure Measurement";
case "0x2B35":
return "Enhanced Intermediate Cuff Pressure";
case "0x2B36":
return "Blood Pressure Record";
case "0x2B37":
return "Registered User";
case "0x2B38":
return "BR - EDR Handover Data";
case "0x2B39":
return "Bluetooth SIG Data";
case "0x2B3A":
return "Server Supported Features";
case "0x2B3B":
return "Physical Activity Monitor Features";
case "0x2B3C":
return "General Activity Instantaneous Data";
case "0x2B3D":
return "General Activity Summary Data";
case "0x2B3E":
return "CardioRespiratory Activity Instantaneous Data";
case "0x2B3F":
return "CardioRespiratory Activity Summary Data";
case "0x2B40":
return "Step Counter Activity Summary Data";
case "0x2B41":
return "Sleep Activity Instantaneous Data";
case "0x2B42":
return "Sleep Activity Summary Data";
case "0x2B43":
return "Physical Activity Monitor Control Point";
case "0x2B44":
return "Activity Current Session";
case "0x2B45":
return "Physical Activity Session Descriptor";
case "0x2B46":
return "Preferred Units";
case "0x2B47":
return "High Resolution Height";
case "0x2B48":
return "Middle Name";
case "0x2B49":
return "Stride Length";
case "0x2B4A":
return "Handedness";
case "0x2B4B":
return "Device Wearing Position";
case "0x2B4C":
return "Four Zone Heart Rate Limits";
case "0x2B4D":
return "High Intensity Exercise Threshold";
case "0x2B4E":
return "Activity Goal";
case "0x2B4F":
return "Sedentary Interval Notification";
case "0x2B50":
return "Caloric Intake";
case "0x2B51":
return "TMAP Role";
case "0x2B77":
return "Audio Input State";
case "0x2B78":
return "Gain Settings Attribute";
case "0x2B79":
return "Audio Input Type";
case "0x2B7A":
return "Audio Input Status";
case "0x2B7B":
return "Audio Input Control Point";
case "0x2B7C":
return "Audio Input Description";
case "0x2B7D":
return "Volume State";
case "0x2B7E":
return "Volume Control Point";
case "0x2B7F":
return "Volume Flags";
case "0x2B80":
return "Volume Offset State";
case "0x2B81":
return "Audio Location";
case "0x2B82":
return "Volume Offset Control Point";
case "0x2B83":
return "Audio Output Description";
case "0x2B84":
return "Set Identity Resolving Key";
case "0x2B85":
return "Coordinated Set Size";
case "0x2B86":
return "Set Member Lock";
case "0x2B87":
return "Set Member Rank";
case "0x2B88":
return "Encrypted Data Key Material";
case "0x2B89":
return "Apparent Energy 32";
case "0x2B8A":
return "Apparent Power";
case "0x2B8B":
return "Live Health Observations";
case "0x2B8C":
return "CO \\{} text-subscript { 2 } Concentration";
case "0x2B8D":
return "Cosine of the Angle";
case "0x2B8E":
return "Device Time Feature";
case "0x2B8F":
return "Device Time Parameters";
case "0x2B90":
return "Device Time";
case "0x2B91":
return "Device Time Control Point";
case "0x2B92":
return "Time Change Log Data";
case "0x2B93":
return "Media Player Name";
case "0x2B94":
return "Media Player Icon Object ID";
case "0x2B95":
return "Media Player Icon URL";
case "0x2B96":
return "Track Changed";
case "0x2B97":
return "Track Title";
case "0x2B98":
return "Track Duration";
case "0x2B99":
return "Track Position";
case "0x2B9A":
return "Playback Speed";
case "0x2B9B":
return "Seeking Speed";
case "0x2B9C":
return "Current Track Segments Object ID";
case "0x2B9D":
return "Current Track Object ID";
case "0x2B9E":
return "Next Track Object ID";
case "0x2B9F":
return "Parent Group Object ID";
case "0x2BA0":
return "Current Group Object ID";
case "0x2BA1":
return "Playing Order";
case "0x2BA2":
return "Playing Orders Supported";
case "0x2BA3":
return "Media State";
case "0x2BA4":
return "Media Control Point";
case "0x2BA5":
return "Media Control Point Opcodes Supported";
case "0x2BA6":
return "Search Results Object ID";
case "0x2BA7":
return "Search Control Point";
case "0x2BA8":
return "Energy 32";
case "0x2BA9":
return "Media Player Icon Object Type";
case "0x2BAA":
return "Track Segments Object Type";
case "0x2BAB":
return "Track Object Type";
case "0x2BAC":
return "Group Object Type";
case "0x2BAD":
return "Constant Tone Extension Enable";
case "0x2BAE":
return "Advertising Constant Tone Extension Minimum Length";
case "0x2BAF":
return "Advertising Constant Tone Extension Minimum Transmit Count";
case "0x2BB0":
return "Advertising Constant Tone Extension Transmit Duration";
case "0x2BB1":
return "Advertising Constant Tone Extension Interval";
case "0x2BB2":
return "Advertising Constant Tone Extension PHY";
case "0x2BB3":
return "Bearer Provider Name";
case "0x2BB4":
return "Bearer UCI";
case "0x2BB5":
return "Bearer Technology";
case "0x2BB6":
return "Bearer URI Schemes Supported List";
case "0x2BB7":
return "Bearer Signal Strength";
case "0x2BB8":
return "Bearer Signal Strength Reporting Interval";
case "0x2BB9":
return "Bearer List Current Calls";
case "0x2BBA":
return "Content Control ID";
case "0x2BBB":
return "Status Flags";
case "0x2BBC":
return "Incoming Call Target Bearer URI";
case "0x2BBD":
return "Call State";
case "0x2BBE":
return "Call Control Point";
case "0x2BBF":
return "Call Control Point Optional Opcodes";
case "0x2BC0":
return "Termination Reason";
case "0x2BC1":
return "Incoming Call";
case "0x2BC2":
return "Call Friendly Name";
case "0x2BC3":
return "Mute";
case "0x2BC4":
return "Sink ASE";
case "0x2BC5":
return "Source ASE";
case "0x2BC6":
return "ASE Control Point";
case "0x2BC7":
return "Broadcast Audio Scan Control Point";
case "0x2BC8":
return "Broadcast Receive State";
case "0x2BC9":
return "Sink PAC";
case "0x2BCA":
return "Sink Audio Locations";
case "0x2BCB":
return "Source PAC";
case "0x2BCC":
return "Source Audio Locations";
case "0x2BCD":
return "Available Audio Contexts";
case "0x2BCE":
return "Supported Audio Contexts";
case "0x2BCF":
return "Ammonia Concentration";
case "0x2BD0":
return "Carbon Monoxide Concentration";
case "0x2BD1":
return "Methane Concentration";
case "0x2BD2":
return "Nitrogen Dioxide Concentration";
case "0x2BD3":
return "Non -Methane Volatile Organic Compounds Concentration";
case "0x2BD4":
return "Ozone Concentration";
case "0x2BD5":
return "Particulate Matter - PM1 Concentration";
case "0x2BD6":
return "Particulate Matter - PM2.5 Concentration";
case "0x2BD7":
return "Particulate Matter - PM10 Concentration";
case "0x2BD8":
return "Sulfur Dioxide Concentration";
case "0x2BD9":
return "Sulfur Hexafluoride Concentration";
case "0x2BDA":
return "Hearing Aid Features";
case "0x2BDB":
return "Hearing Aid Preset Control Point";
case "0x2BDC":
return "Active Preset Index";
case "0x2BDD":
return "Stored Health Observations";
case "0x2BDE":
return "Fixed String 64";
case "0x2BDF":
return "High Temperature";
case "0x2BE0":
return "High Voltage";
case "0x2BE1":
return "Light Distribution";
case "0x2BE2":
return "Light Output";
case "0x2BE3":
return "Light Source Type";
case "0x2BE4":
return "Noise";
case "0x2BE5":
return "Relative Runtime in a Correlated Color Temperature Range";
case "0x2BE6":
return "Time Second 32";
case "0x2BE7":
return "VOC Concentration";
case "0x2BE8":
return "Voltage Frequency";
case "0x2BE9":
return "Battery Critical Status";
case "0x2BEA":
return "Battery Health Status";
case "0x2BEB":
return "Battery Health Information";
case "0x2BEC":
return "Battery Information";
case "0x2BED":
return "Battery Level Status";
case "0x2BEE":
return "Battery Time Status";
case "0x2BEF":
return "Estimated Service Date";
case "0x2BF0":
return "Battery Energy Status";
case "0x2BF1":
return "Observation Schedule Changed";
case "0x2BF2":
return "Current Elapsed Time";
case "0x2BF3":
return "Health Sensor Features";
case "0x2BF4":
return "GHS Control Point";
case "0x2BF5":
return "LE GATT Security Levels";
case "0x2BF6":
return "ESL Address";
case "0x2BF7":
return "AP Sync Key Material";
case "0x2BF8":
return "ESL Response Key Material";
case "0x2BF9":
return "ESL Current Absolute Time";
case "0x2BFA":
return "ESL Display Information";
case "0x2BFB":
return "ESL Image Information";
case "0x2BFC":
return "ESL Sensor Information";
case "0x2BFD":
return "ESL LED Information";
case "0x2BFE":
return "ESL Control Point";
case "0x2BFF":
return "UDI for Medical Devices";
default:
return "Unknown Characteristics";
}
}
同修改一下原有的getServiceUUID()
为getShortUUID()
,只改名字而已,之前命名有点不太严谨,记得改一下使用的地方,如下图所示,在ServiceProvider
中
四、特性提供者
首先我们在layout下创建一个item_characteristic.xml
,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<DependentLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_content"
ohos:width="match_parent"
ohos:background_element="#FFFFFF"
ohos:bottom_margin="2vp"
ohos:bottom_padding="8vp"
ohos:end_padding="16vp"
ohos:start_padding="16vp"
ohos:top_padding="8vp">
<Text
ohos:id="$+id:tx_character_name"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$color:black"
ohos:text="服务"
ohos:text_size="16fp"/>
<Text
ohos:id="$+id:tx_uuid_title"
ohos:height="match_content"
ohos:width="match_content"
ohos:below="$id:tx_character_name"
ohos:text="UUID:"
ohos:text_color="$color:gray"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
<Text
ohos:id="$+id:tx_uuid"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$color:black"
ohos:below="$id:tx_character_name"
ohos:end_of="$id:tx_uuid_title"
ohos:text="UUID"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
<Text
ohos:id="$+id:tx_property_title"
ohos:height="match_content"
ohos:width="match_content"
ohos:below="$id:tx_uuid_title"
ohos:text="Properties:"
ohos:text_color="$color:gray"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
<ListContainer
ohos:id="$+id:lc_property"
ohos:height="match_content"
ohos:width="match_parent"
ohos:orientation="horizontal"
ohos:align_bottom="$id:tx_property_title"
ohos:align_top="$id:tx_property_title"
ohos:end_of="$id:tx_property_title"/>
</DependentLayout>
这个布局中唯一说明的一点就是ListContainer
的orientation="horizontal"
属性,这表示列表是横向,默认是纵向的。这里显示特性的名称和UUIID,同时加载属性列表,然后写适配器,因为需要操作属性的缘故,这些写一个接口,在provider
包下新建一个OperateCallback
接口,代码如下所示:
public interface OperateCallback {
/**
* 属性操作
*/
void onPropertyOperate(GattCharacteristic characteristic, String operateName);
}
通过这个接口可以知道当前操作的是那个特性和属性名称。下面我们写适配器,在provider
包下新建一个CharacteristicProvider
类,代码如下所示:
public class CharacteristicProvider extends BaseItemProvider {
private final List<GattCharacteristic> characteristicList;
private final AbilitySlice slice;
private final OperateCallback operateCallback;
public CharacteristicProvider(List<GattCharacteristic> list, AbilitySlice slice, OperateCallback operateCallback) {
this.characteristicList = list;
this.slice = slice;
this.operateCallback = operateCallback;
}
@Override
public int getCount() {
return characteristicList == null ? 0 : characteristicList.size();
}
@Override
public Object getItem(int position) {
if (characteristicList != null && position >= 0 && position < characteristicList.size()) {
return characteristicList.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;
ServiceHolder holder;
GattCharacteristic characteristic = characteristicList.get(position);
if (component == null) {
cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_characteristic, null, false);
holder = new ServiceHolder(cpt);
//将获取到的子组件信息绑定到列表项的实例中
cpt.setTag(holder);
} else {
cpt = component;
// 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。
holder = (ServiceHolder) cpt.getTag();
}
holder.txCharacterName.setText(BleUtils.getCharacteristicsName(characteristic.getUuid()));
holder.txUuid.setText(BleUtils.getShortUUID(characteristic.getUuid()));
List<String> properties = BleUtils.getProperties(characteristic.getProperties());
//加载属性
holder.lcProperty.setItemProvider(new PropertyProvider(properties, slice));
//属性列表点击
holder.lcProperty.setItemClickedListener((listContainer, component1, propertyPosition, l) -> {
if (operateCallback != null) {
//属性操作回调
operateCallback.onPropertyOperate(characteristic, properties.get(propertyPosition));
}
});
return cpt;
}
/**
* 用于保存列表项的子组件信息
*/
public static class ServiceHolder {
Text txCharacterName;
Text txUuid;
ListContainer lcProperty;
public ServiceHolder(Component component) {
txCharacterName = (Text) component.findComponentById(ResourceTable.Id_tx_character_name);
txUuid = (Text) component.findComponentById(ResourceTable.Id_tx_uuid);
lcProperty = (ListContainer) component.findComponentById(ResourceTable.Id_lc_property);
}
}
}
在这里我们就可以处理特性的名称和UUID显示,同时加载属性提供者,显示出来。
五、加载特性
因为特性是在服务下的,所以我们可以在服务适配器中加载特性适配器。首先我们修改一下item_service.xml
,代码如下所示:
<?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:background_element="#FFFFFF"
ohos:bottom_margin="2vp"
ohos:orientation="vertical">
<DependentLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:id="$+id:item_service"
ohos:height="match_content"
ohos:width="match_parent"
ohos:background_element="#FFFFFF"
ohos:bottom_padding="8vp"
ohos:end_padding="16vp"
ohos:start_padding="16vp"
ohos:top_padding="8vp">
<Text
ohos:id="$+id:tx_service_name"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$color:black"
ohos:text="服务"
ohos:text_size="16fp"/>
<Text
ohos:id="$+id:tx_uuid_title"
ohos:height="match_content"
ohos:width="match_content"
ohos:below="$id:tx_service_name"
ohos:text="UUID:"
ohos:text_color="$color:gray"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
<Text
ohos:id="$+id:tx_uuid"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$color:black"
ohos:below="$id:tx_service_name"
ohos:end_of="$id:tx_uuid_title"
ohos:text="UUID"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
<Text
ohos:id="$+id:tx_service_info"
ohos:height="match_content"
ohos:width="match_content"
ohos:below="$id:tx_uuid_title"
ohos:text="PRIMARY SERVICE"
ohos:text_color="$color:gray"
ohos:text_size="16fp"
ohos:top_margin="2vp"/>
<Image
ohos:id="$+id:iv_state"
ohos:height="24vp"
ohos:width="24vp"
ohos:align_parent_end="true"
ohos:end_margin="16vp"
ohos:background_element="$graphic:ic_right_24"
ohos:vertical_center="true"/>
</DependentLayout>
<ListContainer
ohos:id="$+id:lc_characteristics"
ohos:height="match_content"
ohos:width="match_parent"
ohos:start_padding="16vp"
ohos:visibility="hide"/>
</DirectionalLayout>
整体上变化不大,只是加了一个ListContainer
,同时增加了一个Image
,用于显示当前的服务下的特性列表是否显示,可以通过当前的服务item的方式控制是否显示特性列表,这里用到两个图标,在graphic
下创建ic_right_24.xml
,代码如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<vector
xmlns:ohos="http://schemas.android.com/apk/res/android"
ohos:height="24vp"
ohos:tint="#000000"
ohos:viewportHeight="24"
ohos:viewportWidth="24"
ohos:width="24vp">
<path
ohos:fillColor="#000000"
ohos:pathData="M10,17l5,-5 -5,-5v10z"/>
</vector>
还有一个ic_down_24.xml
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="24vp"
ohos:width="24vp"
ohos:tint="#000000"
ohos:viewportHeight="24"
ohos:viewportWidth="24">
<path
ohos:fillColor="#000000"
ohos:pathData="M7,10l5,5 5,-5z"/>
</vector>
下面修改一下ServiceAdapter,代码如下所示:
public class ServiceProvider extends BaseItemProvider {
private final List<GattService> serviceList;
private final AbilitySlice slice;
private OperateCallback operateCallback;
public void setOperateCallback(OperateCallback operateCallback) {
this.operateCallback = operateCallback;
}
public ServiceProvider(List<GattService> list, AbilitySlice slice) {
this.serviceList = list;
this.slice = slice;
}
@Override
public int getCount() {
return serviceList == null ? 0 : serviceList.size();
}
@Override
public Object getItem(int position) {
if (serviceList != null && position >= 0 && position < serviceList.size()) {
return serviceList.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;
ServiceHolder holder;
GattService service = serviceList.get(position);
if (component == null) {
cpt = LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_item_service, null, false);
holder = new ServiceHolder(cpt);
//将获取到的子组件信息绑定到列表项的实例中
cpt.setTag(holder);
} else {
cpt = component;
// 从缓存中获取到列表项实例后,直接使用绑定的子组件信息进行数据填充。
holder = (ServiceHolder) cpt.getTag();
}
holder.itemService.setClickedListener(component1 -> {
boolean isShow = holder.lcCharacteristics.getVisibility() == Component.VISIBLE;
//显示特性列表
holder.lcCharacteristics.setVisibility(isShow ? Component.HIDE : Component.VISIBLE);
//更换图标
VectorElement vectorElement = new VectorElement(slice.getContext(), isShow ? ResourceTable.Graphic_ic_right_24 : ResourceTable.Graphic_ic_down_24);
holder.ivState.setBackground(vectorElement);
//刷新Item高度,这个很重要,不加会造成内容覆盖。
notifyDataSetItemChanged(position);
});
//加载特性 设置属性回调
holder.lcCharacteristics.setItemProvider(new CharacteristicProvider(service.getCharacteristics(), slice, operateCallback));
holder.txServiceName.setText(BleUtils.getServiceName(service.getUuid()));
holder.txUuid.setText(BleUtils.getShortUUID(service.getUuid()));
return cpt;
}
/**
* 用于保存列表项的子组件信息
*/
public static class ServiceHolder {
DependentLayout itemService;
Text txServiceName;
Text txUuid;
Image ivState;
ListContainer lcCharacteristics;
public ServiceHolder(Component component) {
itemService = (DependentLayout) component.findComponentById(ResourceTable.Id_item_service);
txServiceName = (Text) component.findComponentById(ResourceTable.Id_tx_service_name);
txUuid = (Text) component.findComponentById(ResourceTable.Id_tx_uuid);
ivState = (Image) component.findComponentById(ResourceTable.Id_iv_state);
lcCharacteristics = (ListContainer) component.findComponentById(ResourceTable.Id_lc_characteristics);
}
}
}
和之前的区别就在于构造的时候增加了一个回调,并且在getComponent()
方法中就处理了服务Item的点击事件,而不是像之前一样回调到页面中,在服务Item的点击事件中判断是否显示特性列表同时修改图标资源。最后再将接口回调到页面中。
另外还需要注意一点,那就是Image我在xml中是设置的背景,而不是图片资源,因为在java代码中无法设置矢量图的资源,所以我就改成使用背景资源,需要注意的是背景资源要设置Image具体的大小,否则不会显示,在Java代码中通过VectorElement
来加载矢量图资源,然后设置背景。同时notifyDataSetItemChanged(position)
这样代码也很重要,因为我们的服务Item实际上有两部分内容,服务本身内容和特性列表内容,默认情况下显示服务内容,当点击服务Item时显示特性列表内容,此时如果你按照Android的习惯去搞,你就会发现,Item展开的内容会被其他Item遮挡,所以我们需要加上这样一行代码,让Item进行刷新,这样就不会被其他Item遮挡了。
最后我们在MainAbilitySlice中添加serviceProvider.setOperateCallback(this);
,然后再实现接口
重写里面的onPropertyOperate()
方法。这个方法在下一篇文章中会用到。
@Override
public void onPropertyOperate(GattCharacteristic characteristic, String operateName) {
}
运行一下看看效果。
六、源码
如果对你有所帮助的话,不妨 Star 或 Fork,山高水长,后会有期~
源码地址:HarmonyBle-Java