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

简介

ArkUI-X框架支持UI与逻辑解耦,实现不带UI的业务逻辑跨平台。开发者在Hap的开发过程中可以不实现UI界面,仅关心业务逻辑的实现。
应用UI使用Android侧能力实现,通过平台桥接能力调用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:内存结果受多种因素影响,以上数值仅供参考,内存结果使用Android adb调试命令得出。

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

综上所述:判断以下条件

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

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

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

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

开发流程

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

(一)通过loadModule加载Hap

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

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

    当前StageApplication初始化有继承StageApplication和创建StageApplicationDelegate实例2种方式。对应是否加载UI部分的方法如下。

    推荐使用方式:继承StageApplication

    • 其一:使用StageApplication作为Android应用的入口基类。

      通过Override onShouldLoadUI接口,控制函数返回值来决定ArkUI-X框架是否加载UI部分。

      // MyStageApplication.java
      
      import ohos.stage.ability.adapter.StageApplication;
      
      public class MyStageApplication extends StageApplication {
          @Override
          public boolean onShouldLoadUI() {
              // 函数返回值false,表示不加载UI部分。
              return false;
          }
      }
      
    • 其二:使用android.app.Application作为Android应用的入口基类。

      初始化StageApplicationDelegate对象,并调用initApplication初始化ArkUI-X框架,通过第二个函数入参决定ArkUI-X框架是否加载UI部分。

      // MyApplication.java
      
      import android.app.Application;
      import ohos.stage.ability.adapter.StageApplicationDelegate;
      
      public class MyApplication extends Application {
          @Override
          public void onCreate() {
              StageApplicationDelegate stageApplicationDelegate = new StageApplicationDelegate();
              // 第二个函数入参为false,表示不加载UI部分。
              stageApplicationDelegate.initApplication(this, false);
              super.onCreate();
          }
      }
      
  2. Android侧:调用loadModule接口加载 HAP 包

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

    // NativeActivity.java
    import android.app.Activity;
    import android.os.Bundle;
    import ohos.stage.ability.adapter.StageApplicationDelegate;
    
    public class NativeActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            StageApplicationDelegate.loadModule("entry", "./ets/MyModuleLoader.ets");
        }
    }
    
  3. ArkTS侧:继承ModuleLoader,重载onLoad()方法,实现初始化逻辑。

    实现ModuleLoader接口。当Android侧调用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. Android侧:Android通过平台桥接能力调用 ArkTS Hap中业务逻辑

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

    // NativeActivity.java
    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    
    import ohos.stage.ability.adapter.StageApplicationDelegate;
    
    public class NativeActivity extends Activity {
        private static final String TAG = "LOG";
    
        private static final String LOG_TAG = "[Test][JAVA][NativeActivity]:: ";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            Log.e(TAG, LOG_TAG + "onCreate");
            super.onCreate(savedInstanceState);
            setContentView(R.layout.native_page);
    
            findViewById(R.id.BTN_LoadHap).setOnClickListener(v -> {
                // 添加按钮点击事件,在点击按钮后,触发加载Hap:entry。
                StageApplicationDelegate.loadModule("entry", "./ets/MyModuleLoader.ets");
            });
    
            findViewById(R.id.BTN_CallMethod).setOnClickListener(v -> {
                // 当触发加载Hap后,通过点击页面的Button,通过平台桥接调用ArkTS侧方法,并拿到返回数据,通过日志形式输出。
                try {
                    BridgeUtil object = BridgeUtil.getInstance();
                    if (object == null) {
                        Log.e(TAG, LOG_TAG + "BridgeUtil object is null");
                        return;
                    }
                    Object data = object.callMethodSync("getDeviceInfo");
                    Log.i(TAG, LOG_TAG + "DeviceInfo is " + data);
                } catch (Exception e) {
                    Log.e(TAG, LOG_TAG + "callMethodSync failed, error is :", e);
                }
            });
        }
    }
    

(二)通过loadAbility加载Hap

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

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

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

    当前StageApplication初始化有继承StageApplication和创建StageApplicationDelegate实例2种方式。对应是否加载UI部分的方法如下。

    推荐使用方式:继承StageApplication

    • 其一:使用StageApplication作为Android应用的入口基类。

      通过Override onShouldLoadUI接口,控制函数返回值来决定ArkUI-X框架是否加载UI部分。

      // MyStageApplication.java
      
      import ohos.stage.ability.adapter.StageApplication;
      
      public class MyStageApplication extends StageApplication {
          @Override
          public boolean onShouldLoadUI() {
              // 函数返回值false,表示不加载UI部分。
              return false;
          }
      }
      
    • 其二:使用android.app.Application作为Android应用的入口基类。

      初始化StageApplicationDelegate对象,并调用initApplication初始化ArkUI-X框架,通过第二个函数入参决定ArkUI-X框架是否加载UI部分。

      // MyApplication.java
      
      import android.app.Application;
      import ohos.stage.ability.adapter.StageApplicationDelegate;
      
      public class MyApplication extends Application {
          @Override
          public void onCreate() {
              StageApplicationDelegate stageApplicationDelegate = new StageApplicationDelegate();
              // 第二个函数入参为false,表示不加载UI部分。
              stageApplicationDelegate.initApplication(this, false);
              super.onCreate();
          }
      }
      
  2. Android侧:通过loadAbility控制加载UIAbility

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

    // NativeActivity.java
    
    import android.app.Activity;
    import android.os.Bundle;
    import ohos.stage.ability.adapter.AbilityLoader;
    
    public class NativeActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            AbilityLoader.loadAbility("com.example.hspui", "entry", "EntryAbility", "");
        }
    }
    
  3. ArkTS侧:在UIAbility组件onCreate()中实现初始化逻辑。

    当Android侧调用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. Android侧:Android通过平台桥接能力调用 ArkTS Hap中业务逻辑

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

    // NativeActivity.java
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    import ohos.stage.ability.adapter.AbilityLoader;
    
    public class NativeActivity extends Activity {
        private static final String TAG = "LOG";
    
        private static final String LOG_TAG = "[Test][JAVA][NativeActivity]:: ";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            Log.e(TAG, LOG_TAG + "onCreate");
            super.onCreate(savedInstanceState);
            setContentView(R.layout.native_page);
    
            findViewById(R.id.BTN_LoadHap).setOnClickListener(v -> {
                // 添加按钮点击事件,在点击按钮后,触发加载Hap:entry。
                AbilityLoader.loadAbility("com.example.hspui", "entry", "EntryAbility", "");
            });
    
            findViewById(R.id.BTN_CallMethod).setOnClickListener(v -> {
                // 当触发加载Hap后,通过点击页面的Button,通过平台桥接调用ArkTS侧方法,并拿到返回数据,通过日志形式输出。
                try {
                    BridgeUtil object = BridgeUtil.getInstance();
                    if (object == null) {
                        Log.e(TAG, LOG_TAG + "BridgeUtil object is null");
                        return;
                    }
                    Object data = object.callMethodSync("getDeviceInfo");
                    Log.i(TAG, LOG_TAG + "DeviceInfo is " + data);
                } catch (Exception e) {
                    Log.e(TAG, LOG_TAG + "callMethodSync failed, error is :", e);
                }
            });
        }
    }
    

约束与限制

  1. Android侧:Activity需要使用Android原生提供的android.app.Activity,禁止使用ArkUI-X框架提供的ohos.stage.ability.adapter.StageActivity

  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. Android侧: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示例,供开发者参考。