深入理解arkui_ace_engine - 开篇

装饰器背后的秘密

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

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

探索出发

我们以一个例子出发,从官方的demo开始,我们添加一个自定义的试图

typescript 复制代码
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');
  }
};
less 复制代码
@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后缀安装包

对hap进行解包

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

此时,我们得到了关键的产物,就是modules.abc

方舟字节码abc

方舟字节码(ArkCompiler Bytecode)文件,是ArkCompiler的编译工具链以源代码作为输入编译生成的产物,其文件后缀名为.abc。也就是说,我们得到的modules.abc,其实是运行在鸿蒙中的字节码(初始形态)。

也就是说,如果我们能够对abc文件进行解析,比如像dex文件一样进行格式解析,那么我们是能够得到所有的运行时信息的。遗憾的是,abc字节码目前资料比较少,关键的段定义还在变化,但是熟悉编译的我们都知道。代码的text段,无论是dex或者是abc,都是应该也原型记录的。如果我们能用一种工具直接解析abc文件的string字段,那么我们就能够看到真实的源码。

这里只需要对16进行进行字符解析的任何工具,都是可以的,我这里推荐010Editor,通过解析modules.abc,同时采取16进行解析后,我们可以得到以下代码

没错!这就是代码的"真面目"! 因为abc 通过ArkCompiler 解析后,其实就是TS的真实代码

理解Component

当我们用@Component修饰一个struct的时候,通过ArkCompiler编译后,其实会生成一个类,这个继承于ViewPU。这就是所有鸿蒙组件的基类,它承担着ui刷新,localstore存储更新等关键逻辑。

ViewPU定义在ArkTS framework arkui_ace_engine当中,是Openharmony中UI承载的关键类,其关键渲染逻辑在C++中(以后会讲到)。ViewPU 定义在pu_view.ts文件中,我们来看一下

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

typescript 复制代码
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~

相关推荐
IT女孩儿42 分钟前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡2 小时前
commitlint校验git提交信息
前端
踏雪Vernon2 小时前
[OpenHarmony5.0][Docker][环境]OpenHarmony5.0 Docker编译环境镜像下载以及使用方式
linux·docker·容器·harmonyos
虾球xz2 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇2 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒2 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐3 小时前
前端图像处理(一)
前端
程序猿阿伟3 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒3 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript