以Hap为主体的共享逻辑包开发指南

简介

ArkUI-X框架支持UI与逻辑解耦,实现不带UI的业务逻辑跨平台。开发者在Hap的开发过程中可以不实现UI界面,仅关心业务逻辑的实现。
应用UI使用iOS侧能力实现,通过平台桥接能力调用ArkTS侧Hap包的业务逻辑。从而实现应用UI与业务逻辑解耦的目的。
应注意:本套业务流程仅支持Android、iOS,不支持HarmonyOS。

方案对比和适用场景分析

默认方式加载Hap 通过loadModule加载Hap 通过loadAbility加载Hap
UIAbility 创建UIAbility组件
根据UIAbility规则会触发所有生命周期函数
不创建UIAbility
不会触发UIAbility的任何生命周期函数
创建UIAbility组件
调用loadAbility时仅会触发UIAbility的onCreate()
调用unloadAbility时仅会触发UIAbility的onWindowStageDestroy()和onDestroy()
UI界面 可使用ArkTS侧与UI相关的组件和API 不可使用ArkTS侧与UI相关的组件和API 不可使用ArkTS侧与UI相关的组件和API
API限制 全量API均可使用,无限制 涉及下列内容的API将无法使用
1.涉及使用UIAbility组件的上下文信息(context),比如@ohos.data.preferences
2.涉及UI组件相关的API
涉及下列内容的API将无法使用
1.涉及UI组件相关的API
特殊配置项 详见ModuleLoader
重复性 已加载的UIAbility通过startAbility加载本身时,会创建新的UIAbility,即可重复创建 重复调用loadModule时,每一次调用都会触发onLoad()回调 重复调用loadAbility加载相同的UIAbility时,仅第一次加载时会触发onCreate(),后续接口调用将不会生效,即不可重复创建
unloadAbility同理
内存

注1:内存结果受多种因素影响,以上数值仅供参考,内存结果使用Xcode得出。

注2:"API限制"中API指OpenHarmony接口定义跨平台支持列表

综上所述:判断以下条件

  1. 应用使用iOS系统能力实现UI界面。
  2. ArkTS生成的Hap逻辑中,只涉及使用API接口,不涉及使用UIAbility组件、UI组件等内容。
  3. ArkTS侧Hap中的自定义函数逻辑可通过平台桥接功能在iOS侧调用到。
  4. ArkTS侧使用的API接口是否需要用到UIAbility组件的上下文信息。

当满足上述所有条件且第4点为时;可采用方案:通过loadModule加载Hap。

当满足上述所有条件且第4点为时;可采用方案:通过loadAbility加载Hap。

上述条件任一不满足时;可采用方案:默认方式加载Hap。

开发流程

  • 开发前请务必仔细阅览本文档的约束与限制部分。
  • 每一步骤前声明该步骤的所属平台侧。

(一)通过loadModule加载Hap

  1. iOS侧:控制ArkUI-X框架是否加载UI部分

    应用UI使用iOS侧能力实现,基于减小应用运行内存的目的,可由开发者控制ArkUI-X框架是否加载UI部分。

    • 控制ArkUI-X框架加载UI部分,通过使用接口launchApplication实现。

      // .arkui-x/ios/app/AppDelegate.m
      - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
      {
          [StageApplication configModuleWithBundleDirectory:BUNDLE_DIRECTORY];
          // 使用接口launchApplication,控制加载UI部分
          [StageApplication launchApplication];
          NativeViewController* mainView = [[NativeViewController alloc] init];
          [self setNavRootVC:mainView];
          return YES;
      }
      
    • 控制ArkUI-X框架不加载UI部分,通过使用接口launchApplicationWithoutUI实现。

      // .arkui-x/ios/app/AppDelegate.m
      - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
      {
          [StageApplication configModuleWithBundleDirectory:BUNDLE_DIRECTORY];
          // 使用接口launchApplicationWithoutUI,控制不加载UI部分
          [StageApplication launchApplicationWithoutUI];
          NativeViewController* mainView = [[NativeViewController alloc] init];
          [self setNavRootVC:mainView];
          return YES;
      }
      
  2. iOS侧:调用loadModule接口加载HAP包

    在合适的时机(如应用启动时)调用loadModule接口加载目标 HAP 包。具体调用时机应根据实际业务场景调整。

    // .arkui-x/ios/app/AppDelegate.m
    - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
    {
        [StageApplication configModuleWithBundleDirectory:BUNDLE_DIRECTORY];
        [StageApplication launchApplicationWithoutUI];
        // 调用loadModule接口加载HAP包
        [StageApplication loadModule:@"entry" entryFile:@"./ets/MyModuleLoader.ets"];
        NativeViewController* mainView = [[NativeViewController alloc] init];
        [self setNavRootVC:mainView];
        return YES;
    }
    
  3. ArkTS侧:继承ModuleLoader,重载onLoad()方法,实现初始化逻辑。

    实现ModuleLoader接口。当iOS侧调用loadModule触发加载Hap并完成后,会触发onLoad回调。

    示例:

    // MyModuleLoader.ets
    // 自定义class MyModuleLoader 继承ModuleLoader,覆写onLoad方法,实现业务逻辑。
    // 示例代码中实现了初始化ArkTS侧Bridge对象。
    
    import ModuleLoader from '@arkui-x.ModuleLoader'
    import Bridge from '@arkui-x.bridge'
    
    export default class MyModuleLoader extends ModuleLoader {
      onLoad(): void {
        console.info('MyModuleLoader onLoad start')
        Bridge.createBridge('BridgeObject', Bridge.BridgeType.JSON_TYPE)
        console.info('MyModuleLoader onLoad end')
      }
    }
    
  4. ArkTS侧:在 hap模块对应build-profile.json5中配置模块生命周期实现ets文件(示例为MyModuleLoader.ets)的路径,使文件参与编译。

    需要配置的信息如下,其中路径信息需要开发者修改为实际的路径信息。

    "arkOptions": {
        "runtimeOnly": {
            "sources": [
                "./src/main/ets/MyModuleLoader.ets"		// 继承ModuleLoader的实例class所在文件的相对路径
            ]
        }
    }
    

    配置完毕后,整体的build-profile.json5文件范例如下。

    // ${工程}/entry/build-profile.json5
    // 配置" MyModuleLoader.ets "的信息
    
    {
      "apiType": "stageMode",
      "buildOption": {
        "resOptions": {
          "copyCodeResource": {
            "enable": false
          }
        },
        "arkOptions": {
          "runtimeOnly": {
            "sources": [
              "./src/main/ets/MyModuleLoader.ets"
            ]
          }
        }
      },
      "buildOptionSet": [
        {
          "name": "release",
          "arkOptions": {
            "obfuscation": {
              "ruleOptions": {
                "enable": false,
                "files": [
                  "./obfuscation-rules.txt"
                ]
              }
            }
          }
        }
      ],
      "targets": [
        {
          "name": "default"
        }
      ]
    }
    
  5. iOS侧:iOS通过平台桥接能力调用 ArkTS Hap中业务逻辑

    通过平台桥接机制实现iOS侧调用 HAP 包内的 ArkTS 业务逻辑,完成跨语言/跨环境的调用与数据交互。

    // NativeViewController.m
    - (void)BTN_CallMethod:(UIButton*)sender
    {
        @try {
            id data = [self.bridgeObject callMethodSync:@"getDeviceInfo" parameters:nil];
            if (data != nil && [data isKindOfClass:[NSString class]]) {
                NSString* resultString = (NSString*)data;
                NSLog(@"[Test][iOS][NativeViewController]:: callMethodSync success");
            }
        } @catch (NSException* exception) {
            NSLog(@"[Test][iOS][NativeViewController]:: callMethodSync failed, error is : %@", exception);
        }
    }
    

(二)通过loadAbility加载Hap

ArkTS侧部分API在调用时需要用到UIAbility组件的上下文信息,而通过loadModule接口加载hap时,框架仅初始化了运行时,加载了Hap字节码,不会触发UIAbility的创建流程,因此此时的应用环境中UIAbility组件的上下文信息为空,导致这部分API将无法使用。为了解决此问题,提供第二种方案:通过AbilityLoader接口loadAbility加载Hap,此方案会触发UIAbility的创建流程。由此开发者可顺利使用此前限制的API。

  1. iOS侧:控制ArkUI-X框架是否加载UI部分

    应用UI使用iOS侧能力实现,基于减小应用运行内存的目的,可由开发者控制ArkUI-X框架是否加载UI部分。

    • 控制ArkUI-X框架加载UI部分,通过使用接口launchApplication实现。

      // .arkui-x/ios/app/AppDelegate.m
      - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
      {
          [StageApplication configModuleWithBundleDirectory:BUNDLE_DIRECTORY];
          // 使用接口launchApplication,控制加载UI部分
          [StageApplication launchApplication];
          NativeViewController* mainView = [[NativeViewController alloc] init];
          [self setNavRootVC:mainView];
          return YES;
      }
      
    • 控制ArkUI-X框架不加载UI部分,通过使用接口launchApplicationWithoutUI实现。

      // .arkui-x/ios/app/AppDelegate.m
      - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
      {
          [StageApplication configModuleWithBundleDirectory:BUNDLE_DIRECTORY];
          // 使用接口launchApplicationWithoutUI,控制不加载UI部分
          [StageApplication launchApplicationWithoutUI];
          NativeViewController* mainView = [[NativeViewController alloc] init];
          [self setNavRootVC:mainView];
          return YES;
      }
      
  2. iOS侧:通过loadAbility控制加载UIAbility

    在合适的时机(如应用启动时)调用loadAbility接口加载目标 HAP 包。具体调用时机应根据实际业务场景调整。

    // .arkui-x/ios/app/AppDelegate.m
    - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
    {
        [StageApplication configModuleWithBundleDirectory:BUNDLE_DIRECTORY];
        [StageApplication launchApplicationWithoutUI];
        // 调用loadAbility接口加载HAP包
        [AbilityLoader loadAbility:@"com.example.hspui" moduleName:@"entry" abilityName:@"EntryAbility" params:@""];
        NativeViewController* mainView = [[NativeViewController alloc] init];
        [self setNavRootVC:mainView];
        return YES;
    }
    
  3. ArkTS侧:在UIAbility组件onCreate()中实现初始化逻辑。

    当iOS侧调用loadAbility触发加载Hap后并完成后,会触发UIAbility的onCreate()回调。

    示例:

    // ets/entryability/EntryAbility.ets
    
    import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
    import Bridge from '@arkui-x.bridge'
    
    const LOG_TAG: string = '[Test][ArkTS][entry:EntryAbility]:: '
    
    export default class EntryAbility extends UIAbility {
      onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
        console.info(LOG_TAG + 'Ability onCreate');
        Bridge.createBridge('BridgeObject', Bridge.BridgeType.JSON_TYPE)
      }
    }
    
  4. iOS侧:iOS通过平台桥接能力调用 ArkTS Hap中业务逻辑

    通过平台桥接机制实现iOS侧调用 HAP 包内的 ArkTS 业务逻辑,完成跨语言/跨环境的调用与数据交互。

    // NativeViewController.m
    - (void)BTN_CallMethod:(UIButton*)sender
    {
        @try {
            id data = [self.bridgeObject callMethodSync:@"getDeviceInfo" parameters:nil];
            if (data != nil && [data isKindOfClass:[NSString class]]) {
                NSString* resultString = (NSString*)data;
                NSLog(@"[Test][iOS][NativeViewController]:: callMethodSync success");
            }
        } @catch (NSException* exception) {
            NSLog(@"[Test][iOS][NativeViewController]:: callMethodSync failed, error is : %@", exception);
        }
    }
    

约束与限制

  1. iOS侧:应用视图控制器实现需要使用iOS原生提供的UIViewController,禁止使用ArkUI-X框架提供的 <libarkui_ios/StageViewController.h>

  2. ArkTS侧:如使用接口loadModule,需要开发者在ArkTS侧继承ModuleLoader后实现自定义的实例类 "MyModuleLoader"(自定义实例class的类名可以由开发者自行定义,这里方便文档描述使用MyModuleLoader作为自定义实例class类名进行描述)。

    • MyModuleLoader代码所在的实际文件(MyModuleLoader.ets):文件后缀必须为"ets",禁止使用其它后缀名
    • MyModuleLoader代码所在的实际文件(MyModuleLoader.ets):文件必须位于工程 ets目录下(即 {应用工程}/{Hap模块}/src/main/ets)。
      允许存放在 ets目录的任意子目录中(例如 ets/subdir/MyModuleLoader.etsets/utils/MyModuleLoader.ets等均符合要求)。
      禁止存放在 ets目录之外
  3. iOS侧:loadModule接口的entryFile参数,该参数是ArkTS侧MyModuleLoader.ets的相对路径(相对于工程中ets目录)。

    • 要求入参的字符串字段必须以"./ets"作为字段开头。后面跟随ArkTS侧MyModuleLoader.ets的相对路径。

      举例:

      • MyModuleLoader.ets的路径如下: {应用工程}/{Hap模块}/src/main/ets/MyModuleLoader.ets ,则entryFile参数为./ets/MyModuleLoader.ets
      • MyModuleLoader.ets的路径如下: {应用工程}/{Hap模块}/src/main/ets/utils/MyModuleLoader/MyModuleLoader.ets ,则entryFile参数为./ets/utils/MyModuleLoader/MyModuleLoader.ets
  4. loadModule接口、loadAbility接口目前仅支持加载Hap包,不支持加载Hsp包、Har包。

实践参考

提供完整的Sample示例,供开发者参考。