本文作者:江苏润和软件股份有限公司 戴海
1. 框架简介
包管理子系统,是OpenHarmony为开发者提供的安装包管理框架,应用程序的安装、卸载、升级、权限管理等一系列操作都是通过包管理器完成的。包管理子系统由如下图模块组成:
· 包扫描器:用来解析本地预制或者安装的安装包,提取里面的各种信息,供管理子模块进行管理,持久化。
· 包安装子模块:安装,卸载,升级一个包;包安装服务一个单独进程的用于创建删除安装目录,具有较高的权限。
· 包管理子模块:管理安装包相关的信息,存储持久化包信息。
· 包安全管理子模块:签名检查、权限授予、权限管理。
2. 代码目录结构
foundation/appexecfwk/interfaces/kits/bundle_lite | BundleKit为开发者提供的接口 |
---|---|
foundation/appexecfwk/interfaces/innerkits/bundlemgr_lite | BundleKit实现的核心代码,及包管理服务为其它子系统提供的接口 |
foundation/appexecfwk/frameworks/bundle_lite | 管理BundleKit与包管理服务通信的客户端代码 |
foundation/appexecfwk/utils/bundle_lite | 包管理服务实现中用到的工具性的代码 |
foundation/appexecfwk/services/bundlemgr_lite | 包管理服务的实现代码 |
3. 实例讲解
为了能够熟悉包管理的是如何工作的,接下来将以包安装和包信息查询来进行详解。
Ø 案例一:包安装
Install是包安装的入口函数,首先创建了GetBmsInnerClient对象,这是用于和Server端进行IPC通讯的IClientProxy,通过Invoke发送了ID为INSTALL的消息,在Server端(bundlems)将对INSTALL进行功能实现,安装结果通过BundleSelfCallback回调回来。需要注意这里的Client和Server并不是直接进行IPC通讯的,而是通过/dev/lite_ipc驱动中转调用到Server端,这个中转过程就不在本章进行详细讲解。
//bundle_manager.cpp
bool Install(const char *hapPath, const InstallParam *installParam, InstallerCallback installerCallback)
{
……
const SvcIdentity *svc = OHOS::BundleSelfCallback::GetInstance().RegisterBundleSelfCallback(installerCallback); //注册回调,用于反馈包安装的状态结果
IpcIoPushSvc(&ipcIo, svc);
……
auto bmsInnerClient = GetBmsInnerClient();
……
int32_t ret = bmsInnerClient->Invoke(bmsInnerClient, INSTALL, &ipcIo, &result, Notify);//通过IPC和包管理服务通讯
……
}
static IClientProxy *GetBmsInnerClient()
{
……
IUnknown *iUnknown = SAMGR_GetInstance()->GetFeatureApi(BMS_SERVICE, BMS_INNER_FEATURE);//获取BMS_SERVICE的FeatureApi,用于调用包安装接口
……
}
定义INSTALL的Invoke ID,在Server端通过ID来映射到对应的包安装接口函数,这里同时还定义了卸载、包信息查询等,GET_BUNDLE_INFO后面的包信息查询中会用到。
// bundle_inner_interface.h
enum BmsCmd {
……
GET_BUNDLE_INFO,
……
INSTALL = BMS_INNER_BEGIN, // bms install application
……
};
刚才提到了Client端IClientProxy的创建,那对应Server端则有一个IServerProxy,用于远程接口调用。g_bmsInnerImpl 就是声明了IServerProxy,当收到INSTALL的消息时,则会通过BundleMsInvokeFuc根据Invoke ID来映射到对应的函数指针InstallInnerBundle。 由于包安装是个较为耗时的操作,所以采用异步线程Request的请求方式。包安装完成后向Client端反馈包安装状态。
//bundle_inner_feature.cpp
static BmsInnerImpl g_bmsInnerImpl = {
SERVER_IPROXY_IMPL_BEGIN,
.Invoke = BundleInnerFeature::Invoke, //创建IServerProxy
IPROXY_END
};
BundleInvokeType BundleInnerFeature::BundleMsInvokeFuc[BMS_CMD_END - BMS_INNER_BEGIN] {
InstallInnerBundle,//绑定包安装的接口函数
……
};
uint8_t BundleInnerFeature::InstallInnerBundle(const uint8_t funcId, IpcIo *req, IpcIo *reply)
{
"......"
Request request = {
.msgId = BUNDLE_INSTALLED,
.len = static_cast<int16>(sizeof(SvcIdentityInfo)),
.data = reinterpret_cast<void *>(info),
.msgValue = 0
};
int32 propRet = SAMGR_SendRequest(&(GetInstance()->identity_), &request, nullptr);//发送BUNDLE_INSTALLED消息
"......"
}
//bundle_manager_service.cpp
void ManagerService::ServiceMsgProcess(Request* request)
{
switch (request->msgId) {
case BUNDLE_INSTALLED: {
"......"
InstallThirdBundle(info->path, *(info->svc));//处理消息
break;
}
}
}
void ManagerService::InstallThirdBundle(const char *path, const SvcIdentity &svc)
{
"......"
uint8_t bResult = installer_->Install(path);//包安装
InnerSelfTransact(INSTALL_CALLBACK, bResult, svc);//回调安装结果
InnerTransact(INSTALL_CALLBACK, bResult, bundleName);
"......"
}
前面介绍的是包安装是如何从Client调用到Server端的,而包安装真正功能代码是在BundleInstaller::Install实现的,以下是具体步骤描述:
(1)首先需要检查包的路径是否有效。
(2)HapSignVerify::VerifySignature签名认证,在debug模式下也分签名模式和非签名模式,通过ManagerService::IsSignMode来查询,非debug模式是必须要进行签名认证,appid就是在这里生成的,也是APP的唯一标识。
(3)BundleParser::ParseHapProfile解析包中配置文件config.json,用来获取app、deviceConfig、module、reqPermissions信息。
(4)BundleInstaller::CheckProvisionInfoIsValid检查config.json配置文件是否有效,这里主要检查包的Permissions和bundleName是否和包签名信息匹配,这一步在debug且非签名模式下是不会检查配置文件的。
(5)BundleInstaller::CheckVersionAndSignature旧检查版本号和签名,首先需要根据bundleName通过包查询接口来获取bundleInfo信息,如果有值则表示此APP已经安装,这也是判断是否是升级的依据;接着需要检查旧包和新包的版本号,如果是降级安装是会报错的;最后将旧的安装包的codePath和codeData数据记录下来,保存在budleInfo对象中。
(6)BundleDaemonClient::ExtractHap->BundleDaemonHandler::ExtractHap包解压到目标路径的app/ace/run/ {moduleName}/temp目录下,moduleName是通过包查询从config.json中读取到的,如果有旧版本的路径则先会删除再解压,到此代码就解压完成了。
(7)BundleInstaller::HandleFileAndBackUpRecord 备份bundleInfo,通过包查询的bundleInfo的信息以及安装过程中保存的信息都会被记录,最终保存在/app/etc/{bundleName}_tmp.json中。
非升级情况:首先为APP生成一个UID,并记录下来,并创建app/ace/data /{bundleName}目录用来存放APP的持久化数据
升级情况:记录老的APP的UID
(8)BundleInstaller::StorePermissions将包权限以Json格式的字符串存储在/storage/app/etc/permissions/{bundleName}路径下
(9)BundleInstaller::UpdateBundleInfo-> ManagerService::AddBundleInfo保存包的BundleInfo数据到bundlemap中,后面包查询会根据bundleName从bundlemap查询对应的包信息
补充说明,在上述步骤中Bundle_ms再通过 BundleDaemonClient 进行IPC操作,远程调用到 bundle_daemon,包的解压就是在bundle_daemon中完成的,下图简要描述了调用流程:
Ø 案例二:包信息查询
包信息查询用于获取HAP包的参数信息,参数信息如下表:
Variable Name | Description |
---|---|
isKeepAlive | 查询包是否是活跃状态 |
isNativeApp | 是否是本地应用,本地应用程序是指在系统中使用C++开发的应用程序 |
uid | 应用安装时分配的uid |
gid | 应用程序安装时分配的应用程序组ID |
isSystemApp | 查询是否是系统应用,系统应用是无法被卸载的 |
compatibleApi | 应用开发所需的最低API版本 |
targetApi | 应用开发API版本 |
versionCode | 应用版本号,这是开发的内部版本号,对用户不可见 |
versionName | 应用版本号,对用户可见 |
bundleName | 应用程序包名,是应用的ID,对用户不可见 |
label | 应用程序包名,对用户可见 |
bigIconPath | 应用图标路径 |
codePath | 应用的安装的路径 |
dataPath | 应用本地数据保存路径 |
vendor | 应用程序的供应商名称 |
moduleInfos | 应用程序的moduleInfos信息 |
numOfModule | 应用程序中包含的ModuleInfo对象个数 |
appId | 应用程序ID,唯一标识应用程序,它是捆绑包名称和应用程序签名的组合 |
abilityInfos | 应用程序的abilityInfos信息 |
numOfAbility | 应用程序中包含的abilityInfos对象个数 |
包信息查询和包安装一样,需要创建IClientProxy和IServerProxy进行IPC通讯。首先创建GetBmsClient得到IClientProxy指针,并发送GET_BUNDLE_INFO消息。GetBundleInfo是包信息查询的接口函数。
//bundle_manager.cpp
static IClientProxy *GetBmsClient()
{
......
IUnknown *iUnknown = SAMGR_GetInstance()->GetFeatureApi(BMS_SERVICE, BMS_FEATURE);//获取BMS_SERVICE的FeatureApi,用于查询包信息的接口调用
......
}
uint8_t GetBundleInfo(const char *bundleName, int32_t flags, BundleInfo *bundleInfo)
{
......
auto bmsClient = GetBmsClient();
......
int32_t ret = bmsClient->Invoke(bmsClient, GET_BUNDLE_INFO, &ipcIo, &resultOfGetBundleInfo, Notify);//Invoke跨进程通信
......
}
g_bmsImpl是IServerProxy的声明,用来处理IClientProxy的远程接口调用,之前有提到过Invoke ID (GET_BUNDLE_INFO),这是用来映射到Server端的GetInnerBundleInfo函数。Server端获取数据后将查询结果反馈给Client端。
//bundle_ms_feature.cpp
static BmsImpl g_bmsImpl = {
SERVER_IPROXY_IMPL_BEGIN,
.Invoke = BundleMsFeature::Invoke,
……
.GetBundleInfo = BundleMsFeature::GetBundleInfo,
……
IPROXY_END
};
BundleInvokeType BundleMsFeature::BundleMsInvokeFuc[BMS_INNER_BEGIN] {
QueryInnerAbilityInfo,
GetInnerBundleInfo,
ChangeInnerCallbackServiceId,
GetInnerBundleNameForUid,
HandleGetBundleInfos,
};
uint8_t BundleMsFeature::GetInnerBundleInfo(const uint8_t funcId, IpcIo *req, IpcIo *reply)
{
......
uint8_t errorCode = GetBundleInfo(bundleName, IpcIoPopInt32(req), &bundleInfo);
......
IpcIoPushUint8(reply, OHOS_SUCCESS);//用户反馈包查询结果
......
}
uint8_t BundleMsFeature::GetBundleInfo(const char *bundleName, int32_t flags, BundleInfo *bundleInfo)
{
return OHOS::ManagerService::GetInstance().GetBundleInfo(bundleName, flags, *bundleInfo);//调用bundle_manager_service
}
在此包信息查询和包安装有所差异,由于当前操作并非有等待耗时,所以直接进行了函数调用,而不像包安装需要起一个异步线程等待安装结果。在GetCopyBundleInfo 方法中最终调用了BundleMap::GetBundleInfo接口来获取包信息,这个包信息就是在安装包时保存在BundleMap中的bundleInfo数据。
// bundle_manager_service.cpp
uint8_t ManagerService::GetBundleInfo(const char *bundleName, int32_t flags, BundleInfo &bundleInfo)
{
if (bundleName == nullptr || bundleMap_ == nullptr) {
return ERR_APPEXECFWK_QUERY_PARAMETER_ERROR;
}
return bundleMap_->GetBundleInfo(bundleName, flags, bundleInfo);
}
//bundle_map.cpp
uint8_t BundleMap::GetBundleInfo(const char *bundleName, int32_t flags, BundleInfo &bundleInfo) const
{
if (bundleName == nullptr) {
return ERR_APPEXECFWK_QUERY_PARAMETER_ERROR;
}
BundleInfo *specialBundleInfo = Get(bundleName);
if (specialBundleInfo == nullptr) {
return ERR_APPEXECFWK_QUERY_NO_INFOS;
}
GetCopyBundleInfo (flags, specialBundleInfo, bundleInfo);//获取包数据
return ERR_OK;
}
通过流程图总结一下调用流程: