当前位置: 首页 > 科技观察

HarmonyOSJS应用开发需要注意哪些线程?官方解析在此_0

时间:2023-03-18 12:54:13 科技观察

更多信息请访问:HarmonyOS2提供了Java和JavaScript(以下简称JS)两种开发语言的支持。从事过Android开发的同学对Java都非常熟悉。其多线程特性允许多个任务并行执行,充分利用硬件资源开发高性能应用程序。在HarmonyOS2上,JS目前还不能像Java那样直接创建新的Threads,那么使用JS语言开发HarmonyOS应用会不会遇到硬件资源不能得到充分利用的情况呢?虽然目前JS语言还不能直接创建新的Threads,但是HarmonyOS的JSUI框架提供了一个多线程的宿主环境,可以帮助应用开发丰富的业务逻辑。在开发HarmonyOS2应用时,除了JS线程,开发者还需要关注哪些线程?这些线程之间是什么关系?我们一起学习吧。一、HarmonyOS的JSUI框架HarmonyOS的JSUI框架包括应用层(Application)、前端框架层(Framework)、引擎层(Engine)和平台适配层(PortingLayer),如下图所示:Application应用层表示开发者使用JSUI框架开发的FA应用,这里的FA应用特指JSFA应用。https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-js-fa-developing-0000001063300612Framework前端框架层主要完成前端页面解析,提供MVVM(ModelViewViewModel)开发模式和页面路由机制以及自定义组件。Engine层主要提供动画解析、DOM(DocumentObjectModel)树构建、布局计算、渲染命令构建与绘制、事件管理等能力。PortingLayer适配层主要完成平台层的抽象,提供抽象接口,可以对接系统平台。例如:事件对接、渲染管线对接、系统生命周期对接等。2.JSUI框架的线程模型HarmonyOSJS应用都是通过JSUI框架来加载和渲染的。HarmonyOS的JSUI框架包括4个线程:JS线程、UI线程、GPU线程、IO线程,在JSUI框架之外还会有一类后台任务线程。其中,GPU线程和IO线程主要用于JSUI框架初始化和页面加载渲染的过程。它们是JSUI框架内部的专有线程,不会被应用程序直接操作。应用不需要特别注意;UI线程、JS线程和后台任务线程会和应用开发代码相关,后面会分析这三个线程的作用和关系。UI线程:负责应用程序界面的绘制和刷新,与应用程序的进程号相同,也叫主线程。如果开发JS+JAVA混合编程(https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-apis-fa-calls-pa-mechanism-0000001050022401),需要特别注意JAVAAbility生命周期回调如PA(ParticleAbility)的onStart/onConnect等运行在主线程。如果对这些生命周期回调进行耗时操作,会导致JSUI的绘制刷新卡住。JS线程:应用的JS代码会被JS引擎解析执行,运行在JS线程上。目前我们项目中看到的所有JS代码都会在这个进程下唯一的JS线程上执行。后台任务线程:这是JSUI框架之外的后台线程的总称,不是一个线程,也不是唯一一个。包括JavaPA中onRemoteRequest()业务逻辑的执行线程、文件操作API、网络访问API的内部实现等相关线程。下面结合测试代码来看一下应用开发需要注意的三个线程之间的关系。3.JS线程和UI线程的关系为了验证JS线程和UI线程的关系,我们准备了一个实验Demo,主要代码和运行过程的日志如下:首先,我们创建一个EmptyIDEHelloWorld工程中的Ability(JS)模板,在生命周期和按钮响应回调方法中添加Log,观察线程情况。新建的app.js中的Applicationlifecycle默认已经有Log了,不用再添加。我们需要在自动创建的MainAbility.java中的onStart/onTouchEvent回调函数中添加HiLog打印:我们只需要在主界面index.js文件的onInit中添加一条日志:console.info('page.defaultonInit');然后在index.hml中添加一个按钮和一个始终动画的进度组件:I'mabutton按钮点击响应事件并登录index.js,尝试休眠阻塞js线程:functionsleep(delay){for(vart=Date.now();Date.now()-t<=delay;);}onButtonClick(){console.info('onButtonClickbegin');sleep(1000);console.info('onButtonClickend');}我们将应用程序运行起来,点击一次按钮,你会发现progress组件不会因为onButtonClick被阻塞1秒而暂停,我们分析一下这个过程中的Log:15:30:07.32315870-15870/com.blancwu.testI01100/MainAbility:MainAbility::onStart15:30:07.34215870-18938/com。blancwu.testI03B00/JSApp:appLog:AceApplicationonCreate15:30:07.35215870-18938/com.blancwu.testI03B00/JSApp:appLog:page.defaultonCreate15:30:07.35215870-18938/com.blancwu.testI03B00/JSApp:appLog:page.defaultonCreate:306初始化351个5870/com.blancwu.testI01100/MainAbility:MainAbility::onTouchEvent15:30:31.04115870-15870/com.blancwu.testI01100/MainAbility:MainAbility::onTouchEvent15:30:31.10415870-15870/com.blancwu.testMainAinAbility:0:onTouchEvent15:30:31.10615870-15870/com.blancwu.testI01100/MainAbility:MainAbility::onTouchEvent15:30:31.11215870-18938/com.blancwu.testI03B00/JSApp:appLog:onButtonClickbegin15:30:38.1188-comblanc15:30:38.118970/comblanctestI03B00/JSApp:appLog:onButtonClickend从输出Log来看,时间点后面是我们执行log的代码行的进程号和线程号。我们刚才添加的日志都是15870进程下的,这个进程下有15870个线程和18938个线程,其中15870和进程号一样,也就是我们所说的UI线程;我们在.js文件中添加的所有日志都会打印在18938线程上,也就是JS线程。应用初始化时,首先进入MainAbility.java的onStart生命周期回调,然后进入AceApplication、Page等JS代码逻辑;应用初始化后,progress组件的动画会在UI线程上不断刷新,而当用户点击按钮触发onButtonClick时会阻塞1秒,因为只有JS线程被阻塞,所以动画刷新UI线程上的progress组件不会有任何影响,会继续刷新。所以我们可以确定JS线程和UI线程之间的相互调用应该是通过某种消息机制来完成的,而不是阻塞调用。4、JS线程与后台任务线程的关系JSUI框架提供了JSFA(FeatureAbility)调用JavaPA(ParticleAbility)的机制,提供了传递方法调用、处理数据返回、订阅的通道基于事件报告机制,JavaPA运行在独立的后台任务线程上,可以支持应用程序开发中的多线程业务逻辑。我们还做了一个Demo来验证JS线程和JavaPA线程的关系:在之前Demo的基础上,我们修改了onButtonClick的JS代码,通过FeatureAbility.callAbility拉起调用了一个名为ServiceAbility的JavaPA,并得到返回结果:varaction={};action.bundleName='com.blancwu.test';action.abilityName='com.blancwu.test.ServiceAbility';action.messageCode=1001;action.abilityType=0;action.syncOption=0;console.info('FeatureAbility.callAbilitybegin'+JSON.stringify(action));FeatureAbility.callAbility(action).then(function(value){console.info('FeatureAbility.callAbilityasyncresult'+JSON.stringify(value));})console.info('FeatureAbility.callAbilityend'+JSON.stringify(action));在ServiceAbility的onRemoteRequest中增加Log输出,休眠1秒,观察线程情况与:@OverridepublicbooleanonRemoteRequest(intcode,MessageParceldata,MessageParcelreply,MessageOptionoption)throwsRemoteException{HiLog.info(LABEL_LOG,"onRemoteRequestbegin"+code);if(code==1001){try{Thread.sleep(1000);}catch(InterruptedExceptione){printStackTrace();}Mapresult=newHashMap();result.put("result",1);reply.writeString(ZSONObject.toZSONString(result));}HiLog.info(LABEL_LOG,"onRemoteRequestend"+code);returnsuper.onRemoteRequest(code,data,reply,option);}上面的代码完成后,我们执行,进度组件的动画不会被打断,得到的Log如下:06-2513:31:48.0904133-5887/com.blancwu.testI03B00/JSApp:appLog:FeatureAbility.callAbilitybegin{"bundleName":"com.blancwu.test","abilityName":"com.blancwu.test.ServiceAbility","messageCode":1001,"abilityType":0,"syncOption":0}06-2513:31:48.0944133-5887/com.blancwu.testI03B00/JSApp:appLog:FeatureAbility.callAbilityend{"bundleName":"com.blancwu.test","abilityName":"com.blancwu.test.ServiceAbility","messageCode":1001,"abilityType":0,"syncOption":0}06-2513:31:48.1124133-4133/com.blancwu.testE01100/ServiceAbility:[8187916a4418bed,399b373,f521b3]服务Ability::onStart06-2513:31:48.1264133-5837/com.blancwu.testI01100/ServiceAbility:[8187916a4418bed,171378f,385abb1]onRemoteRequestbegin107913557206-2513:31:48.1264133-5837/com.blancwu.testI01100/ServiceAbility:[8187916a4418bed,171378f,385abb1]onRemoteRequestend107913557206-2513:31:48.1264133-5837/com.blancwu.testI00000/RemoteObject:[8187916a4418bed,171378f,385abb1]JavaonRemoteRequestcalled06-2513:31:48.1434133-5837/com.blancwu.testI01100/ServiceAbility:onRemoteRequestbegin100106-2513:31:49.1454133-5837/com.blancwu.testI01100/ServiceAbility:onRemoteRequestend100106-2513:31:49.1454133-5837/com.blancwu.testI00000/RemoteObject:JavaonRemoteRequestcalled06-2513:31:49.1514133-5887/com.blancwu.testI03B00/JSApp:appLog:FeatureAbility.callAbilityasyncresult"{\"result\":1}"整个执行过程可以描述如下:我们观察到本次运行的主进程(UI线程)数量为4133,JS代码为在JS线程中执行(5887)。JavaPA在另一个后台任务线程中响应onRemoteRequest执行(5837)通过Log可以看出,即使onRemoteRequst阻塞了后台任务线程1s,也不会影响主线程(UI线程)上JS线程的执行和动画的刷新,从而使JS线程和后台任务线程可以并行处理。5、JS的异步机制从代码实验的角度,我们观察了应用开发中JS线程与其他线程的关系,那么JS线程是如何与其他线程进行异步通信的呢?我们先来看看传统的浏览器环境。机制:上图中,JS线程中的函数调用都会存在栈中。栈中的函数可以调用浏览器环境提供的WebAPI,包括DOM、ajax、timeout等API。这些API将显示在浏览器中。执行环境提供的另一个外部线程。执行完成后,相应的回调事件(如onClick、onLoad、onDone)会被添加到任务队列(callbackqueue)中。当栈中的代码执行完毕,也就是清空栈后,JS线程会通过事件循环取出任务队列中的下一个任务执行,以此类推完成整个程序的执行。更具体的机制可以看阮一峰老师介绍的关于JSEventLoop的文章。●JSEventLoop简介http://www.ruanyifeng.com/blog/2014/10/eventloop.htmlHarmonyOS的JSUI框架也沿用了上述最基本的EventLoop调度机制,并提供了更多的机制和API来让业务逻辑可以在外线程中执行;包括上面提到的JavaPA机制和还没有提到的支持异步回调的系统能力API。其中,支持异步回调的系统能力API包括文件系统操作、网络操作等,有兴趣的同学可以通过类似我们实验demo的方式进行尝试。●文件系统操作API参考https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-apis-file-storage-00000000006294456.未来展望目前HarmonyOSJS应用实现最多的是threading最好的方式是通过混合编程的方式调用JavaPA方法,但是不能支持纯JS应用开发多线程服务。目前,纯JS应用只能使用框架提供的异步API。这些异步API能否解决各种复杂场景下的问题?JS线程加异步API可以解决单一I/O阻塞的问题,但是如果遇到大量I/O事件,比如批量删除大量文件,通过for循环,执行效率也会降低。甚至会阻塞其他异步任务的执行。而如果要用JS语言开发计算量大的任务,在唯一的JS线程上是做不到的。这时候就需要真正的JS多线程处理机制了。虽然HarmonyOS2还没有支持,但未来HarmonyOS会考虑规划类似HTML5的WebWorker机制,支持开发多线程的JS代码进行应用开发。更大的发展空间。更多信息请访问:Harmonyos.51cto.com,与华为官方合作打造的鸿蒙技术社区