问题1:如何将类Java语言的线程模型(内存共享)的实现方式转换成在ArkTS的线程模型下(内存隔离)的实现方式
可以利用TaskPool接口转换,大概可以分为如下五个场景:
- 场景一:主线程将独立的耗时任务放到子线程执行。代码示例:
共享内存写法:
class Task {
static run(args) {
// 做一些独立的任务
}
}
let thread = new Thread(() => {
let result = Task.run(args)
// deal with result
})
ArkTS写法:
import { taskpool } from '@kit.ArkTS';
@Concurrent
function run(args: number) {
// 做一些独立的任务
}
let task: taskpool.Task = new taskpool.Task(run, 100); // 100: test number
taskpool.execute(task).then((res) => {
// Return result
});
- 场景二:主线程将创建的类对象实例在子线程使用。代码示例:
共享内存写法:
class Material {
action(args) {
// 做一些独立的任务
}
}
let material = new Material()
let thread = new Thread(() => {
let result = material.action(args)
// deal with result
})
ArkTS写法:
import { taskpool } from '@kit.ArkTS';
@Concurrent
function runner(material: Material): void {
return material.action(100); // 100: test number
}
@Sendable
class Material {
action(args: number) {
// 做一些独立的任务
}
}
let material = new Material()
taskpool.execute(runner, material).then((ret) => {
// 返回结果
})
- 场景三:子线程主动更新主线程状态。代码示例:
共享内存写法:
class Task {
run(args) {
// deal with result
runOnUiThread(() => {
UpdateUI(result)
})
}
}
let task = new Task()
let thread = new Thread(() => {
let result = task.run(args)
// 处理结果
})
ArkTS写法:
import taskpool from '@ohos.taskpool'
@Concurrent
function runner(task) {
task.run()
}
@Sendable
class Task {
run(args) {
// 做一些独立的任务
taskpool.Task.sendData(result)
}
}
let task = new Task()
let run = new taskpool.Task(runner, task)
run.onReceiveData((result) => {
UpdateUI(result)
})
taskpool.execute(run).then((ret) => {
// 返回结果
})
- 场景四:子线程同步调用主线程的接口。代码示例:
class SdkU3d {
static getInst() {
return SdkMgr.getInst();
}
getPropStr(str: string) {
return xx;
}
}
let thread = new Thread(() => {
// 游戏线程
let sdk = SdkU3d.getInst()
let ret = sdk.getPropStr("xx")
})
ArkTS写法:
import { MessageEvents, taskpool, worker } from '@kit.ArkTS';
class SdkU3d {
static getInst(): Object {
return SdkMgr.getInst();
}
getPropStr(str: string) { }
}
let workerInstance = new worker.ThreadWorker("xx/worker.ts");
workerInstance.registerGlobalCallObject("instance_xx", SdkU3d.getInst());
workerInstance.postMessage("start");
// 游戏worker线程
const mainPort = worker.workerPort;
mainPort.onmessage = (e: MessageEvents): void => {
let ret = mainPort.callGlobalCallObjectMethod("instance_xx", "getPropStr", 100); // 100:test number
}
问题2:ArkTS中this的常用场景及使用
在ArkTS中,this是常用于类中访问对象属性及方法或者自定义组件的回调中使用getContext(this)。
- 类中使用this,this实际指向的是实例化后的实例对象。
class UserInfo {
name: string = 'xxx';
getName() {
return this.name
}
}
const user: UserInfo = new UserInfo();
- 自定义组件中使用this,一般是在回调事件中使用,此时的this是指向自定义组件自身,一般用法是采用getContext(this)的方式。
问题3:如何访问类的静态变量和方法
在ArkTS中,静态变量和方法是属于类自身的,无法通过this访问,因为this是指向类的实例化对象。如果要在类中访问静态变量和静态方法,需要使用类名访问。
// 访问静态变量或者执行静态方法
class TestStatic {
static aaa: string = '3333';
static getAAA () {
// console.log(this.aaa) 不能通过this访问静态变量,且静态变量只能在静态方法中使用
return TestStatic.aaa;
}
}
TestStatic.aaa;
问题4:如何合并两个对象
出于性能考虑,目前ArkTS限制了ES6的Object.assign()方法的使用。若需要在ets文件中扩展对象属性或合并两个对象时,可以自行实现assign方法。
- 自定义assign方法
function assign(target: Record<string, Object>, ...source: Object[]): Record<string, Object> {
for (const items of source) {
for (const key of Object.keys(items)) {
target[key] = Reflect.get(items, key)
}
}
return target;
}
2. 使用assign方法
interface IMergeSub {
testString: string,
testObject?: IMergeSub,
testArray?: Array<number>
}
interface IMerge {
a: IMergeSub,
b: IMergeSub[],
c: string[],
d: number
}
export function testAssign() {
let objectOne: IMerge =
{
a: {
testString: 'objectOne-a-testString',
testObject: {
testString: 'objectOne-a-testObject-testString'
},
testArray: [1]
},
b: [{
testString: 'objectOne-b-testString',
testObject: {
testString: 'objectOne-b-testObject-testString'
},
testArray: [2]
}],
c: ['objectOne-c'],
d: 3
}
let objectTwo: Record<string, Object> = {
'a': 'objectTwo-a',
'c': ['objectTwo-c'],
'e': 1
}
let objectThree: Record<string, Object> = {
'f': ['objectThree-f']
}
// 合并多个对象,ObjectOne和ObjectTwo的属性都将附加到ObjectThree上,属性名相同时入参下标靠后的对象属性覆盖前面的对象属性
const multiObjectMerged = assign(objectThree, objectTwo, objectOne);
console.log('multiObjectMerged is:' + JSON.stringify(multiObjectMerged));
console.log('objectThree is:' + JSON.stringify(objectThree));
// 合并ObjectOne的属性到ObjectTwo,ObjectTwo的值会改变,属性名称相同时ObjectOne会覆盖ObjectTwo的属性
const objectMerged = assign(objectTwo, objectOne);
console.log('objectTwo is:' + JSON.stringify(objectTwo));
console.log('objectMerged is:' + JSON.stringify(objectMerged));
}
问题5:如何实现类似Java中的反射方法调用能力
可以通过动态import的方式实现类似反射能力,具体实现可参考以下代码。
// Index.ets
import('./module').then(
module => {
const t = module.DataTable.tagName();
});
// module.ets
export class DataTable {
constructor() {
}
static tagName(){
return 'data-table'
}
}
问题6:是否支持模块的动态加载?如何实现
当前不支持动态加载设备侧的二进制包;可以使用动态import进行异步加载,达到类似于Class.forName()反射的效果。
示例如下,hap动态import harlibrary,并调用静态成员函数staticAdd()、实例成员函数instanceAdd(),以及全局方法addHarLibrary()。
// harlibrary的src/main/ets/utils/Calc.ets
export class Calc {
public constructor() {
}
public static staticAdd(a: number, b: number): number {
let c = a + b;
console.log("DynamicImport I'm harLibrary in staticAdd, %d + %d = %d", a, b, c);
return c;
}
public instanceAdd(a: number, b: number): number {
let c = a + b;
console.log("DynamicImport I'm harLibrary in instanseAdd, %d + %d = %d", a, b, c);
return c;
}
}
export function addHarLibrary(a: number, b: number): number {
let c = a + b;
console.log("DynamicImport I'm harLibrary in addHarLibrary, %d + %d = %d", a, b, c);
return c;
}
// harlibrary的index.ets
export { Calc, addHarLibrary } from './src/main/ets/utils/Calc';
// hap的index.ets
let harLibrary = 'harlibrary';
import(harLibrary).then((ns: ESObject) => { // 动态import变量是新增特性,入参换成字符串'harlibrary'是现有特性。也可使用await import方式。
ns.Calc.staticAdd(7, 8); // 反射调用静态成员函数staticAdd()
let calc: ESObject = new ns.Calc(); // 实例化类Calc
calc.instanceAdd(8, 9); // 调用实例成员函数instanceAdd()
ns.addHarLibrary(6, 7); // 调用全局方法addHarLibrary()
});
问题7:如何实现AOP(代码插桩)能力
目前ArkTS的util工具库中提供了Aspect的能力,可以对类方法进行前后插桩以及替换实现。
问题8:如何使用AOP接口实现重复插桩或替换
AOP提供的接口支持对方法重复插桩或替换的操作。
采用addBefore(编译前插桩)作为参考例子,重复插桩后,后插桩的代码段先执行。
import { util } from '@kit.ArkTS';
class Test {
static data: string = "initData";
static printData(): void {
console.log("execute original printData");
}
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
Test.printData();
util.Aspect.addBefore(Test, "printData", true, (classObj: Test) => {
console.log("execute before 1");
});
Test.printData();
util.Aspect.addBefore(Test, "printData", true, (classObj: Test) => {
console.log("execute before 2");
});
util.Aspect.addBefore(Test, "printData", true, (classObj: Test) => {
console.log("execute before 3");
});
Test.printData();
})
}
.width('100%')
}
.height('100%')
}
}
问题9:如何判断能否对接口进行插桩或替换
只要类和方法在运行时是实际存在的对象,并且方法的属性描述符的writable字段为true,就可以对接口进行插桩和替换。
获取方法的属性描述符的writable字段:
在创建ObjectUtil工具类,实现ObjectGetOwnPropertyDescriptor方法:
export class ObjectUtil {
static ObjectGetOwnPropertyDescriptor(o: any, p: PropertyKey): PropertyDescriptor | undefined{
return Object.getOwnPropertyDescriptor(o, p)
}
}
调用工具类获取方法的属性描述符:
import { ObjectUtil } from './ObjectUtil'
class Test {
static data: string = "initData";
static printData(): void {
console.log("execute original printData");
}
}
@Entry
@Component
export struct AOPReplaced {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
// 获取 myMethod 的属性描述符
let des = ObjectUtil.ObjectGetOwnPropertyDescriptor(Test, 'printData')
console.log('des',JSON.stringify(des))
// 判断 writable 字段是否为 true
if (des && des.writable) {
console.log('Method is writable');
} else {
console.log('Method is not writable or does not exist');
}
})
}
.width('100%')
}
.height('100%')
}
}
问题10:如何解析JSON字符串为实例对象
问题背景:
需要将JSON数据转换成ArkTS中类的实例对象,要求可以使用实例对象的属性,调用实例对象的方法,包括嵌套对象的场景。
对于这种情况需要借助三方库class-transformer和reflect-metadata实现(需通过ohpm install 安装后使用),通过@Type指定嵌套情况下的类型,并通过plainToClass转换创建对应实例对象。
完整示例如下:
import { Type, plainToClass } from 'class-transformer'
import "reflect-metadata"
// 假设为接受的Json数据
let testJSON: Record<string, ESObject> = {
'id': 1,
'firstName': "Johny",
'lastName': "Cage",
'age': 27,
'arr': [
{
'name': 'john'
},
{
'name': 'tom'
}
],
'instanceA': {
'name': 'john'
},
}
// 如果有对应嵌套结构,需要指定对应的类型
class A {
name: string = 'john';
getName(): string {
return this.name
}
}
// 当尝试转换具有嵌套对象的对象时,需要知道要转换的对象类型,使用@Type装饰器隐式地指定每个属性包含的对象类型
class User {
id: number = 0;
firstName: string = '';
lastName: string = '';
age: number = 0;
@Type(() => A)
arr: A[] = [new A()]
@Type(() => A)
instanceA: A = new A();
getName() {
return this.firstName + " " + this.lastName;
}
isAdult() {
return this.age > 36 && this.age < 60;
}
}
@Entry
@Component
struct parsingJSONStringsIntoInstanceObjects {
aboutToAppear(): void {
const instance = plainToClass(User, testJSON);
console.info('instance:' + JSON.stringify(instance))
}
build() {
}
}