装修器背面的秘密

学习完鸿蒙开发第一课,我兴高采烈的把一个个比如写出来后,一个个绚丽多彩的ui展示在屏幕上。依靠着ArkTS 供给的装修器,比如Component等,咱们可以便利界说好各种声明式的写法。装修器!这么一个奇特的东西,背面必定是离不开编译器的“加工”,把TS本身的写法进行填充。比如Compsoe中的Composable ,其实便是离不开kotlin 编译器的背面“加工”,才得以让咱们如此便利使用。当第一次接触ArkTS的装修器概念的时分,我就猜到,这必定也是编译器的“魔法”。

下面让咱们经过反编译,破解这一层面纱。

探究动身

咱们以一个比如动身,从官方的demo开端,咱们增加一个自界说的试图

import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import errorManager from '@ohos.app.ability.errorManager';
export default class EntryAbility extends UIAbility {
  onCreate(want, launchParam) {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    this.registerErrorObserver1();
    this.registerErrorObserver2();
    throw URIError
  }
  onDestroy() {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  }
  onWindowStageCreate(windowStage: window.WindowStage) {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
    });
  }
  onWindowStageDestroy() {
    // Main window is destroyed, release UI related resources
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }
  private registerErrorObserver1() {
    errorManager.on("error", {
      onUnhandledException(error: string) {
        hilog.error(0x0000, 'hello',"registerErrorObserver1:"+error)
      }
    })
  }
  private registerErrorObserver2() {
    errorManager.on("error", {
      onUnhandledException(error: string) {
        hilog.error(0x0000, 'hello',"registerErrorObserver2: "+error)
      }
    })
  }
  onForeground() {
    // Ability has brought to foreground
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  }
  onBackground() {
    // Ability has back to background
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  }
};
@Entry
@Component
struct Index {
  @State message: string = 'Hello World'
  build() {
    Row() {
      Column() {
        RelativeContainer() {
          Text("text1")
            .fontSize(25)
            .id("text1")
            .textAlign(TextAlign.Center)
            .backgroundColor("#ccaabb")
            .alignRules({
              top:{
                anchor:"text2",
                align:VerticalAlign.Top
              }
            })
          Text("text2")
            .fontSize(25)
            .textAlign(TextAlign.Center)
            .backgroundColor("#bbccaa")
            .alignRules({
              // 以text1 为参考系,顶部对齐
              top:{
                anchor:"text1",
                align:VerticalAlign.Top
              }
            })
        }
        .width('100%')
        .height(200)
        .backgroundColor("#111111")
      }
      .width('100%')
    }
    .height('100%')
  }
}

当咱们进行编译的时分,可以在output路径下得到一个.hap后缀安装包

深化了解arkui_ace_engine - 开篇

对hap进行解包

许多小伙伴对hap包非常陌生,其实跟android apk原理是类似的,都是从zip文件的变种而来,此时咱们只需要把后缀改为zip,然后进行解压缩,咱们就能看到里边的东西。

深化了解arkui_ace_engine - 开篇

此时,咱们得到了要害的产品,便是modules.abc

方舟字节码abc

方舟字节码(ArkCompiler Bytecode)文件,是ArkCompiler的编译东西链以源代码作为输入编译生成的产品,其文件后缀名为.abc。也便是说,咱们得到的modules.abc,其实是运转在鸿蒙中的字节码(初始形状)。

也便是说,假如咱们可以对abc文件进行解析,比如像dex文件一样进行格式解析,那么咱们是可以得到一切的运转时信息的。惋惜的是,abc字节码目前资料比较少,要害的段界说还在改变,可是熟悉编译的咱们都知道。代码的text段,无论是dex或者是abc,都是应该也原型记载的。假如咱们能用一种东西直接解析abc文件的string字段,那么咱们就可以看到实在的源码

这儿只需要对16进行进行字符解析的任何东西,都是可以的,我这儿推荐010Editor,经过解析modules.abc,一起采取16进行解析后,咱们可以得到以下代码

深化了解arkui_ace_engine - 开篇

没错!这便是代码的“真面目”! 由于abc 经过ArkCompiler 解析后,其实便是TS的实在代码

深化了解arkui_ace_engine - 开篇

了解Component

当咱们用@Component润饰一个struct的时分,经过ArkCompiler编译后,其实会生成一个类,这个承继于ViewPU。这便是一切鸿蒙组件的基类,它承担着ui刷新,localstore存储更新等要害逻辑。

ViewPU界说在ArkTS framework arkui_ace_engine傍边,是Openharmony中UI承载的要害类,其要害烘托逻辑在C++中(今后会讲到)。ViewPU 界说在pu_view.ts文件中,咱们来看一下

深化了解arkui_ace_engine - 开篇

咱们本章不介绍ViewPU的具体内容,留到下一章节中,先看一下结构函数

constructor(parent: ViewPU, localStorage: LocalStorage, elmtId : number = -1) {
  super();
  结构特有id
  this.id_= elmtId == -1 ? SubscriberManager.MakeId() : elmtId;
  this.providedVars_ = parent ? new Map(parent.providedVars_)
    : new Map<string, ObservedPropertyAbstractPU<any>>();
  this.localStoragebackStore_ = undefined;
  // 设置parent的关系
  stateMgmtConsole.log(`ViewPU constructor: Creating @Component '${this.constructor.name}' from parent '${parent?.constructor.name}}'`);
  if (parent) {
    // this View is not a top-level View
    this.setCardId(parent.getCardId());
    // Call below will set this.parent_ to parent as well
    parent.addChild(this);
  } else if (localStorage) {
    this.localStorage_ = localStorage;
    stateMgmtConsole.debug(`${this.debugInfo()}: constructor: Using LocalStorage instance provided via @Entry.`);
  }
  增加订阅
  SubscriberManager.Add(this);
  stateMgmtConsole.debug(`${this.debugInfo()}: constructor: done`);
}

ViewPU的概念其实很简略,它负责调用SubscriberManager增加自身,后边state的回调会进行callback,一起它也有父子组件的概念,ViewPU有一个parent属性,代表当前的父组件,父子双方共用同一个localStorage,其实便是经过结构函数保证的。

一起结构进程,每一个ViewPU会结构一个特有的id,用作组件的区分,后续刷新逻辑会用到。咱们开胃菜就先到这儿完毕。

完毕

到这儿,本章先简略介绍,如何经过反编译检查abc字节码的内容,经过反编译后的类,咱们可以窥视鸿蒙运转进程的真相。学习源码可以帮开发者快速了解体系背面的运转逻辑,也便利咱们后续定制修改。当然,在不影响Android内容输出的一起,我后续将会发布更多ArkTS的内容,让咱们了解鸿蒙运转的机制,bye~