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开发必备

​​

总结

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

相关推荐
一只栖枝9 分钟前
华为 HCIE 大数据认证中 Linux 命令行的运用及价值
大数据·linux·运维·华为·华为认证·hcie·it
wuicer2 小时前
ubuntu 20.04 安装anaconda以及安装spyder
linux·运维·ubuntu
加班是不可能的,除非双倍日工资2 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi3 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip3 小时前
vite和webpack打包结构控制
前端·javascript
excel4 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
cui__OaO4 小时前
Linux软件编程--线程
linux·开发语言·线程·互斥锁·死锁·信号量·嵌入式学习
小狗爱吃黄桃罐头4 小时前
正点原子【第四期】Linux之驱动开发篇学习笔记-1.1 Linux驱动开发与裸机开发的区别
linux·驱动开发·学习
阿华的代码王国4 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼4 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin