鴻蒙子系統解讀-包管理子系統篇
本文作者:江蘇潤和軟體股份有限公司 戴海
1. 架構簡介
包管理子系統,是OpenHarmony為開發者提供的安裝包管理架構,應用程式的安裝、解除安裝、更新、權限管理等一系列操作都是通過包管理器完成的。包管理子系統由如下圖子產品組成:

· BundleKit:是包管理服務對外提供的接口,有安裝/解除安裝接口、包資訊查詢接口、包狀态變化監聽接口。
· 包掃描器:用來解析本地預制或者安裝的安裝包,提取裡面的各種資訊,供管理子子產品進行管理,持久化。
· 包安裝子子產品:安裝,解除安裝,更新一個包;包安裝服務一個單獨程序的用于建立删除安裝目錄,具有較高的權限。
· 包管理子子產品:管理安裝包相關的資訊,存儲持久化包資訊。
· 包安全管理子子產品:簽名檢查、權限授予、權限管理。
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;
}
通過流程圖總結一下調用流程: