OpenHarmony技术开发:Launcher架构应用启动流程分析

简介

Launcher 作为系统人机交互的首要入口,提供应用图标的显示、点击启动、卸载应用,并提供桌面布局设置以及最近任务管理等功能。 Launcher 采用 扩展的 TS 语言(eTS)开发,主要的结构如下:

  • product 业务形态层:区分不同产品、不同屏幕的各形态桌面,含有桌面窗口、个性化业务,组件的配置,以及个性化资源包。
  • feature 公共特性层:抽象的公共特性组件集合,可以被各桌面形态引用。
  • common 公共能力层:基础能力集,每个桌面形态都必须依赖的模块。

代码结构

/applications/standard/launcher/
├── common                    # 公共能力层目录
├── docs                      # 开发指南
├── feature                   # 公共特性层目录
│   ├── appcenter             # 应用中心
│   ├── bigfolder             # 智能文件夹
│   ├── form                  # 桌面卡片管理功能
│   ├── gesturenavigation     # 手势导航
│   ├── pagedesktop           # 工作区
│   ├── recents               # 最近任务
│   ├── settings              # 桌面设置
│   └── smartdock             # dock工具栏
├── product                   # 业务形态层目录
└── signature                 # 签名证书

功能介绍

1.应用启动流程

账户子系统在进行用户切换时,调用 foundation\aafwk\standard\services\abilitymgr\src\ability_manager_service.cpp 中的 SwitchToUser

void AbilityManagerService::SwitchToUser(int32_t oldUserId, int32_t userId)
{
    HILOG_INFO("%{public}s, oldUserId:%{public}d, newUserId:%{public}d", __func__, oldUserId, userId);
    SwitchManagers(userId);
    PauseOldUser(oldUserId);
    bool isBoot = false;
    if (oldUserId == U0_USER_ID) {
        isBoot = true;
    }
    StartUserApps(userId, isBoot);
    PauseOldConnectManager(oldUserId);
}

调用 StartUserApps 拉起用户应用

void AbilityManagerService::StartUserApps(int32_t userId, bool isBoot)
{
    HILOG_INFO("StartUserApps, userId:%{public}d, currentUserId:%{public}d", userId, GetUserId());
#ifdef SUPPORT_GRAPHICS
    if (currentMissionListManager_ && currentMissionListManager_->IsStarted()) {
        HILOG_INFO("missionListManager ResumeManager");
        currentMissionListManager_->ResumeManager();
        return;
    }
#endif
    StartSystemAbilityByUser(userId, isBoot);
}

调用 StartSystemAbilityByUser 拉起用户系统应用

void AbilityManagerService::StartSystemAbilityByUser(int32_t userId, bool isBoot)
{
    HILOG_INFO("StartSystemAbilityByUser, userId:%{public}d, currentUserId:%{public}d", userId, GetUserId());
    ConnectBmsService();

    if (!amsConfigResolver_ || amsConfigResolver_->NonConfigFile()) {
        HILOG_INFO("start all");
        StartingLauncherAbility(isBoot);
#ifdef SUPPORT_GRAPHICS
        StartingScreenLockAbility();
#endif
        return;
    }

    if (amsConfigResolver_->GetStartLauncherState()) {
        HILOG_INFO("start launcher");
        StartingLauncherAbility(isBoot);
    }

#ifdef SUPPORT_GRAPHICS
    if (amsConfigResolver_->GetStartScreenLockState()) {
        StartingScreenLockAbility();
    }
#endif

    if (amsConfigResolver_->GetPhoneServiceState()) {
        HILOG_INFO("start phone service");
        StartingPhoneServiceAbility();
    }

    if (amsConfigResolver_->GetStartMmsState()) {
        HILOG_INFO("start mms");
        StartingMmsAbility();
    }
}

调用 StartingLauncherAbility 拉起桌面应用

注:这里会等待 launcher 应用的拉起,会尝试等待 SWITCH_ACCOUNT_TRY(3)次,每次等待 REPOLL_TIME_MICRO_SECONDS(1000000)微秒,也就是 1 秒

bool AbilityManagerService::StartingLauncherAbility(bool isBoot)
{
    HILOG_DEBUG("%{public}s", __func__);
    auto bms = GetBundleManager();
    CHECK_POINTER_AND_RETURN(bms, false);

    /* query if launcher ability has installed */
    AppExecFwk::AbilityInfo abilityInfo;
    /* First stage, hardcoding for the first launcher App */
    auto userId = GetUserId();
    Want want;
    want.SetElementName(AbilityConfig::LAUNCHER_BUNDLE_NAME, AbilityConfig::LAUNCHER_ABILITY_NAME);
    HILOG_DEBUG("%{public}s, QueryAbilityInfo, userId is %{public}d", __func__, userId);
    int attemptNums = 0;
    while (!IN_PROCESS_CALL(bms->QueryAbilityInfo(want, AppExecFwk::AbilityInfoFlag::GET_ABILITY_INFO_WITH_APPLICATION,
        userId, abilityInfo))) {
        HILOG_INFO("Waiting query launcher ability info completed.");
        if (!isBoot && ++attemptNums > SWITCH_ACCOUNT_TRY) {
            HILOG_ERROR("Start launcher failed.");
            return false;
        }
        usleep(REPOLL_TIME_MICRO_SECONDS);
    }

    HILOG_INFO("Start Home Launcher Ability.");
    /* start launch ability */
    (void)StartAbility(want, userId, DEFAULT_INVAL_VALUE);
    return true;
}

StartAbility 底层源码就不再追溯,通过 want,拉起应用。

2.主体功能介绍

注:这里以 3568 为例

2.1 应用中心

2.1.1 应用管理

注:应用长按,弹出【打开】、【卸载】操作弹窗

点击【打开】,拉起应用;

点击【卸载】,卸载应用。

2.1.2 桌面管理

注:桌面空白区域长按,弹出【桌面设置】、【添加空白页】操作弹窗

点击【桌面设置】,弹出手势导航开关设置页面

手势开启后,桌面导航栏按键隐藏

手势开启后,可通过短按左划或右划进行返回操作,短按上划返回桌面,长按上划进入后台任务窗口(所有的手势操作都要从对应的屏幕边缘开始)

注:具体功能见手势管理

2.1.3 桌面背景

桌面默认背景图:applications_launcher\feature\appcenter\src\main\ets\default\common\pics\img_wallpaper_default.jpg

2.2 文件夹管理

当应用拖拽区域重叠时,自动创建文件夹,用于放置应用。其中包含:文件夹重命名、移出文件夹、添加新应用等功能。

2.2.1 移出文件夹
/**
 * Delete app from open folder
 *
 * @param {any} appInfo.
 */
deleteAppFromOpenFolder(appInfo): any {
  let openFolderData: {
    folderId: string,
    layoutInfo: any
  } = AppStorage.Get('openFolderData');
  const folderLayoutInfo = this.getFolderLayoutInfo(openFolderData, appInfo);

  // Delete app from the folder
  const gridLayoutInfo = this.mSettingsModel.getLayoutInfo();
  const folderIndex = gridLayoutInfo.layoutInfo.findIndex(item => {
    return item.typeId === CommonConstants.TYPE_FOLDER && item.folderId === openFolderData.folderId;
  });

  const appListInfo = this.mSettingsModel.getAppListInfo();
  if (folderLayoutInfo.length == 1 && folderLayoutInfo[0].length == 1) {
    // delete from folder and add app to desktop
    const appLayout = {
      bundleName: folderLayoutInfo[0][0].bundleName,
      abilityName: folderLayoutInfo[0][0].abilityName,
      moduleName: folderLayoutInfo[0][0].moduleName,
      keyName: folderLayoutInfo[0][0].keyName,
      typeId: folderLayoutInfo[0][0].typeId,
      area: folderLayoutInfo[0][0].area,
      page: gridLayoutInfo.layoutInfo[folderIndex].page,
      column: gridLayoutInfo.layoutInfo[folderIndex].column,
      row: gridLayoutInfo.layoutInfo[folderIndex].row
    };
    gridLayoutInfo.layoutInfo.push(appLayout);
    appListInfo.push(folderLayoutInfo[0][0]);
    gridLayoutInfo.layoutInfo.splice(folderIndex, 1);
    openFolderData = {
      folderId: '', layoutInfo: []
    };
  } else {
    this.updateBadgeNumber(gridLayoutInfo.layoutInfo[folderIndex], appInfo);
    openFolderData.layoutInfo = folderLayoutInfo;
  }
  this.mSettingsModel.setAppListInfo(appListInfo);
  this.mSettingsModel.setLayoutInfo(gridLayoutInfo);
  return openFolderData;
}

查看应用源码,其实就是从 layout 中移除指定应用信息(注:当文件夹中只剩一个应用时,自动移出文件夹,文件夹布局信息移除),然后更新布局

这里我们关注下应用信息和布局信息的存储:

async insertDesktopApplication(desktopApplicationInfo: any): Promise<boolean> {
  Log.showInfo(TAG, 'insertDesktopApplication start');
  let result: boolean = true;
  if (CheckEmptyUtils.isEmptyArr(desktopApplicationInfo)) {
    Log.showError(TAG, 'insertDesktopApplication desktopApplicationInfo is empty');
    result = false;
    return result;
  }
  try {
    this.mRdbStore.beginTransaction();
    // delete desktopApplicationInfo table
    await this.deleteTable(RdbStoreConfig.DesktopApplicationInfo.TABLE_NAME);
    // insert into desktopApplicationInfo
    for (let i in desktopApplicationInfo) {
      let element = desktopApplicationInfo[i];
      let item = {
        'app_name': element.appName,
        'is_system_app': element.isSystemApp ? 1 : 0,
        'is_uninstallAble': element.isUninstallAble ? 1 : 0,
        'appIcon_id': element.appIconId,
        'appLabel_id': element.appLabelId,
        'bundle_name': element.bundleName,
        'module_name': element.moduleName,
        'ability_name': element.abilityName,
        'key_name': element.bundleName + element.abilityName + element.moduleName,
        'install_time': element.installTime
      }
      this.mRdbStore.insert(RdbStoreConfig.DesktopApplicationInfo.TABLE_NAME, item)
        .then((ret) => {
          Log.showDebug(TAG, `insertDesktopApplication ${i} ret: ${ret}`);
          if (ret === -1) {
            result = false;
          }
        });
    }
    this.mRdbStore.commit();
  } catch (e) {
    Log.showError(TAG, 'insertDesktopApplication error:' + e);
    this.mRdbStore.rollBack();
  }
  return result;
}

应用信息是通过 rdb 进行持久化的,循环存储在 Launcher.db 的 RdbStoreConfig.DesktopApplicationInfo.TABLE_NAME(DESKTOPAPPLICATIONINFO)数据表中,设备存储库路径为:/data/app/el2/100/database/com.ohos.launcher/phone-launcher/db/Launcher.db

/**
 * Update workspace layout data.
 *
 * @params gridLayoutInfo
 */
updateGridLayoutInfo(gridLayoutInfo: any): void {
  const temp = {
    layoutDescription: {},
    layoutInfo: []
  };
  temp.layoutDescription = gridLayoutInfo.layoutDescription;
  FileUtils.writeStringToFile(JSON.stringify(temp), this.getConfigFileAbsPath());
  this.mGridLayoutInfo = gridLayoutInfo;
  globalThis.RdbStoreManagerInstance.insertGridLayoutInfo(gridLayoutInfo).then(() => {
    Log.showInfo(TAG, 'updateGridLayoutInfo success.');
  }).catch((err) => {
    Log.showError(TAG, `updateGridLayoutInfo error: ${err.toString()}`);
  });
}

更新布局信息时,会先将布局描述写入到文件中,再将布局信息持久化到存储库

/**
 * Write string to a file.
 *
 * @param {string} str - target string will be written to file.
 * @param {string} filePath - filePath as the absolute path to the target file.
 */
static writeStringToFile(str: string, filePath: string): void {
  Log.showDebug(TAG, 'writeStringToFile start execution');
  let writeStreamSync = null;
  try {
    writeStreamSync = Fileio.createStreamSync(filePath, 'w+');
    let number = writeStreamSync.writeSync(str);
    Log.showInfo(TAG, 'writeStringToFile number: ' + number);
  } catch (e) {
    Log.showError(TAG, `writeStringToFile error: ${e.toString()}`);
  } finally {
    writeStreamSync.closeSync();
    Log.showDebug(TAG, 'writeStringToFile close sync');
  }
}

布局描述写入到文件(/data/app/el2/100/base/com.ohos.launcher/haps/phone-launcher/files/GridLayoutInfo.json)中

示例内容:

{"layoutDescription":{"pageCount":1,"row":6,"column":5},"layoutInfo":[]}
async insertGridLayoutInfo(gridlayoutinfo: any): Promise<void> {
  Log.showInfo(TAG, 'insertGridLayoutInfo start');
  if (CheckEmptyUtils.isEmpty(gridlayoutinfo) || CheckEmptyUtils.isEmptyArr(gridlayoutinfo.layoutInfo)) {
    Log.showError(TAG, 'insertGridLayoutInfo gridlayoutinfo is empty');
    return;
  }

  try {
    this.mRdbStore.beginTransaction();
    // delete gridlayoutinfo table
    await this.dropTable(RdbStoreConfig.GridLayoutInfo.TABLE_NAME);
    // insert into gridlayoutinfo
    let layoutinfo: any[] = gridlayoutinfo.layoutInfo;
    for (let i in layoutinfo) {
      let element = layoutinfo[i];
      let item = {};
      Log.showDebug(TAG, 'insertGridLayoutInfo' + JSON.stringify(element));
      if (element.typeId === CommonConstants.TYPE_APP) {
        item = {
          'bundle_name': element.bundleName,
          'ability_name': element.abilityName,
          'module_name': element.moduleName,
          'key_name': element.bundleName + element.abilityName + element.moduleName,
          'type_id': element.typeId,
          'area': element.area[0] + ',' + element.area[1],
          'page': element.page,
          'column': element.column,
          'row': element.row,
          'container': -100
        }
        this.mRdbStore.insert(RdbStoreConfig.GridLayoutInfo.TABLE_NAME, item)
          .then((ret) => {
            Log.showDebug(TAG, `insertGridLayoutInfo type is app ${i} ret: ${ret}`);
          });
      } else if (element.typeId === CommonConstants.TYPE_CARD) {
        item = {
          'bundle_name':element.bundleName,
          'ability_name': element.abilityName,
          'module_name': element.moduleName,
          'key_name': "" + element.cardId,
          'card_id': element.cardId,
          'type_id': element.typeId,
          'area': element.area[0] + ',' + element.area[1],
          'page': element.page,
          'column': element.column,
          'row': element.row,
          'container': -100
        }
        this.mRdbStore.insert(RdbStoreConfig.GridLayoutInfo.TABLE_NAME, item)
          .then((ret) => {
            Log.showDebug(TAG, `insertGridLayoutInfo type is card ${i} ret: ${ret}`);
          });
      } else {
        item = {
          'bundle_name':element.bundleName,
          'ability_name': element.abilityName,
          'module_name': element.moduleName,
          'folder_id': element.folderId,
          'folder_name': element.folderName,
          'type_id': element.typeId,
          'area': element.area[0] + ',' + element.area[1],
          'page': element.page,
          'column': element.column,
          'row': element.row,
          'container': -100,
          'badge_number': element.badgeNumber
        }
        this.mRdbStore.insert(RdbStoreConfig.GridLayoutInfo.TABLE_NAME, item).then(ret => {
          if (ret != -1) {
            this.insertLayoutInfo(element.layoutInfo, ret);
          }
          Log.showDebug(TAG, `insertGridLayoutInfo type is bigfolder ${i} ret: ${ret}`);
        });
      }
    }
    this.mRdbStore.commit();
  } catch (e) {
    Log.showError(TAG, 'insertGridLayoutInfo error:' + e);
    this.mRdbStore.rollBack();
  }
}

布局信息循环持久化在 RdbStoreConfig.GridLayoutInfo.TABLE_NAME(GRIDLAYOUTINFO)数据表中,数据类型分为:app、card 和 bigfolder,其中会存储各个类型的应用信息、特征信息及布局信息。

2.2.2 添加新应用

文件夹相关的操作其实都是针对布局信息的调整更新,基本都是类似逻辑。

2.3 卡片管理

该功能设备上暂未开放,暂不做分析了。

2.4 手势管理

当桌面设置手势导航开关打开后,手势生效

initWindowSize(display: any) {
  if (globalThis.sGestureNavigationExecutors) {
    globalThis.sGestureNavigationExecutors.setScreenWidth(display.width);
    globalThis.sGestureNavigationExecutors.setScreenHeight(display.height);
    this.touchEventCallback = globalThis.sGestureNavigationExecutors.touchEventCallback
      .bind(globalThis.sGestureNavigationExecutors);
    this.getGestureNavigationStatus();
  }
}

手势生效与关闭都会进行窗口尺寸设置,隐藏或显示导航栏

/**
 * touchEvent Callback.
 * @return true: Returns true if the gesture is within the specified hot zone.
 */
touchEventCallback(event: any): boolean {
  Log.showDebug(TAG, 'touchEventCallback enter');
  if (event.touches.length != 1) {
    return false;
  }
  const startXPosition = event.touches[0].globalX;
  const startYPosition = event.touches[0].globalY;
  if (event.type == 'down' && this.isSpecifiesRegion(startXPosition, startYPosition)) {
    this.initializationParameters();
    this.startEventPosition = this.preEventPosition = {
      x: startXPosition,
      y: startYPosition
    };
    this.startTime = this.preEventTime = event.timestamp;
    this.curEventType = event.type;
    if (vp2px(16) >= startXPosition || startXPosition >= (this.screenWidth - vp2px(16))) {
      this.eventName = 'backEvent';
      return true;
    }
  }
  if (this.startEventPosition && this.isSpecifiesRegion(this.startEventPosition.x, this.startEventPosition.y)) {
    if (event.type == 'move') {
      this.curEventType = event.type;
      const curTime = event.timestamp;
      const speedX = (startXPosition - this.preEventPosition.x) / ((curTime - this.preEventTime) / 1000);
      const speedY = (startYPosition - this.preEventPosition.y) / ((curTime - this.preEventTime) / 1000);
      const sqrt = Math.sqrt(speedX * speedX + speedY * speedY);
      const curSpeed = startYPosition <= this.preEventPosition.y ? -sqrt : sqrt;
      const acceleration = (curSpeed - this.preSpeed) / ((curTime - this.preEventTime) / 1000);
      this.preEventPosition = {
        x: startXPosition,
        y: startYPosition
      };
      this.preSpeed = curSpeed;
      const isDistance = this.isRecentsViewShowOfDistanceLimit(startYPosition);
      const isSpeed = this.isRecentsViewShowOfSpeedLimit(curTime, acceleration, curSpeed);
      this.preEventTime = curTime;
      if (isDistance && isSpeed && !this.eventName && curSpeed) {
        this.eventName = 'recentEvent';
        this.recentEventCall();
        return true;
      }
      if (this.eventName == 'backEvent' && startXPosition > vp2px(16) && !this.timeOfFirstLeavingTheBackEventHotArea) {
        this.timeOfFirstLeavingTheBackEventHotArea = (curTime - this.startTime) / 1000;
      }
    }
    if (event.type == 'up') {
      let distance = 0;
      let slidingSpeed = 0;
      if (this.curEventType == 'move') {
        if (this.eventName == 'backEvent') {
          distance = Math.abs((startXPosition - this.startEventPosition.x));
          if (distance >= vp2px(16) * 1.2 && this.timeOfFirstLeavingTheBackEventHotArea <= 120) {
            this.backEventCall();
            this.initializationParameters();
            return true;
          }
        } else if (this.eventName == 'recentEvent') {
          this.initializationParameters();
          return true;
        } else {
          distance = this.startEventPosition.y - startYPosition;
          const isDistance = this.isHomeViewShowOfDistanceLimit(startYPosition);
          Log.showDebug(TAG, `touchEventCallback isDistance: ${isDistance}`);
          if (isDistance) {
            slidingSpeed = distance / ((event.timestamp - this.startTime) / GestureNavigationExecutors.NS_PER_MS);
            Log.showDebug(TAG, `touchEventCallback homeEvent slidingSpeed: ${slidingSpeed}`);
            if (slidingSpeed >= vp2px(500)) {
              this.homeEventCall();
            }
            this.initializationParameters();
            return true;
          }
        }
      }
      this.initializationParameters();
    }
  }
  return false;
}

从 touchEventCallback 中可以看出,手势分为:向下、向上和移动三种,执行手势操作时,会记录手势的起始坐标和开始时间,手势执行结束后,根据结束位置和结束时间,来计算距离和速度,从而来执行相应的操作,例:返回操作(this.backEventCall())、HOME 操作(this.homeEventCall())及 RECENT 操作(this.recentEventCall())。

注:手势操作需要从特殊区域开始,判断逻辑如下:

private isSpecifiesRegion(startXPosition: number, startYPosition: number) {
  const isStatusBarRegion = startYPosition <= this.screenHeight * 0.07;
  const isSpecifiesXRegion = startXPosition <= vp2px(16) || startXPosition >= (this.screenWidth - vp2px(16));
  const isSpecifiesYRegion = (this.screenHeight - vp2px(22)) <= startYPosition && startYPosition <= this.screenHeight;
  return (isSpecifiesXRegion && !isStatusBarRegion) || (isSpecifiesYRegion && !isSpecifiesXRegion);
}

2.5 工作区

工作区即设备桌面,这里主要针对布局信息进行桌面渲染,然后针对拖拽事件的处理。

onDragDrop(x: number, y: number): boolean {
  const dragItemInfo: any = AppStorage.Get('dragItemInfo');
  if (JSON.stringify(dragItemInfo) == '{}') {
    return false;
  }
  const dragItemType: number = AppStorage.Get('dragItemType');
  const deviceType: string = AppStorage.Get('deviceType')
  // dock appInfo has no location information.
  if (dragItemType === CommonConstants.DRAG_FROM_DOCK && deviceType == CommonConstants.DEFAULT_DEVICE_TYPE) {
    dragItemInfo.typeId = CommonConstants.TYPE_APP;
    dragItemInfo.area = [1, 1];
    dragItemInfo.page = AppStorage.Get('pageIndex');
  }
  Log.showDebug(TAG, `onDragEnd dragItemInfo: ${JSON.stringify(dragItemInfo)}`);
  const endIndex = this.getItemIndex(x, y);
  const startPosition: DragItemPosition = this.copyPosition(this.mStartPosition);
  let endPosition: DragItemPosition = null;
  this.mEndPosition = this.getTouchPosition(x, y);
  Log.showInfo(TAG, `onDragEnd mEndPosition: ${JSON.stringify(this.mEndPosition)}`);
  endPosition = this.copyPosition(this.mEndPosition);
  const info = this.mSettingsModel.getLayoutInfo();
  const layoutInfo = info.layoutInfo;
  if (dragItemInfo.typeId == CommonConstants.TYPE_FOLDER || dragItemInfo.typeId == CommonConstants.TYPE_CARD ) {
    this.updateEndPosition(dragItemInfo);
    AppStorage.SetOrCreate('positionOffset', []);
  } else {
    if (this.isMoveToSamePosition(dragItemInfo)) {
      this.deleteBlankPageAfterDragging(startPosition, endPosition);
      return false;
    }
    const endLayoutInfo = this.getEndLayoutInfo(layoutInfo);
    if (endLayoutInfo != undefined) {
      // add app to folder
      if (endLayoutInfo.typeId === CommonConstants.TYPE_FOLDER) {
        this.mBigFolderViewModel.addOneAppToFolder(dragItemInfo, endLayoutInfo.folderId);
        if (dragItemType === CommonConstants.DRAG_FROM_DOCK && deviceType == CommonConstants.DEFAULT_DEVICE_TYPE) {
          localEventManager.sendLocalEventSticky(EventConstants.EVENT_REQUEST_RESIDENT_DOCK_ITEM_DELETE, dragItemInfo);
        }
        this.deleteBlankPageAfterDragging(startPosition, endPosition);
        return true;
      } else if (endLayoutInfo.typeId === CommonConstants.TYPE_APP) {
        // create a new folder
        const layoutInfoList = [endLayoutInfo];
        let startLayoutInfo = null;
        if (dragItemType === CommonConstants.DRAG_FROM_DOCK && deviceType == CommonConstants.DEFAULT_DEVICE_TYPE) {
          let appInfoList = this.mSettingsModel.getAppListInfo();
          const appIndex = appInfoList.findIndex(item => {
            return item.keyName === dragItemInfo.keyName;
          })
          if (appIndex == CommonConstants.INVALID_VALUE) {
            appInfoList.push({
              "appName": dragItemInfo.appName,
              "isSystemApp": dragItemInfo.isSystemApp,
              "isUninstallAble": dragItemInfo.isUninstallAble,
              "appIconId": dragItemInfo.appIconId,
              "appLabelId": dragItemInfo.appLabelId,
              "bundleName": dragItemInfo.bundleName,
              "abilityName": dragItemInfo.abilityName,
              "moduleName": dragItemInfo.moduleName,
              "keyName": dragItemInfo.keyName,
              "typeId": dragItemInfo.typeId,
              "area": dragItemInfo.area,
              "page": dragItemInfo.page,
              "column": this.getColumn(endIndex),
              "row": this.getRow(endIndex),
              "x": 0,
              "installTime": dragItemInfo.installTime
            })
            this.mSettingsModel.setAppListInfo(appInfoList);
          }
          startLayoutInfo = dragItemInfo;
          localEventManager.sendLocalEventSticky(EventConstants.EVENT_REQUEST_RESIDENT_DOCK_ITEM_DELETE, dragItemInfo);
        } else {
          startLayoutInfo = this.getStartLayoutInfo(layoutInfo, dragItemInfo);
        }
        layoutInfoList.push(startLayoutInfo);
        this.mBigFolderViewModel.addNewFolder(layoutInfoList).then(()=> {
          this.deleteBlankPageAfterDragging(startPosition, endPosition);
        });
        return true;
      }
    }
  }

  if (dragItemType === CommonConstants.DRAG_FROM_DOCK && deviceType == CommonConstants.DEFAULT_DEVICE_TYPE) {
    let appInfoTemp = {
      "bundleName": dragItemInfo.bundleName,
      "typeId": dragItemInfo.typeId,
      "abilityName": dragItemInfo.abilityName,
      "moduleName": dragItemInfo.moduleName,
      "keyName": dragItemInfo.keyName,
      "area": dragItemInfo.area,
      "page": dragItemInfo.page,
      "column": this.getColumn(endIndex),
      "row": this.getRow(endIndex)
    };
    layoutInfo.push(appInfoTemp);
    localEventManager.sendLocalEventSticky(EventConstants.EVENT_REQUEST_RESIDENT_DOCK_ITEM_DELETE, dragItemInfo);
  } else {
    this.checkAndMove(this.mStartPosition, this.mEndPosition, layoutInfo, dragItemInfo);
  }

  info.layoutInfo = layoutInfo;
  this.mSettingsModel.setLayoutInfo(info);
  localEventManager.sendLocalEventSticky(EventConstants.EVENT_SMARTDOCK_INIT_FINISHED, null);
  this.deleteBlankPageAfterDragging(startPosition, endPosition);
  return true;
}

其中就有拖拽应用创建文件夹(this.mBigFolderViewModel.addNewFolder(layoutInfoList)),拖拽应用到文件夹(this.mBigFolderViewModel.addOneAppToFolder(dragItemInfo, endLayoutInfo.folderId)),拖拽应用、文件夹、卡片布局位置等操作。

2.6 最近任务

最近任务即 Recent 窗口,主要是对后台任务的管理,主体功能通过 MessionManager 实现

2.7 桌面设置

桌面设置主要涉及添加空白页和手势导航开关设置功能,具体功能不多做介绍了。

3.桌面初始化

Launcher 初始化流程如下:初始化上下文、初始化全局常量、初始化手势导航、初始化 rdb、注册窗口事件、注册导航栏事件、创建桌面窗口(加载 pages/EntryView)、创建 Recent 窗口

async initLauncher(): Promise<void> {
  // init Launcher context
  globalThis.desktopContext = this.context;

  // init global const
  this.initGlobalConst();

  // init Gesture navigation
  this.startGestureNavigation();

  // init rdb
  let dbStore = RdbStoreManager.getInstance();
  await dbStore.initRdbConfig();
  await dbStore.createTable();

  windowManager.registerWindowEvent();
  navigationBarCommonEventManager.registerNavigationBarEvent();

  // create Launcher entry view
  windowManager.createWindow(globalThis.desktopContext, windowManager.DESKTOP_WINDOW_NAME,
    windowManager.DESKTOP_RANK, 'pages/' + windowManager.DESKTOP_WINDOW_NAME);

  // load recent
  windowManager.createRecentWindow();
}

桌面渲染即 EntryView 的页面渲染,通过 AppInfo 进行页面布局。

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

希望这一份鸿蒙学习资料能够给大家带来帮助~


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频学习资料+学习PDF文档

(鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

 纯血版鸿蒙全套学习资料(面试、文档、全套视频等)

                   

鸿蒙APP开发必备

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值