作者:wuyawei,華為軟件開發(fā)工程師
HarmonyOS 2提供了對兩種開發(fā)語言的支持:Java和JavaScript(下文簡稱JS)。從事過Android開發(fā)的同學對Java都很熟悉了,其多線程特性能夠讓多任務(wù)并行執(zhí)行,充分利用硬件資源開發(fā)出高性能的應(yīng)用。在HarmonyOS 2上,JS目前無法像Java一樣直接創(chuàng)建新的Thread,那么使用JS語言開發(fā)HarmonyOS應(yīng)用是否會遇到硬件資源無法充分利用的情況呢?
雖然使用JS語言目前無法直接創(chuàng)建新的Thread,但是HarmonyOS的JS UI框架提供了多線程的宿主環(huán)境,可以幫助應(yīng)用開發(fā)豐富的業(yè)務(wù)邏輯。在開發(fā)HarmonyOS 2應(yīng)用時,開發(fā)者除了需要了解JS線程外,還需要關(guān)注哪些線程?這些線程之間的關(guān)系又是什么樣的?下面讓我們一起來研究一下。
一、HarmonyOS的JS UI框架
HarmonyOS的JS UI框架包括應(yīng)用層(Application)、前端框架層(Framework)、引擎層(Engine)和平臺適配層(Porting Layer),如下圖所示:
-
Application
應(yīng)用層表示開發(fā)者使用JS UI框架開發(fā)的FA應(yīng)用,這里的FA應(yīng)用特指JS FA應(yīng)用。https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-js-fa-developing-0000001063300612
-
Framework
前端框架層主要完成前端頁面解析,以及提供MVVM(Model-View-ViewModel)開發(fā)模式、頁面路由機制和自定義組件等能力。
-
Engine
引擎層主要提供動畫解析、DOM(Document Object Model)樹構(gòu)建、布局計算、渲染命令構(gòu)建與繪制、事件管理等能力。
-
Porting Layer
適配層主要完成對平臺層進行抽象,提供抽象接口,可以對接到系統(tǒng)平臺。比如:事件對接、渲染管線對接和系統(tǒng)生命周期對接等。
二、JS UI框架的線程模型
每個HarmonyOS JS應(yīng)用,都是通過JS UI框架進行加載渲染的。HarmonyOS的JS UI框架包含了JS線程、UI線程、GPU線程、IO線程這4個線程,并且在JS UI框架外還會存在一類后臺任務(wù)線程。
其中GPU線程與IO線程主要是JS UI框架初始化與頁面加載渲染的過程需要的,為JS UI框架內(nèi)部的專有線程,不會被應(yīng)用直接操作到,應(yīng)用不需要特別關(guān)注;UI線程、JS線程和后臺任務(wù)線程會與應(yīng)用開發(fā)代碼相關(guān),后面著重分析這三個線程的作用和關(guān)系。
-
UI線程:負責應(yīng)用界面的繪制刷新,與應(yīng)用的進程號相同,又叫主線程。如果開發(fā)JS+JAVA的混合編程(https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-apis-fa-calls-pa-mechanism-0000001050022401),需要特別注意的是JAVA PA(Particle Ability)的onStart/onConnect等Ability生命周期回調(diào)便是運行在主線程,若在這些聲明周期回調(diào)上執(zhí)行耗時操作則會導致JS UI的繪制刷新卡住。
-
JS線程:應(yīng)用的JS代碼會被JS引擎解析執(zhí)行,并運行在JS線程上,目前我們工程中看到的所有的JS代碼都會執(zhí)行在這個進程下唯一的JS線程上。
-
后臺任務(wù)線程:這是對JS UI框架外部的后臺線程的一個統(tǒng)稱,并不單指一個線程,也并不唯一。它包含了Java PA中onRemoteRequest()業(yè)務(wù)邏輯的執(zhí)行線程、文件操作API、網(wǎng)絡(luò)訪問API內(nèi)部實現(xiàn)等相關(guān)線程。
下面我們結(jié)合測試代碼的來看一下這3個應(yīng)用開發(fā)需要關(guān)注的線程之間的關(guān)系。
三、JS線程與UI線程的關(guān)系
為了驗證JS線程與UI線程的關(guān)系,我們準備了一個實驗性質(zhì)的Demo,主要代碼以及運行過程的Log如下:
首先我們在IDE建立一個Empty Ability(JS)模板的HelloWorld工程,在生命周期、按鈕響應(yīng)回調(diào)方法里增加Log以觀察線程情況。剛創(chuàng)建的app.js中Application生命周期默認已經(jīng)有Log,無需額外添加。
我們需要在自動創(chuàng)建的MainAbility.java中onStart/onTouchEvent回調(diào)函數(shù)增加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組件:
I'm a button
最后在index.js中增加按鈕點擊響應(yīng)事件以及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');
}
我們將應(yīng)用運行起來,點擊一次按鈕,會發(fā)現(xiàn)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中,時間點后面跟著的便是我們執(zhí)行日志的代碼行所在的進程號與線程號,剛才我們增加的日志均在15870這個進程下,這個進程下又存在15870線程以及18938線程。其中15870與進程號相同,這便是我們說的UI線程;我們在.js文件中增加的日志全都會在18938線程上打印出來,這個便是JS線程。
在應(yīng)用初始化時,首先進入MainAbility.java的onStart生命周期回調(diào),然后才進入AceApplication、Page等JS代碼邏輯;應(yīng)用初始化完畢后,UI線程上便會持續(xù)刷新progress組件的動畫,當用戶點擊按鈕觸發(fā)onButtonClick阻塞1秒時,因為阻塞的僅僅是JS線程,所以UI線程上progress組件的動畫刷新并不會有任何影響,還是在持續(xù)刷新。所以我們可以確定JS線程與UI線程的相互調(diào)用應(yīng)該是通過某種消息機制完成的,而不是阻塞式的調(diào)用。
四、JS線程與后臺任務(wù)線程的關(guān)系
JS UI框架提供了JS FA(Feature Ability)調(diào)用Java PA(Particle Ability)的機制,該機制提供了一種通道來傳遞方法調(diào)用、處理數(shù)據(jù)返回以及訂閱事件上報的機制,Java PA運行在一個獨立的后臺任務(wù)線程,可以支撐應(yīng)用開發(fā)多線程的業(yè)務(wù)邏輯。我們同樣制作一個Demo來驗證JS線程與Java PA線程的關(guān)系:
在上一個Demo基礎(chǔ)上,我們修改onButtonClick的JS代碼,通過FeatureAbility.callAbility拉起并調(diào)用了名為一個類名為ServiceAbility的Java PA,并拿到返回結(jié)果:
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秒種,以便觀察線程情況與之間關(guān)系:
@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 result = new HashMap();,>,>
result.put("result", 1);
reply.writeString(ZSONObject.toZSONString(result));
}
HiLog.info(LABEL_LOG, "onRemoteRequest end " + code);
return super.onRemoteRequest(code, data, reply, option);
}
以上代碼完成后,我們進行執(zhí)行,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}" |
整個執(zhí)行過程可以描述如下圖:
我們觀察到本次運行主進程(UI線程)號為4133,JS代碼執(zhí)行在JS線程(5887),Java PA響應(yīng)onRemoteRequest執(zhí)行在另一個后臺任務(wù)線程(5837)。通過Log我們看到onRemoteRequst即使阻塞了后臺任務(wù)線程1s也不會影響JS線程的執(zhí)行以及主線程(UI線程)上動畫的刷新,做到了JS線程與后臺任務(wù)線程上的任務(wù)并行處理。
五、JS的異步機制
上面從代碼實驗角度觀察到了應(yīng)用開發(fā)中JS線程與其他線程的關(guān)系,那么JS線程是怎么與其他線程進行異步通信的呢?我們先來看一下傳統(tǒng)的瀏覽器環(huán)境下的機制:
上圖中,JS線程中的函數(shù)調(diào)用會存在于棧(stack)中,棧中的函數(shù)可以調(diào)用瀏覽器環(huán)境提供的WebAPIs,包含了DOM、ajax、timeout等API,這些API會在瀏覽器環(huán)境提供的另外一個外部線程執(zhí)行,執(zhí)行完成后會在任務(wù)隊列(callback queue)中加入對應(yīng)的回調(diào)事件(如onClick、onLoad、onDone)。當棧中的代碼執(zhí)行完畢,即棧清空后,JS線程又會通過event loop取出任務(wù)隊列中的下一個任務(wù)進行執(zhí)行,以此類推完成整個的程序執(zhí)行。更具體的機制可以去看阮一峰老師介紹JS EventLoop的文章。
●JS EventLoop的介紹
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
HarmonyOS的JS UI框架同樣遵循上述最基本的EventLoop調(diào)度機制,并且提供了更多的機制和API,讓業(yè)務(wù)邏輯可以在外部線程執(zhí)行;包括上面提到的Java PA機制以及還未提及的支持異步回調(diào)的系統(tǒng)能力API。其中,支持異步回調(diào)的系統(tǒng)能力API包含文件系統(tǒng)操作和網(wǎng)絡(luò)操作等,感興趣的同學可以按照我們實驗Demo類似的方法去嘗試一下。
● 文件系統(tǒng)操作API參考
https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-apis-file-storage-0000000000629445
六、未來展望
目前HarmonyOS的JS應(yīng)用內(nèi)實現(xiàn)多線程的最佳方式是通過混合編程調(diào)用Java PA方式,但無法支持純JS應(yīng)用開發(fā)多線程業(yè)務(wù),純JS應(yīng)用目前僅可使用框架提供的異步API了,那么這些異步API能解決各種復雜場景的問題嗎?
JS線程加上異步API能夠很好解決單個I/O阻塞的問題,但是如果遇到大量的I/O事件,比如批刪除大量文件,通過for循環(huán)發(fā)起了大量異步任務(wù),也會降低執(zhí)行效率,甚至阻塞其他異步任務(wù)的執(zhí)行。并且如果要使用JS語言開發(fā)計算密集型的任務(wù),也無法在唯一的JS線程上進行。
這時就需要一個真正的JS多線程處理機制了,雖然目前HarmonyOS 2還未支持,但未來HarmonyOS會考慮規(guī)劃出與HTML5類似提供支持WebWorker機制,支持開發(fā)出多線程的JS代碼,提供給應(yīng)用開發(fā)者更多的發(fā)揮空間。
fqj
-
線程
+關(guān)注
關(guān)注
0文章
505瀏覽量
19703 -
HarmonyOS
+關(guān)注
關(guān)注
79文章
1978瀏覽量
30269
發(fā)布評論請先 登錄
相關(guān)推薦
評論