Skip to content

Commit deb12d7

Browse files
committed
chore(Android): 支持NAPI版字节码;JS引擎改用单Runtime多Context模式;处理公共包抽离情况下的页面渲染回调;
1 parent 6f97df6 commit deb12d7

16 files changed

Lines changed: 390 additions & 79 deletions

File tree

android/hermes-debugger/src/main/assets/HummerDefinition_es5.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ var Hummer = {
188188
render: view => {
189189
invoke("Hummer", 0, "render", view.objID);
190190
},
191+
onRenderFinished: isSucceed => {
192+
invoke("Hummer", 0, "onRenderFinished", isSucceed);
193+
},
191194
getRootView: () => {
192195
return invoke("Hummer", 0, "getRootView");
193196
},

android/hummer-core/fetch_napi_hermes_libs.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
def fetch_dir = new File(project.buildDir, 'libs')
22
def fetch_url = project.hasProperty('Hermes_LIBS_URL') ?
33
new URL(project.getProperty('Hermes_LIBS_URL').toString()) :
4-
new URL('https://github.com/OrangeLab/N-API/releases/download/2.1.1/napi_hermes_android.tar.gz')
4+
new URL('https://github.com/OrangeLab/Hummer-Virtual-JS-Engine/releases/download/2.1.5/napi_hermes_v2.1.5.tar.gz')
55

66
def zip_file = new File(fetch_dir, fetch_url.path.split('/').last())
77

android/hummer-core/fetch_napi_qjs_libs.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
def fetch_dir = new File(project.buildDir, 'libs')
22
def fetch_url = project.hasProperty('QuickJS_LIBS_URL') ?
33
new URL(project.getProperty('QuickJS_LIBS_URL').toString()) :
4-
new URL('https://github.com/OrangeLab/N-API/releases/download/2.1.3/napi_qjs_android.tar.gz')
4+
new URL('https://github.com/OrangeLab/Hummer-Virtual-JS-Engine/releases/download/2.1.5/napi_qjs_v2.1.5.tar.gz')
55

66
def zip_file = new File(fetch_dir, fetch_url.path.split('/').last())
77

android/hummer-core/src/main/java/com/didi/hummer/core/engine/jsc/JSCContext.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,10 @@ public void evaluateJavaScriptAsync(String script, String scriptId, JSEvaluateCa
9999
jsExecutor = Executors.newSingleThreadExecutor();
100100
}
101101
jsExecutor.submit(() -> {
102-
byte[] bytecode2 = JavaScriptRuntime.compileJavaScript(JSCContext.create().context, script, scriptId);
102+
JSCContext ctx = JSCContext.create();
103+
byte[] bytecode2 = JavaScriptRuntime.compileJavaScript(ctx.context, script, scriptId);
103104
BytecodeCacheUtil.putBytecode(scriptId, bytecode2);
105+
ctx.release();
104106

105107
if (mainHandler == null) {
106108
mainHandler = new Handler(Looper.getMainLooper());

android/hummer-core/src/main/java/com/didi/hummer/core/engine/napi/NAPIContext.java

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
package com.didi.hummer.core.engine.napi;
22

3+
import android.os.Handler;
4+
import android.os.Looper;
35
import android.text.TextUtils;
46

57
import com.didi.hummer.core.engine.JSContext;
68
import com.didi.hummer.core.engine.base.IRecycler;
79
import com.didi.hummer.core.engine.napi.jni.JSEngine;
10+
import com.didi.hummer.core.util.BytecodeCacheUtil;
11+
12+
import java.util.concurrent.ExecutorService;
13+
import java.util.concurrent.Executors;
814

915
/**
1016
* Created by XiaoFeng on 2021/6/29.
1117
*/
1218
public class NAPIContext extends NAPIValue implements JSContext {
1319

20+
private ExecutorService jsExecutor;
21+
private Handler mainHandler;
22+
1423
public static NAPIContext create() {
1524
return wrapper(JSEngine.createJSContext());
1625
}
@@ -36,22 +45,76 @@ public Object evaluateJavaScript(String script, String scriptId) {
3645
if (scriptId == null) {
3746
scriptId = "";
3847
}
39-
return JSEngine.evaluateJavaScript(context, script, scriptId);
48+
Object ret;
49+
if (TextUtils.isEmpty(scriptId)) {
50+
ret = JSEngine.evaluateJavaScript(context, script, scriptId);
51+
} else {
52+
byte[] bytecode = BytecodeCacheUtil.getBytecode(scriptId);
53+
if (bytecode == null || bytecode.length <= 0) {
54+
bytecode = JSEngine.compileJavaScript(context, script, scriptId);
55+
}
56+
if (bytecode == null || bytecode.length <= 0) {
57+
ret = JSEngine.evaluateJavaScript(context, script, scriptId);
58+
} else {
59+
BytecodeCacheUtil.putBytecode(scriptId, bytecode);
60+
ret = JSEngine.evaluateBytecode(context, bytecode);
61+
}
62+
}
63+
return ret;
4064
}
4165

4266
@Override
4367
public Object evaluateJavaScriptOnly(String script, String scriptId) {
44-
return null;
68+
if (TextUtils.isEmpty(script)) {
69+
return null;
70+
}
71+
if (scriptId == null) {
72+
scriptId = "";
73+
}
74+
return JSEngine.evaluateJavaScript(context, script, scriptId);
4575
}
4676

4777
@Override
4878
public void evaluateJavaScriptAsync(String script, String scriptId, JSEvaluateCallback callback) {
79+
// 如果之前已有缓存字节码,直接主线程执行
80+
byte[] bytecode = BytecodeCacheUtil.getBytecode(scriptId);
81+
if (bytecode != null && bytecode.length > 0) {
82+
Object ret = JSEngine.evaluateBytecode(context, bytecode);
83+
if (callback != null) {
84+
callback.onJSEvaluated(ret);
85+
}
86+
return;
87+
}
4988

89+
// 如果没有缓存过,或者scriptId为空,则需要异步预编译字节码
90+
if (jsExecutor == null) {
91+
jsExecutor = Executors.newSingleThreadExecutor();
92+
}
93+
jsExecutor.submit(() -> {
94+
NAPIContext ctx = NAPIContext.create();
95+
byte[] bytecode2 = JSEngine.compileJavaScript(ctx.context, script, scriptId);
96+
BytecodeCacheUtil.putBytecode(scriptId, bytecode2);
97+
ctx.release();
98+
99+
if (mainHandler == null) {
100+
mainHandler = new Handler(Looper.getMainLooper());
101+
}
102+
mainHandler.post(() -> {
103+
Object ret = JSEngine.evaluateBytecode(context, bytecode2);
104+
105+
if (callback != null) {
106+
callback.onJSEvaluated(ret);
107+
}
108+
});
109+
});
50110
}
51111

52112
@Override
53113
public Object evaluateBytecode(byte[] bytecode) {
54-
return null;
114+
if (bytecode == null || bytecode.length <= 0) {
115+
return null;
116+
}
117+
return JSEngine.evaluateBytecode(context, bytecode);
55118
}
56119

57120
@Override
@@ -66,6 +129,12 @@ public long getIdentify() {
66129

67130
@Override
68131
public void release() {
132+
if (jsExecutor != null) {
133+
jsExecutor.shutdown();
134+
}
135+
if (mainHandler != null) {
136+
mainHandler.removeCallbacksAndMessages(null);
137+
}
69138
JSEngine.unregisterJSCallback(context);
70139
JSEngine.unregisterJSRecycler(context);
71140
JSEngine.destroyJSContext(context);

android/hummer-core/src/main/java/com/didi/hummer/core/engine/napi/jni/JSEngine.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public static void unregisterJSRecycler(long context) {
8484
public static native long createJSContext();
8585
public static native void destroyJSContext(long jsContext);
8686
public static native Object evaluateJavaScript(long jsContext, String script, String scriptId);
87+
public static native byte[] compileJavaScript(long jsContext, String script, String scriptId);
88+
public static native Object evaluateBytecode(long jsContext, byte[] bytecode);
8789
public static native void setProperty(long jsContext, long object, String key, Object value);
8890
public static native Object getProperty(long jsContext, long object, String key);
8991
public static native boolean delProperty(long jsContext, long object, String key);

android/hummer-core/src/main/java/com/didi/hummer/core/util/BytecodeCacheUtil.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ public static byte[] getBytecode(String key) {
3939
return bytecodeCache.get(key);
4040
}
4141

42+
public static void removeBytecode(String key) {
43+
if (TextUtils.isEmpty(key)) {
44+
return;
45+
}
46+
bytecodeCache.remove(key);
47+
}
48+
4249
public static boolean contains(String key) {
4350
if (TextUtils.isEmpty(key)) {
4451
return false;

android/hummer-core/src/main/jni/napi/hummer/JSEngine.cpp

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,18 @@ static NAPIValue invoke(NAPIEnv globalEnv, NAPICallbackInfo info) {
3737
return JSUtils::JavaObjectToJsValue(globalEnv, ret);
3838
}
3939

40+
// 单线程共用
41+
thread_local NAPIRuntime runtime;
42+
thread_local int ctxCount = 0;
43+
4044
extern "C"
4145
JNIEXPORT jlong JNICALL
4246
Java_com_didi_hummer_core_engine_napi_jni_JSEngine_createJSContext(JNIEnv *env, jclass clazz) {
47+
if (ctxCount == 0) {
48+
NAPICreateRuntime(&runtime);
49+
}
4350
NAPIEnv globalEnv;
44-
NAPICreateEnv(&globalEnv);
51+
NAPICreateEnv(&globalEnv, runtime);
4552

4653
NAPIHandleScope handleScope;
4754
napi_open_handle_scope(globalEnv, &handleScope);
@@ -50,6 +57,8 @@ Java_com_didi_hummer_core_engine_napi_jni_JSEngine_createJSContext(JNIEnv *env,
5057

5158
int64_t ctxPtr = JSUtils::toJsContextPtr(globalEnv);
5259
JSUtils::addHandleScope(ctxPtr, handleScope);
60+
61+
ctxCount++;
5362
return ctxPtr;
5463
}
5564

@@ -66,6 +75,13 @@ Java_com_didi_hummer_core_engine_napi_jni_JSEngine_destroyJSContext(JNIEnv *env,
6675
}
6776

6877
NAPIFreeEnv(globalEnv);
78+
79+
if (ctxCount > 0) {
80+
ctxCount--;
81+
if (ctxCount == 0) {
82+
NAPIFreeRuntime(runtime);
83+
}
84+
}
6985
}
7086

7187
extern "C"
@@ -93,6 +109,61 @@ Java_com_didi_hummer_core_engine_napi_jni_JSEngine_evaluateJavaScript(JNIEnv *en
93109
return JSUtils::JsValueToJavaObject(globalEnv, result);
94110
}
95111

112+
extern "C"
113+
JNIEXPORT jbyteArray JNICALL
114+
Java_com_didi_hummer_core_engine_napi_jni_JSEngine_compileJavaScript(JNIEnv *env, jclass clazz, jlong js_context, jstring script, jstring scriptId) {
115+
auto globalEnv = JSUtils::toJsContext(js_context);
116+
const char *charScript = env->GetStringUTFChars(script, nullptr);
117+
const char *charScriptId = env->GetStringUTFChars(scriptId, nullptr);
118+
119+
const uint8_t *byteBuffer;
120+
size_t bufferSize;
121+
auto status = NAPICompileToByteBuffer(globalEnv, charScript, charScriptId, &byteBuffer, &bufferSize);
122+
LOGD("compile bytecode status: %d", status);
123+
124+
env->ReleaseStringUTFChars(script, charScript);
125+
env->ReleaseStringUTFChars(script, charScriptId);
126+
127+
if (status == NAPIExceptionPendingException) {
128+
reportExceptionIfNeed(globalEnv);
129+
return nullptr;
130+
}
131+
132+
jbyteArray ret = env->NewByteArray((jsize) bufferSize);
133+
env->SetByteArrayRegion(ret, 0, (jsize) bufferSize, reinterpret_cast<const jbyte*>(byteBuffer));
134+
NAPIFreeByteBuffer(globalEnv, byteBuffer);
135+
136+
return ret;
137+
}
138+
139+
extern "C"
140+
JNIEXPORT jobject JNICALL
141+
Java_com_didi_hummer_core_engine_napi_jni_JSEngine_evaluateBytecode(JNIEnv *env, jclass clazz, jlong js_context, jbyteArray bytecode) {
142+
auto globalEnv = JSUtils::toJsContext(js_context);
143+
144+
jsize bufferSize = env->GetArrayLength(bytecode);
145+
auto *byteBuffer = (jbyte *) malloc(sizeof(jbyte) * bufferSize);
146+
memset(byteBuffer, 0, sizeof(jbyte) * bufferSize);
147+
148+
env->GetByteArrayRegion(bytecode, 0, bufferSize, byteBuffer);
149+
150+
NAPIValue result;
151+
auto status = NAPIRunByteBuffer(globalEnv, (uint8_t *) byteBuffer, bufferSize, &result);
152+
LOGD("run bytecode status: %d", status);
153+
154+
free(byteBuffer);
155+
156+
if (status == NAPIExceptionPendingException) {
157+
reportExceptionIfNeed(globalEnv);
158+
jstring msg = env->NewStringUTF("JavaScript evaluate exception");
159+
jobject obj = env->NewObject(JSUtils::jsExceptionCls, JSUtils::jsExceptionInitMethodID, msg);
160+
env->DeleteLocalRef(msg);
161+
return obj;
162+
}
163+
164+
return JSUtils::JsValueToJavaObject(globalEnv, result);
165+
}
166+
96167
extern "C"
97168
JNIEXPORT void JNICALL
98169
Java_com_didi_hummer_core_engine_napi_jni_JSEngine_setProperty(JNIEnv *env, jclass clazz, jlong js_context, jlong js_object, jstring key, jobject value) {

android/hummer-core/src/main/jni/qjs/hummer/JavaScriptRuntime.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,20 @@
1010
#include "JSException.h"
1111
#include "HummerClassRegister.h"
1212

13+
// 单线程共用
14+
thread_local JSRuntime *runtime;
15+
thread_local int ctxCount = 0;
16+
1317
extern "C"
1418
JNIEXPORT jlong JNICALL
1519
Java_com_didi_hummer_core_engine_jsc_jni_JavaScriptRuntime_createJSContextNative(JNIEnv *env, jclass clazz) {
16-
JSRuntime *rt = JS_NewRuntime();
17-
JSContext *ctx = JS_NewContext(rt);
20+
if (ctxCount == 0) {
21+
runtime = JS_NewRuntime();
22+
}
23+
JSContext *ctx = JS_NewContext(runtime);
1824
// 在创建完JSRuntime后第一时间注册自定义Class,是为了使所有JSRuntime公用一个classId,不被相互覆盖
19-
HummerClassRegister::init(rt, ctx);
25+
HummerClassRegister::init(runtime, ctx);
26+
ctxCount++;
2027
return QJS_CONTEXT_ID(ctx);
2128
}
2229

@@ -26,9 +33,13 @@ Java_com_didi_hummer_core_engine_jsc_jni_JavaScriptRuntime_destroyJSContextNativ
2633
auto context = QJS_CONTEXT(js_context);
2734
QJS_CONTEXT_REMOVE(js_context);
2835
if (context != nullptr) {
29-
JSRuntime *rt = JS_GetRuntime(context);
3036
JS_FreeContext(context);
31-
JS_FreeRuntime(rt);
37+
}
38+
if (ctxCount > 0) {
39+
ctxCount--;
40+
if (ctxCount == 0) {
41+
JS_FreeRuntime(runtime);
42+
}
3243
}
3344
}
3445

android/hummer-sdk/src/main/java/com/didi/hummer/adapter/tracker/ITrackerAdapter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class EventName {
2323
* Hummer 上下文销毁
2424
*/
2525
public static final String CONTEXT_DESTROY = "tech_hummer_context_destroy";
26+
/**
27+
* RootView 页面渲染开始
28+
*/
29+
public static final String RENDER_START = "tech_hummer_render_start";
2630
/**
2731
* JS 代码执行开始
2832
*/

0 commit comments

Comments
 (0)