OpenHarmony开发者论坛
标题:
Launcher架构分析
[打印本页]
作者:
Laval社区小助手
时间:
2024-3-4 09:26
标题:
Launcher架构分析
[md]**简介**
**Launcher 作为系统人机交互的首要入口,提供应用图标的显示、点击启动、卸载应用,并提供桌面布局设置以及最近任务管理等功能。** **Launcher 采用 扩展的TS语言(eTS)开发,主要的结构如下:**
![](file:///C:/Users/kuangansheng/Desktop/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B/Launcher%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90/image/launcherl2-zh.png?lastModify=1686792052)
![](
https://devpress.csdnimg.cn/6f51f5aa3d914eaf864951b0e92e9d3f.png
)
* **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:
AUNCHER_BUNDLE_NAME, AbilityConfig:
AUNCHER_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应用管理
**注:应用长按,弹出【打开】、【卸载】操作弹窗**
![](file:///C:/Users/kuangansheng/Desktop/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B/Launcher%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90/image/%E5%BA%94%E7%94%A8%E7%AE%A1%E7%90%86.jpg?lastModify=1686792052)
![](
https://devpress.csdnimg.cn/2c7c4b35ccf84e27970ae4a7057f4198.jpg
)
**点击【打开】,拉起应用;**
**点击【卸载】,卸载应用。**
#### 2.1.2桌面管理
**注:桌面空白区域长按,弹出【桌面设置】、【添加空白页】操作弹窗**
![](file:///C:/Users/kuangansheng/Desktop/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B/Launcher%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90/image/%E6%A1%8C%E9%9D%A2%E7%AE%A1%E7%90%86.jpg?lastModify=1686792052)
![](
https://devpress.csdnimg.cn/fc1ae16b5c6f42b3a884006eb886b564.jpg
)
**点击【桌面设置】,弹出手势导航开关设置页面**
![](file:///C:/Users/kuangansheng/Desktop/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B/Launcher%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90/image/%E6%A1%8C%E9%9D%A2%E8%AE%BE%E7%BD%AE.jpg?lastModify=1686792052)
![](
https://devpress.csdnimg.cn/c5fa600de93a44a9add1666b8c326cb6.jpg
)
**手势开启后,桌面导航栏按键隐藏**
![](file:///C:/Users/kuangansheng/Desktop/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B/Launcher%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90/image/%E5%BC%80%E5%90%AF%E6%89%8B%E5%8A%BF.jpg?lastModify=1686792052)
![](
https://devpress.csdnimg.cn/52b5f7eff5584e40a73d5a60aef490c0.jpg
)
**手势开启后,可通过短按左划或右划进行返回操作,短按上划返回桌面,长按上划进入后台任务窗口(所有的手势操作都要从对应的屏幕边缘开始)**
**注:具体功能见手势管理**
#### 2.1.3桌面背景
**桌面默认背景图:applications\_launcher\\feature\\appcenter\\src\\main\\ets\\default\\common\\pics\\img\_wallpaper\_default.jpg**
### 2.2文件夹管理
**当应用拖拽区域重叠时,自动创建文件夹,用于放置应用。其中包含:文件夹重命名、移出文件夹、添加新应用等功能。**
![](file:///C:/Users/kuangansheng/Desktop/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B+%E6%A1%88%E4%BE%8B/%E8%AF%BE%E7%A8%8B/Launcher%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90/image/%E6%96%87%E4%BB%B6%E5%A4%B9%E7%AE%A1%E7%90%86.jpg?lastModify=1686792052)
![](
https://devpress.csdnimg.cn/df2e6671bb2c4803921707e2499c38cc.jpg
)
![](
https://devpress.csdnimg.cn/c8c9a5024b2645ffa015deab68636f10.jpg
)
#### 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
;
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
;
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实现,详解见:**
[OpenHarmony任务管理MissionManager](
https://codehub-g.huawei.com/ope ... amp;login=from_csdn
)
### 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进行页面布局。**
[/md]
欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/)
Powered by Discuz! X3.5