作者:wuyawei,华为软件开发工程师

HarmonyOS 2提供了对两种开发语言的反对:Java和JavaScript(下文简称JS)。从事过Android开发的同学对Java都很相熟了,其多线程个性可能让多任务并行执行,充分利用硬件资源开发出高性能的利用。在HarmonyOS 2上,JS目前无奈像Java一样间接创立新的Thread,那么应用JS语言开发HarmonyOS利用是否会遇到硬件资源无奈充分利用的状况呢?

尽管应用JS语言目前无奈间接创立新的Thread,然而HarmonyOS的JS UI框架提供了多线程的宿主环境,能够帮忙利用开发丰盛的业务逻辑。在开发HarmonyOS 2利用时,开发者除了须要理解JS线程外,还须要关注哪些线程?这些线程之间的关系又是什么样的?上面让咱们一起来钻研一下。

一、HarmonyOS的JS UI框架

HarmonyOS的JS UI框架包含应用层(Application)、前端框架层(Framework)、引擎层(Engine)和平台适配层(Porting Layer),如下图所示:

• Application

应用层示意开发者应用JS UI框架开发的FA利用,这里的FA利用特指JS FA利用。https://developer.harmonyos.c...

• Framework

前端框架层次要实现前端页面解析,以及提供MVVM(Model-View-ViewModel)开发模式、页面路由机制和自定义组件等能力。

• Engine

引擎层次要提供动画解析、DOM(Document Object Model)树构建、布局计算、渲染命令构建与绘制、事件治理等能力。

• Porting Layer

适配层次要实现对平台层进行形象,提供形象接口,能够对接到零碎平台。比方:事件对接、渲染管线对接和零碎生命周期对接等。

二、JS UI框架的线程模型

每个HarmonyOS JS利用,都是通过JS UI框架进行加载渲染的。HarmonyOS的JS UI框架蕴含了JS线程、UI线程、GPU线程、IO线程这4个线程,并且在JS UI框架外还会存在一类后台任务线程。

其中GPU线程与IO线程次要是JS UI框架初始化与页面加载渲染的过程须要的,为JS UI框架外部的专有线程,不会被利用间接操作到,利用不须要特地关注;UI线程、JS线程和后台任务线程会与利用开发代码相干,前面着重剖析这三个线程的作用和关系。

UI线程:负责利用界面的绘制刷新,与利用的过程号雷同,又叫主线程。如果开发JS+JAVA的混合编程(https://developer.harmonyos.c...),须要特地留神的是JAVA PA(Particle Ability)的onStart/onConnect等Ability生命周期回调便是运行在主线程,若在这些申明周期回调上执行耗时操作则会导致JS UI的绘制刷新卡住。
JS线程:利用的JS代码会被JS引擎解析执行,并运行在JS线程上,目前咱们工程中看到的所有的JS代码都会执行在这个过程下惟一的JS线程上。
后台任务线程:这是对JS UI框架内部的后盾线程的一个统称,并不单指一个线程,也并不惟一。它蕴含了Java PA中onRemoteRequest()业务逻辑的执行线程、文件操作API、网络拜访API外部实现等相干线程。

上面咱们联合测试代码的来看一下这3个利用开发须要关注的线程之间的关系。

三、JS线程与UI线程的关系

为了验证JS线程与UI线程的关系,咱们筹备了一个试验性质的Demo,次要代码以及运行过程的Log如下:

首先咱们在IDE建设一个Empty Ability(JS)模板的HelloWorld工程,在生命周期、按钮响应回调办法里减少Log以察看线程状况。刚创立的app.js中Application生命周期默认曾经有Log,无需额定增加。

咱们须要在主动创立的MainAbility.java中onStart/onTouchEvent回调函数减少HiLog打印:

HiLog.info(LABEL_LOG, "MainAbility::onStart");

HiLog.info(LABEL_LOG, "MainAbility::onTouchEvent");

咱们只须要在主界面index.js文件中onInit减少日志:

CoffeeScript

console.info('page.default onInit');

而后在index.hml中减少一个button以及会始终进行动画的progress组件:

<button id='button1' onclick="onButtonClick">I'm a button</button>

<progress type="circular"/>

最初在index.js中减少按钮点击响应事件以及Log,并且尝试sleep阻塞js线程:

function sleep(delay) {

for (var t = Date.now(); Date.now() - t <= delay; );

}

onButtonClick() {

console.info('onButtonClick begin');

sleep(1000);

console.info('onButtonClick end');

}

咱们将利用运行起来,点击一次按钮,会发现progress组件动画并不会因为onButtonClick阻塞了1秒钟而有任何暂停,咱们一起来剖析一下这个过程中的Log:

15:30:07.323 15870-15870/com.blancwu.test I 01100/MainAbility: MainAbility::onStart

15:30:07.342 15870-18938/com.blancwu.test I 03B00/JSApp: app Log: AceApplication onCreate

15:30:07.352 15870-18938/com.blancwu.test I 03B00/JSApp: app Log: page.default onInit

15:30:31.006 15870-15870/com.blancwu.test I 01100/MainAbility: MainAbility::onTouchEvent

15:30:31.041 15870-15870/com.blancwu.test I 01100/MainAbility: MainAbility::onTouchEvent

15:30:31.104 15870-15870/com.blancwu.test I 01100/MainAbility: MainAbility::onTouchEvent

15:30:31.106 15870-15870/com.blancwu.test I 01100/MainAbility: MainAbility::onTouchEvent

15:30:31.112 15870-18938/com.blancwu.test I 03B00/JSApp: app Log: onButtonClick begin

15:30:32.113 15870-18938/com.blancwu.test I 03B00/JSApp: app Log: onButtonClick end

从输入的Log中,工夫点前面跟着的便是咱们执行日志的代码行所在的过程号与线程号,方才咱们减少的日志均在15870这个过程下,这个过程下又存在15870线程以及18938线程。其中15870与过程号雷同,这便是咱们说的UI线程;咱们在.js文件中减少的日志全都会在18938线程上打印进去,这个便是JS线程。

在利用初始化时,首先进入MainAbility.java的onStart生命周期回调,而后才进入AceApplication、Page等JS代码逻辑;利用初始化结束后,UI线程上便会继续刷新progress组件的动画,当用户点击按钮触发onButtonClick阻塞1秒时,因为阻塞的仅仅是JS线程,所以UI线程上progress组件的动画刷新并不会有任何影响,还是在继续刷新。所以咱们能够确定JS线程与UI线程的互相调用应该是通过某种音讯机制实现的,而不是阻塞式的调用。

四、JS线程与后台任务线程的关系

JS UI框架提供了JS FA(Feature Ability)调用Java PA(Particle Ability)的机制,该机制提供了一种通道来传递办法调用、解决数据返回以及订阅事件上报的机制,Java PA运行在一个独立的后台任务线程,能够撑持利用开发多线程的业务逻辑。咱们同样制作一个Demo来验证JS线程与Java PA线程的关系:

在上一个Demo根底上,咱们批改onButtonClick的JS代码,通过FeatureAbility.callAbility拉起并调用了名为一个类名为ServiceAbility的Java PA,并拿到返回后果:

var action = {};

action.bundleName = 'com.blancwu.test';

action.abilityName = 'com.blancwu.test.ServiceAbility';

action.messageCode = 1001;

action.abilityType = 0;

action.syncOption = 0;

console.info('FeatureAbility.callAbility begin' + JSON.stringify(action));

FeatureAbility.callAbility(action).then(function (value) {

console.info('FeatureAbility.callAbility async result ' + JSON.stringify(value));

})

console.info('FeatureAbility.callAbility end' + JSON.stringify(action));

在ServiceAbility的onRemoteRequest中减少Log输入,并sleep 1秒种,以便察看线程状况与之间关系:

@Override

public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) throws RemoteException {

HiLog.info(LABEL_LOG, "onRemoteRequest begin " + code);

if (code == 1001) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

Map<String, Object> result = new HashMap<String, Object>();

result.put("result", 1);

reply.writeString(ZSONObject.toZSONString(result));

}

HiLog.info(LABEL_LOG, "onRemoteRequest end " + code);

return super.onRemoteRequest(code, data, reply, option);

}

以上代码实现后,咱们进行执行,progress组件的动画同样不会被打断,失去的Log如下:

06-25 13:31:48.090 4133-5887/com.blancwu.test I 03B00/JSApp: app Log: FeatureAbility.callAbility begin{"bundleName":"com.blancwu.test","abilityName":"com.blancwu.test.ServiceAbility","messageCode":1001,"abilityType":0,"syncOption":0}

06-25 13:31:48.094 4133-5887/com.blancwu.test I 03B00/JSApp: app Log: FeatureAbility.callAbility end{"bundleName":"com.blancwu.test","abilityName":"com.blancwu.test.ServiceAbility","messageCode":1001,"abilityType":0,"syncOption":0}

06-25 13:31:48.112 4133-4133/com.blancwu.test E 01100/ServiceAbility: [8187916a4418bed, 399b373, f521b3] ServiceAbility::onStart

06-25 13:31:48.126 4133-5837/com.blancwu.test I 01100/ServiceAbility: [8187916a4418bed, 171378f, 385abb1] onRemoteRequest begin 1079135572

06-25 13:31:48.126 4133-5837/com.blancwu.test I 01100/ServiceAbility: [8187916a4418bed, 171378f, 385abb1] onRemoteRequest end 1079135572

06-25 13:31:48.126 4133-5837/com.blancwu.test I 00000/RemoteObject: [8187916a4418bed, 171378f, 385abb1] Java onRemoteRequest called

06-25 13:31:48.143 4133-5837/com.blancwu.test I 01100/ServiceAbility: onRemoteRequest begin 1001

06-25 13:31:49.145 4133-5837/com.blancwu.test I 01100/ServiceAbility: onRemoteRequest end 1001

06-25 13:31:49.145 4133-5837/com.blancwu.test I 00000/RemoteObject: Java onRemoteRequest called

06-25 13:31:49.151 4133-5887/com.blancwu.test I 03B00/JSApp: app Log: FeatureAbility.callAbility async result "{\"result\":1}"

整个执行过程能够形容如下图:

咱们察看到本次运行主过程(UI线程)号为4133,JS代码执行在JS线程(5887),Java PA响应onRemoteRequest执行在另一个后台任务线程(5837)。通过Log咱们看到onRemoteRequst即便阻塞了后台任务线程1s也不会影响JS线程的执行以及主线程(UI线程)上动画的刷新,做到了JS线程与后台任务线程上的工作并行处理。

五、JS的异步机制

下面从代码试验角度观察到了利用开发中JS线程与其余线程的关系,那么JS线程是怎么与其余线程进行异步通信的呢?咱们先来看一下传统的浏览器环境下的机制:

上图中,JS线程中的函数调用会存在于栈(stack)中,栈中的函数能够调用浏览器环境提供的WebAPIs,蕴含了DOM、ajax、timeout等API,这些API会在浏览器环境提供的另外一个内部线程执行,执行实现后会在工作队列(callback queue)中退出对应的回调事件(如onClick、onLoad、onDone)。当栈中的代码执行结束,即栈清空后,JS线程又会通过event loop取出工作队列中的下一个工作进行执行,以此类推实现整个的程序执行。更具体的机制能够去看阮一峰老师介绍JS EventLoop的文章。

JS EventLoop的介绍

http://www.ruanyifeng.com/blo...

HarmonyOS的JS UI框架同样遵循上述最根本的EventLoop调度机制,并且提供了更多的机制和API,让业务逻辑能够在内部线程执行;包含下面提到的Java PA机制以及还未提及的反对异步回调的零碎能力API。其中,反对异步回调的零碎能力API蕴含文件系统操作和网络操作等,感兴趣的同学能够依照咱们试验Demo相似的办法去尝试一下。

文件系统操作API参考

https://developer.harmonyos.c...

六、将来瞻望

目前HarmonyOS的JS利用内实现多线程的最佳形式是通过混合编程调用Java PA形式,但无奈反对纯JS利用开发多线程业务,纯JS利用目前仅可应用框架提供的异步API了,那么这些异步API能解决各种简单场景的问题吗?

JS线程加上异步API可能很好解决单个I/O阻塞的问题,然而如果遇到大量的I/O事件,比方批删除大量文件,通过for循环发动了大量异步工作,也会升高执行效率,甚至阻塞其余异步工作的执行。并且如果要应用JS语言开发计算密集型的工作,也无奈在惟一的JS线程上进行。

这时就须要一个真正的JS多线程解决机制了,尽管目前HarmonyOS 2还未反对,但将来HarmonyOS会思考布局出与HTML5相似提供反对WebWorker机制,反对开发出多线程的JS代码,提供给利用开发者更多的施展空间。