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