鸿蒙中ArkTS常见问题(二)

124 阅读7分钟

问题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方法。

  1. 自定义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() { 

  } 
}