#过年不停更#OpenHarmony 源码解析之SystemUi—Statusbar(TS) 原创 精华

深开鸿
发布于 2022-2-9 10:51
浏览
4收藏

春节不停更,此文正在参加「星光计划-春节更帖活动」
作者:董伟

简介

SystemUI应用是OpenHarmony中预置的系统应用,为用户提供系统相关信息展示及交互界面,包括系统状态、系统提示、系统提醒等,例如系统时间、电量信息。

源码地址:https://gitee.com/openharmony/applications_systemui/tree/master

本文主要分析batterycomponent、clockcomponent、wificomponent三大组件:

  1. 导入batteryInfo模块,监听系统电池事件,实时获取电池电量状态
  2. 导入时间模块,调用JS内置函数,实时获取系统时间、日期、星期信息
  3. 导入Wifi模块,监听设备Wlan通信与连接事件,实时获取Wifi开关、供电、连接名等信息

发布-订阅模式

源码中灵活使用了发布-订阅模式(发布-订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知)来对battery和wifi事件进行监听,当页面初始化时定义订阅事件,页面取消初始化时定义退订事件,由此,当页面初始化时,即执行对事件的订阅与监听,页面初始化完成时,订阅监听的逻辑处理已完成并返回相应的结果。

在本文中以battery和wifi组件为例,将要监听的事件放在数组中,创建对该数组内事件的订阅来执行监听,订阅回调中设置该数组为data,监听到事件发生,触发事件回调,传入err和data参数,当err.code为0时(表示函数正常运行),对data进行判断,监听到对应的事件(即监听到battery或者wifi状态发生了变化),再通过调用相应模块下的方法来获取所需的数值,这样一来,电池和wifi的状态信息一有变化,便重新获取一次,就实现了对电池和wifi状态信息的实时获取。

相较于常规的回调函数,发布-订阅模式存在以下的优缺点:

优点:发布者与订阅者耦合性降低,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件名,只要发布者的事件名不变,它不管发布者如何改变。

缺点:1.创建订阅者需要消耗一定的时间和内存。
2.虽然可以弱化对象之间的联系,如果过度使用的话,反而使代码不好理解及代码不好维护等等。

架构图

#过年不停更#OpenHarmony 源码解析之SystemUi—Statusbar(TS)-鸿蒙开发者社区

目录

                    /applications/standard/systemui
                        ├── build.gradle                    # 全局编译配置文件
                        ├── settings.gradle                 # 编译模块配置文件
                        ├── LICENSE                         # 许可文件
                        ├── common                          # 通用工具类目录
                        ├── entry                           # entry模块目录
                        ├── signature                       # 证书文件目录
                        ├── features                        # 子组件目录
                        │   ├── batterycomponent            # 电池组件
                        │   ├── clockcomponent              # 时间组件
                        │   ├── control                     # 控制中心组件
                        │   ├── navigationservice           # 导航栏服务组件
                        │   ├── noticeitem                  # 通知子组件
                        │   ├── notificationservice         # 通知服务组件
                        │   ├── signalcomponent             # sim卡信号组件
                        │   ├── wificomponent               # wifi组件
                        ├── product                         # SystemUI总体功能目录
                            ├── navigationBar               # 导航栏模块目录
                            ├── statusbar                   # 状态栏模块目录
                            ├── systemDialog                # 系统弹框模块目录

batterycomponent

简介

电池组件只需监听‘usual.event.BATTERY_CHANGED’即电池变化事件即可,监听到电池变化,便获取一次电池的状态信息,实时获取电池的状态信息(是否在充电,电量百分比),以实现以下功能:

1.自动在充电时展示更醒目的图标,比如填充绿色、带闪电等

2.动态展示电量百分比变化

官方接口文档

https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-apis-battery-info-0000001100730486

源码地址

https://gitee.com/nicklaus0602/applications_systemui/tree/master/features/batterycomponent

导入模块

import BatteryInfo from '@ohos.batteryInfo';			
import BatterySubscriber from '@ohos.commonEvent';		

申明变量与初始化

var mBatterySoc;
var mBatteryCharging;

let mProgress = Constants.DEFAULT_PROGRESS;
let mBatteryEventSubscriber = null;
let mBatteryEventSubscribeInfo = {
  events: ['usual.event.BATTERY_CHANGED']
}

export class BatteryStatus {
  /**
    * 自定义页面初始化函数,由此函数开始一层一层调用下一个函数,将当前类导出后,只需调用此初始化函数即可完成对Battery模块的使用
    * 这里使用AppStorage.SetAndLink('batterySoc', 0)来跨页面关联数据,只需要在另一个页面中导入当前自定义组件, 	
 	* 并使用@StorageLink('batterySoc')batterySoc: number = 1,即可关联变量mBatterySoc与batterySoc,然后在本页面修改mBatterySoc的值,
 	* 另一个页面的变量batterySoc值也会跟着变动。
 	* 
 	* 页面初始化 即注册订阅事件,监听mBatteryEventSubscribeInfo中的事件
 	*
 	*/
    initBatteryStatus() {
      Log.showInfo(TAG, 'initBatteryStatus');
      mBatterySoc = AppStorage.SetAndLink('batterySoc', 0);
      mBatteryCharging = AppStorage.SetAndLink('batteryCharging', false);
      if (mBatteryEventSubscriber == null) {
        this.registerBatteryListener();
      }
      this.getBatteryStatus();
    }

    uninitBatteryStatus() {
      Log.showInfo(TAG, 'uninitBatteryModel');
      this.unregisterBatteryListener();
    }
    
}

订阅与回调

/**
   * Subscribe Battery events   创建订阅
   */
private registerBatteryListener() {
  Log.showInfo(TAG, 'registerBatteryListener start');
  BatterySubscriber.createSubscriber(
    mBatteryEventSubscribeInfo,
    this.createBatterySubscriberCallBack.bind(this)
  );
}

/**
 * Unsubscribe Battery events  取消订阅
 *
 */
private unregisterBatteryListener() {
  Log.showInfo(TAG, 'unregisterBatteryListener');
  BatterySubscriber.unsubscribe(mBatteryEventSubscriber, () => {
    Log.showInfo(TAG, `unregister Battery Status Listener ===============`);
  });
}

/**
   * Callback of the subscriber 订阅回调,输出日志
   *
   * @param {Object} err - error returns from the caller
   * @param {Object} data - data returns from the caller
   */

private createBatterySubscriberCallBack(err, data) {
  Log.showInfo(TAG, `Subscriberregister createBatterySubscriberCallBack err: ${JSON.stringify(err)} data: ${JSON.stringify(data)}`);
  mBatteryEventSubscriber = data;
  BatterySubscriber.subscribe(mBatteryEventSubscriber, this.batterySubscriberCallBack.bind(this));
}


/**
   * Callback of the events  事件回调
   若err.code == 0,表示函数执行正常,此时判断事件是否为'usual.event.BATTERY_CHANGED',如果该事件发生,即调用电池模块内的函数来获取电池状态信息
   *
   * @param {Object} err - error returns from the caller
   * @param {Object} data - data returns from the caller
   */

private batterySubscriberCallBack(err, data) {
  Log.showInfo(TAG, `batterySubscriberCallBack err: ${JSON.stringify(err)} data: ${JSON.stringify(data)}`);
  if (err.code == 0) {
    if (data.event == 'usual.event.BATTERY_CHANGED') {
      this.getBatteryStatus();
    }
  } else {
    Log.showInfo(TAG, 'Subscriberregister error when subscribing ========');
  }
}

获取电池状态、电量

/**
   *  获取电池状态和剩余电量
   */
private getBatteryStatus() {
  Log.showInfo(TAG,'getBatteryStatus')
  
  // 调用BatteryInfo.batterySOC获取电量值
  let batterySoc = BatteryInfo.batterySOC;
  
  // 调用BatteryInfo.chargingStatus获取充电状态枚举值
  let batteryCharging = BatteryInfo.chargingStatus;
  
  if (null == batterySoc) {
    // Set the battery Soc as full when there is no battery hardware 设备无电池硬件时,电量默认为满电
    batterySoc = mProgress;
  }
  if (batterySoc <= 0) {
    // 确保电量值不为负数
    batterySoc = Math.abs(batterySoc) * Constants.PERCENT_NUMBER;
  }
  
  this.checkBatteryStatus(batteryCharging, (result) => {               
    let batteryStatus = result;
    // 检查电池的充电状态 将电量的值赋给mBatterySoc,
    mBatterySoc.set(batterySoc);
    // 电池状态赋给mBatteryCharging
    // 0是false,1、2、3是true
    mBatteryCharging.set(batteryStatus);
  });
}

/**
   * 传入参数charging,根据charging的值来赋boolean值给batteryStatus,charging为0时表示电池未充电
   表示电池充电状态的枚举:
   NONE 	0  	表示电池充电状态未知
   ENABLE 	1 	表示电池充电状态为使能状态
   DISABLE	2	表示电池充电状态为停止状态
   FULL	    3	表示电池充电状态为已充满状态
   *
   * @param {number} charging - the battery charging status
   * @param {object} callback - Function callback
   */
private checkBatteryStatus(charging, callback) {
  Log.showInfo(TAG, `checkBatteryStatus charging: ${charging}`);
  let batteryStatus;
  switch (charging) {
    case Constants.NONE:
      batteryStatus = false;
      break;
    case Constants.DISABLE:
    case Constants.ENABLE:
    case Constants.FULL:
      batteryStatus = true;
      break;
    default:
      batteryStatus = false;
      break;
  }
  callback(batteryStatus);
}

Wificomponent

注:官方暂无WifiInfo模块的接口文档,只有“import wifi from ‘@ohos.wifi’”模块的相关文档,二者部分属性的枚举值相同,可以稍做参考。

wifi 模块地址:https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-apis-wlan-0000001121342036

简介

wifi模块需要监听的事件较多,需要监听以下事件:

1.连接事件’usual.event.wifi.CONN_STATE’,

2.供电事件’usual.event.wifi.POWER_STATE’(常用于路由器等设备)

3.连接名事件’WIFI_CONNECT_NAME’,

通过监听wifi的连接状态变化来判断wifi的开关状态,通过监听wifi的供电状态变化来判断wifi是否可用,通过监听wifi连接的名称变化来判断当前连接的是哪一个wifi网络。

通过对这三个事件的监听,我们可以实时获取wifi的开关状态,供电状态,连接名称,借此可以实现以下功能:

1.自动根据wifi开关状态设置不同图标

2.自动展示连接的wifi名称

源码地址

https://gitee.com/openharmony/applications_systemui/tree/master/features/wificomponent

导入模块

import WifiInfo from '@ohos.wifi_native_js';
import Subscriber from '@ohos.commonEvent';

申明变量与初始化

var mCommonEventSubscribeInfo = {
  events: [Constants.EVENT_CONN_STATE,
  Constants.EVENT_POWER_STATE, Constants.Event_CONN_NAME]
};

var mCommonEventSubscriber = null;
var mWifiInfo;
var mWifiStatus;
var mWifiOpenStatus;

 export class WifiModel {
 	/**
 	* 自定义页面初始化函数,由此函数开始一层一层调用下一个函数,将当前类导出后,只需调用此初始化函数即可完成对Wifi模块的使用
 	* 页面初始化,这里使用AppStorage.SetAndLink('wifiInfo', 0)来跨页面关联数据,只需要在另一个页面中导入当前自定义组件, 	
 	* 并使用@StorageLink('wifiInfo')wifiInfo: number = 1,即可关联变量mWifiInfo与wifiInfo,然后再本页面修改mWifiInfo的值,
 	* 另一个页面的变量wifiInfo值也会跟着变动。
 	* 
 	* 页面初始化 即注册订阅事件,监听wifi事件变化,并获取wifi当前状态信息
 	*
 	*/
    initWifiModel() {
        Log.showInfo(TAG, `initWifiModel`)
        mWifiInfo = AppStorage.SetAndLink("wifiInfo", 0);
        mWifiStatus = AppStorage.SetAndLink("wifiStatus", false);
        mWifiOpenStatus = AppStorage.SetAndLink("wifiOpenStatus", false);
        if(mCommonEventSubscriber == null){
          this.registerWiFiStatusListener();
        }
        this.getWifiMessage();
      }

    /**
     * 取消初始化时取消订阅
     *
     */
     uninitWifiModel() {
       Log.showInfo(TAG, `uninitWifiModel`)
       this.unregisterWiFiStatusListener()
     }
}

订阅与回调

通过创建对wifi事件的订阅,来监听mCommonEventSubscribeInfo中的三个事件,‘usual.event.wifi.CONN_STATE’,‘usual.event.wifi.POWER_STATE’,‘WIFI_CONNECT_NAME’,若事件发生,则触发回调,

/**
 * Subscribe wifi events  订阅wifi事件,无返回值,创建订阅,监听mCommonEventSubscribeInfo中的事件
 *
 */
registerWiFiStatusListener() : void {
  Log.showInfo(TAG, `register Wifi status listener ===========`);
  Subscriber.createSubscriber(
    mCommonEventSubscribeInfo,
    this.createWifiStatusSubscriberCallBack.bind(this)
  );
}

/**
 * Callback of the subscriber    订阅回调
 *
 * @param {Object} err - error returns from the caller
 * @param {Object} data - data returns from the caller
 */
createWifiStatusSubscriberCallBack(err, data) {
  Log.showInfo(TAG, `createWifiStatusSubscriberCallBack start err: ${JSON.stringify(err)} data: ${JSON.stringify(data)}`);
  mCommonEventSubscriber = data;
  Subscriber.subscribe(mCommonEventSubscriber, this.wifiStatusSubscriberCallBack.bind(this));
}

/**
 * Callback of the events     事件回调
 *
 * @param {Object} err - error returns from the caller
 * @param {Object} data - data returns from the caller
 */
private wifiStatusSubscriberCallBack(err, data) {
  Log.showInfo(TAG, `wifiStatusSubscriberCallBack start err:${ JSON.stringify(err)} data: ${ JSON.stringify(data) }`);
  // 默认枚举值 DEFAULT_ERR_CODE: number = 0,代表函数正常运行
  if (err.code == Constants.DEFAULT_ERR_CODE) {
    Log.showInfo(TAG, `wifi data == ${JSON.stringify(data)}`);
    // 枚举值 EVENT_CONN_STATE: string = 'usual.event.wifi.CONN_STATE',表示wifi连接状态变化
    if (data.event == Constants.EVENT_CONN_STATE) {
    /**
     * 枚举值 
     * WIFI_STATE_AP_CONNECTING: number = 1;     正在建立WLAN连接
     * WIFI_STATE_NETWORK_ENABLED: number = 3;	 网络可用
     * WIFI_STATE_NO_NETWORK: number = 4;		 无网络连接
     * 如果正在建立WLAN连接||网络可用||无网络连接,则调用changeWifiStatus()方法,设置mWifiStatus为true,表示当前wifi已激活,否则即设置为false,表	  * 示当前wifi未打开
     */
      if(data.code == Constants.WIFI_STATE_AP_CONNECTING ||
      data.code == Constants.WIFI_STATE_NETWORK_ENABLED ||
      data.code == Constants.WIFI_STATE_NO_NETWORK){
        this.updateWifiInfo();
        this.changeWifiStatus(true);
      }else {
        this.updateWifiInfo();
        this.changeWifiStatus(false);
        mWifiName.set('WLAN');
      }
    }
    // WIFI_POWER_OFF: number = 0;  WIFI_POWER_ON: number = 4;
    // data.code == 0,wifi不可用,mWifiOpenStatus.set(false),
    // data.code == 4,wifi可用,mWifiOpenStatus.set(true)
    
    // 枚举值 EVENT_POWER_STATE: string = 'usual.event.wifi.POWER_STATE',表示wifi供电状态变化(常用于路由器等设备)
    if (data.event == Constants.EVENT_POWER_STATE) {
      if( data.code == Constants.WIFI_POWER_OFF){
        this.updateWifiInfo();
        this.changeWifiStatus(false);
        mWifiOpenStatus.set(false);
        mWifiName.set('WLAN');
      }else if(data.code == Constants.WIFI_POWER_ON){
        mWifiOpenStatus.set(true);
      }
    }
    // 枚举值 Event_CONN_NAME: string = 'WIFI_CONNECT_NAME',表示连接名称变化,意味着连接到了wifi网络(或者切换到了其他的wifi网络),可以获取当前wifi的信号强度了,更新wifi名称,并设置wifiStatus为true,表示已连接
    if(data.event == 'WIFI_CONNECT_NAME'){
      this.updateWifiName(JSON.parse(data.data).name)
      this.changeWifiStatus(true)
      
      // rssi是热点的信号强度(dBm),类型为number,取值范围为[0, 4],rssi为4时wifi信号满格
      // band是WLAN接入点的频段,类型为number
      // 将rssi,band传入函数updateWifiInfo()
      let rssi = JSON.parse(data.data).rssi
      let band = JSON.parse(data.data).band
      Log.showInfo(TAG, `WIFI_CONNECT_NAME ================ ${rssi}`)
      this.updateWifiInfo(rssi,band);
    }
  } else {
    Log.showError(TAG, `error when subscribing ========`);
  }
}

/**
 * Unsubscribe wifi events    取消wifi事件订阅(退订)
 *
 */
private unregisterWiFiStatusListener() {
  Subscriber.unsubscribe(mCommonEventSubscriber, () => {
    Log.showInfo(TAG, `unregister Wifi Status Listener ===============`);
  });
}

获取Wifi状态

/**
 * Check the connection status of Wifi  检查wifi的连接状态
 *
 * @param {boolean} status - if display wifi icon
 */
private changeWifiStatus(status) {
  Log.showInfo(TAG, `enter changeWifiStatus ================ ${status}`);
  mWifiStatus.set(status)
}

// 更新wifi信息,传入参数rssi,band
private updateWifiInfo(rssi=Constants.UPDATE_WIFI_INFO_DEFAULT_PARAM,band=Constants.SIGN_LEVEL) {
  Log.showInfo(TAG, `enter changeWifiInfos ================ ${rssi}+${band}`);
  mWifiInfo.set(this.getWifiInfo(rssi,band))
  Log.showInfo(TAG, `mWifiInfo wifiInfo ${mWifiInfo.get()}`);
}
// 更新wifi名称
private updateWifiName(name) {
  Log.showInfo(TAG, `enter changeWifiNames ================ ${name}`);
  mWifiName.set(name);
}


/**
 * Update the image of wifi status   更新wifi状态的图标(根据level,即信号的强度,取值范围为[0, 4]),信号4代表信号满格
 *
 * @param {number} rssi - the rssi number of the connected wifi
 * @return {string} image used to show wifi status
 */
private getWifiInfo(rssi,band) {
  //The current version keeps return 0 as result, set the level as 4(full level) by hand.
  Log.showInfo(TAG, `getWifiImage enter =========`);
  //Fake number of band and rssi for wifi signal level temporarily
  let level = WifiInfo.getSignalLevel(rssi, band);
  Log.showInfo(TAG, `wifi level = ${level}`);
  return level;
}
// 启用wifi(暂不可用)
enableWifi() {
  Log.showInfo(TAG, 'enableWifi ing');
  WifiInfo.enableWifi();
}
// 关闭wifi(暂不可用)
disableWifi() {
  Log.showInfo(TAG, 'disableWifi ing');
  WifiInfo.disableWifi();
}

Clockcomponent

简介

对于系统时间的获取较为简单,只需在页面初始化时调用JS的内置函数来获取系统时间即可,需要注意的是因为要保持所展示的时间实时更新,需要使用以下函数来设置时间间隔,确保每隔一定的时间就调用一次函数,保证时间的实时更新。

setInterval(() => {
    函数名;
  }, 时间间隔);

本文中通过每隔3秒获取一次时间来展示系统时间,满足设备展示“xx:xx”即“小时-分钟“的需要,如果需要精度更高,就需要调整LOOP_TIME,使得调用函数的间隔更短。

源码地址

https://gitee.com/openharmony/applications_systemui/tree/master/features/clockcomponent

获取时间数据

const LOOP_TIME = 3000; 
var mTimeLink;
var mDayLink;
var mWeekDayLink;
var mMonthLink;
/**
  * 获取当前日期和时间 调用getDate()函数,实例化Date对象,获取系统当前时间,调用JS内置函数,每隔三秒将date对象传入
  *	this.updateTime(date);
  *	this.updateDay(date);
  *	this.updateWeekDay(date);
  *	this.updateMonth(date);
  *	函数,获取时间、日、周、月数据
  *
  */
private getCurrentDate() {
  Log.showInfo(TAG, 'getCurrentDate');
  this.getDate();
  timeInterval = setInterval(() => {
    this.getDate();
  }, LOOP_TIME);
}

// 实例化Date对象,获取系统当前时间,并作为参数传入JS内置函数获取想要的时分秒、日、周、月等值
private getDate(){
  Log.showInfo(TAG, 'getDate');
  let date = new Date();
  this.updateTime(date);
  this.updateDay(date);
  this.updateWeekDay(date);
  this.updateMonth(date);
}

/**
 * Update Time.
 * date.toTimeString()返回一个表示该Date对象时分秒部分的时间字符串
   substring(0, 5) 截取下标第0个到第5个字符,即 xx:xx:xx
 * @param {Object} date - Object of Date.
 */
private updateTime(date) {
  let time = date.toTimeString().substring(0, 5);
  mTimeLink.set(time);
}

/**
 * Update Day.
 * date.getDate() 获取每个月的第几号-number
 * @param {Object} date - Object of Date.
 */
private updateDay(date) {
  let day = date.getDate();
  mDayLink.set(day);
}

/**
 * Update WeekDay.
 * date.getDay() 返回每周中的第几天-number,想获得'星期一'这样的value,还需要定义数组来获取,例如:
 var weekdaylist=new Array(7)
	weekdaylist[0]="周日"
	weekdaylist[1]="周一"
	weekdaylist[2]="周二"
	weekdaylist[3]="周三"
	weekdaylist[4]="周四"
	weekdaylist[5]="周五"
	weekdaylist[6]="周六"
	weekdaylist[this.mWeekDay]
 * @param {Object} date - Object of Date.
 */
private updateWeekDay(date) {
  let weekDay = date.getDay();
  mWeekDayLink.set(weekDay);
}

/**
 * Update Month.
 * date.getMonth() 返回月份-number,月份取值范围【0,11】,这里需要加1
 * @param {Object} date - Object of Date.
 */
private updateMonth(date) {
  let month = (date.getMonth() + 1);
  mMonthLink.set(month);
}

结语

本文对源码的方法进行了细致的解读,目标是节省广大开发者分析源码的时间,希望大家阅读本文后,可以将源码中从订阅监听事件到调用方法获得枚举值的整个模块直接拿来使用,以此助力鸿蒙OS生态搭建!

​ 更多原创内容请关注:开鸿 HarmonyOS 学院

​ 入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

更多原创内容请关注:深开鸿技术团队

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-2-9 11:46:15修改
9
收藏 4
回复
举报
8条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

看完之后深刻的认识到的做UI的不易

回复
2022-2-9 14:15:20
dirk41201
dirk41201

666

回复
2022-2-9 16:03:10
深开鸿董伟
深开鸿董伟 回复了 dirk41201
666

谢谢大佬支持

回复
2022-2-9 16:12:23
深开鸿董伟
深开鸿董伟 回复了 红叶亦知秋
看完之后深刻的认识到的做UI的不易

星星之火,可以燎原

回复
2022-2-9 16:12:41
業

不错呀

回复
2022-2-17 09:41:36
深开鸿董伟
深开鸿董伟 回复了
不错呀

谢谢大佬支持 不妥之处欢迎指正

回复
2022-2-18 18:37:48
元气也
元气也

照着安卓画瓢,我希望有创意创新而不是给人只是安卓copy

回复
2022-2-20 12:57:18
wx64831c0048677
wx64831c0048677

大佬们好,小白怎么编译3.2release systemui

回复
2024-1-4 19:34:26
回复
    相关推荐