diff --git a/core/plugins/android/features/share/src/main/java/org/hapjs/features/service/share/Share.java b/core/plugins/android/features/share/src/main/java/org/hapjs/features/service/share/Share.java index b1903462..b1c783d1 100644 --- a/core/plugins/android/features/share/src/main/java/org/hapjs/features/service/share/Share.java +++ b/core/plugins/android/features/share/src/main/java/org/hapjs/features/service/share/Share.java @@ -11,6 +11,7 @@ import android.graphics.Bitmap; import android.net.Uri; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.text.TextUtils; import android.util.Log; @@ -84,7 +85,7 @@ public class Share extends FeatureExtension { private ShareAction mShareAction; private static final int MSG_ONLINE_SHARE = 0x01; - private Handler mHandler = new Handler() { + private Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { diff --git a/core/runtime/android/card-support/src/main/java/org/hapjs/card/support/impl/CardServiceWorker.java b/core/runtime/android/card-support/src/main/java/org/hapjs/card/support/impl/CardServiceWorker.java index e6d543d5..5170c219 100644 --- a/core/runtime/android/card-support/src/main/java/org/hapjs/card/support/impl/CardServiceWorker.java +++ b/core/runtime/android/card-support/src/main/java/org/hapjs/card/support/impl/CardServiceWorker.java @@ -41,8 +41,11 @@ import org.hapjs.logging.CardLogManager; import org.hapjs.model.AppInfo; import org.hapjs.render.jsruntime.JsThreadFactory; +import org.hapjs.render.jsruntime.SandboxProvider; +import org.hapjs.render.jsruntime.SandboxProviderImpl; import org.hapjs.render.jsruntime.module.ModuleBridge; import org.hapjs.runtime.HapEngine; +import org.hapjs.runtime.ProviderManager; import org.hapjs.runtime.ResourceConfig; import org.hapjs.runtime.Runtime; @@ -54,6 +57,7 @@ public void init(Context context, String platform) { Log.i(TAG, "CardServiceWorker init, platform=" + platform); ResourceConfig.getInstance().init(context, platform); Runtime.setPlatform(platform); + ProviderManager.getDefault().addProvider(SandboxProvider.NAME, new SandboxProviderImpl()); Runtime.getInstance().onCreate(context); configBlacklist(); JsThreadFactory.getInstance().preload(context); diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/AbstractRequest.java b/core/runtime/android/features/src/main/java/org/hapjs/features/AbstractRequest.java index 72490723..6f931632 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/AbstractRequest.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/AbstractRequest.java @@ -10,7 +10,10 @@ import android.text.TextUtils; import android.util.Log; import android.webkit.MimeTypeMap; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; + +import com.eclipsesource.v8.V8ArrayBuffer; +import com.eclipsesource.v8.utils.ArrayBuffer; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -32,6 +35,7 @@ import okhttp3.OkHttpClient; import okhttp3.RequestBody; import okhttp3.internal.http.HttpMethod; + import org.hapjs.bridge.CallbackHybridFeature; import org.hapjs.bridge.Request; import org.hapjs.bridge.Response; @@ -251,15 +255,12 @@ private RequestBody getSimplePostBody(Headers headers, Object objData, String pk String textParams = joinParams((SerializeObject) objData); return RequestBody.create( MediaType.parse(RequestHelper.CONTENT_TYPE_FORM_URLENCODED), textParams); - } else if (objData instanceof ArrayBuffer) { + } else if (objData instanceof byte[]) { if (TextUtils.isEmpty(contentType)) { contentType = RequestHelper.CONTENT_TYPE_OCTET_STREAM; } - ByteBuffer b = ((ArrayBuffer) objData).getByteBuffer(); - // copy memory to heap - byte[] buffer = new byte[b.remaining()]; - b.get(buffer); - return RequestBody.create(MediaType.parse(contentType), buffer); + + return RequestBody.create(MediaType.parse(contentType), (byte[]) objData); } contentType = @@ -434,7 +435,8 @@ private void parseData(Request request, SerializeObject result, okhttp3.Response throw new IOException("Fail to Parsing Data to Json!"); } } else if (RESPONSE_TYPE_ARRAYBUFFER.equalsIgnoreCase(responseType)) { - result.put(RESULT_KEY_DATA, new ArrayBuffer(response.body().bytes())); + byte[] bytes = response.body().bytes(); + result.put(RESULT_KEY_DATA, bytes); } else if (RESPONSE_TYPE_FILE.equalsIgnoreCase(responseType)) { result.put(RESULT_KEY_DATA, parseFile(request, response)); } else { diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/Decode.java b/core/runtime/android/features/src/main/java/org/hapjs/features/Decode.java index 4e71467d..ce04fcd7 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/Decode.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/Decode.java @@ -6,12 +6,12 @@ package org.hapjs.features; import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; + import org.hapjs.bridge.FeatureExtension; import org.hapjs.bridge.Request; import org.hapjs.bridge.Response; @@ -19,6 +19,7 @@ import org.hapjs.bridge.annotation.FeatureExtensionAnnotation; import org.hapjs.common.json.JSONObject; import org.hapjs.render.jsruntime.serialize.SerializeObject; +import org.hapjs.render.jsruntime.serialize.TypedArrayProxy; import org.json.JSONException; @FeatureExtensionAnnotation( @@ -67,13 +68,14 @@ private Response decode(Request request) throws JSONException { String encoding = params.optString(PARAMS_ENCODING, "UTF-8"); boolean fatal = params.optBoolean(PARAMS_FATAL, false); boolean ignoreBom = params.optBoolean(PARAMS_IGNORE_BOM, false); - TypedArray typedArray = params.optTypedArray(PARAMS_ARRAY_BUFFER); - if (typedArray == null) { + TypedArrayProxy typedArrayProxy = params.optTypedArrayProxy(PARAMS_ARRAY_BUFFER); + if (typedArrayProxy == null) { jsonObject.put(KEY_ERROR_CODE, ERROR_CODE_TYPE_ERROR); jsonObject.put(KEY_ERROR_MSG, "The encoded data was not valid."); return new Response(ERROR_CODE_TYPE_ERROR, jsonObject); } - return decode(encoding, typedArray.getByteBuffer(), ignoreBom, fatal); + + return decode(encoding, typedArrayProxy.getBuffer(), ignoreBom, fatal); } catch (Exception e) { Log.e(TAG, "params are not valid.", e); jsonObject.put(KEY_ERROR_CODE, Response.CODE_ILLEGAL_ARGUMENT); diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/Record.java b/core/runtime/android/features/src/main/java/org/hapjs/features/Record.java index ebcd9122..51e0af99 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/Record.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/Record.java @@ -14,13 +14,13 @@ import android.os.Looper; import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.UInt8Array; +import com.eclipsesource.v8.V8Value; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; import org.hapjs.bridge.Callback; import org.hapjs.bridge.CallbackContext; import org.hapjs.bridge.CallbackHybridFeature; @@ -36,6 +36,7 @@ import org.hapjs.render.Display; import org.hapjs.render.jsruntime.serialize.JavaSerializeObject; import org.hapjs.render.jsruntime.serialize.SerializeObject; +import org.hapjs.render.jsruntime.serialize.TypedArrayProxy; import org.json.JSONException; import org.json.JSONObject; @@ -472,8 +473,7 @@ public void run() { private SerializeObject makeResult(boolean isLastFrame, byte[] bytes) { SerializeObject result = new JavaSerializeObject(); result.put(RESULT_IS_LAST_FRAME, isLastFrame); - UInt8Array array = new UInt8Array(new ArrayBuffer(bytes)); - result.put(RESULT_FRAME_BUFFER, array); + result.put(RESULT_FRAME_BUFFER, new TypedArrayProxy(V8Value.UNSIGNED_INT_8_ARRAY, bytes)); return result; } diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/bluetooth/Bluetooth.java b/core/runtime/android/features/src/main/java/org/hapjs/features/bluetooth/Bluetooth.java index ee1e0da3..69586c92 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/bluetooth/Bluetooth.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/bluetooth/Bluetooth.java @@ -26,7 +26,10 @@ import android.util.Log; import android.util.Pair; import androidx.annotation.NonNull; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; + +import com.eclipsesource.v8.V8ArrayBuffer; +import com.eclipsesource.v8.utils.ArrayBuffer; + import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -38,6 +41,7 @@ import java.util.Vector; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.Semaphore; + import org.hapjs.bridge.CallbackContext; import org.hapjs.bridge.CallbackHybridFeature; import org.hapjs.bridge.FeatureExtension; @@ -631,11 +635,7 @@ private void writeCharacteristic(final Request request) throws SerializeExceptio String address = params.getString(PARAM_DEVICE_ID); String serviceUUID = params.getString(PARAM_SERVICE_UUID); String charaUUID = params.getString(PARAM_CHARACTERISTIC_UUID); - ArrayBuffer value = (ArrayBuffer) params.get(PARAM_VALUE); - ByteBuffer b = value.getByteBuffer(); - // copy memory to heap - byte[] buffer = new byte[b.remaining()]; - b.get(buffer); + byte[] buffer = (byte[]) params.get(PARAM_VALUE); BleManager.getInstance() .writeCharacteristic( address, serviceUUID, charaUUID, buffer, getOperationCallback(request)); @@ -863,7 +863,7 @@ public void onCharacteristicChanged( serviceUUID.toUpperCase()); result.put(RESULT_CHARACTERISTIC_UUID, characteristicUUID.toUpperCase()); - result.put(RESULT_VALUE, new ArrayBuffer(data)); + result.put(RESULT_VALUE, data); runCallbackContext( EVENT_ON_CHARACTERISTIC_VALUE_CHANGE, CODE_ON_CHARACTERISTIC_VALUE_CHANGE, @@ -972,10 +972,11 @@ private JavaSerializeObject toJavaSerializeObject() { new JavaSerializeArray(new JSONArray(mAdvertisServiceUUIDs))); JavaSerializeObject serviceData = new JavaSerializeObject(); for (Pair d : mServiceData) { - serviceData.put(d.first, new ArrayBuffer(d.second)); + byte[] bytes = d.second; + serviceData.put(d.first, bytes); } result.put(RESULT_SERVICE_DATA, serviceData); - result.put(RESULT_ADVERTIS_DATA, new ArrayBuffer(mAdvertisData)); + result.put(RESULT_ADVERTIS_DATA, mAdvertisData); return result; } diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/RequestTask.java b/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/RequestTask.java index 7d55ce83..3f005ca9 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/RequestTask.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/RequestTask.java @@ -25,7 +25,7 @@ @FeatureExtensionAnnotation( name = RequestTask.FEATURE_NAME, actions = { - @ActionAnnotation(name = RequestTask.ACTION_REQUEST, mode = FeatureExtension.Mode.SYNC_CALLBACK), + @ActionAnnotation(name = RequestTask.ACTION_REQUEST, mode = FeatureExtension.Mode.SYNC_CALLBACK, normalize = Extension.Normalize.RAW), @ActionAnnotation(name = RequestTask.ACTION_ON_HEADERS_RECEIVED, mode = FeatureExtension.Mode.CALLBACK, multiple = Extension.Multiple.MULTI), @ActionAnnotation(name = RequestTask.ACTION_OFF_HEADERS_RECEIVED, mode = FeatureExtension.Mode.SYNC, multiple = Extension.Multiple.MULTI), @ActionAnnotation(name = RequestTask.ACTION_ABORT, mode = FeatureExtension.Mode.ASYNC) diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/RequestTaskImpl.java b/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/RequestTaskImpl.java index e4660802..fff293e9 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/RequestTaskImpl.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/RequestTaskImpl.java @@ -11,7 +11,7 @@ import android.text.TextUtils; import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; +import com.eclipsesource.v8.V8Value; import org.hapjs.bridge.ExtensionManager; import org.hapjs.bridge.InstanceManager; @@ -27,6 +27,7 @@ import org.hapjs.render.jsruntime.serialize.SerializeException; import org.hapjs.render.jsruntime.serialize.SerializeHelper; import org.hapjs.render.jsruntime.serialize.SerializeObject; +import org.hapjs.render.jsruntime.serialize.TypedArrayProxy; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -34,7 +35,6 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -101,7 +101,7 @@ public void execute() { String pkg = mRequest.getApplicationContext().getPackage(); SerializeObject reader = mRequest.getSerializeParams(); String url = reader.getString(RequestTask.PARAMS_KEY_URL); - String responseType = reader.optString(RequestTask.PARAMS_KEY_RESPOSNE_TYPE, RequestTask.RESPONSE_TYPE_TEXT); + String responseType = reader.optString(RequestTask.PARAMS_KEY_RESPOSNE_TYPE, RequestTask.RESPONSE_TYPE_TEXT).toLowerCase(); String dataType = reader.optString(RequestTask.PARAMS_KEY_DATA_TYPE, RequestTask.DATA_TYPE_JSON); Object dataObj = reader.opt(RequestTask.PARAMS_KEY_DATA); SerializeObject jsonHeader = reader.optSerializeObject(RequestTask.PARAMS_KEY_HEADER); @@ -252,16 +252,17 @@ private RequestBody getSimplePostBody(Headers headers, Object objData) return RequestBody.create( MediaType.parse(CONTENT_TYPE_FORM_URLENCODED), textParams); - } else if (objData instanceof ArrayBuffer) { + } else if (objData instanceof byte[]) { Log.d(TAG, "getSimplePost objData is ArrayBuffer, contentType=" + contentType); if (TextUtils.isEmpty(contentType)) { contentType = CONTENT_TYPE_JSON; } - ByteBuffer b = ((ArrayBuffer) objData).getByteBuffer(); - //copy memory to heap - byte[] buffer = new byte[b.remaining()]; - b.get(buffer); - return RequestBody.create(MediaType.parse(contentType), buffer); + + try { + return RequestBody.create(MediaType.parse(contentType), new JSONArray(objData).toString()); + } catch (JSONException e) { + throw new RuntimeException(e); + } } contentType = TextUtils.isEmpty(contentType) ? CONTENT_TYPE_TEXT_PLAIN : contentType; @@ -319,7 +320,9 @@ public void onResponse(Call call, okhttp3.Response response) throws IOException result.put(RequestTask.RESULT_KEY_STATUS_CODE, response.code()); result.put(RequestTask.RESULT_KEY_HEADER, parseHeaders(response)); if (response.body() != null) { - result.put(RequestTask.RESULT_KEY_DATA, new ArrayBuffer(response.body().bytes())); + byte[] bytes = response.body().bytes(); + + result.put(RequestTask.RESULT_KEY_DATA, new TypedArrayProxy(V8Value.UNSIGNED_INT_8_ARRAY, bytes)); } else { Log.w(TAG, "response body is invalid"); } diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/UploadCallbackImpl.java b/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/UploadCallbackImpl.java index 684f84e8..8a8cdad1 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/UploadCallbackImpl.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/net/task/UploadCallbackImpl.java @@ -8,8 +8,6 @@ import android.util.Log; import android.webkit.URLUtil; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; - import org.hapjs.bridge.Request; import org.hapjs.bridge.Response; import org.hapjs.common.utils.FileHelper; @@ -23,6 +21,7 @@ import java.io.File; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.Locale; import okhttp3.Call; @@ -97,7 +96,8 @@ private void parseData(SerializeObject result, okhttp3.Response response, String throw new IOException("Fail to Parsing Data to Json!"); } } else if (RESPONSE_TYPE_ARRAYBUFFER.equalsIgnoreCase(responseType)) { - result.put(RESULT_KEY_DATA, new ArrayBuffer(response.body().bytes())); + byte[] bytes = response.body().bytes(); + result.put(RESULT_KEY_DATA, bytes); } else if (RESPONSE_TYPE_FILE.equalsIgnoreCase(responseType)) { result.put(RESULT_KEY_DATA, parseFile(response)); } else { diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/IsoDepInstance.java b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/IsoDepInstance.java index 62c0de32..f648b987 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/IsoDepInstance.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/IsoDepInstance.java @@ -8,14 +8,13 @@ import android.nfc.NfcAdapter; import android.nfc.tech.IsoDep; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; - import org.hapjs.bridge.Request; import org.hapjs.bridge.Response; import org.hapjs.features.nfc.base.BaseTagTechInstance; import org.hapjs.render.jsruntime.serialize.JavaSerializeObject; import java.io.IOException; +import java.nio.ByteBuffer; public class IsoDepInstance extends BaseTagTechInstance { @@ -44,7 +43,7 @@ public byte[] transceive(byte[] buffer) throws IOException { public void getHistoricalBytes(Request request) { byte[] historicalBytes = mIsoDep.getHistoricalBytes(); JavaSerializeObject result = new JavaSerializeObject(); - result.put(NFC.RESULT_HISTORICAL_BYTES, new ArrayBuffer(historicalBytes)); + result.put(NFC.RESULT_HISTORICAL_BYTES, historicalBytes); request.getCallback().callback(new Response(result)); } diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NFC.java b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NFC.java index 84571e96..4902efd5 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NFC.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NFC.java @@ -51,7 +51,7 @@ @ActionAnnotation(name = NFC.ACTION_SET_TIMEOUT, mode = FeatureExtension.Mode.ASYNC, permissions = {Manifest.permission.NFC}), @ActionAnnotation(name = NFC.ACTION_TRANSCEIVE, mode = FeatureExtension.Mode.ASYNC, normalize = FeatureExtension.Normalize.RAW, permissions = {Manifest.permission.NFC}), // Ndef - @ActionAnnotation(name = NFC.ACTION_WRITE_NDEF_MESSAGE, mode = FeatureExtension.Mode.CALLBACK, permissions = {Manifest.permission.NFC}), + @ActionAnnotation(name = NFC.ACTION_WRITE_NDEF_MESSAGE, mode = FeatureExtension.Mode.CALLBACK, permissions = {Manifest.permission.NFC}, normalize = FeatureExtension.Normalize.RAW), // IsoDep @ActionAnnotation(name = NFC.ACTION_GET_HISTORICAL_BYTES, mode = FeatureExtension.Mode.ASYNC, normalize = FeatureExtension.Normalize.RAW), } diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NFCAdapterInstance.java b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NFCAdapterInstance.java index 4b01770b..428668e3 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NFCAdapterInstance.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NFCAdapterInstance.java @@ -24,8 +24,6 @@ import android.text.TextUtils; import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; - import org.hapjs.bridge.HybridManager; import org.hapjs.bridge.InstanceManager; import org.hapjs.bridge.LifecycleListener; @@ -37,6 +35,7 @@ import org.hapjs.render.jsruntime.serialize.SerializeArray; import org.hapjs.render.jsruntime.serialize.SerializeObject; +import java.nio.ByteBuffer; import java.util.Arrays; public class NFCAdapterInstance extends BaseInstance { @@ -212,7 +211,7 @@ private void onTagDiscovered(Intent intent) { byte[] id = mDiscoveredTag.getId(); Log.d(TAG, "id: " + Arrays.toString(id)); - resultObj.put(RESULT_ID, new ArrayBuffer(id)); + resultObj.put(RESULT_ID, id); } else { Log.e(TAG, "null of discovered tag"); } @@ -264,9 +263,9 @@ private SerializeArray getMessages(Parcelable[] rawMessages) { SerializeArray arrayRecord = new JavaSerializeArray(); for (NdefRecord record : ndefRecords) { SerializeObject recordObj = new JavaSerializeObject(); - recordObj.put(RESULT_MESSAGES_RECORD_ID, new ArrayBuffer(record.getId())); - recordObj.put(RESULT_MESSAGES_RECORD_PAYLOAD, new ArrayBuffer(record.getPayload())); - recordObj.put(RESULT_MESSAGES_RECORD_TYPE, new ArrayBuffer(record.getType())); + recordObj.put(RESULT_MESSAGES_RECORD_ID, record.getId()); + recordObj.put(RESULT_MESSAGES_RECORD_PAYLOAD, record.getPayload()); + recordObj.put(RESULT_MESSAGES_RECORD_TYPE, record.getType()); recordObj.put(RESULT_MESSAGES_RECORD_TNF, record.getTnf()); arrayRecord.put(recordObj); } diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NdefInstance.java b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NdefInstance.java index 8dbb47cf..2f27d156 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NdefInstance.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NdefInstance.java @@ -12,8 +12,6 @@ import android.nfc.tech.Ndef; import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; - import org.hapjs.bridge.Request; import org.hapjs.bridge.Response; import org.hapjs.features.nfc.base.BaseTechInstance; @@ -135,20 +133,14 @@ private NdefMessage createBufferNdefMessage(SerializeArray arrayRecords) { SerializeObject obj = arrayRecords.getSerializeObject(i); short tnf = (short) obj.getInt(NFC.PARAM_RECORDS_TNF); - ArrayBuffer typeArrayBuffer = obj.getArrayBuffer(NFC.PARAM_RECORDS_TYPE); - ByteBuffer typeByteBuffer = typeArrayBuffer.getByteBuffer(); - byte[] type = new byte[typeByteBuffer.remaining()]; - typeByteBuffer.get(type); + ByteBuffer typeByteBuffer = obj.getByteBuffer(NFC.PARAM_RECORDS_TYPE); + byte[] type = typeByteBuffer.array(); - ArrayBuffer idArrayBuffer = obj.getArrayBuffer(NFC.PARAM_RECORDS_ID); - ByteBuffer idByteBuffer = idArrayBuffer.getByteBuffer(); - byte[] id = new byte[idByteBuffer.remaining()]; - idByteBuffer.get(id); + ByteBuffer idByteBuffer = obj.getByteBuffer(NFC.PARAM_RECORDS_ID); + byte[] id = idByteBuffer.array(); - ArrayBuffer payloadArrayBuffer = obj.getArrayBuffer(NFC.PARAM_RECORDS_PAYLOAD); - ByteBuffer payloadByteBuffer = payloadArrayBuffer.getByteBuffer(); - byte[] payload = new byte[payloadByteBuffer.remaining()]; - payloadByteBuffer.get(payload); + ByteBuffer payloadByteBuffer = obj.getByteBuffer(NFC.PARAM_RECORDS_PAYLOAD); + byte[] payload = payloadByteBuffer.array(); recordArray[i] = new NdefRecord(tnf, type, id, payload); } catch (IllegalArgumentException | NullPointerException | SerializeException e) { diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NfcAInstance.java b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NfcAInstance.java index 9a803717..129466ef 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NfcAInstance.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/NfcAInstance.java @@ -8,8 +8,6 @@ import android.nfc.NfcAdapter; import android.nfc.tech.NfcA; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; - import org.hapjs.bridge.Request; import org.hapjs.bridge.Response; import org.hapjs.features.nfc.base.BaseTagTechInstance; @@ -19,6 +17,7 @@ import org.json.JSONObject; import java.io.IOException; +import java.nio.ByteBuffer; public class NfcAInstance extends BaseTagTechInstance { @@ -47,7 +46,7 @@ public byte[] transceive(byte[] buffer) throws IOException { public void getAtqa(Request request) { byte[] atqa = mNfcA.getAtqa(); SerializeObject resultObj = new JavaSerializeObject(); - resultObj.put(NFC.RESULT_ATQA, new ArrayBuffer(atqa)); + resultObj.put(NFC.RESULT_ATQA, atqa); request.getCallback().callback(new Response(resultObj)); } diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/base/BaseTagTechInstance.java b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/base/BaseTagTechInstance.java index 66276e95..e7ed0c90 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/base/BaseTagTechInstance.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/nfc/base/BaseTagTechInstance.java @@ -10,7 +10,8 @@ import android.nfc.tech.TagTechnology; import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; +import com.eclipsesource.v8.V8ArrayBuffer; +import com.eclipsesource.v8.utils.ArrayBuffer; import org.hapjs.bridge.Request; import org.hapjs.bridge.Response; @@ -24,7 +25,6 @@ import java.io.IOException; import java.nio.ByteBuffer; -import java.util.Arrays; abstract public class BaseTagTechInstance extends BaseTechInstance { @@ -38,7 +38,7 @@ public BaseTagTechInstance(NfcAdapter nfcAdapter, TagTechnology tech) { abstract public int getMaxTransceiveLength(); - public void getMaxTransceiveLength(Request request) throws JSONException{ + public void getMaxTransceiveLength(Request request) throws JSONException { int maxTransceiveLength = getMaxTransceiveLength(); JSONObject resultObj = new JSONObject(); resultObj.put(NFC.RESULT_MAX_TRANSCEIVE_LENGTH, maxTransceiveLength); @@ -81,14 +81,13 @@ public void transceive(Request request) { } try { if (null != value) { - ByteBuffer byteBuffer = value.getByteBuffer(); - byte[] transceiveParams = new byte[byteBuffer.remaining()]; - byteBuffer.get(transceiveParams); + V8ArrayBuffer v8ArrayBuffer = value.getV8ArrayBuffer(); + byte[] transceiveParams = new byte[v8ArrayBuffer.remaining()]; + v8ArrayBuffer.get(transceiveParams); byte[] resultBytes = transceive(transceiveParams); SerializeObject resultObj = new JavaSerializeObject(); - ArrayBuffer resultBuffer = new ArrayBuffer(resultBytes); - resultObj.put(NFC.RESULT_TRANSCEIVE_DATA, resultBuffer); + resultObj.put(NFC.RESULT_TRANSCEIVE_DATA, resultBytes); request.getCallback().callback(new Response(resultObj)); } else { request.getCallback().callback(new Response(NFCConstants.CODE_INVALID_PARAMETER, NFCConstants.DESC_INVALID_PARAMETER)); diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/storage/file/FileStorage.java b/core/runtime/android/features/src/main/java/org/hapjs/features/storage/file/FileStorage.java index 3c03117f..437a691c 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/storage/file/FileStorage.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/storage/file/FileStorage.java @@ -6,8 +6,11 @@ package org.hapjs.features.storage.file; import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.UInt8Array; + + +import com.eclipsesource.v8.V8; +import com.eclipsesource.v8.V8Value; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -17,6 +20,7 @@ import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.List; + import org.hapjs.bridge.Response; import org.hapjs.bridge.storage.file.IResourceFactory; import org.hapjs.bridge.storage.file.Resource; @@ -24,6 +28,7 @@ import org.hapjs.common.utils.FileUtils; import org.hapjs.render.jsruntime.serialize.JavaSerializeObject; import org.hapjs.render.jsruntime.serialize.SerializeObject; +import org.hapjs.render.jsruntime.serialize.TypedArrayProxy; import org.json.JSONException; import org.json.JSONObject; @@ -278,10 +283,9 @@ public Response readArrayBuffer( "Fail to get resource by " + internalUri); } InputStream input = resource.openInputStream(); - ByteBuffer byteBuffer = FileUtils.readStreamAsBuffer(input, position, length, true); - UInt8Array array = new UInt8Array(new ArrayBuffer(byteBuffer)); + byte[] bytes = FileUtils.readStreamAsBytes(input, position, length, true); SerializeObject result = new JavaSerializeObject(); - result.put(FileStorageFeature.RESULT_BUFFER, array); + result.put(FileStorageFeature.RESULT_BUFFER, new TypedArrayProxy(V8Value.UNSIGNED_INT_8_ARRAY, bytes)); return new Response(result); } catch (FileNotFoundException e) { return new Response(Response.CODE_FILE_NOT_FOUND, e.getMessage()); diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/storage/file/FileStorageFeature.java b/core/runtime/android/features/src/main/java/org/hapjs/features/storage/file/FileStorageFeature.java index 1ce6a1c2..04dfccc6 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/storage/file/FileStorageFeature.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/storage/file/FileStorageFeature.java @@ -6,7 +6,7 @@ package org.hapjs.features.storage.file; import android.text.TextUtils; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; + import org.hapjs.bridge.ApplicationContext; import org.hapjs.bridge.FeatureExtension; import org.hapjs.bridge.Request; @@ -14,11 +14,15 @@ import org.hapjs.bridge.annotation.ActionAnnotation; import org.hapjs.bridge.annotation.FeatureExtensionAnnotation; import org.hapjs.bridge.storage.file.IResourceFactory; +import org.hapjs.render.RootView; import org.hapjs.render.jsruntime.serialize.SerializeException; import org.hapjs.render.jsruntime.serialize.SerializeObject; +import org.hapjs.render.jsruntime.serialize.TypedArrayProxy; import org.json.JSONException; import org.json.JSONObject; +import java.nio.ByteBuffer; + @FeatureExtensionAnnotation( name = FileStorageFeature.FEATURE_NAME, residentType = FeatureExtensionAnnotation.ResidentType.USEABLE, @@ -276,8 +280,8 @@ private void doWriteArrayBuffer(Request request) throws SerializeException { return; } - TypedArray buffer = params.optTypedArray(PARAMS_BUFFER); - if (buffer == null) { + TypedArrayProxy typedArrayProxy = params.optTypedArrayProxy(PARAMS_BUFFER); + if (typedArrayProxy == null) { request .getCallback() .callback(new Response(Response.CODE_ILLEGAL_ARGUMENT, @@ -295,8 +299,9 @@ private void doWriteArrayBuffer(Request request) throws SerializeException { boolean append = params.optBoolean(PARAMS_APPEND, false); IResourceFactory resourceFactory = getResourceFactory(request.getApplicationContext()); + ByteBuffer byteBuffer = typedArrayProxy.getBuffer(); Response response = - mFileStorage.writeBuffer(resourceFactory, uri, buffer.getByteBuffer(), position, + mFileStorage.writeBuffer(resourceFactory, uri, byteBuffer, position, append); request.getCallback().callback(response); } diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/websocket/SocketTask.java b/core/runtime/android/features/src/main/java/org/hapjs/features/websocket/SocketTask.java index 9962c5c5..ab75c027 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/websocket/SocketTask.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/websocket/SocketTask.java @@ -6,13 +6,14 @@ package org.hapjs.features.websocket; import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import okhttp3.Headers; import okhttp3.WebSocketListener; import okio.ByteString; + import org.hapjs.bridge.InstanceManager; import org.hapjs.bridge.Request; import org.hapjs.bridge.Response; @@ -179,7 +180,8 @@ private void onSocketMessage(ByteString byteString) { if (request != null) { byte[] bytes = byteString != null ? byteString.toByteArray() : new byte[0]; SerializeObject serializeObject = new JavaSerializeObject(); - serializeObject.put(WebSocket.RESULT_DATA, new ArrayBuffer(bytes)); + + serializeObject.put(WebSocket.RESULT_DATA, bytes); request.getCallback().callback(new Response(serializeObject)); } } diff --git a/core/runtime/android/features/src/main/java/org/hapjs/features/websocket/WebSocket.java b/core/runtime/android/features/src/main/java/org/hapjs/features/websocket/WebSocket.java index 48990566..a522703d 100644 --- a/core/runtime/android/features/src/main/java/org/hapjs/features/websocket/WebSocket.java +++ b/core/runtime/android/features/src/main/java/org/hapjs/features/websocket/WebSocket.java @@ -10,10 +10,12 @@ import android.text.TextUtils; import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; +import com.eclipsesource.v8.V8ArrayBuffer; +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; import java.nio.ByteBuffer; import okio.ByteString; + import org.hapjs.bridge.FeatureExtension; import org.hapjs.bridge.InstanceManager; import org.hapjs.bridge.Request; @@ -21,6 +23,7 @@ import org.hapjs.bridge.annotation.ActionAnnotation; import org.hapjs.bridge.annotation.FeatureExtensionAnnotation; import org.hapjs.render.jsruntime.serialize.SerializeObject; +import org.hapjs.render.jsruntime.serialize.TypedArrayProxy; import org.json.JSONObject; @FeatureExtensionAnnotation( @@ -118,16 +121,19 @@ private void send(Request request) throws Exception { String str = ((SerializeObject) dataObj).toJSONObject().toString(); sendOk = !TextUtils.isEmpty(str) && socketTask.send(str); - } else if (dataObj instanceof ArrayBuffer) { - ByteBuffer buffer = ((ArrayBuffer) dataObj).getByteBuffer(); - ByteString data = getByteString(buffer); + } else if (dataObj instanceof ByteBuffer) { + ByteString data = getByteString((ByteBuffer) dataObj); sendOk = null != data && socketTask.send(data); - - } else if (dataObj instanceof TypedArray) { - ByteBuffer buffer = ((TypedArray) dataObj).getByteBuffer(); + } else if (dataObj instanceof TypedArrayProxy) { + ByteBuffer buffer = ((TypedArrayProxy) dataObj).getBuffer(); ByteString data = getByteString(buffer); sendOk = null != data && socketTask.send(data); - + } else if (dataObj instanceof byte[]) { + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(((byte[]) dataObj).length); + byteBuffer.put((byte[]) dataObj); + byteBuffer.rewind(); + ByteString data = getByteString(byteBuffer); + sendOk = null != data && socketTask.send(data); } else if (null != dataObj) { String str = dataObj.toString(); sendOk = !TextUtils.isEmpty(str) && socketTask.send(str); diff --git a/core/runtime/android/runtime/build.gradle b/core/runtime/android/runtime/build.gradle index 76e148f2..1de23a53 100755 --- a/core/runtime/android/runtime/build.gradle +++ b/core/runtime/android/runtime/build.gradle @@ -28,7 +28,7 @@ def getPlatformVersionName() { String PLATFORM_VERSION_TEXT = getPlatformVersionCode() String PLATFORM_VERSION_NAME = getPlatformVersionName() -def JSENV_VERSION = "1.2.8" +def JSENV_VERSION = "1.2.9" // add version file task addVersionFile(type: Exec) { @@ -88,10 +88,14 @@ dependencies { def jsenv = jsenvArtifactId + "-" + jsenvVersionCode + ".aar" api fileTree(dir: 'jsenv-libs', include: [jsenv]) - // api fileTree(include: ['*.aar'], dir: '/yourproject/build/outputs/aar') + + //api jsenv + //api fileTree(include: ['*.aar'], dir: '/libs') implementation "androidx.recyclerview:recyclerview:${rootProject.recyclerviewVersion}" implementation "androidx.constraintlayout:constraintlayout:${rootProject.androidXconstraintlayoutVersion}" implementation "com.google.android.material:material:${rootProject.androidXVersion}" + + implementation 'com.esotericsoftware:kryo:5.5.0' } apply plugin: 'com.moowork.node' @@ -117,4 +121,4 @@ task copyInfrasJS(type: Copy, dependsOn: 'buildInfrasJS') { task cleanJS(type: Delete) { delete 'src/main/assets/js' } -clean.dependsOn cleanJS +clean.dependsOn cleanJS \ No newline at end of file diff --git a/core/runtime/android/runtime/jsenv-libs/jsenv-1.2.8.aar b/core/runtime/android/runtime/jsenv-libs/jsenv-1.2.8.aar deleted file mode 100644 index 14e5846b..00000000 Binary files a/core/runtime/android/runtime/jsenv-libs/jsenv-1.2.8.aar and /dev/null differ diff --git a/core/runtime/android/runtime/jsenv-libs/jsenv-1.2.9.aar b/core/runtime/android/runtime/jsenv-libs/jsenv-1.2.9.aar new file mode 100644 index 00000000..791fc9c6 Binary files /dev/null and b/core/runtime/android/runtime/jsenv-libs/jsenv-1.2.9.aar differ diff --git a/core/runtime/android/runtime/jsenv-libs/jsenv-no-v8symbols-1.2.8.aar b/core/runtime/android/runtime/jsenv-libs/jsenv-no-v8symbols-1.2.8.aar deleted file mode 100644 index 61e17242..00000000 Binary files a/core/runtime/android/runtime/jsenv-libs/jsenv-no-v8symbols-1.2.8.aar and /dev/null differ diff --git a/core/runtime/android/runtime/jsenv-libs/jsenv-no-v8symbols-1.2.9.aar b/core/runtime/android/runtime/jsenv-libs/jsenv-no-v8symbols-1.2.9.aar new file mode 100644 index 00000000..e0dff4d9 Binary files /dev/null and b/core/runtime/android/runtime/jsenv-libs/jsenv-no-v8symbols-1.2.9.aar differ diff --git a/core/runtime/android/runtime/proguard-rules.pro b/core/runtime/android/runtime/proguard-rules.pro index 79276a26..03c59175 100644 --- a/core/runtime/android/runtime/proguard-rules.pro +++ b/core/runtime/android/runtime/proguard-rules.pro @@ -38,3 +38,11 @@ -keep class * extends com.eclipsesource.v8.V8Object { public *; } + +-keep class * extends com.eclipsesource.v8.V8Object$Undefined { + *; +} + +-keep class org.hapjs.render.jsruntime.V8InspectorNative { + public *; +} diff --git a/core/runtime/android/runtime/src/main/AndroidManifest.xml b/core/runtime/android/runtime/src/main/AndroidManifest.xml index 78a53058..2a0ab80b 100644 --- a/core/runtime/android/runtime/src/main/AndroidManifest.xml +++ b/core/runtime/android/runtime/src/main/AndroidManifest.xml @@ -123,6 +123,27 @@ + + + + + + diff --git a/core/runtime/android/runtime/src/main/aidl/org/hapjs/analyzer/model/LogData.aidl b/core/runtime/android/runtime/src/main/aidl/org/hapjs/analyzer/model/LogData.aidl new file mode 100644 index 00000000..6d0cc2df --- /dev/null +++ b/core/runtime/android/runtime/src/main/aidl/org/hapjs/analyzer/model/LogData.aidl @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.analyzer.model; + +parcelable LogData; \ No newline at end of file diff --git a/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ILogListener.aidl b/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ILogListener.aidl new file mode 100644 index 00000000..0f0df905 --- /dev/null +++ b/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ILogListener.aidl @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ + +package org.hapjs.runtime.sandbox; + +import org.hapjs.analyzer.model.LogData; + +interface ILogListener { + void onLog(in List logs); +} \ No newline at end of file diff --git a/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ILogProvider.aidl b/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ILogProvider.aidl new file mode 100644 index 00000000..c119c945 --- /dev/null +++ b/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ILogProvider.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +interface ILogProvider { + void logCountEvent(String appPackage, String category, String key); + + void logCountEventWithParams(String appPackage, String category, String key, in Map params); + + void logCalculateEvent(String appPackage, String category, String key, long value); + + void logCalculateEventWithParams(String appPackage, String category, String key, long value, in Map params); + + void logNumericPropertyEvent(String appPackage, String category, String key, long value); + + void logNumericPropertyEventWithParams(String appPackage, String category, String key, long value, in Map params); + + void logStringPropertyEvent(String appPackage, String category, String key, String value); + + void logStringPropertyEventWithParams(String appPackage, String category, String key, String value, in Map params); +} \ No newline at end of file diff --git a/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ISandbox.aidl b/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ISandbox.aidl new file mode 100644 index 00000000..ba4ef604 --- /dev/null +++ b/core/runtime/android/runtime/src/main/aidl/org/hapjs/runtime/sandbox/ISandbox.aidl @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ + +package org.hapjs.runtime.sandbox; + +import android.os.ParcelFileDescriptor; + +import org.hapjs.runtime.sandbox.ILogListener; +import org.hapjs.runtime.sandbox.ILogProvider; + +interface ISandbox { + void init(in Map configs); + ParcelFileDescriptor[] createChannel(in ParcelFileDescriptor[] readSide); + void setLogProvider(ILogProvider logProvider); + void setLogListener(ILogListener listener); +} \ No newline at end of file diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/AnalyzerContext.java b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/AnalyzerContext.java index c0ed231c..9a2ba790 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/AnalyzerContext.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/AnalyzerContext.java @@ -26,6 +26,7 @@ import org.hapjs.render.PageManager; import org.hapjs.render.RootView; import org.hapjs.render.VDomChangeAction; +import org.hapjs.render.jsruntime.AppJsThread; import org.hapjs.render.jsruntime.JsThread; import org.hapjs.render.vdom.DocComponent; import org.hapjs.render.vdom.VDocument; @@ -59,7 +60,7 @@ final void initAnalyzerContext(RootView rootView) { VDomActionApplier vdomActionApplier = rootView.mVdomActionApplier; rootView.mVdomActionApplier = new VDomActionApplier() { @Override - public void applyChangeAction(HapEngine hapEngine, Context context, JsThread jsThread, VDomChangeAction action, VDocument doc, RenderEventCallback renderEventCallback) { + public void applyChangeAction(HapEngine hapEngine, Context context, AppJsThread jsThread, VDomChangeAction action, VDocument doc, RenderEventCallback renderEventCallback) { vdomActionApplier.applyChangeAction(hapEngine, context, jsThread, action, doc, renderEventCallback); if (action.action == VDomChangeAction.ACTION_CREATE_FINISH) { //page create finish diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogData.java b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogData.java new file mode 100644 index 00000000..6b00b13f --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogData.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.analyzer.model; + +import android.os.Parcel; +import android.os.Parcelable; + +public class LogData implements Parcelable { + public @LogPackage.LogLevel + int mLevel; + public @LogPackage.LogType + int mType; + public String mContent; + + public LogData(@LogPackage.LogLevel int level, @LogPackage.LogType int type, String content) { + mLevel = level; + mType = type; + mContent = content; + } + + protected LogData(Parcel in) { + mLevel = in.readInt(); + mType = in.readInt(); + mContent = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public LogData createFromParcel(Parcel in) { + return new LogData(in); + } + + @Override + public LogData[] newArray(int size) { + return new LogData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mLevel); + dest.writeInt(mType); + dest.writeString(mContent); + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogPackage.java b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogPackage.java index f8003bfc..e20e97e1 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogPackage.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/model/LogPackage.java @@ -28,18 +28,6 @@ public LogPackage(int position, List datas) { this.datas = datas; } - public static class LogData { - public @LogLevel int mLevel; - public @LogType int mType; - public String mContent; - - public LogData(@LogLevel int level, @LogType int type, String content) { - mLevel = level; - mType = type; - mContent = content; - } - } - @IntDef({LOG_LEVEL_DEFAULT, Log.VERBOSE, Log.DEBUG, Log.INFO, Log.WARN, Log.ERROR}) public @interface LogLevel { } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/AbsLogDumper.java b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/AbsLogDumper.java new file mode 100644 index 00000000..8fa1e790 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/AbsLogDumper.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.analyzer.monitors; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import org.hapjs.analyzer.model.LogData; +import org.hapjs.analyzer.model.LogPackage; +import org.hapjs.common.executors.Executors; +import org.hapjs.common.utils.FileUtils; + +public abstract class AbsLogDumper implements Runnable { + protected static final String STR_DUMP_FAIL = "--- LOGCAT_CONSOLE dump log fail ! ---"; + private static final int DUMP_LOG_INTERVAL = 100; + private static final int DUMP_LOG_BATCH_CNT = 100; + private static final int MSG_DUMP_LOG = 0; + + private java.lang.Process mLogcatProcess; + private boolean mIsStop; + private List mPendingLogs = new ArrayList<>(); + private final Handler mMainHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_DUMP_LOG) { + List pendingLogs = new ArrayList<>(); + synchronized (AbsLogDumper.this) { + pendingLogs.addAll(mPendingLogs); + mPendingLogs.clear(); + } + doDumpLog(pendingLogs); + } + } + }; + + void clearLogcat() { + Executors.io().execute(new Runnable() { + @Override + public void run() { + java.lang.Process process = null; + try { + process = Runtime.getRuntime().exec("logcat -b main -c"); + Thread.sleep(1000); + } catch (Exception e) { + // ignore + } finally { + if (process != null) { + process.destroy(); + } + } + } + }); + } + + private BufferedReader getLogReader() throws IOException { + String command = "logcat -b main -v time --pid " + android.os.Process.myPid(); + if (mLogcatProcess != null) { + mLogcatProcess.destroy(); + } + mLogcatProcess = Runtime.getRuntime().exec(command); + return new BufferedReader(new InputStreamReader(mLogcatProcess.getInputStream(), StandardCharsets.UTF_8)); + } + + @Override + public void run() { + BufferedReader reader = null; + try { + String line; + reader = getLogReader(); + while (!mIsStop) { + line = reader.readLine(); + if (line == null) { + dumpFailLog(); + break; + } else { + dumpLog(line); + } + } + } catch (Exception e) { + // ignore + } finally { + FileUtils.closeQuietly(reader); + } + } + + private void dumpLog(String log) { + int logLevel = getLogLevel(log); + boolean isJsLog = log.contains(LogcatMonitor.JS_TAG); + LogData logData = new LogData(logLevel, isJsLog ? LogPackage.LOG_TYPE_JS : LogPackage.LOG_TYPE_NATIVE, log); + dumpLog(logData); + } + + public void dumpFailLog() { + LogData logData = new LogData(LogPackage.LOG_LEVEL_DEFAULT, LogPackage.LOG_TYPE_DEFAULT, STR_DUMP_FAIL); + dumpLog(logData); + } + + private synchronized void dumpLog(LogData log) { + List logs = new ArrayList<>(); + logs.add(log); + dumpLog(logs); + } + + protected synchronized void dumpLog(List logs) { + boolean noPending = mPendingLogs.isEmpty(); + mPendingLogs = mergeLogs(mPendingLogs, logs); + if (noPending) { + if (!mMainHandler.hasMessages(MSG_DUMP_LOG)) { + mMainHandler.sendEmptyMessageDelayed(MSG_DUMP_LOG, DUMP_LOG_INTERVAL); + } + } else if (mPendingLogs.size() > DUMP_LOG_BATCH_CNT) { + if (!mMainHandler.hasMessages(MSG_DUMP_LOG)) { + mMainHandler.sendEmptyMessage(MSG_DUMP_LOG); + } + } + } + + private static List mergeLogs(List logs1, List logs2) { + if (logs1 == null || logs1.isEmpty()) { + return logs2; + } + if (logs2 == null || logs2.isEmpty()) { + return logs1; + } + if (compareTimestamp(logs1.get(logs1.size() - 1), logs2.get(0)) <= 0) { + logs1.addAll(logs2); + return logs1; + } + if (compareTimestamp(logs2.get(logs2.size() - 1), logs1.get(0)) <= 0) { + logs2.addAll(logs1); + return logs2; + } + + List mergedLogs = new ArrayList<>(); + for (int i = 0, j = 0; i < logs1.size() || j < logs2.size(); ) { + if (i >= logs1.size()) { + mergedLogs.add(logs2.get(j++)); + } else if (j >= logs2.size()) { + mergedLogs.add(logs1.get(i++)); + } else if (compareTimestamp(logs1.get(i), logs2.get(j)) <= 0) { + mergedLogs.add(logs1.get(i++)); + } else { + mergedLogs.add(logs2.get(j++)); + } + } + return mergedLogs; + } + + private static int compareTimestamp(LogData logData1, LogData logData2) { + return getTimestamp(logData1).compareTo(getTimestamp(logData2)); + } + + private static String getTimestamp(LogData logData) { + int length = logData.mContent.length(); + return logData.mContent.substring(0, Math.min(length, 18)); + } + + protected abstract void doDumpLog(List log); + + public void close() { + mIsStop = true; + clearLogcat(); + closeLogcatProcess(); + } + + private void closeLogcatProcess() { + if (mLogcatProcess != null) { + try { + mLogcatProcess.destroy(); + } catch (Exception e) { + // ignore + } + } + mLogcatProcess = null; + } + + private @LogPackage.LogLevel int getLogLevel(String log) { + if (log.length() < 20) { + return Log.VERBOSE; + } + char level = log.charAt(19); + switch (level) { + case 'V': + return Log.VERBOSE; + case 'D': + return Log.DEBUG; + case 'I': + return Log.INFO; + case 'W': + return Log.WARN; + case 'E': + return Log.ERROR; + } + return Log.VERBOSE; + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/LogcatMonitor.java b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/LogcatMonitor.java index 71f861b2..98735fbe 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/LogcatMonitor.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/monitors/LogcatMonitor.java @@ -4,34 +4,44 @@ */ package org.hapjs.analyzer.monitors; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; +import org.hapjs.analyzer.Analyzer; +import org.hapjs.analyzer.model.LogData; import org.hapjs.analyzer.model.LogPackage; import org.hapjs.analyzer.monitors.abs.AbsMonitor; import org.hapjs.analyzer.tools.AnalyzerThreadManager; import org.hapjs.common.executors.Executors; -import org.hapjs.common.utils.FileUtils; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import org.hapjs.common.utils.ProcessUtils; + +import org.hapjs.render.jsruntime.SandboxProvider; +import org.hapjs.runtime.ProviderManager; +import org.hapjs.runtime.sandbox.ILogListener; +import org.hapjs.runtime.sandbox.ISandbox; public class LogcatMonitor extends AbsMonitor { + private static final String TAG = "LogcatMonitor"; public static final String NAME = "logcat"; public static final int LOG_JS = 1; public static final int LOG_NATIVE = 1 << 1; public static final int TYPE_LOG_STYLE_WEB = 0; public static final int TYPE_LOG_STYLE_ANDROID = 1; - private static final String JS_TAG = "LOGCAT_CONSOLE"; + public static final String JS_TAG = "LOGCAT_CONSOLE"; private static final int CACHE_SIZE = 500; private static final int MSG_SEND_ONE_LOG = 1; private int mLogLevel = Log.VERBOSE; @@ -39,7 +49,7 @@ public class LogcatMonitor extends AbsMonitor { private int mLogStyle = TYPE_LOG_STYLE_WEB; private String mFilter = ""; private Dumper mDumper; - private LinkedList mCaches = new LinkedList<>(); + private LinkedList mCaches = new LinkedList<>(); private final Handler mMainHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { @@ -104,7 +114,7 @@ private void onRuleChanged() { if (mCaches.isEmpty()) { return; } - List logDatas; + List logDatas; synchronized (LogcatMonitor.this) { logDatas = filterData(new ArrayList<>(mCaches)); } @@ -112,20 +122,14 @@ private void onRuleChanged() { }); } - private List filterData(LogPackage.LogData logData) { - ArrayList singleList = new ArrayList<>(1); - singleList.add(logData); - return filterData(singleList); - } - - private List filterData(List originData) { + private List filterData(List originData) { if (originData == null || originData.isEmpty()) { return new ArrayList<>(); } String filter = mFilter == null ? "" : mFilter.toLowerCase(); - Iterator iterator = originData.iterator(); + Iterator iterator = originData.iterator(); while (iterator.hasNext()) { - LogPackage.LogData logData = iterator.next(); + LogData logData = iterator.next(); if (logData.mType == LogPackage.LOG_TYPE_DEFAULT) { continue; } @@ -156,11 +160,11 @@ private List filterData(List originData) return originData; } - private synchronized void cacheLog(LogPackage.LogData logData) { - if (mCaches.size() >= CACHE_SIZE) { + private synchronized void cacheLog(List logDatas) { + mCaches.addAll(logDatas); + while (mCaches.size() >= CACHE_SIZE) { mCaches.removeFirst(); } - mCaches.add(logData); } public void clearLog() { @@ -168,7 +172,7 @@ public void clearLog() { mCaches.clear(); } - private void sendLogDatas(int position, List logDatas) { + private void sendLogDatas(int position, List logDatas) { if (logDatas != null) { Pipeline pipeline = getPipeline(); if (pipeline != null) { @@ -177,77 +181,22 @@ private void sendLogDatas(int position, List logDatas) { } } - private class Dumper implements Runnable { - private static final String STR_DUMP_FAIL = "--- LOGCAT_CONSOLE dump log fail ! ---"; - private Process mLogcatProcess; - private boolean mIsStop; + private class Dumper extends AbsLogDumper { + private ISandbox mSandbox; + private ServiceConnection mConnection; - void clearLogcat() { - Executors.io().execute(new Runnable() { - @Override - public void run() { - Process process = null; - try { - process = Runtime.getRuntime().exec("logcat -b main -c"); - Thread.sleep(1000); - } catch (Exception e) { - // ignore - } finally { - if (process != null) { - process.destroy(); - } - } - } - }); - } - - private BufferedReader getLogReader() throws IOException { - String command = "logcat -b main -v time --pid " + android.os.Process.myPid(); - if (mLogcatProcess != null) { - mLogcatProcess.destroy(); + public Dumper() { + SandboxProvider sandboxProvider = ProviderManager.getDefault().getProvider(SandboxProvider.NAME); + if (sandboxProvider != null && sandboxProvider.isSandboxEnabled()) { + connectSandboxService(); } - mLogcatProcess = Runtime.getRuntime().exec(command); - return new BufferedReader(new InputStreamReader(mLogcatProcess.getInputStream(), StandardCharsets.UTF_8)); } @Override - public void run() { - BufferedReader reader = null; - try { - String line; - reader = getLogReader(); - while (!mIsStop) { - line = reader.readLine(); - if (line == null) { - dumpFailLog(); - break; - } else { - dumpLog(line); - } - } - } catch (Exception e) { - // ignore - } finally { - FileUtils.closeQuietly(reader); - } - } - - private void dumpLog(String log) { - int logLevel = getLogLevel(log); - boolean isJsLog = log.contains(JS_TAG); - LogPackage.LogData logData = new LogPackage.LogData(logLevel, isJsLog ? LogPackage.LOG_TYPE_JS : LogPackage.LOG_TYPE_NATIVE, log); - dumpLog(logData); - } - - private void dumpFailLog() { - LogPackage.LogData logData = new LogPackage.LogData(LogPackage.LOG_LEVEL_DEFAULT, LogPackage.LOG_TYPE_DEFAULT, STR_DUMP_FAIL); - dumpLog(logData); - } - - private void dumpLog(LogPackage.LogData logData) { - cacheLog(logData); + protected void doDumpLog(List logs) { + cacheLog(logs); AnalyzerThreadManager.getInstance().getAnalyzerHandler().post(() -> { - List filterData = filterData(logData); + List filterData = filterData(logs); if (!filterData.isEmpty()) { Message message = mMainHandler.obtainMessage(MSG_SEND_ONE_LOG); message.obj = new LogPackage(filterData); @@ -256,41 +205,58 @@ private void dumpLog(LogPackage.LogData logData) { }); } - private @LogPackage.LogLevel int getLogLevel(String log) { - if (log.length() < 20) { - return Log.VERBOSE; + @Override + public void close() { + super.close(); + + if (mConnection != null) { + Context context = Analyzer.get().getApplicationContext(); + context.unbindService(mConnection); + mConnection = null; } - char level = log.charAt(19); - switch (level) { - case 'V': - return Log.VERBOSE; - case 'D': - return Log.DEBUG; - case 'I': - return Log.INFO; - case 'W': - return Log.WARN; - case 'E': - return Log.ERROR; + if (mSandbox != null) { + try { + mSandbox.setLogListener(null); + mSandbox = null; + } catch (RemoteException e) { + Log.e(TAG, "failed to setLogListener null", e); + } } - return Log.VERBOSE; } - void close() { - mIsStop = true; - clearLogcat(); - closeLogcatProcess(); - } + private void connectSandboxService() { + String currentProcessName = ProcessUtils.getCurrentProcessName(); + String sandboxName = "org.hapjs.runtime.sandbox.SandboxService$Sandbox" + + currentProcessName.charAt(currentProcessName.length() - 1); - void closeLogcatProcess(){ - if (mLogcatProcess != null) { - try { - mLogcatProcess.destroy(); - } catch (Exception e) { - // ignore + Context context = Analyzer.get().getApplicationContext(); + Intent intent = new Intent(); + intent.setClassName(context.getPackageName(), sandboxName); + + mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mSandbox = ISandbox.Stub.asInterface(service); + try { + mSandbox.setLogListener(new ILogListener.Stub() { + public void onLog(List logs) { + dumpLog(logs); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "failed to setLogListener", e); + } } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + }; + + boolean bindResult = context.bindService(intent, mConnection, 0); + if (!bindResult) { + Log.e(TAG, "bind sandboxService failed. sandboxName=" + sandboxName); } - mLogcatProcess = null; } } } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/panels/ConsolePanel.java b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/panels/ConsolePanel.java index debb8972..83d15fc4 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/panels/ConsolePanel.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/analyzer/panels/ConsolePanel.java @@ -30,6 +30,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import org.hapjs.analyzer.model.LogData; import org.hapjs.analyzer.model.LogPackage; import org.hapjs.analyzer.monitors.LogcatMonitor; import org.hapjs.analyzer.tools.AnalyzerThreadManager; @@ -321,7 +322,7 @@ protected void onShowAnimationFinished() { super.onShowAnimationFinished(); mVisibleComplete = true; if (!mTmpLogCache.isEmpty()) { - List logDatas = new LinkedList<>(); + List logDatas = new LinkedList<>(); for (LogPackage p : mTmpLogCache) { logDatas.addAll(p.datas); } @@ -381,7 +382,7 @@ private void addLog(LogPackage logPackage) { private static class LogListAdapter extends RecyclerView.Adapter { private Context mContext; - private LinkedList mLogDatas; + private LinkedList mLogDatas; private RecyclerView mRecyclerView; private boolean mLockLog = false; @@ -400,7 +401,7 @@ public LogItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) @Override public void onBindViewHolder(@NonNull LogItemHolder holder, int position) { - LogPackage.LogData logData = mLogDatas.get(position); + LogData logData = mLogDatas.get(position); holder.setLogData(logData); } @@ -409,19 +410,19 @@ public int getItemCount() { return mLogDatas.size(); } - void addLogDatas(int position, List logDatas) { + void addLogDatas(int position, List logDatas) { if (logDatas == null || logDatas.isEmpty()) { return; } if (logDatas.size() > MAX_DISPLAY__COUNT) { - ListIterator iterator = logDatas.listIterator(0); + ListIterator iterator = logDatas.listIterator(0); for (int i = 0, n = logDatas.size() - MAX_DISPLAY__COUNT; i < n; i++) { iterator.next(); iterator.remove(); } } if (mLogDatas.size() >= MAX_DISPLAY__COUNT - logDatas.size()) { - ListIterator iterator = mLogDatas.listIterator(0); + ListIterator iterator = mLogDatas.listIterator(0); for (int i = 0, n = MAX_DISPLAY__COUNT - logDatas.size(); i < n; i++) { iterator.next(); iterator.remove(); @@ -476,7 +477,7 @@ void setTextColor(int color) { mTv.setTextColor(color); } - void setLogData(LogPackage.LogData logData) { + void setLogData(LogData logData) { setText(logData.mContent); itemView.setSelected(logData.mType == LogPackage.LOG_TYPE_JS); switch (logData.mLevel) { diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/ExtensionManager.java b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/ExtensionManager.java index 7908c530..3e6ad16d 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/ExtensionManager.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/ExtensionManager.java @@ -9,10 +9,7 @@ import android.text.TextUtils; import android.util.Log; import androidx.annotation.Nullable; -import com.eclipsesource.v8.JavaCallback; -import com.eclipsesource.v8.V8; -import com.eclipsesource.v8.V8Array; -import com.eclipsesource.v8.V8Object; + import org.hapjs.bridge.permission.HapPermissionManager; import org.hapjs.bridge.permission.PermissionCallback; import org.hapjs.common.executors.Executor; @@ -23,8 +20,8 @@ import org.hapjs.model.CardInfo; import org.hapjs.render.PageManager; import org.hapjs.render.RootView; +import org.hapjs.render.jsruntime.IJsEngine; import org.hapjs.render.jsruntime.JsThread; -import org.hapjs.render.jsruntime.JsUtils; import org.hapjs.render.jsruntime.module.ModuleBridge; import org.hapjs.render.jsruntime.serialize.JavaSerializeObject; import org.hapjs.render.jsruntime.serialize.Serializable; @@ -53,7 +50,6 @@ public class ExtensionManager { private LifecycleListenerImpl mLifecycleListener; private JsThread mJsThread; private WidgetBridge mWidgetBridge; - private V8Object mRegisteredInterface; private FeatureInvokeListener mFeatureInvokeListener; @@ -69,27 +65,19 @@ public static boolean isValidCallback(String jsCallback) { return !TextUtils.isEmpty(jsCallback) && !UNSET_JS_CALLBACK.equals(jsCallback); } - public void onRuntimeInit(V8 v8) { - register(v8); - - publish(ModuleBridge.getModuleMapJSONString()); - } - - private void register(V8 v8) { - JsInterface jsInterface = new JsInterface(this); - mRegisteredInterface = - JsInterfaceProxy.register(v8, jsInterface, JsInterface.INTERFACE_NAME); + public void onRuntimeInit(IJsEngine engine) { + publish(engine, ModuleBridge.getModuleMapJSONString()); } public void attach(RootView rootView, PageManager pageManager, AppInfo appInfo) { mModuleBridge.attach(rootView, pageManager, appInfo); } - public void onRuntimeCreate(AppInfo appInfo) { + public void onRuntimeCreate(IJsEngine engine, AppInfo appInfo) { mFeatureBridge.addFeatures(appInfo.getFeatureInfos()); - publish(FeatureBridge.getFeatureMapJSONString()); - publish(WidgetBridge.getWidgetMetaDataJSONString()); + publish(engine, FeatureBridge.getFeatureMapJSONString()); + publish(engine, WidgetBridge.getWidgetMetaDataJSONString()); } protected String buildRegisterScript(String bridgeJson) { @@ -100,9 +88,9 @@ protected String buildRegisterScript(String bridgeJson) { return sb.toString(); } - private void publish(String bridgeJson) { + private void publish(IJsEngine engine, String bridgeJson) { String registerScript = buildRegisterScript(bridgeJson); - mJsThread.getJsContext().getV8().executeScript(registerScript); + engine.executeScript(registerScript, null, 0); } public void configApplication(AppInfo appInfo) { @@ -242,8 +230,6 @@ private void setCallbackToRequest(String pkg, String name, String action, String } } public void dispose() { - JsUtils.release(mRegisteredInterface); - mRegisteredInterface = null; disposeFeature(true, mLifecycleListener); } @@ -275,45 +261,6 @@ public void callback(Response response, String jsCallback, Callback realCallback } } - private static class JsInterfaceProxy extends V8Object { - private final JsInterface jsInterface; - private final JavaCallback invoke = - new JavaCallback() { - @Override - public Object invoke(V8Object receiver, V8Array parameters) { - Object rawParams = parameters.get(2); - Object instanceId = parameters.get(4); - if (!(instanceId instanceof Integer)) { - instanceId = -1; - } - Response response = - jsInterface.invoke( - parameters.getString(0), - parameters.getString(1), - rawParams, - parameters.getString(3), - (int) instanceId); - - if (rawParams instanceof V8Object) { - JsUtils.release((V8Object) rawParams); - } - return response == null ? null : response.toJavascriptResult(v8); - } - }; - - private JsInterfaceProxy(V8 v8, JsInterface jsInterface) { - super(v8); - this.jsInterface = jsInterface; - } - - static V8Object register(V8 v8, JsInterface jsInterface, String name) { - JsInterfaceProxy proxy = new JsInterfaceProxy(v8, jsInterface); - v8.add(name, proxy); - proxy.registerJavaMethod(proxy.invoke, "invoke"); - return proxy; - } - } - class AsyncInvocation implements Runnable { private AbstractExtension mFeature; private Request mRequest; diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/InstanceManager.java b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/InstanceManager.java index 8930520e..9d880f80 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/InstanceManager.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/InstanceManager.java @@ -85,6 +85,8 @@ private static class Holder { public static class InstanceHandler implements HandlerObject { private int mId; + private InstanceHandler() {} + public InstanceHandler(int id) { this.mId = id; } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/JsInterface.java b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/JsInterface.java index 70ef83d0..1ac38423 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/JsInterface.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/JsInterface.java @@ -7,6 +7,8 @@ import android.webkit.JavascriptInterface; import com.eclipsesource.v8.V8Object; + +import org.hapjs.render.jsruntime.IJavaNative; import org.hapjs.render.jsruntime.serialize.JavaSerializeObject; /** @@ -16,10 +18,10 @@ public class JsInterface { public static final String INTERFACE_NAME = "JsBridge"; - private ExtensionManager mManager; + private IJavaNative mNative; - public JsInterface(ExtensionManager manager) { - mManager = manager; + public JsInterface(IJavaNative javaNative) { + mNative = javaNative; } /** @@ -44,7 +46,7 @@ public Response invoke( rawParams = new JavaSerializeObject(V8ObjectHelper.toMap((V8Object) rawParams)); } - Response response = mManager.invoke(feature, action, rawParams, callback, instanceId); + Response response = mNative.invoke(feature, action, rawParams, callback, instanceId); return response; } } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/Response.java b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/Response.java index 2750f07c..1b7ac2c7 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/Response.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/Response.java @@ -133,11 +133,13 @@ public class Response implements java.io.Serializable { private static final String CODE = "code"; private static final String CONTENT = "content"; - private final int mCode; - private final Object mContent; + private int mCode; + private Object mContent; private JSONObject mJSONResult; private transient SerializeObject mSerializeObjectResult; + private Response() {} + /** * Construct a new instance with code {@link #CODE_SUCCESS} and specified content. * diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/V8ObjectHelper.java b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/V8ObjectHelper.java index cfd69f14..c45bcf83 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/V8ObjectHelper.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/V8ObjectHelper.java @@ -12,18 +12,10 @@ import com.eclipsesource.v8.V8Object; import com.eclipsesource.v8.V8TypedArray; import com.eclipsesource.v8.V8Value; +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; import com.eclipsesource.v8.utils.V8Map; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.Float32Array; -import com.eclipsesource.v8.utils.typedarrays.Float64Array; -import com.eclipsesource.v8.utils.typedarrays.Int16Array; -import com.eclipsesource.v8.utils.typedarrays.Int32Array; -import com.eclipsesource.v8.utils.typedarrays.Int8Array; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; -import com.eclipsesource.v8.utils.typedarrays.UInt16Array; -import com.eclipsesource.v8.utils.typedarrays.UInt32Array; -import com.eclipsesource.v8.utils.typedarrays.UInt8Array; -import com.eclipsesource.v8.utils.typedarrays.UInt8ClampedArray; + import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; @@ -32,6 +24,7 @@ import java.util.List; import java.util.Map; import org.hapjs.render.jsruntime.serialize.HandlerObject; +import org.hapjs.render.jsruntime.serialize.TypedArrayProxy; public class V8ObjectHelper { private static final Object IGNORE = new Object(); @@ -100,7 +93,10 @@ private static Object getValue(Object value, int valueType, V8Map cache) case V8Value.V8_TYPED_ARRAY: return toTypedArray((V8Array) value); case V8Value.V8_ARRAY_BUFFER: - return new ArrayBuffer(((V8ArrayBuffer) value).getBackingStore()); + V8ArrayBuffer v8ArrayBuffer = (V8ArrayBuffer) value; + byte[] bytes = new byte[v8ArrayBuffer.remaining()]; + v8ArrayBuffer.get(bytes); + return bytes; case V8Value.UNDEFINED: return V8.getUndefined(); default: @@ -111,26 +107,21 @@ private static Object getValue(Object value, int valueType, V8Map cache) private static Object toTypedArray(V8Array typedArray) { int arrayType = typedArray.getType(); - ByteBuffer buffer = ((V8TypedArray) typedArray).getByteBuffer(); + switch (arrayType) { case V8Value.INT_32_ARRAY: - return new Int32Array(buffer); case V8Value.DOUBLE: - return new Float64Array(buffer); case V8Value.INT_8_ARRAY: - return new Int8Array(buffer); case V8Value.UNSIGNED_INT_8_ARRAY: - return new UInt8Array(buffer); case V8Value.UNSIGNED_INT_8_CLAMPED_ARRAY: - return new UInt8ClampedArray(buffer); case V8Value.INT_16_ARRAY: - return new Int16Array(buffer); case V8Value.UNSIGNED_INT_16_ARRAY: - return new UInt16Array(buffer); case V8Value.UNSIGNED_INT_32_ARRAY: - return new UInt32Array(buffer); case V8Value.FLOAT_32_ARRAY: - return new Float32Array(buffer); + V8ArrayBuffer v8ArrayBuffer = ((V8TypedArray) typedArray).getBuffer(); + byte[] bytes = new byte[v8ArrayBuffer.remaining()]; + v8ArrayBuffer.get(bytes); + return new TypedArrayProxy(arrayType, bytes); case V8Value.BOOLEAN: case V8Value.STRING: case V8Value.V8_ARRAY: @@ -213,7 +204,7 @@ public static V8Object toV8Object(final V8 v8, final Map cache) { + if (value == null) { result.pushUndefined(); } else if (value instanceof Integer) { @@ -295,12 +299,23 @@ private static void pushValue(V8 v8, V8Array result, Object value, Map cache) { + if (cache.containsKey(byteBuffer)) { + return (V8ArrayBuffer) cache.get(byteBuffer); + } + V8ArrayBuffer result = new V8ArrayBuffer(v8, byteBuffer); + cache.put(byteBuffer, result); + return result; + } + + private static V8ArrayBuffer toV8ArrayBuffer(final V8 v8, final byte[] array, final Map cache) { + if (cache.containsKey(array)) { + return (V8ArrayBuffer) cache.get(array); + } + + ByteBuffer buff = ByteBuffer.allocateDirect(array.length); + buff.put(array); + buff.rewind(); + V8ArrayBuffer result = new V8ArrayBuffer(v8, buff); + cache.put(array, result); + return result; + } + static class ListWrapper { private List list; diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/impl/android/HybridViewImpl.java b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/impl/android/HybridViewImpl.java index 023e2848..ffb352e4 100755 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/impl/android/HybridViewImpl.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/impl/android/HybridViewImpl.java @@ -12,6 +12,7 @@ import org.hapjs.bridge.HybridSettings; import org.hapjs.bridge.HybridViewClient; import org.hapjs.render.RootView; +import org.hapjs.render.jsruntime.AppJsThread; import org.hapjs.render.jsruntime.JsThread; public class HybridViewImpl implements org.hapjs.bridge.HybridView { @@ -90,7 +91,7 @@ public void setOnVisibilityChangedListener(OnVisibilityChangedListener l) { private class InternWebViewClient extends AndroidViewClient { @Override public void onRuntimeCreate(RootView view) { - JsThread jsThread = view.getJsThread(); + AppJsThread jsThread = view.getJsThread(); jsThread.getBridgeManager().setHybridManager(mHybridManager); } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/impl/webkit/HybridViewImpl.java b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/impl/webkit/HybridViewImpl.java index ec79e5d4..6107f307 100755 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/impl/webkit/HybridViewImpl.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/bridge/impl/webkit/HybridViewImpl.java @@ -305,12 +305,18 @@ public void setGeolocationEnabled(boolean flag) { @Override public void setAppCacheEnabled(boolean flag) { - mWebView.getSettings().setAppCacheEnabled(flag); + if (flag){ + mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); + }else { + mWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); + } } @Override public void setAppCachePath(String appCachePath) { - mWebView.getSettings().setAppCachePath(appCachePath); + /* this method is deprecated with setAppCacheEnabled(). + * see https://developer.android.com/sdk/api_diff/30-incr/changes/android.webkit.WebSettings . + */ } @Override diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/common/resident/ResidentManager.java b/core/runtime/android/runtime/src/main/java/org/hapjs/common/resident/ResidentManager.java index e6f53a79..3ed16951 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/common/resident/ResidentManager.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/common/resident/ResidentManager.java @@ -34,6 +34,7 @@ import org.hapjs.bridge.permission.HapCustomPermissions; import org.hapjs.model.AppInfo; import org.hapjs.render.RootView; +import org.hapjs.render.jsruntime.AppJsThread; import org.hapjs.render.jsruntime.JsThread; import org.hapjs.runtime.HapEngine; @@ -60,7 +61,7 @@ public class ResidentManager { private ResidentService.ResidentBinder mResidentBinder; private Context mContext; private AppInfo mAppInfo; - private JsThread mJsThread; + private AppJsThread mJsThread; private ServiceConnection mServiceConnection; private ResidentHandler mResidentHandler; private ResidentChangeListener mDbUpdateListener; @@ -75,7 +76,8 @@ public ResidentManager() { mResidentHandler = new ResidentHandler(Looper.getMainLooper()); } - public void init(Context context, AppInfo appInfo, JsThread jsThread) { + + public void init(Context context, AppInfo appInfo, AppJsThread jsThread) { if (null != context) { this.mContext = context.getApplicationContext(); } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/common/utils/MenubarUtils.java b/core/runtime/android/runtime/src/main/java/org/hapjs/common/utils/MenubarUtils.java index 314d551e..dc2a31aa 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/common/utils/MenubarUtils.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/common/utils/MenubarUtils.java @@ -30,6 +30,7 @@ import org.hapjs.render.Page; import org.hapjs.render.PageManager; import org.hapjs.render.RootView; +import org.hapjs.render.jsruntime.AppJsThread; import org.hapjs.render.jsruntime.JsThread; import org.hapjs.runtime.ProviderManager; import org.hapjs.system.SysOpProvider; @@ -367,7 +368,7 @@ public void run() { private static ExtensionManager getExtensionManager(RootView rootView) { ExtensionManager extensionManager = null; if (null != rootView) { - JsThread jsThread = rootView.getJsThread(); + AppJsThread jsThread = rootView.getJsThread(); if (null != jsThread) { extensionManager = jsThread.getBridgeManager(); } else { diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/common/utils/ProcessUtils.java b/core/runtime/android/runtime/src/main/java/org/hapjs/common/utils/ProcessUtils.java index faedc7d4..ffe9f3c6 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/common/utils/ProcessUtils.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/common/utils/ProcessUtils.java @@ -24,6 +24,7 @@ public class ProcessUtils { private static String sCurrentProcessName; private static Boolean sIsMainProcess; private static Boolean sIsAppProcess; + private static Boolean sIsSandboxProcess; /** * Only for extends, please don't instantiate this class. @@ -63,6 +64,13 @@ public static boolean isAppProcess(Context context) { return sIsAppProcess; } + public static boolean isSandboxProcess(Context context) { + if (sIsSandboxProcess == null) { + sIsSandboxProcess = getCurrentProcessName().startsWith(context.getPackageName() + ":Sandbox"); + } + return sIsSandboxProcess; + } + /** * Return a process map(processName-pid), or null */ diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/component/ComponentRegistry.java b/core/runtime/android/runtime/src/main/java/org/hapjs/component/ComponentRegistry.java index ec6ed5a0..ae55fd36 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/component/ComponentRegistry.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/component/ComponentRegistry.java @@ -6,23 +6,16 @@ package org.hapjs.component; import android.util.Log; -import com.eclipsesource.v8.V8; -import com.eclipsesource.v8.V8Array; -import org.hapjs.render.jsruntime.JsUtils; + +import org.hapjs.render.jsruntime.IJsEngine; public class ComponentRegistry { private static final String TAG = "ComponentRegistry"; - public static void registerBuiltInComponents(V8 v8) { + public static void registerBuiltInComponents(IJsEngine engine) { String components = ComponentManager.getWidgetListJSONString(); if (components != null) { - V8Array parameters = new V8Array(v8); - try { - parameters.push(components); - v8.executeVoidFunction("registerComponents", parameters); - } finally { - JsUtils.release(parameters); - } + engine.registerComponents(components); } else { Log.e(TAG, "Fail to registerBuiltInComponents, components=" + components); } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/logging/LogProvider.java b/core/runtime/android/runtime/src/main/java/org/hapjs/logging/LogProvider.java index ae97ecd0..d65d42cb 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/logging/LogProvider.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/logging/LogProvider.java @@ -14,21 +14,21 @@ public interface LogProvider { void logCountEvent(String appPackage, String category, String key); - void logCountEvent(String appPackage, String category, String key, Map params); + void logCountEvent(String appPackage, String category, String key, Map params); void logCalculateEvent(String appPackage, String category, String key, long value); void logCalculateEvent( - String appPackage, String category, String key, long value, Map params); + String appPackage, String category, String key, long value, Map params); void logNumericPropertyEvent(String appPackage, String category, String key, long value); void logNumericPropertyEvent( - String appPackage, String category, String key, long value, Map params); + String appPackage, String category, String key, long value, Map params); void logStringPropertyEvent(String appPackage, String category, String key, String value); void logStringPropertyEvent( String appPackage, String category, String key, String value, - Map params); + Map params); } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/logging/RuntimeLogManager.java b/core/runtime/android/runtime/src/main/java/org/hapjs/logging/RuntimeLogManager.java index b5570855..bd131a8e 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/logging/RuntimeLogManager.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/logging/RuntimeLogManager.java @@ -158,6 +158,11 @@ public class RuntimeLogManager { public static final String PARAM_FEATURE_ACTION = "action"; public static final String PARAM_FEATURE_RESULT = "featureResult"; + private static final String KEY_SANDBOX_MESSAGE_SLOW = "sandboxMessageSlow"; + private static final String PARAM_METHOD = "method"; + private static final String PARAM_DATA_SIZE = "dataSize"; + private static final String PARAM_MILLISECOND = "millisecond"; + private Map mStates; private Object mStateLock; @@ -1048,6 +1053,18 @@ public void recordIllegalAccessFile(String pkg, String filePath) { mProvider.logCountEvent(pkg, CATEGORY_APP, KEY_ILLEGAL_ACCESS_FILE, params); } + public void recordSandboxMessageSlow(String pkg, String method, int dataSize, long time) { + if (mProvider == null) { + return; + } + + Map params = new HashMap<>(); + params.put(PARAM_METHOD, method); + params.put(PARAM_DATA_SIZE, String.valueOf(dataSize)); + params.put(PARAM_MILLISECOND, String.valueOf(time)); + mProvider.logCountEvent(pkg, CATEGORY_APP, KEY_SANDBOX_MESSAGE_SLOW, params); + } + private static class DiskUsageTask implements Runnable { private String pkg; diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/RootView.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/RootView.java index 8d97cd9d..c7c7645f 100755 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/RootView.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/RootView.java @@ -9,6 +9,7 @@ import android.app.Activity; import android.content.Context; +import android.content.pm.ActivityInfo; import android.graphics.Color; import android.graphics.Rect; import android.hardware.display.DisplayManager; @@ -37,6 +38,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -48,6 +50,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; + import org.greenrobot.eventbus.EventBus; import org.hapjs.bridge.ApplicationContext; import org.hapjs.bridge.HybridRequest; @@ -91,10 +94,12 @@ import org.hapjs.model.ScreenOrientation; import org.hapjs.model.videodata.VideoCacheManager; import org.hapjs.render.component.CallingComponent; +import org.hapjs.render.jsruntime.AppJsThread; import org.hapjs.render.jsruntime.JsBridge; import org.hapjs.render.jsruntime.JsThread; import org.hapjs.render.jsruntime.JsThreadFactory; import org.hapjs.render.jsruntime.Profiler; +import org.hapjs.render.jsruntime.ProfilerHelper; import org.hapjs.render.skeleton.SkeletonProvider; import org.hapjs.render.skeleton.SkeletonSvgView; import org.hapjs.render.vdom.DocAnimator; @@ -147,7 +152,7 @@ public class RootView extends FrameLayout protected AppInfo mAppInfo; protected String mUrl; protected boolean mInitialized; - JsThread mJsThread; + AppJsThread mJsThread; Handler mHandler = new H(); public VDomActionApplier mVdomActionApplier = new VDomActionApplier(); public CallingComponent mCallingComponent = new CallingComponent(); @@ -218,7 +223,9 @@ public void onJsEventCallback( mJsThread.postFireKeyEvent(data); return; } - mJsThread.postFireEvent(data); + List list = new ArrayList<>(); + list.add(data); + mJsThread.postFireEvent(list); } @Override @@ -353,17 +360,26 @@ public File createFileOnCache(String prefix, String suffix) throws IOException { @Override public void onPageReachTop() { - mJsThread.postPageReachTop(getCurrentPage()); + Page curPage = getCurrentPage(); + if (curPage != null) { + mJsThread.postPageReachTop(curPage.pageId); + } } @Override public void onPageReachBottom() { - mJsThread.postPageReachBottom(getCurrentPage()); + Page curPage = getCurrentPage(); + if (curPage != null) { + mJsThread.postPageReachBottom(curPage.pageId); + } } @Override public void onPageScroll(int scrollTop) { - mJsThread.postPageScroll(getCurrentPage(), scrollTop); + Page curPage = getCurrentPage(); + if (curPage != null) { + mJsThread.postPageScroll(curPage.getPageId(), scrollTop); + } } }; private boolean mFirstRenderActionReceived = false; @@ -428,7 +444,7 @@ public void setDirectBack(boolean directBack) { mDirectBack = directBack; } - public JsThread getJsThread() { + public AppJsThread getJsThread() { return mJsThread; } @@ -925,7 +941,7 @@ public void run() { if (mWaitDevTools && mRequest != null) { mRequest = null; } else { - mJsThread = JsThreadFactory.getInstance().create(getContext()); + mJsThread = (AppJsThread) JsThreadFactory.getInstance().create(getContext()); } mJsThread.getJsChunksManager().initialize(mAppInfo); @@ -960,7 +976,7 @@ public void run() { String css = AppResourcesLoader.getAppCss(getContext(), mPackage); RuntimeLogManager.getDefault().logAppJsLoadEnd(mPackage); - mJsThread.postCreateApplication(content, css, request); + mJsThread.postCreateApplication(content, css); mHasAppCreated.set(true); if (mAndroidViewClient != null) { @@ -1365,7 +1381,7 @@ public void onSendRenderActions(final RenderActionPackage renderActionPackage) { && renderActionPackage.type != RenderActionPackage.TYPE_PRE_CREATE_BODY) { mFirstRenderActionReceived = true; EventBus.getDefault().post(new FirstRenderActionEvent()); - Profiler.recordFirstFrameRendered(System.nanoTime(), mJsThread.getId()); + ProfilerHelper.recordFirstFrameRendered(System.nanoTime(), mJsThread.getId()); } } @@ -1610,7 +1626,7 @@ void onPageChangedInMainThread(int oldIndex, int newIndex, Page oldPage, Page cu if (oldPage == currPage) { mJsThread.postChangeVisiblePage(currPage, true); if (refresh) { - mJsThread.postRefreshPage(currPage); + mJsThread.postRefreshPage(currPage.pageId, currPage.params, currPage.intent); } currPage.setShouldRefresh(false); return; @@ -1710,10 +1726,10 @@ private void backward(Page currPage) { pageEnterListener); mJsThread.postChangeVisiblePage(currPage, true); if (refresh) { - mJsThread.postRefreshPage(currPage); + mJsThread.postRefreshPage(currPage.pageId, currPage.params, currPage.intent); } } else { - mJsThread.postRecreatePage(currPage); + mJsThread.postRecreatePage(currPage.pageId); RuntimeLogManager.getDefault() .logPageRecreateRenderStart(mAppInfo.getPackage(), currPage.getName()); mDocument = new VDocument(createDocComponent(currPage.pageId)); @@ -1799,7 +1815,7 @@ void onMultiWindowPageChangedInMainThreadInMainThread(int oldIndex, int newIndex if (oldPage == currPage) { mJsThread.postChangeVisiblePage(currPage, true); if (refresh) { - mJsThread.postRefreshPage(currPage); + mJsThread.postRefreshPage(currPage.pageId, currPage.params, currPage.intent); } currPage.setShouldRefresh(false); return; @@ -1927,10 +1943,10 @@ private void multiWindowBackward(MultiWindowManager.MultiWindowPageChangeExtraIn } mJsThread.postChangeVisiblePage(backwardTargetPage, true); if (refresh) { - mJsThread.postRefreshPage(backwardTargetPage); + mJsThread.postRefreshPage(backwardTargetPage.pageId, backwardTargetPage.params, backwardTargetPage.intent); } } else { - mJsThread.postRecreatePage(backwardTargetPage); + mJsThread.postRecreatePage(backwardTargetPage.pageId); RuntimeLogManager.getDefault().logPageRecreateRenderStart( mAppInfo.getPackage(), backwardTargetPage.getName()); newDoc = new VDocument(createDocComponent(backwardTargetPage.pageId)); @@ -2337,7 +2353,7 @@ public void handleMessage(Message msg) { break; } case MSG_PAGE_CLEAR_CACHE: { - Page page = (Page) msg.obj; + Page page = mPageManager.getPageById((int) msg.obj); if (page != null) { page.clearCache(); } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/AppJsThread.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/AppJsThread.java new file mode 100644 index 00000000..6398049c --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/AppJsThread.java @@ -0,0 +1,864 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.render.jsruntime; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.text.TextUtils; +import android.util.Log; +import android.widget.Toast; + +import com.eclipsesource.v8.V8RuntimeException; + +import org.hapjs.bridge.EnvironmentManager; +import org.hapjs.bridge.ExtensionManager; +import org.hapjs.bridge.HybridView; +import org.hapjs.chunk.JsChunksManager; +import org.hapjs.common.compat.BuildPlatform; +import org.hapjs.common.executors.AbsTask; +import org.hapjs.common.executors.Executors; +import org.hapjs.common.utils.FrescoUtils; +import org.hapjs.common.utils.LogUtils; +import org.hapjs.common.utils.RouterUtils; +import org.hapjs.component.ComponentRegistry; +import org.hapjs.component.bridge.RenderEventCallback; +import org.hapjs.component.view.keyevent.KeyEventDelegate; +import org.hapjs.component.view.keyevent.KeyEventManager; +import org.hapjs.io.AssetSource; +import org.hapjs.io.FileSource; +import org.hapjs.io.JavascriptReader; +import org.hapjs.io.RpkSource; +import org.hapjs.io.TextReader; +import org.hapjs.logging.RuntimeLogManager; +import org.hapjs.model.AppInfo; +import org.hapjs.model.RoutableInfo; +import org.hapjs.model.ScreenOrientation; +import org.hapjs.render.IdGenerator; +import org.hapjs.render.Page; +import org.hapjs.render.PageManager; +import org.hapjs.render.RenderActionPackage; +import org.hapjs.render.AppResourcesLoader; +import org.hapjs.render.RootView; +import org.hapjs.render.VDomChangeAction; +import org.hapjs.render.action.RenderActionDocument; +import org.hapjs.render.action.RenderActionManager; +import org.hapjs.render.css.CSSParser; +import org.hapjs.render.css.CSSStyleSheet; +import org.hapjs.render.skeleton.DefaultSkeletonProviderImpl; +import org.hapjs.render.skeleton.SkeletonConfigParser; +import org.hapjs.render.skeleton.SkeletonDSLParser; +import org.hapjs.render.skeleton.SkeletonProvider; +import org.hapjs.runtime.ProviderManager; +import org.hapjs.runtime.ResourceConfig; +import org.hapjs.runtime.RuntimeActivity; +import org.hapjs.runtime.inspect.InspectorManager; +import org.hapjs.runtime.inspect.InspectorProvider; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.hapjs.render.RootView.MSG_APP_LOAD_END; + +public class AppJsThread extends JsThread { + + private static final String TAG = "AppJsThread"; + + public static final String INFRASJS_SNAPSHOT_SO_NAME = "infrasjs_snapshot"; + public static final boolean HAS_INFRASJS_SNAPSHOT; + + static { + boolean hasInfraSnapshot; + try { + System.loadLibrary(INFRASJS_SNAPSHOT_SO_NAME); + hasInfraSnapshot = true; + } catch (UnsatisfiedLinkError e) { + hasInfraSnapshot = false; + } + HAS_INFRASJS_SNAPSHOT = hasInfraSnapshot; + } + + private final Context mContext; + private final int mAppId; + + Handler mMainHandler; + PageManager mPageManager; + AppInfo mAppInfo; + RootView mRootView; + private LifecycleCallback mCallback; + + private JsContext mJsContext; + private JsBridgeRegisterHelper mJsBridgeRegisterHelper; + private ExtensionManager mExtensionManager; + private RenderActionManager mRenderActionManager; + private JsChunksManager mJsChunksManager; + + private volatile boolean mBlocked; + private String mSessionLastAppShow; + private boolean mIsTerminateExecution; + private static final int STATE_NONE = -1; + private static final int STATE_RUNTIME_INITED = 0; + private static final int STATE_DESTROYING = 1; + private static final int STATE_DESTROYED = 2; + private int mApplicationState = STATE_NONE; + + final private List APPLICATION_MSGS = Arrays.asList( + H.MSG_CREATE_APPLICATION, + H.MSG_DESTROY_APPLICATION, + H.MSG_CREATE_PAGE, + H.MSG_REFRESH_PAGE, + H.MSG_PAGE_NOT_FOUND, + H.MSG_RECREATE_PAGE, + H.MSG_DESTROY_PAGE, + H.MSG_FIRE_EVENT, + H.MSG_FIRE_KEY_EVENT, + H.MSG_BACK_PRESS, + H.MSG_MENU_PRESS, + H.MSG_FIRE_CALLBACK, + H.MSG_ORIENTATION_CHANGE + ); + + public class H extends JsThread.H { + static final private int MSG_CODE_BASE = 100; + static final private int MSG_INIT_INSPECTOR_JSCONTEXT = MSG_CODE_BASE + 1; + + H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if ((mApplicationState == STATE_NONE || mApplicationState == STATE_DESTROYED) + && Collections.binarySearch(APPLICATION_MSGS, msg.what) >= 0) { + // not handle application msg when runtime is not initialized or is destroying + return; + } + switch (msg.what) { + case MSG_INIT_INSPECTOR_JSCONTEXT: { + onJsContextCreated(); + break; + } + default: + super.handleMessage(msg); + } + } + } + + protected AppJsThread(Context context) { + super("AppJsThread"); + + mContext = context; + mAppId = IdGenerator.generateAppId(); + mRenderActionManager = new RenderActionManager(); + Log.d(TAG, "AppJsThread create"); + Message.obtain(mHandler, H.MSG_INIT).sendToTarget(); + } + + @Override + protected H createHandler() { + return new H(getLooper()); + } + + public void attach(Handler mainHandler, + AppInfo appInfo, + RootView rootView, + LifecycleCallback lifecycleCallback, + PageManager pageManager) { + mMainHandler = mainHandler; + mAppInfo = appInfo; + mRootView = rootView; + mCallback = lifecycleCallback; + mPageManager = pageManager; + + Message.obtain(mHandler, H.MSG_ATTACH).sendToTarget(); + } + + @Override + protected void onInit() { + SandboxProvider provider = ProviderManager.getDefault().getProvider(SandboxProvider.NAME); + mNative = provider.createNativeImpl(mRenderActionManager, + frameTimeNanos -> { + mHandler.post(new Runnable() { + @Override + public void run() { + mEngine.onFrameCallback(frameTimeNanos); + } + }); + }); + + if (provider.isSandboxEnabled()) { + ParcelFileDescriptor[][] channelDescriptors = SandboxProcessLauncher.getInstance().getChannelDescriptor(); + ParcelFileDescriptor[] positiveDescriptors = channelDescriptors[0]; + ParcelFileDescriptor[] passiveDescriptors = channelDescriptors[1]; + + mEngine = provider.createAppChannelSender(positiveDescriptors[0], positiveDescriptors[1], mHandler); + provider.createAppChannelReceiver(passiveDescriptors[0], passiveDescriptors[1], mNative); + } else { + mJsContext = new JsContext(this); + mJsBridgeRegisterHelper = new JsBridgeRegisterHelper(mContext, mJsContext, this, getId(), mNative); + mJsBridgeRegisterHelper.registerBridge(); + mEngine = provider.createEngineImpl(mJsContext, + new InspectorNativeCallbackImpl(), + e -> processV8Exception(e), + frameTimeNanos -> mJsBridgeRegisterHelper.onFrameCallback(frameTimeNanos)); + } + + InspectorManager.getInstance().notifyJsThreadReady(this); + + createRuntime(); + initInfras(); + + mExtensionManager = new ExtensionManager(this, mContext); + mExtensionManager.onRuntimeInit(mEngine); + + ComponentRegistry.registerBuiltInComponents(mEngine); + FrescoUtils.initialize(mContext); + } + + private void initInfras() { + executeVoidFunction(new Object[]{"initInfras", null}); + } + + public boolean isApplicationDebugEnabled() { + return (mContext.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) + == ApplicationInfo.FLAG_DEBUGGABLE; + } + + @Override + protected void onAttach(Object msgObj) { + mRenderActionManager.attach(mRootView); + mExtensionManager.attach(mRootView, mPageManager, mAppInfo); + mEngine.setQuickAppPkg(mAppInfo.getPackage()); + mNative.setQuickAppPkg(mAppInfo.getPackage()); + ((JavaNativeImpl) mNative).attachView(mRootView); + if (mJsBridgeRegisterHelper != null) { + mJsBridgeRegisterHelper.attach(mAppInfo.getPackage()); + } + + mEngine.onAttach(EnvironmentManager.buildRegisterJavascript(mContext, mAppInfo), mAppInfo.getPackage()); + + if (mCallback != null) { + mCallback.onRuntimeCreate(); + } + mExtensionManager.onRuntimeCreate(mEngine, mAppInfo); + } + + public ExtensionManager getBridgeManager() { + return mExtensionManager; + } + + public RenderActionManager getRenderActionManager() { + return mRenderActionManager; + } + + private void createRuntime() { + try { + if (!HAS_INFRASJS_SNAPSHOT) { + String script = null; + if (isApplicationDebugEnabled()) { + File file = new File(Environment.getExternalStorageDirectory(), "quickapp/assets/js/infras.js"); + script = JavascriptReader.get().read(new FileSource(file)); + if (script != null) { + Toast.makeText(mContext, "load infras.js from sdcard, please remove quickapp folder in sdcard if you are not dev", Toast.LENGTH_SHORT).show(); + Log.d(TAG, "load infras.js from sdcard"); + } + } + + RuntimeLogManager.getDefault().logJsThreadTaskStart(mContext.getPackageName(), RuntimeLogManager.KEY_INFRAS_JS_LOAD); + if (script == null) { + script = readInfrasAsset(); + } + + if (script == null) { + Log.e(TAG, "failed to read js/infras.js"); + String platform = ResourceConfig.getInstance().getPlatform(); + if (!TextUtils.equals(platform, mContext.getPackageName())) { + script = readInfrasAsset(platform); + } + } + RuntimeLogManager.getDefault().logJsThreadTaskEnd(mContext.getPackageName(), RuntimeLogManager.KEY_INFRAS_JS_LOAD); + executeVoidScript(new Object[]{script, "infras.js", 0}); + } + mApplicationState = STATE_RUNTIME_INITED; + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } + } + + private String readInfrasAsset() { + return JavascriptReader.get().read(new AssetSource(mContext, "js/infras.js") { + @Override + public InputStream open() throws IOException { + try { + return super.open(); + } catch (IOException e) { + String host = mContext.getPackageName(); + String platform = ResourceConfig.getInstance().getPlatform(); + RuntimeLogManager.getDefault().logResourceNotFound(host, platform, "js/infras.js", e); + + throw e; + } + } + }); + } + + private String readInfrasAsset(String platform) { + Log.i(TAG, "try to load infras.js from " + platform); + try { + Context platformContext = mContext.createPackageContext(platform, 0); + return JavascriptReader.get().read(new AssetSource(platformContext, "js/infras.js")); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "failed to createPackageContext for " + platform, e); + } + return null; + } + + public void postNotifyConfigurationChanged(Page page, String type) { + if (page == null) { + return; + } + postNotifyConfigurationChanged(page.pageId, type); + } + + public void postUpdateLocale(Locale locale, Map resources) { + if (locale == null || resources == null) { + return; + } + postUpdateLocale(locale.getLanguage(), locale.getCountry(), resources); + } + + public void postCreateApplication(String jsContent, String css) { + Object[] params = new Object[]{mAppId, jsContent, css, mAppInfo.getMetaInfo()}; + Message.obtain(mHandler, H.MSG_CREATE_APPLICATION, params) + .sendToTarget(); + } + + public void postOnRequestApplication() { + Message.obtain(mHandler, H.MSG_ON_REQUEST_APPLICATION, new Object[]{mAppId}).sendToTarget(); + } + + public void postOnShowApplication() { + Message.obtain(mHandler, H.MSG_ON_SHOW_APPLICATION, new Object[]{mAppId}).sendToTarget(); + } + + public void postOnHideApplication() { + Message.obtain(mHandler, H.MSG_ON_HIDE_APPLICATION, new Object[]{mAppId}).sendToTarget(); + } + + public void postOnMenuButtonPress(Page page, HybridView.OnKeyUpListener onKeyUpIsConsumption) { + Object[] params = new Object[]{page, onKeyUpIsConsumption}; + mHandler.obtainMessage(H.MSG_ON_MENU_BUTTON_PRESS, params).sendToTarget(); + } + + public void postPageNotFound(Page page) { + Message.obtain(mHandler, H.MSG_PAGE_NOT_FOUND, new Object[]{mAppId, page.getTargetPageUri(), page.getPageId()}).sendToTarget(); + } + + public void postBackPress(Page page) { + mHandler.obtainMessage(H.MSG_BACK_PRESS, page).sendToTarget(); + } + + @Override + protected boolean backPress(Object msgObj) { + Page page = (Page) msgObj; + boolean consumed = false; + if (page != null && page.getState() >= Page.STATE_CREATED) { + consumed = super.backPress(new Object[]{page.pageId}); + } + if (!consumed && null != mMainHandler) { + mMainHandler.sendEmptyMessage(RootView.MSG_BACK_PRESS); + } + return consumed; + } + + @Override + protected boolean menuButtonPressPage(Object msgObj) { + Page page = (Page) ((Object[]) msgObj)[0]; + HybridView.OnKeyUpListener onKeyUpIsConsumption = (HybridView.OnKeyUpListener) ((Object[]) msgObj)[1]; + boolean consumed = false; + if (page != null && page.getState() >= Page.STATE_CREATED) { + consumed = super.menuButtonPressPage(new Object[]{page.pageId}); + // TODO: onKeyUpIsConsumption.consume(false);? + } + onKeyUpIsConsumption.consume(consumed); + return consumed; + } + + @Override + protected boolean firePageKeyEvent(JsEventCallbackData data) { + Page page = mPageManager.getPageById(data.pageId); + if (page == null) { + return false; + } + + boolean consumed = super.firePageKeyEvent(data); + + if (null != mMainHandler) { + Object hashCode = data.params.get(KeyEventDelegate.KEY_HASHCODE); + if (hashCode instanceof Integer) { + KeyEventManager.getInstance().injectKeyEvent(consumed, mRootView, (Integer) hashCode); + } + } + + return consumed; + } + + public void postMenuPress(Page page) { + mHandler.obtainMessage(H.MSG_MENU_PRESS, new Object[]{page}).sendToTarget(); + } + + @Override + protected boolean onMenuPress(Object msgObj) { + Page page = (Page) ((Object[]) msgObj)[0]; + boolean consumed = false; + if (page != null && page.getState() >= Page.STATE_CREATED) { + consumed = super.onMenuPress(new Object[]{page.pageId}); + } + + if (!consumed) { + mMainHandler.obtainMessage(RootView.MSG_MENU_PRESS, page).sendToTarget(); + } + return consumed; + } + + public void postOrientationChange(Page page, ScreenOrientation screenOrientation) { + mHandler.obtainMessage(H.MSG_ORIENTATION_CHANGE, new Object[]{page, screenOrientation}).sendToTarget(); + } + + @Override + protected void onOrientationChange(Object msgObj) { + Page page = (Page) ((Object[]) msgObj)[0]; + ScreenOrientation screenOrientation = (ScreenOrientation) ((Object[]) msgObj)[1]; + if (page != null && page.getState() >= Page.STATE_CREATED) { + super.onOrientationChange(new Object[]{page.pageId, screenOrientation.getOrientation(), screenOrientation.getAngel()}); + } + } + + public void postChangeVisiblePage(Page page, boolean visible) { + if (page != null) { + // 当thread被block的时候,activity已经stop,不应该发送visible=true事件, + // 仅发送visible=false事件,在activity变为start时会重新发送visible=true事件 + if (visible && page.getState() == Page.STATE_INITIALIZED && !mBlocked) { + if (page.shouldReload()) { + RouterUtils.replace(mPageManager, page.getRequest()); + return; + } + requestFocus(); + page.setState(Page.STATE_VISIBLE); + postExecuteScript("changeVisiblePage(" + page.pageId + ", " + JsUtils.toJsBoolean(visible) + ");"); + Log.d(TAG, "show page: " + page.getName()); + + String session = System.getProperty(RuntimeActivity.PROP_SESSION); + if (!TextUtils.equals(session, mSessionLastAppShow)) { + mSessionLastAppShow = session; + RuntimeLogManager.getDefault().logAppShow(mAppInfo.getPackage(), mAppInfo.getVersionCode()); + } + + Page referrer = page.getReferrer(); + String referrerName = referrer == null ? null : referrer.getName(); + RuntimeLogManager.getDefault().logPageViewStart( + mAppInfo.getPackage(), page.getName(), referrerName); + } else if (!visible && page.getState() == Page.STATE_VISIBLE) { + page.setState(Page.STATE_INITIALIZED); + postExecuteScript("changeVisiblePage(" + page.pageId + ", " + JsUtils.toJsBoolean(visible) + ");"); + Log.d(TAG, "hide page: " + page.getName()); + RuntimeLogManager.getDefault().logPageViewEnd(mAppInfo.getPackage(), page.getName()); + } else { + Log.i(TAG, "Skip page visible change: page=" + page + ", visible=" + visible + ", mBlocked=" + mBlocked); + } + } + } + + private void requestFocus() { + RootView rootView = mRootView; + if (!BuildPlatform.isTV() || rootView == null || rootView.hasFocus()) { + return; + } + rootView.post(rootView::requestFocus); + } + + public void postInitializePage(int pageId) { + Page page = mPageManager.getPageById(pageId); + if (page != null) { + page.setState(Page.STATE_INITIALIZED); + Message.obtain(mMainHandler, RootView.MSG_PAGE_INITIALIZED, page).sendToTarget(); + } else { + Log.w(TAG, "postInitializePage: page is null"); + } + } + + public void postDestroyPage(Page page) { + if (page.getState() > Page.STATE_NONE) { + mHandler.obtainMessage(H.MSG_DESTROY_PAGE, new Object[]{page.pageId}).sendToTarget(); + page.setState(Page.STATE_NONE); + } else { + Log.d(TAG, "skip page destroy: " + page.toString()); + } + } + + public void loadPage(final Page page) { + RuntimeLogManager.getDefault().logPageLoadStart( + mAppInfo.getPackage(), page.getName()); + + mMainHandler.obtainMessage(RootView.MSG_LOAD_PAGE_JS_START, page).sendToTarget(); + Executors.io().execute(new AbsTask() { + @Override + protected String[] doInBackground() { + mJsChunksManager.registerPageChunks(page); + String pageJs = AppResourcesLoader.getPageJs(mContext, mAppInfo.getPackage(), page); + String pageCss = AppResourcesLoader.getPageCss(mContext, mAppInfo.getPackage(), page); + parseStyleSheets(pageCss, page); + return new String[]{pageJs, pageCss}; + } + + @Override + protected void onPostExecute(String[] contents) { + RuntimeLogManager.getDefault().logPageLoadEnd( + mAppInfo.getPackage(), page.getName()); + int result = TextUtils.isEmpty(contents[0]) ? Page.JS_LOAD_RESULT_FAIL : Page.JS_LOAD_RESULT_SUCC; + page.setLoadJsResult(result); + mMainHandler.obtainMessage(RootView.MSG_LOAD_PAGE_JS_FINISH, page).sendToTarget(); + + RoutableInfo routableInfo = page.getRoutableInfo(); + final String jsUri = routableInfo.getUri(); + postCreatePage(page, contents[0], jsUri, contents[1]); + Log.d(TAG, "loadPage onPostExecute uri=" + jsUri + " result=" + result); + } + }); + } + + private void parseStyleSheets(String css, Page page) { + if (TextUtils.isEmpty(css)) { + return; + } + Executors.io().execute(new AbsTask() { + @Override + protected Void doInBackground() { + RuntimeLogManager.getDefault().logAsyncThreadTaskStart(mAppInfo.getPackage(), "parseStyleSheets"); + try { + org.hapjs.common.json.JSONObject styles = new org.hapjs.common.json.JSONObject(css); + org.hapjs.common.json.JSONArray styleList = styles.getJSONArray("list"); + int N = styleList.length(); + for (int i = 0; i < N; i++) { + org.hapjs.common.json.JSONObject styleSheetPlain = styleList.getJSONObject(i); + CSSStyleSheet styleSheet = CSSParser.parseCSSStyleSheet(styleSheetPlain); + + // 注册样式表 + RenderActionDocument document = mRenderActionManager.getOrCreateDocument(page.getPageId()); + if (isVue()) { + document.registerDocLevelStyleSheet(styleSheet.getStyleObjectId(), styleSheet); + } else { + document.registerStyleSheet(styleSheet.getStyleObjectId(), styleSheet); + } + } + } catch (JSONException e) { + Log.e(TAG, "parse css failed: " + e.getMessage()); + } + RuntimeLogManager.getDefault().logAsyncThreadTaskEnd(mAppInfo.getPackage(), "parseStyleSheets"); + return null; + } + }); + } + + private boolean isVue() { + JSONObject config = mRootView.getAppInfo().getConfigInfo().getData(); + JSONObject dsl = config.optJSONObject("dsl"); + if (dsl != null) { + String dslName = dsl.optString("name"); + return TextUtils.equals(dslName, "vue"); + } + return false; + } + + private void postCreatePage(Page page, String js, String uri, String css) { + Object[] params = new Object[]{page, js, uri, css}; + Message.obtain(mHandler, H.MSG_CREATE_PAGE, params).sendToTarget(); + page.setState(Page.STATE_CREATED); + } + + protected void createPage(Object msgObj) { + Page page = (Page) ((Object[]) msgObj)[0]; + String js = (String) ((Object[]) msgObj)[1]; + String css = (String) ((Object[]) msgObj)[2]; + + preCreateSkeleton(page); + preCreateBody(page.pageId); + super.createPage(new Object[]{mAppId, page.pageId, js, css, page.params, page.intent, page.meta}); + Message.obtain(mMainHandler, MSG_APP_LOAD_END).sendToTarget(); + } + + private void preCreateSkeleton(Page page) { + if (mAppInfo != null && mContext != null && page != null) { + SkeletonProvider skeletonProvider = ProviderManager.getDefault().getProvider(SkeletonProvider.NAME); + if (skeletonProvider == null) { + // If you have not added a customized provider, use the default (enable function)) + skeletonProvider = new DefaultSkeletonProviderImpl(mContext); + } + if (skeletonProvider.isSkeletonEnable(mAppInfo.getPackage())) { + Executors.io().execute(new AbsTask() { + @Override + protected JSONObject doInBackground() { + RpkSource skeletonConfigSource = new RpkSource(mContext, mAppInfo.getPackage(), "skeleton/config.json"); + String skeletonConfig = TextReader.get().read(skeletonConfigSource); + JSONObject parseResult = null; + if (!TextUtils.isEmpty(skeletonConfig)) { + String skFileName = SkeletonConfigParser.getSkeletonFileName(page, skeletonConfig); + Log.i(TAG, "LOG_SKELETON parse skeleton config, current page is " + page.getName()); + if (!TextUtils.isEmpty(skFileName)) { + // read and parse sk file + String skFilePathName = "skeleton/page/" + skFileName; + RpkSource skFileSource = new RpkSource(mContext, mAppInfo.getPackage(), skFilePathName); + InputStream inputStream = null; + try { + Log.i(TAG, "LOG_SKELETON parse sk file, path = " + skFilePathName); + inputStream = skFileSource.open(); + parseResult = SkeletonDSLParser.parse(inputStream); + } catch (Exception e) { + Log.e(TAG, "LOG_SKELETON parse sk file fail, ", e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException ioe) { + Log.e(TAG, "LOG_SKELETON close sk inputStream fail, ", ioe); + } + } + } + } else { + Log.i(TAG, "LOG_SKELETON no matching sk file for current page"); + } + } else { + Log.i(TAG, "LOG_SKELETON skeleton config file is empty"); + } + return parseResult; + } + + @Override + protected void onPostExecute(JSONObject parseResult) { + if (mRenderActionManager != null) { + mRenderActionManager.showSkeleton(mAppInfo.getPackage(), parseResult); + } + } + }); + } else { + Log.i(TAG, "LOG_SKELETON prevent render skeleton because not enable"); + } + } + } + + // Debugger: When paused at any breakpoint, v8.terminateExecution will terminate javascript + // execution and cause JsThread throwing exception which is no need for processing. + private boolean isTerminateExecution(String exception_message) { + return mIsTerminateExecution && "null".equals(exception_message); + } + + @Override + protected void recreatePage(Object msgObj) { + int pageId = (int) ((Object[]) msgObj)[0]; + // page VDoc is destroyed, apply the cached render actions will throw exception + mMainHandler.obtainMessage(RootView.MSG_PAGE_CLEAR_CACHE, pageId).sendToTarget(); + preCreateBody(pageId); + super.recreatePage(msgObj); + } + + @Override + protected void refreshPage(Object msgObj) { + int pageId = (int) ((Object[]) msgObj)[0]; + preCreateBody(pageId); + super.refreshPage(msgObj); + } + + @Override + protected void notifyPageNotFound(Object msgObj) { + int pageId = (int) ((Object[]) msgObj)[2]; + preCreateBody(pageId); + super.notifyPageNotFound(msgObj); + } + + private void preCreateBody(int pageId) { + VDomChangeAction action = new VDomChangeAction(); + action.action = VDomChangeAction.ACTION_PRE_CREATE_BODY; + action.pageId = pageId; + RenderActionPackage renderActionPackage = new RenderActionPackage(pageId, + RenderActionPackage.TYPE_PRE_CREATE_BODY); + renderActionPackage.renderActionList.add(action); + mRenderActionManager.sendRenderActions(renderActionPackage); + } + + @Override + protected void destroyPage(Object msgObj) { + int pageId = (int) ((Object[]) msgObj)[0]; + super.destroyPage(msgObj); + if (mJsBridgeRegisterHelper != null) { + mJsBridgeRegisterHelper.destroyPage(pageId); + } + mRenderActionManager.destroyPage(pageId); + } + + private void postDestroyApplication() { + Message.obtain(mHandler, H.MSG_DESTROY_APPLICATION, new Object[]{mAppId}).sendToTarget(); + } + + @Override + protected void destroyApplication(Object msgObj) { + try { + super.destroyApplication(msgObj); + } finally { + mApplicationState = STATE_DESTROYED; + } + } + + public void postFireEvent(final int pageId, final List datas, final RenderEventCallback.EventPostListener listener) { + post(new Runnable() { + @Override + public void run() { + fireEvent(new Object[]{pageId, datas, listener}); + } + }); + } + + protected void fireEvent(Object msgObj) { + RenderEventCallback.EventPostListener listener = (RenderEventCallback.EventPostListener) ((Object[]) msgObj)[2]; + try { + super.fireEvent(msgObj); + } finally { + if (listener != null) { + listener.finish(); + } + } + } + + public void postInitInspectorJsContext() { + Message.obtain(mHandler, H.MSG_INIT_INSPECTOR_JSCONTEXT).sendToTarget(); + } + + private void onJsContextCreated() { + InspectorManager.getInspector().onJsContextCreated(mEngine); + } + + public void block(long delay) { + super.block(delay); + mBlocked = true; + } + + @Override + public void unblock() { + super.unblock(); + mBlocked = false; + } + + public boolean isBlocked() { + return mBlocked; + } + + public void shutdown(long delay) { + Log.d(TAG, "shutdown: " + this); + unblock(); + if (mApplicationState == STATE_RUNTIME_INITED) { + mApplicationState = STATE_DESTROYING; + Page page = null; + if (null != mPageManager) { + page = mPageManager.getCurrPage(); + } + if (page != null) { + postChangeVisiblePage(page, false); + postDestroyPage(page); + } + postDestroyApplication(); + } + mHandler.sendEmptyMessageDelayed(H.MSG_SHUTDOWN, delay); + } + + @Override + protected void doShutdown() { + InspectorManager.getInspector().onJsContextDispose(mEngine); + if (mCallback != null) { + mCallback.onRuntimeDestroy(); + } + mRenderActionManager.release(); + if (mJsBridgeRegisterHelper != null) { + mJsBridgeRegisterHelper.unregister(); + } + mExtensionManager.dispose(); + quit(); + super.doShutdown(); + Log.d(TAG, "shutdown finish: " + this); + } + + public void processV8Exception(Exception ex) { + if (isTerminateExecution(ex.getMessage())) { + mIsTerminateExecution = false; + } else { + String msg = LogUtils.getStackTrace(ex); + Log.e(TAG, msg); + InspectorManager.getInspector().onConsoleMessage( + InspectorProvider.CONSOLE_ERROR, msg); + Message.obtain(mMainHandler, RootView.MSG_USER_EXCEPTION, ex).sendToTarget(); + } + notifyAppError(ex); + } + + @Override + protected void terminateExecution() { + mIsTerminateExecution = true; + super.terminateExecution(); + } + + private void notifyAppError(Exception ex) { + String message = ex.getMessage(); + if (NotifyAppErrorHelper.isExceptionFromOnError(message)) { + Log.i(TAG, "Exception from onError()"); + return; + } + String stack = LogUtils.getStackTrace(ex); + String script = NotifyAppErrorHelper.generateScript(mAppId, message, stack); + postExecuteScript(script); + } + + public AppInfo getAppInfo() { + return mAppInfo; + } + + public synchronized JsChunksManager getJsChunksManager() { + if (mJsChunksManager == null) { + mJsChunksManager = new JsChunksManager(this); + } + return mJsChunksManager; + } + + private class InspectorNativeCallbackImpl implements V8InspectorNative.InspectorNativeCallback { + @Override + public void inspectorResponse(int sessionId, int callId, String message) { + InspectorManager.getInspector().inspectorResponse(sessionId, callId, message); + } + + @Override + public void inspectorSendNotification(int sessionId, int callId, String message) { + InspectorManager.getInspector().inspectorSendNotification(sessionId, callId, message); + } + + @Override + public void inspectorRunMessageLoopOnPause(int contextGroupId) { + InspectorManager.getInspector().inspectorRunMessageLoopOnPause(contextGroupId); + } + + @Override + public void inspectorQuitMessageLoopOnPause() { + InspectorManager.getInspector().inspectorQuitMessageLoopOnPause(); + } + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/IJavaNative.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/IJavaNative.java new file mode 100644 index 00000000..ff3675dc --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/IJavaNative.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.render.jsruntime; + +import java.util.Map; +import org.hapjs.bridge.Response; + +// Object representing the native/java side +public interface IJavaNative { + void setQuickAppPkg(String pkg); + String getQuickAppPkg(); + + void callNative(int pageId, String argsString); + int getViewId(int ref); + String readDebugAsset(String path); + void onKeyEventCallback(boolean consumed, int hash); + Response invoke(String feature, String action, Object rawParams, String callback, int instanceId); + + void routerBack(); + void routerClear(); + void routerReplace(String uri, Map params); + void routerPush(String uri, Map params); + + void inspectorResponse(int sessionId, int callId, String message); + void inspectorSendNotification(int sessionId, int callId, String message); + void inspectorRunMessageLoopOnPause(int contextGroupId); + void inspectorQuitMessageLoopOnPause(); + + boolean profilerIsEnabled(); + void profilerRecord(String msg, long threadId); + void profilerSaveProfilerData(String data); + void profilerTimeEnd(String msg); + + void onV8Exception(StackTraceElement[] stack, String msg); + + void requestAnimationFrameNative(); +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/IJsEngine.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/IJsEngine.java new file mode 100644 index 00000000..7f58ecc5 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/IJsEngine.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.render.jsruntime; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.hapjs.render.Page; +import org.json.JSONObject; + +// Object representing the V8/js side +public interface IJsEngine { + void setQuickAppPkg(String pkg); + String getQuickAppPkg(); + + void block(); + void unblock(); + void onAttach(String environmentScript, String pkg); + void notifyConfigurationChanged(int pageId, String type); + void updateLocale(String language, String country, Map resourcesJson); + void registerBundleChunks(String content); + void createApplication(int appId, String js, String css, String metaInfo); + void onRequestApplication(int appId); + void onShowApplication(int appId); + void onHideApplication(int appId); + boolean backPressPage(int pageId); + boolean menuButtonPressPage(int pageId); + boolean keyPressPage(int pageId, Map params); + boolean menuPressPage(int pageId); + void orientationChangePage(int pageId, String orientation, float angle); + void executeVoidScript(String script, String scriptName, int lineNumber); + void executeScript(String script, String scriptName, int lineNumber); + void executeVoidFunction(String func, Object[] params); + String executeObjectScriptAndStringify(String script); + void createPage(int appId, int pageId, String js, String css, Map params, Map intent, Map meta); + void recreatePage(int pageId); + void refreshPage(int pageId, Map params, Map intent); + void notifyPageNotFound(int appId, String pageUri); + void destroyPage(int pageId); + void destroyApplication(int appId); + void fireEvent(List datas); + void fireKeyEvent(JsThread.JsEventCallbackData data); + void fireCallback(JsThread.JsMethodCallbackData data); + void onFoldCard(int s, boolean f); + void reachPageTop(int pageId); + void reachPageBottom(int pageId); + void pageScroll(int pageId, int scrollTop); + void registerComponents(String builtInComponents); + void terminateExecution(); + void shutdown(); + + void inspectorHandleMessage(long ptr, int sessionId, String message); + long inspectorInit(boolean autoEnable, int sessionId); + void inspectorSetV8Context(long ptr, int isJsContextReCreated); + void inspectorDisposeV8Context(long ptr); + void inspectorDestroy(long ptr); + void inspectorBeginLoadJsCode(String uri, String content); + void inspectorEndLoadJsCode(String uri); + String inspectorExecuteJsCode(long ptr, String jsCode); + void inspectorFrontendReload(long ptr); + + void onFrameCallback(long frameTimeNanos); +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JavaNativeImpl.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JavaNativeImpl.java new file mode 100644 index 00000000..8b469711 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JavaNativeImpl.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.render.jsruntime; + +import android.os.Environment; +import android.util.Log; +import android.view.Choreographer; +import java.io.File; +import java.util.Map; +import org.hapjs.bridge.HybridRequest; +import org.hapjs.bridge.Response; +import org.hapjs.common.executors.Executors; +import org.hapjs.common.utils.RouterUtils; +import org.hapjs.common.utils.ViewIdUtils; +import org.hapjs.component.view.keyevent.KeyEventManager; +import org.hapjs.io.FileSource; +import org.hapjs.io.JavascriptReader; +import org.hapjs.render.PageNotFoundException; +import org.hapjs.render.RootView; +import org.hapjs.render.action.RenderActionManager; +import org.hapjs.runtime.inspect.InspectorManager; + +public class JavaNativeImpl implements IJavaNative { + private static final String TAG = "NativeImpl"; + + private RenderActionManager mRenderActionManager; + private JsEngineImpl.FrameCallback mFrameCallback; + protected RootView mRootView; + + public JavaNativeImpl(RenderActionManager renderActionManager, JsEngineImpl.FrameCallback frameCallback) { + mRenderActionManager = renderActionManager; + mFrameCallback = frameCallback; + } + + public void attachView(RootView rootView) { + mRootView = rootView; + } + + @Override + public void setQuickAppPkg(String pkg) { + } + + @Override + public String getQuickAppPkg() { + return mRootView == null ? null : mRootView.getPackage(); + } + + @Override + public void callNative(int pageId, String argsString) { + mRenderActionManager.callNative(pageId, argsString); + } + + @Override + public int getViewId(int ref) { + return ViewIdUtils.getViewId(ref); + } + + @Override + public String readDebugAsset(String path) { + String script = null; + if (path.startsWith("/js")) { + String newPath = path.replace("/js", ""); + File file = new File(Environment.getExternalStorageDirectory(), "quickapp/assets/js" + newPath); + script = JavascriptReader.get().read(new FileSource(file)); + if (script != null) { + Log.d(TAG, String.format("load %s from sdcard success", file.getAbsolutePath())); + } + } + return script; + } + + @Override + public void onKeyEventCallback(boolean consumed, int hashcode) { + KeyEventManager.getInstance().injectKeyEvent(consumed, mRootView, hashcode); + } + + @Override + public Response invoke(String feature, String action, Object rawParams, String callback, int instanceId) { + return mRootView.getJsThread().getBridgeManager().invoke(feature, action, rawParams, callback, instanceId); + } + + @Override + public void routerBack() { + RouterUtils.back(mRootView.getContext(), mRootView.getPageManager()); + } + + @Override + public void routerClear() { + mRootView.getPageManager().clear(); + } + + @Override + public void routerReplace(String uri, Map params) { + HybridRequest request = new HybridRequest.Builder().pkg(mRootView.getPackage()).uri(uri).params(params).build(); + RouterUtils.replace(mRootView.getPageManager(), request); + } + + @Override + public void routerPush(String uri, Map params) { + HybridRequest request = new HybridRequest.Builder().pkg(mRootView.getPackage()).uri(uri).params(params).build(); + try { + RouterUtils.push(mRootView.getPageManager(), request); + } catch (PageNotFoundException ex) { + mRootView.getJsThread().processV8Exception(ex); + } + } + + @Override + public void inspectorResponse(int sessionId, int callId, String message) { + InspectorManager.getInspector().inspectorResponse(sessionId, callId, message); + } + + @Override + public void inspectorSendNotification(int sessionId, int callId, String message) { + InspectorManager.getInspector().inspectorSendNotification(sessionId, callId, message); + } + + @Override + public void inspectorRunMessageLoopOnPause(int contextGroupId) { + InspectorManager.getInspector().inspectorRunMessageLoopOnPause(contextGroupId); + } + + @Override + public void inspectorQuitMessageLoopOnPause() { + InspectorManager.getInspector().inspectorQuitMessageLoopOnPause(); + } + + @Override + public boolean profilerIsEnabled() { + return ProfilerHelper.profilerIsEnabled(); + } + + @Override + public void profilerRecord(String msg, long threadId) { + ProfilerHelper.profilerRecord(msg, threadId); + } + + @Override + public void profilerSaveProfilerData(String data) { + ProfilerHelper.profilerSaveProfilerData(data); + } + + @Override + public void profilerTimeEnd(String msg) { + ProfilerHelper.profilerTimeEnd(msg); + } + + @Override + public void onV8Exception(StackTraceElement[] stack, String msg) { + Exception ex = new Exception("V8Exception: " + msg); + ex.setStackTrace(stack); + mRootView.getJsThread().processV8Exception(ex); + } + + @Override + public void requestAnimationFrameNative() { + Executors.ui().execute(() -> { + if (mFrameCallback != null) { + Choreographer choreographer = Choreographer.getInstance(); + choreographer.postFrameCallback(frameTimeNanos -> mFrameCallback.onFrameCallback(frameTimeNanos)); + } + }); + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridge.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridge.java index bc78afc3..d233378a 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridge.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridge.java @@ -5,6 +5,8 @@ package org.hapjs.render.jsruntime; +import android.content.Context; +import android.content.pm.ApplicationInfo; import android.net.Uri; import android.os.Environment; import android.text.TextUtils; @@ -19,8 +21,6 @@ import org.hapjs.common.utils.UriUtils; import org.hapjs.common.utils.ViewIdUtils; import org.hapjs.io.AssetSource; -import org.hapjs.io.FileSource; -import org.hapjs.io.JavascriptReader; import org.hapjs.io.RpkSource; import org.hapjs.io.TextReader; import org.hapjs.render.RenderActionPackage; @@ -31,20 +31,17 @@ public class JsBridge { private static final String TAG = "JsBridge"; - private JsThread mJsThread; - private RenderActionManager mRenderActionManager; + private Context mContext; + private IJavaNative mNative; + private String mPkg; - public JsBridge(JsThread jsThread, RenderActionManager renderActionManager) { - mJsThread = jsThread; - mRenderActionManager = renderActionManager; + public JsBridge(Context context, IJavaNative javaNative) { + mContext = context; + mNative = javaNative; } - public void attach(JsBridgeCallback callback) { - mRenderActionManager.attach(callback); - } - - void sendRenderActions(RenderActionPackage renderActionPackage) { - mRenderActionManager.sendRenderActions(renderActionPackage); + public void attach(String pkg) { + mPkg = pkg; } public void register(final V8 v8) { @@ -67,7 +64,7 @@ public void invoke(V8Object v8Object, final V8Array v8Array) { final String argsString = v8Array.getString(1); JsUtils.release(v8Array); - mRenderActionManager.callNative(pageId, argsString); + mNative.callNative(pageId, argsString); } }, "callNative"); @@ -79,7 +76,7 @@ public Object invoke(V8Object object, V8Array parameters) { return null; } int ref = Integer.parseInt(parameters.get(0).toString()); - return ViewIdUtils.getViewId(ref); + return mNative.getViewId(ref); } }, "getPageElementViewId"); @@ -96,13 +93,11 @@ public Object invoke(V8Object object, V8Array parameters) { @Nullable private Object readResource(String source) { Uri uri = UriUtils.computeUri(source); - RootView rootView = mJsThread.mRootView; if (uri == null) { // 访问 rpk 包中的资源 String resource = TextReader.get() - .read(new RpkSource(rootView.getContext(), rootView.getPackage(), - source)); + .read(new RpkSource(mContext, mPkg, source)); if (resource == null) { Log.w(TAG, "failed to read resource. source=" + source); } @@ -122,24 +117,13 @@ private Object readResource(String source) { return null; } String script = null; - if (mJsThread.isApplicationDebugEnabled()) { + if (isApplicationDebugEnabled()) { if (path.startsWith("/js")) { - String newPath = path.replace("/js", ""); - File file = - new File(Environment.getExternalStorageDirectory(), - "quickapp/assets/js" + newPath); - script = JavascriptReader.get().read(new FileSource(file)); - if (script != null) { - Log.d(TAG, String.format("load %s from sdcard success", - file.getAbsolutePath())); - } + mNative.readDebugAsset(path); } } if (script == null) { - script = - TextReader.get() - .read(new AssetSource(rootView.getContext(), - path.replaceFirst("/", ""))); + script = TextReader.get().read(new AssetSource(mContext/*rootView.getContext()*/, path.replaceFirst("/", ""))); if (script == null) { Log.w(TAG, "failed to read script. source=" + source + ", uri=" + uri); } @@ -152,6 +136,11 @@ private Object readResource(String source) { } } + private boolean isApplicationDebugEnabled() { + return (mContext.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) + == ApplicationInfo.FLAG_DEBUGGABLE; + } + public interface JsBridgeCallback { void onSendRenderActions(RenderActionPackage renderActionPackage); diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridgeHistory.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridgeHistory.java index 24ef65cd..315ef187 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridgeHistory.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridgeHistory.java @@ -5,34 +5,25 @@ package org.hapjs.render.jsruntime; -import android.content.Context; -import android.os.Bundle; import android.util.Log; +import android.util.Pair; import com.eclipsesource.v8.JavaVoidCallback; import com.eclipsesource.v8.V8Array; import com.eclipsesource.v8.V8Object; -import org.hapjs.bridge.HybridRequest; -import org.hapjs.common.utils.NavigationUtils; -import org.hapjs.common.utils.RouterUtils; -import org.hapjs.render.PageManager; -import org.hapjs.render.PageNotFoundException; - -import static org.hapjs.logging.RuntimeLogManager.VALUE_ROUTER_APP_FROM_JS_PUSH; +import java.util.Map; public class JsBridgeHistory extends V8Object { private static final String TAG = "JsBridgeHistory"; - private Context mContext; - private JsContext mJsContext; - private PageManager mPageManager; + private IJavaNative mNative; public final JavaVoidCallback back = new JavaVoidCallback() { @Override public void invoke(V8Object v8Object, V8Array args) { - RouterUtils.back(mContext, mPageManager); + mNative.routerBack(); } }; @@ -46,19 +37,8 @@ public void invoke(V8Object v8Object, V8Array args) { return; } try { - String pkg = mPageManager.getAppInfo().getPackage(); - HybridRequest request = V8ObjConverter.objToRequest(params, pkg); - if (NavigationUtils.navigate( - mContext, pkg, request, new Bundle(), - VALUE_ROUTER_APP_FROM_JS_PUSH, null)) { - return; - } - - try { - RouterUtils.push(mPageManager, request); - } catch (PageNotFoundException ex) { - mJsContext.getJsThread().processV8Exception(ex); - } + Pair> pair = V8ObjConverter.parseReqeustParams(params); + mNative.routerPush(pair.first, pair.second); } finally { JsUtils.release(params); } @@ -74,11 +54,10 @@ public void invoke(V8Object v8Object, V8Array args) { Log.e(TAG, "replace params is null"); return; } - HybridRequest request = - V8ObjConverter - .objToRequest(params, mPageManager.getAppInfo().getPackage()); try { - RouterUtils.replace(mPageManager, request); + Pair> pair = + V8ObjConverter.parseReqeustParams(params); + mNative.routerReplace(pair.first, pair.second); } finally { JsUtils.release(params); } @@ -89,17 +68,12 @@ public void invoke(V8Object v8Object, V8Array args) { new JavaVoidCallback() { @Override public void invoke(V8Object v8Object, V8Array args) { - mPageManager.clear(); + mNative.routerClear(); } }; - public JsBridgeHistory(Context context, JsContext jsContext) { + public JsBridgeHistory(JsContext jsContext, IJavaNative javaNative) { super(jsContext.getV8()); - mContext = context; - mJsContext = jsContext; - } - - public void attach(PageManager pageManager) { - mPageManager = pageManager; + mNative = javaNative; } } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridgeRegisterHelper.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridgeRegisterHelper.java new file mode 100644 index 00000000..1025f547 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridgeRegisterHelper.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ + +package org.hapjs.render.jsruntime; + +import android.content.Context; +import com.eclipsesource.v8.JavaVoidCallback; +import com.eclipsesource.v8.V8; +import com.eclipsesource.v8.V8Array; +import com.eclipsesource.v8.V8Object; +import org.hapjs.bridge.JsInterface; + +public class JsBridgeRegisterHelper { + private Context mContext; + private IJavaNative mNative; + private JsContext mJsContext; + private String mPkg; + private JsThread mJsThread; + private long mThreadId; + + private V8 mV8; + private JsBridge mJsBridge; + private JsBridgeTimer mJsTimer; + private JsBridgeHistory mJsBridgeHistory; + private Profiler mProfiler; + private V8Object mJsInterfaceProxy; + + public JsBridgeRegisterHelper(Context context, JsContext jsContext, + JsThread jsThread, long threadId, IJavaNative javaNative) { + mContext = context; + mJsContext = jsContext; + mV8 = jsContext.getV8(); + mJsThread = jsThread; + mThreadId = threadId; + mNative = javaNative; + mJsBridge = new JsBridge(mContext, mNative); + } + + public void setJavaNative(IJavaNative javaNative) { + mNative = javaNative; + } + + public void attach(String pkg) { + mPkg = pkg; + + mJsBridge.attach(mPkg); + mJsBridge.register(mV8); + } + + public void registerBridge() { + V8 v8 = mV8; + v8.registerJavaMethod(keyEventCallback, "callKeyEvent"); + + mJsTimer = new JsBridgeTimer(mJsContext, mJsThread.getHandler(), mNative); + JsUtils.registerAllPublicMethodsToRoot(mJsTimer); + + mProfiler = new Profiler(v8, mThreadId, mNative, mJsThread); + v8.add("profiler", mProfiler); + mProfiler.registerJavaMethod(mProfiler.isEnabled, "isEnabled"); + mProfiler.registerJavaMethod(mProfiler.record, "record"); + mProfiler.registerJavaMethod(mProfiler.time, "time"); + mProfiler.registerJavaMethod(mProfiler.timeEnd, "timeEnd"); + mProfiler.registerJavaMethod(mProfiler.saveProfilerData, "saveProfilerData"); + + mJsBridgeHistory = new JsBridgeHistory(mJsContext, mNative); + v8.add("history", mJsBridgeHistory); + mJsBridgeHistory.registerJavaMethod(mJsBridgeHistory.back, "back"); + mJsBridgeHistory.registerJavaMethod(mJsBridgeHistory.push, "push"); + mJsBridgeHistory.registerJavaMethod(mJsBridgeHistory.replace, "replace"); + mJsBridgeHistory.registerJavaMethod(mJsBridgeHistory.clear, "clear"); + + JsInterface jsInterface = new JsInterface(mNative); + mJsInterfaceProxy = JsInterfaceProxy.register(v8, jsInterface, JsInterface.INTERFACE_NAME); + } + + public void unregister() { + JsUtils.release(mJsTimer, mProfiler, mJsBridgeHistory, mJsInterfaceProxy); + mJsTimer = null; + mProfiler = null; + mJsBridgeHistory = null; + mJsInterfaceProxy = null; + } + + private final JavaVoidCallback keyEventCallback = new JavaVoidCallback() { + @Override + public void invoke(V8Object v8Object, V8Array args) { + try { + boolean consumed = Boolean.parseBoolean(args.get(0).toString()); + int hashcode = Integer.parseInt(args.get(1).toString()); + mNative.onKeyEventCallback(consumed, hashcode); + } catch (Exception e) { + e.printStackTrace(); + } finally { + JsUtils.release(args); + } + } + }; + + public void destroyPage(int pageId) { + mJsTimer.clearTimers(pageId); + } + + public void onFrameCallback(long frameTimeNanos) { + mJsTimer.onFrameCallback(frameTimeNanos); + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridgeTimer.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridgeTimer.java index c1ddea61..38c1b5af 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridgeTimer.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsBridgeTimer.java @@ -7,6 +7,7 @@ import android.os.Handler; import android.os.SystemClock; +import android.util.Log; import android.util.SparseArray; import android.view.Choreographer; import androidx.annotation.UiThread; @@ -19,15 +20,20 @@ import org.hapjs.common.executors.Executors; public class JsBridgeTimer extends V8Object { + private static final String TAG = "JsBridgeTimer"; private JsContext mJsContext; private Handler mJsThreadHandler; + private IJavaNative mNative; private Map mCallbackDatas; private SparseArray> mCallbackMap; + private boolean mFrameCallbackRequested; - public JsBridgeTimer(JsContext jsContext, Handler jsThreadHandler) { + public JsBridgeTimer(JsContext jsContext, Handler jsThreadHandler, + IJavaNative javaNative) { super(jsContext.getV8()); mJsContext = jsContext; mJsThreadHandler = jsThreadHandler; + mNative = javaNative; mCallbackDatas = new ConcurrentHashMap<>(); mCallbackMap = new SparseArray<>(); } @@ -39,10 +45,11 @@ public void requestAnimationFrameNative(int pageId, int id) { @Override public void run() { CallbackData callbackData = new CallbackData(id, false, 0); - Choreographer choreographer = Choreographer.getInstance(); - choreographer.postFrameCallback(callbackData); mCallbackDatas.put(id, callbackData); addCallback(pageId, id, CallbackType.Animation); + if (!mFrameCallbackRequested) { + mJsThreadHandler.post(() -> mNative.requestAnimationFrameNative()); + } } }); } @@ -57,13 +64,36 @@ public void run() { if (callbackData == null) { return; } - Choreographer choreographer = Choreographer.getInstance(); - choreographer.removeFrameCallback(callbackData); removeCallback(id); } }); } + public void onFrameCallback(long frameTimeNanos) { + Executors.ui() + .execute(() -> { + mFrameCallbackRequested = false; + for (int i = 0; i < mCallbackMap.size(); ++i) { + SparseArray callbackIds = mCallbackMap.valueAt(i); + if (callbackIds == null || callbackIds.size() == 0) { + continue; + } + final int N = callbackIds.size(); + for (int index = N - 1; index >= 0; index--) { + int callbackId = callbackIds.keyAt(index); + CallbackType type = callbackIds.valueAt(index); + if (type == CallbackType.Animation) { + CallbackData callbackData = mCallbackDatas.get(callbackId); + if (callbackData != null) { + callbackData.doFrame(frameTimeNanos); + } + } + } + } + }); + } + + public void setTimeoutNative(int pageId, int id, int time) { Executors.ui() .execute( @@ -207,18 +237,22 @@ public void run() { } V8 v8 = mJsContext.getV8(); - V8Array arr = new V8Array(v8); - arr.push(id); - try { - if (!isRepeat) { - v8.executeFunction("setTimeoutCallback", arr); - } else { - v8.executeFunction("setIntervalCallback", arr); + if (v8 != null) { + V8Array arr = new V8Array(v8); + arr.push(id); + try { + if (!isRepeat) { + v8.executeFunction("setTimeoutCallback", arr); + } else { + v8.executeFunction("setIntervalCallback", arr); + } + } catch (V8RuntimeException ex) { + mNative.onV8Exception(ex.getStackTrace(), ex.getMessage()); + } finally { + JsUtils.release(arr); } - } catch (V8RuntimeException ex) { - mJsContext.getJsThread().processV8Exception(ex); - } finally { - JsUtils.release(arr); + } else { + Log.w(TAG, "v8 is null."); } } @@ -235,7 +269,7 @@ public void run() { try { v8.executeFunction("requestAnimationFrameCallback", arr); } catch (V8RuntimeException ex) { - mJsContext.getJsThread().processV8Exception(ex); + mNative.onV8Exception(ex.getStackTrace(), ex.getMessage()); } finally { JsUtils.release(arr); } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsEngineImpl.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsEngineImpl.java new file mode 100644 index 00000000..1a86604b --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsEngineImpl.java @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.render.jsruntime; + +import android.os.ConditionVariable; +import com.eclipsesource.v8.V8; +import com.eclipsesource.v8.V8Array; +import com.eclipsesource.v8.V8Object; +import com.eclipsesource.v8.V8RuntimeException; +import com.eclipsesource.v8.V8Value; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.hapjs.bridge.V8ObjectHelper; +import org.hapjs.render.DebugUtils; +import org.hapjs.render.jsruntime.serialize.JavaSerializeObject; +import org.json.JSONObject; + +public class JsEngineImpl implements IJsEngine { + public interface V8ExceptionHandler { + void onV8Exception(V8RuntimeException e); + } + + public interface FrameCallback { + void onFrameCallback(long frameTimeNanos); + } + + + protected JsContext mJsContext; + protected V8 mV8; + protected JsThread mJsThread; + private ConditionVariable mBlocker = new ConditionVariable(true); + private String mPkg; + + private V8ExceptionHandler mV8ExceptionHandler; + private V8InspectorNative mV8InspectorNative; + private V8InspectorNative.InspectorNativeCallback mInspectorNativeCallback; + private FrameCallback mFrameCallback; + + public JsEngineImpl(JsContext jsContext, V8InspectorNative.InspectorNativeCallback inspectorNativeCallback, + V8ExceptionHandler v8ExceptionHandler, FrameCallback frameCallback) { + mJsContext = jsContext; + mV8 = jsContext.getV8(); + mJsThread = jsContext.getJsThread(); + mInspectorNativeCallback = inspectorNativeCallback; + mV8ExceptionHandler = v8ExceptionHandler; + mFrameCallback = frameCallback; + } + + @Override + public void setQuickAppPkg(String pkg) { + mPkg = pkg; + } + + @Override + public String getQuickAppPkg() { + return mPkg; + } + + @Override + public void block() { + mBlocker.close(); + mBlocker.block(); + } + + @Override + public void unblock() { + mBlocker.open(); + } + + @Override + public void onAttach(String environmentScript, String pkg) { + mV8.executeScript(environmentScript); + } + + @Override + public void notifyConfigurationChanged(int pageId, String type) { + V8Array args = new V8Array(mV8); + args.push(pageId); + V8Object options = new V8Object(mV8); + options.add("type", type); + args.push(options); + try { + mV8.executeVoidFunction("notifyConfigurationChanged", args); + } catch (V8RuntimeException e) { + processV8Exception(e); + } finally { + JsUtils.release(args, options); + } + } + + @Override + public void updateLocale(String language, String country, Map resourcesJson) { + V8Array args = new V8Array(mV8); + // locale + V8Object locale = new V8Object(mV8); + locale.add("language", language); + locale.add("countryOrRegion", country); + args.push(locale); + // resources array + V8Array resources = new V8Array(mV8); + for (JSONObject resJson : resourcesJson.values()) { + JavaSerializeObject object = new JavaSerializeObject(resJson); + V8Object res = V8ObjectHelper.toV8Object(mV8, object.toMap()); + resources.push(res); + JsUtils.release(res); + } + args.push(resources); + try { + mV8.executeVoidFunction("changeAppLocale", args); + } catch (V8RuntimeException e) { + processV8Exception(e); + } finally { + JsUtils.release(args, locale, resources); + } + } + + @Override + public void registerBundleChunks(String content) { + V8Array args = new V8Array(mV8); + try { + args.push(content); + mV8.executeVoidFunction("registerBundleChunks", args); + } catch (V8RuntimeException e) { + processV8Exception(e); + } finally { + JsUtils.release(args); + } + } + + @Override + public void createApplication(int appId, String js, String css, String metaInfo) { + if (DebugUtils.DBG) DebugUtils.startRecord("JsThreadExecuteApp"); + V8Array args = new V8Array(mV8); + V8Array args1 = new V8Array(mV8); + try { + args.push(metaInfo); + mV8.executeVoidFunction("registerManifest", args); + mV8.executeVoidFunction("locateDsl", null); + + args1.push(appId); + args1.push(js); + args1.push(css); + mV8.executeVoidFunction("createApplication", args1); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + return; + } finally { + JsUtils.release(args); + JsUtils.release(args1); + if (DebugUtils.DBG) DebugUtils.endRecord("JsThreadExecuteApp"); + } + } + + @Override + public void onRequestApplication(int appId) { + V8Array args = new V8Array(mV8); + args.push(appId); + try { + mV8.executeVoidFunction("onRequestApplication", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + return; + } finally { + JsUtils.release(args); + } + } + + @Override + public void onShowApplication(int appId) { + V8Array args = new V8Array(mV8); + args.push(appId); + try { + mV8.executeVoidFunction("onShowApplication", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + return; + } finally { + JsUtils.release(args); + } + } + + @Override + public void onHideApplication(int appId) { + V8Array args = new V8Array(mV8); + args.push(appId); + try { + mV8.executeVoidFunction("onHideApplication", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + return; + } finally { + JsUtils.release(args); + } + } + + @Override + public boolean backPressPage(int pageId) { + boolean consumed = false; + try { + consumed = mV8.executeBooleanScript("backPressPage(" + pageId + ");"); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } + return consumed; + } + + @Override + public boolean menuButtonPressPage(int pageId) { + boolean consumed = false; + try { + consumed = mV8.executeBooleanScript("menuButtonPressPage(" + pageId + ");"); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } + return consumed; + } + + @Override + public boolean keyPressPage(int pageId, Map params) { + boolean consumed = false; + V8Array args = new V8Array(mV8); + args.push(pageId); + V8Object paramsObj = JsUtils.mapToV8Object(mV8, params); + args.push(paramsObj); + try { + consumed = mV8.executeBooleanFunction("keyPressPage", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(args, paramsObj); + } + return consumed; + } + + @Override + public boolean menuPressPage(int pageId) { + boolean consumed = false; + try { + consumed = mV8.executeBooleanScript("menuPressPage(" + pageId + ");"); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } + return consumed; + } + + @Override + public void orientationChangePage(int pageId, String orientation, float angle) { + V8Array array = new V8Array(mV8); + array.push(pageId); + V8Object object = new V8Object(mV8); + object.add("orientation", orientation); + object.add("angel", angle); + array.push(object); + try { + mV8.executeVoidFunction("orientationChangePage", array); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(array, object); + } + } + + @Override + public void executeVoidScript(String script, String scriptName, int lineNumber) { + if (DebugUtils.DBG) DebugUtils.startRecord("JsThreadExecuteVoidScript"); + try { + mV8.executeVoidScript(script, scriptName, lineNumber); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + if (DebugUtils.DBG) DebugUtils.endRecord("JsThreadExecuteVoidScript"); + } + } + + @Override + public void executeScript(String script, String scriptName, int lineNumber) { + if (DebugUtils.DBG) DebugUtils.startRecord("JsThreadExecuteScript"); + try { + mV8.executeScript(script, scriptName, lineNumber); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + if (DebugUtils.DBG) DebugUtils.endRecord("JsThreadExecuteScript"); + } + } + + @Override + public void executeVoidFunction(String func, Object[] params) { + if (DebugUtils.DBG) DebugUtils.startRecord("JsThreadExecuteFunction"); + V8Array v8Params = params == null ? new V8Array(mV8) : + V8ObjectHelper.toV8Array(mV8, Arrays.asList(params)); + try { + mV8.executeVoidFunction(func, v8Params); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(v8Params); + if (DebugUtils.DBG) DebugUtils.endRecord("JsThreadExecuteFunction"); + } + } + + @Override + public String executeObjectScriptAndStringify(String script) { + V8Object object = null; + V8Object json = null; + V8Array parameters = null; + try { + object = mV8.executeObjectScript(script); + if (object == null) { + throw new IllegalStateException("related value not exists."); + } + + json = mV8.getObject("JSON"); + if (json == null) { + throw new IllegalStateException("V8Object which key is JSON not exists"); + } + parameters = new V8Array(mV8).push(object); + return json.executeStringFunction("stringify", parameters); + } finally { + if (parameters != null) { + parameters.release(); + } + if (object != null) { + object.release(); + } + if (json != null) { + json.release(); + } + } + } + + @Override + public void createPage(int appId, int pageId, String js, String css, Map params, Map intent, Map meta) { + V8Array args = new V8Array(mV8); + args.push(pageId); + args.push(appId); + args.push(js); + V8Object paramsObj = JsUtils.mapToV8Object(mV8, params); + args.push(paramsObj); + V8Object intentObj = JsUtils.mapToV8Object(mV8, intent); + args.push(intentObj); + V8Object metaObj = JsUtils.mapToV8Object(mV8, meta); + args.push(metaObj); + args.push(css); + try { + mV8.executeVoidFunction("createPage", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(args, paramsObj, intentObj, metaObj); + } + } + + @Override + public void recreatePage(int pageId) { + V8Array args = new V8Array(mV8); + args.push(pageId); + try { + mV8.executeVoidFunction("recreatePage", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(args); + } + } + + @Override + public void refreshPage(int pageId, Map params, Map intent) { + V8Array args = new V8Array(mV8); + args.push(pageId); + V8Object paramsObj = JsUtils.mapToV8Object(mV8, params); + args.push(paramsObj); + V8Object intentObj = JsUtils.mapToV8Object(mV8, intent); + args.push(intentObj); + try { + mV8.executeVoidFunction("refreshPage", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(args, paramsObj, intentObj); + } + } + + @Override + public void notifyPageNotFound(int appId, String pageUri) { + V8Array args = new V8Array(mV8); + args.push(appId); + V8Object uriObj = JsUtils.mapToV8Object(mV8, Collections.singletonMap("uri", pageUri)); + args.push(uriObj); + try { + mV8.executeVoidFunction("notifyPageNotFound", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(args, uriObj); + } + } + + @Override + public void destroyPage(int pageId) { + V8Array args = new V8Array(mV8); + args.push(pageId); + try { + mV8.executeVoidFunction("destroyPage", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(args); + } + } + + @Override + public void destroyApplication(int appId) { + V8Array args = new V8Array(mV8); + try { + args.push(appId); + mV8.executeVoidFunction("destroyApplication", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(args); + } + } + + @Override + public void fireEvent(List datas) { + if (datas == null || datas.isEmpty()) { + return; + } + if (DebugUtils.DBG) DebugUtils.startRecord("JsThreadFireEvent"); + + V8Array args = new V8Array(mV8); + int pageId = datas.get(0).pageId; + args.push(pageId);//pageId + + V8Array events = new V8Array(mV8); + List releaseObj = new ArrayList<>(); + for (JsThread.JsEventCallbackData data : datas) { + V8Object event = new V8Object(mV8); + event.add("action", 1);//action + + V8Array eventArg = new V8Array(mV8); + eventArg.push(data.elementId);//ref + eventArg.push(data.eventName);//eventType + V8Object paramsObj = JsUtils.mapToV8Object(mV8, data.params); + eventArg.push(paramsObj);//params + V8Object attributesObj = JsUtils.mapToV8Object(mV8, data.attributes); + eventArg.push(attributesObj);//attributes + + event.add("args", eventArg); + events.push(event); + + releaseObj.add(event); + releaseObj.add(eventArg); + releaseObj.add(paramsObj); + releaseObj.add(attributesObj); + } + args.push(events); + releaseObj.add(events); + + try { + mV8.executeVoidFunction("execJSBatch", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + int size = releaseObj.size(); + V8Value[] temp = new V8Value[size]; + for (int i = 0; i < size; i++) { + temp[i] = releaseObj.get(i); + } + + JsUtils.release(args, temp); + if (DebugUtils.DBG) DebugUtils.endRecord("JsThreadFireEvent"); + } + } + + @Override + public void fireKeyEvent(JsThread.JsEventCallbackData data) { + if (DebugUtils.DBG) DebugUtils.startRecord("JsThreadFireKeyEvent"); + + V8Array args = new V8Array(mV8); + args.push(data.pageId); + V8Array events = new V8Array(mV8); + V8Object event = new V8Object(mV8); + event.add("action", 1); + V8Array eventArg = new V8Array(mV8); + eventArg.push(data.elementId); + eventArg.push(data.eventName); + V8Object paramsObj = JsUtils.mapToV8Object(mV8, data.params); + eventArg.push(paramsObj); + event.add("args", eventArg); + events.push(event); + args.push(events); + + try { + mV8.executeVoidFunction("execJSBatch", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(args, events, event, eventArg, paramsObj); + if (DebugUtils.DBG) DebugUtils.endRecord("JsThreadFireKeyEvent"); + } + } + + @Override + public void fireCallback(JsThread.JsMethodCallbackData data) { + if (DebugUtils.DBG) DebugUtils.startRecord("JsThreadFireCallback"); + + V8Array args = new V8Array(mV8); + args.push(data.pageId); + + V8Array methodArray = new V8Array(mV8); + V8Object methodObject = new V8Object(mV8); + + methodObject.add("action", 2); + + V8Array callbackArgs = new V8Array(mV8); + callbackArgs.push(data.callbackId); + V8Array callbackArgsParams = new V8Array(mV8); + for (Object obj : data.params) { + JsUtils.push(callbackArgsParams, obj); + } + callbackArgs.push(callbackArgsParams); + + methodObject.add("args", callbackArgs); + methodArray.push(methodObject); + args.push(methodArray); + + try { + mV8.executeVoidFunction("execJSBatch", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(args, methodArray, methodObject, callbackArgs, callbackArgsParams); + if (DebugUtils.DBG) DebugUtils.endRecord("JsThreadFireCallback"); + } + } + + @Override + public void onFoldCard(int s, boolean f) { + V8Array args = new V8Array(mV8); + try { + args.push(s); + args.push(f); + mV8.executeVoidFunction("foldCard", args); + } catch (V8RuntimeException ex) { + processV8Exception(ex); + } finally { + JsUtils.release(args); + } + } + + @Override + public void reachPageTop(int pageId) { + try { + mV8.executeVoidScript("reachPageTop(" + pageId + ");"); + } catch (V8RuntimeException e) { + processV8Exception(e); + } + } + + @Override + public void reachPageBottom(int pageId) { + try { + mV8.executeVoidScript("reachPageBottom(" + pageId + ");"); + } catch (V8RuntimeException e) { + processV8Exception(e); + } + } + + @Override + public void pageScroll(int pageId, int scrollTop) { + V8Array args = new V8Array(mV8); + args.push(pageId); + V8Object options = new V8Object(mV8); + options.add("scrollTop", scrollTop); + args.push(options); + try { + mV8.executeVoidFunction("pageScroll", args); + } catch (V8RuntimeException e) { + processV8Exception(e); + } finally { + JsUtils.release(args, options); + } + } + + @Override + public void registerComponents(String builtInComponents) { + V8Array parameters = new V8Array(mV8); + try { + parameters.push(builtInComponents); + mV8.executeVoidFunction("registerComponents", parameters); + } finally { + JsUtils.release(parameters); + } + } + + @Override + public void terminateExecution() { + mV8.terminateExecution(); + } + + @Override + public void shutdown() { + mJsContext.dispose(); + } + + @Override + public void inspectorHandleMessage(long ptr, int sessionId, String message) { + ensureV8InspectorNative(); + mV8InspectorNative.nativeHandleMessage(ptr, sessionId, message); + } + + @Override + public long inspectorInit(boolean autoEnable, int sessionId) { + ensureV8InspectorNative(); + return mV8InspectorNative.initNative(autoEnable, sessionId); + } + + @Override + public void inspectorSetV8Context(long ptr, int isJsContextReCreated) { + ensureV8InspectorNative(); + mV8InspectorNative.nativeSetV8Context(ptr, mV8, isJsContextReCreated); + } + + @Override + public void inspectorDisposeV8Context(long ptr) { + ensureV8InspectorNative(); + mV8InspectorNative.nativeDisposeV8Context(ptr); + } + + @Override + public void inspectorDestroy(long ptr) { + ensureV8InspectorNative(); + mV8InspectorNative.nativeDestroy(ptr); + } + + @Override + public void inspectorBeginLoadJsCode(String uri, String content) { + ensureV8InspectorNative(); + mV8InspectorNative.nativeBeginLoadJsCode(uri, content); + } + + @Override + public void inspectorEndLoadJsCode(String uri) { + ensureV8InspectorNative(); + mV8InspectorNative.nativeEndLoadJsCode(uri); + } + + @Override + public String inspectorExecuteJsCode(long ptr, String jsCode) { + ensureV8InspectorNative(); + return mV8InspectorNative.nativeExecuteJsCode(ptr, jsCode); + } + + @Override + public void inspectorFrontendReload(long ptr) { + ensureV8InspectorNative(); + mV8InspectorNative.nativeFrontendReload(ptr); + } + + @Override + public void onFrameCallback(long frameTimeNanos) { + if (mFrameCallback != null) { + mFrameCallback.onFrameCallback(frameTimeNanos); + } + } + + private void ensureV8InspectorNative() { + if (mV8InspectorNative == null) { + mV8InspectorNative = new V8InspectorNative(mInspectorNativeCallback); + } + } + + public void processV8Exception(V8RuntimeException ex) { + mV8ExceptionHandler.onV8Exception(ex); + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsInterfaceProxy.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsInterfaceProxy.java new file mode 100644 index 00000000..53ff9493 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsInterfaceProxy.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.render.jsruntime; + +import com.eclipsesource.v8.JavaCallback; +import com.eclipsesource.v8.V8; +import com.eclipsesource.v8.V8Array; +import com.eclipsesource.v8.V8Object; +import org.hapjs.bridge.ExtensionManager; +import org.hapjs.bridge.JsInterface; +import org.hapjs.bridge.Response; + +public class JsInterfaceProxy extends V8Object { + static V8Object register(V8 v8, JsInterface jsInterface, String name) { + JsInterfaceProxy proxy = new JsInterfaceProxy(v8, jsInterface); + v8.add(name, proxy); + proxy.registerJavaMethod(proxy.invoke, "invoke"); + return proxy; + } + + private final JsInterface jsInterface; + + private final JavaCallback invoke = new JavaCallback() { + @Override + public Object invoke(V8Object receiver, V8Array parameters) { + Object rawParams = parameters.get(2); + Object instanceId = parameters.get(4); + if (!(instanceId instanceof Integer)) { + instanceId = -1; + } + Response response = jsInterface.invoke( + parameters.getString(0), + parameters.getString(1), + rawParams, + parameters.getString(3), + (int) instanceId); + + if (rawParams instanceof V8Object) { + JsUtils.release((V8Object) rawParams); + } + return response == null ? null : response.toJavascriptResult(v8); + } + }; + + private JsInterfaceProxy(V8 v8, JsInterface jsInterface) { + super(v8); + this.jsInterface = jsInterface; + } +} \ No newline at end of file diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsThread.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsThread.java index c9772a47..f60890ac 100755 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsThread.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsThread.java @@ -8,90 +8,24 @@ import static org.hapjs.render.RootView.MSG_APP_LOAD_END; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.os.ConditionVariable; -import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.text.TextUtils; -import android.util.DisplayMetrics; import android.util.Log; -import android.util.Pair; -import android.widget.Toast; -import com.eclipsesource.v8.JavaVoidCallback; -import com.eclipsesource.v8.V8; -import com.eclipsesource.v8.V8Array; -import com.eclipsesource.v8.V8Object; -import com.eclipsesource.v8.V8RuntimeException; -import com.eclipsesource.v8.V8Value; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; + +import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; - -import java.util.Iterator; -import java.util.concurrent.ConcurrentLinkedQueue; -import org.hapjs.bridge.EnvironmentManager; -import org.hapjs.bridge.ExtensionManager; -import org.hapjs.bridge.HybridRequest; -import org.hapjs.bridge.HybridView; -import org.hapjs.bridge.V8ObjectHelper; -import org.hapjs.chunk.JsChunksManager; -import org.hapjs.common.compat.BuildPlatform; -import org.hapjs.common.executors.AbsTask; -import org.hapjs.common.executors.Executors; -import org.hapjs.common.utils.FrescoUtils; -import org.hapjs.common.utils.LogUtils; -import org.hapjs.common.utils.RouterUtils; -import org.hapjs.component.ComponentRegistry; -import org.hapjs.component.bridge.RenderEventCallback; +import java.util.concurrent.Callable; import org.hapjs.component.constants.Attributes; -import org.hapjs.component.view.keyevent.KeyEventDelegate; -import org.hapjs.component.view.keyevent.KeyEventManager; -import org.hapjs.io.AssetSource; -import org.hapjs.io.FileSource; -import org.hapjs.io.JavascriptReader; -import org.hapjs.io.RpkSource; -import org.hapjs.io.TextReader; import org.hapjs.logging.RuntimeLogManager; -import org.hapjs.model.AppInfo; -import org.hapjs.model.RoutableInfo; -import org.hapjs.model.ScreenOrientation; -import org.hapjs.render.DebugUtils; -import org.hapjs.render.IdGenerator; -import org.hapjs.render.MultiWindowManager; -import org.hapjs.render.Page; -import org.hapjs.render.PageManager; -import org.hapjs.render.RenderActionPackage; -import org.hapjs.render.AppResourcesLoader; -import org.hapjs.render.RootView; -import org.hapjs.render.VDomChangeAction; -import org.hapjs.render.action.RenderActionDocument; -import org.hapjs.render.action.RenderActionManager; -import org.hapjs.render.css.CSSParser; -import org.hapjs.render.css.CSSStyleSheet; -import org.hapjs.render.jsruntime.serialize.JavaSerializeObject; -import org.hapjs.render.skeleton.DefaultSkeletonProviderImpl; -import org.hapjs.render.skeleton.SkeletonConfigParser; -import org.hapjs.render.skeleton.SkeletonDSLParser; -import org.hapjs.render.skeleton.SkeletonProvider; -import org.hapjs.runtime.ProviderManager; import org.hapjs.runtime.ResourceConfig; -import org.hapjs.runtime.RuntimeActivity; -import org.hapjs.runtime.inspect.InspectorManager; -import org.hapjs.runtime.inspect.InspectorProvider; -import org.json.JSONException; +import org.hapjs.runtime.sandbox.SyncWaiter; import org.json.JSONObject; -public class JsThread extends HandlerThread { +public abstract class JsThread extends HandlerThread { public static final String CONFIGURATION_TYPE_LOCALE = "locale"; public static final String CONFIGURATION_TYPE_THEME_MODE = "themeMode"; @@ -116,1256 +50,585 @@ public class JsThread extends HandlerThread { HAS_INFRASJS_SNAPSHOT = hasInfraSnapshot; } - private final Context mContext; - private final int mAppId; - private final H mHandler; - Handler mMainHandler; - PageManager mPageManager; - AppInfo mAppInfo; - RootView mRootView; - private final JavaVoidCallback keyEventCallback = - new JavaVoidCallback() { - @Override - public void invoke(V8Object v8Object, V8Array args) { - try { - boolean consumed = Boolean.parseBoolean(args.get(0).toString()); - int hashcode = Integer.parseInt(args.get(1).toString()); - KeyEventManager.getInstance().injectKeyEvent(consumed, mRootView, hashcode); - } catch (Exception e) { - e.printStackTrace(); - } finally { - JsUtils.release(args); - } - } - }; - private LifecycleCallback mCallback; - private JsContext mJsContext; - private JsBridge mJsBridge; - private JsBridgeTimer mJsTimer; - private JsBridgeHistory mJsBridgeHistory; - private Profiler mProfiler; - private ExtensionManager mExtensionManager; - private RenderActionManager mRenderActionManager; - private JsChunksManager mJsChunksManager; - private ConditionVariable mBlocker = new ConditionVariable(true); - private volatile boolean mBlocked; - private String mSessionLastAppShow; - private boolean mIsTerminateExecution; - private int mApplicationState = STATE_NONE; - - protected JsThread(Context context) { - super("JsThread"); - - start(); - - mContext = context; - mAppId = IdGenerator.generateAppId(); - mHandler = new H(getLooper()); - mRenderActionManager = new RenderActionManager(); - - Message.obtain(mHandler, H.MSG_INIT).sendToTarget(); - } - - public void attach( - Handler mainHandler, - AppInfo appInfo, - RootView rootView, - LifecycleCallback lifecycleCallback, - PageManager pageManager) { - mMainHandler = mainHandler; - mAppInfo = appInfo; - mRootView = rootView; - mCallback = lifecycleCallback; - mPageManager = pageManager; - - Message.obtain(mHandler, H.MSG_ATTACH).sendToTarget(); - } - - private void onInit() { - mJsContext = new JsContext(this); - V8 v8 = mJsContext.getV8(); - - mJsBridge = new JsBridge(this, mRenderActionManager); - mJsBridge.register(v8); - registerKeyEventCallback(); - - mJsTimer = new JsBridgeTimer(mJsContext, mHandler); - JsUtils.registerAllPublicMethodsToRoot(mJsTimer); - - mProfiler = new Profiler(v8, getId()); - v8.add("profiler", mProfiler); - mProfiler.registerJavaMethod(mProfiler.isEnabled, "isEnabled"); - mProfiler.registerJavaMethod(mProfiler.record, "record"); - mProfiler.registerJavaMethod(mProfiler.time, "time"); - mProfiler.registerJavaMethod(mProfiler.timeEnd, "timeEnd"); - mProfiler.registerJavaMethod(mProfiler.saveProfilerData, "saveProfilerData"); + static public class JsEventCallbackData { - mJsBridgeHistory = new JsBridgeHistory(mContext, mJsContext); - v8.add("history", mJsBridgeHistory); - mJsBridgeHistory.registerJavaMethod(mJsBridgeHistory.back, "back"); - mJsBridgeHistory.registerJavaMethod(mJsBridgeHistory.push, "push"); - mJsBridgeHistory.registerJavaMethod(mJsBridgeHistory.replace, "replace"); - mJsBridgeHistory.registerJavaMethod(mJsBridgeHistory.clear, "clear"); + public int pageId; + public int elementId; + public String eventName; + public Map params; + public Map attributes; - InspectorManager.getInstance().notifyJsThreadReady(this); + public JsEventCallbackData() {} - createRuntime(); - initInfras(); - - mExtensionManager = new ExtensionManager(this, mContext); - mExtensionManager.onRuntimeInit(v8); - - ComponentRegistry.registerBuiltInComponents(v8); - FrescoUtils.initialize(mContext); - } - - private void initInfras() { - V8 v8 = mJsContext.getV8(); - try { - v8.executeVoidFunction("initInfras", null); - } catch (V8RuntimeException e) { - processV8Exception(e); + public JsEventCallbackData(int pageId, int elementId, String eventName, + Map params, Map attributes) { + this.pageId = pageId; + this.elementId = elementId; + this.eventName = eventName; + this.params = params; + this.attributes = attributes; } } - public boolean isApplicationDebugEnabled() { - return (mContext.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) - == ApplicationInfo.FLAG_DEBUGGABLE; - } + static public class JsMethodCallbackData { - private void onAttach() { - mJsBridge.attach(mRootView); - mJsBridgeHistory.attach(mPageManager); - mExtensionManager.attach(mRootView, mPageManager, mAppInfo); + public int pageId; + public String callbackId; + public Object[] params; - DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); - V8 v8 = mJsContext.getV8(); - v8.executeScript(EnvironmentManager.buildRegisterJavascript(mContext, mAppInfo)); + public JsMethodCallbackData() {} - if (mCallback != null) { - mCallback.onRuntimeCreate(); + public JsMethodCallbackData(int pageId, String callbackId, Object... params) { + this.pageId = pageId; + this.callbackId = callbackId; + this.params = params; } - mExtensionManager.onRuntimeCreate(mAppInfo); } - public Handler getHandler() { - return mHandler; - } - - public JsContext getJsContext() { - return mJsContext; - } - - public ExtensionManager getBridgeManager() { - return mExtensionManager; - } - - public RenderActionManager getRenderActionManager() { - return mRenderActionManager; - } - - public void postExecuteScript(String script) { - Message.obtain(mHandler, H.MSG_EXECUTE_SCRIPT, script).sendToTarget(); - } + public class H extends Handler { + static final protected int MSG_INIT = 1; + static final protected int MSG_ATTACH = 2; + static final protected int MSG_EXECUTE_SCRIPT = 3; + static final protected int MSG_CREATE_APPLICATION = 4; + static final protected int MSG_DESTROY_APPLICATION = 5; + static final protected int MSG_CREATE_PAGE = 6; + static final protected int MSG_RECREATE_PAGE = 7; + static final protected int MSG_DESTROY_PAGE = 8; + static final protected int MSG_FIRE_EVENT = 9; + static final protected int MSG_BACK_PRESS = 10; + static final protected int MSG_BLOCK = 11; + static final protected int MSG_SHUTDOWN = 12; + static final protected int MSG_MENU_PRESS = 13; + static final protected int MSG_FIRE_CALLBACK = 14; + static final protected int MSG_ORIENTATION_CHANGE = 15; + static final protected int MSG_EXECUTE_FUNCTION = 16; + static final protected int MSG_TERMINATE_EXECUTION = 17; + static final protected int MSG_FOLD_CARD = 18; + static final protected int MSG_REFRESH_PAGE = 19; + static final protected int MSG_UPDATE_LOCALE = 20; + static final protected int MSG_NOTIFY_CONFIGURATION_CHANGED = 21; + static final protected int MSG_PAGE_NOT_FOUND = 22; + static final protected int MSG_ON_REQUEST_APPLICATION = 23; + static final protected int MSG_ON_SHOW_APPLICATION = 24; + static final protected int MSG_ON_HIDE_APPLICATION = 25; + static final protected int MSG_REGISTER_BUNDLE_CHUNKS = 26; + static final protected int MSG_PAGE_SCROLL = 27; + static final protected int MSG_PAGE_REACH_TOP = 28; + static final protected int MSG_PAGE_REACH_BOTTOM = 29; + static final protected int MSG_FIRE_KEY_EVENT = 30; + static final protected int MSG_ON_MENU_BUTTON_PRESS = 31; - public void postExecuteFunction(String name, Object... params) { - Message.obtain(mHandler, H.MSG_EXECUTE_FUNCTION, new Pair(name, params)).sendToTarget(); - } + H(Looper looper) { + super(looper); + } - private void createRuntime() { - try { - if (!HAS_INFRASJS_SNAPSHOT) { - String script = null; - if (isApplicationDebugEnabled()) { - File file = - new File(Environment.getExternalStorageDirectory(), - "quickapp/assets/js/infras.js"); - script = JavascriptReader.get().read(new FileSource(file)); - if (script != null) { - Toast.makeText( - mContext, - "load infras.js from sdcard, please remove quickapp folder in sdcard if you are not dev", - Toast.LENGTH_SHORT) - .show(); - Log.d(TAG, "load infras.js from sdcard"); + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_INIT: + onInit(); + if (mEngine == null || mNative == null) { + throw new RuntimeException("must initialize mEngine and mNative in onInit"); } + break; + case MSG_ATTACH: + onAttach(msg.obj); + break; + case MSG_EXECUTE_SCRIPT: { + executeVoidScript(msg.obj); + break; } - - RuntimeLogManager.getDefault() - .logJsThreadTaskStart(mContext.getPackageName(), - RuntimeLogManager.KEY_INFRAS_JS_LOAD); - if (script == null) { - script = readInfrasAsset(); + case MSG_EXECUTE_FUNCTION: { + executeVoidFunction(msg.obj); + break; } - - if (script == null) { - Log.e(TAG, "failed to read js/infras.js"); - String platform = ResourceConfig.getInstance().getPlatform(); - if (!TextUtils.equals(platform, mContext.getPackageName())) { - script = readInfrasAsset(platform); - } + case MSG_CREATE_APPLICATION: { + createApplication(msg.obj); + break; + } + case MSG_DESTROY_APPLICATION: { + destroyApplication(msg.obj); + break; + } + case MSG_CREATE_PAGE: { + createPage(msg.obj); + break; + } + case MSG_RECREATE_PAGE: { + recreatePage(msg.obj); + break; + } + case MSG_PAGE_NOT_FOUND: { + notifyPageNotFound(msg.obj); + break; + } + case MSG_REFRESH_PAGE: { + refreshPage(msg.obj); + break; + } + case MSG_DESTROY_PAGE: + destroyPage(msg.obj); + break; + case MSG_FIRE_EVENT: { + fireEvent(msg.obj); + break; + } + case MSG_FIRE_KEY_EVENT: { + fireKeyEvent(msg.obj); + break; + } + case MSG_BACK_PRESS: { + backPress(msg.obj); + break; + } + case MSG_ON_MENU_BUTTON_PRESS: { + menuButtonPressPage(msg.obj); + break; + } + case MSG_FIRE_CALLBACK: { + fireCallback(msg.obj); + break; + } + case MSG_BLOCK: { + doBlock(); + break; + } + case MSG_SHUTDOWN: { + doShutdown(); + break; + } + case MSG_MENU_PRESS: { + onMenuPress(msg.obj); + break; + } + case MSG_ORIENTATION_CHANGE: { + onOrientationChange(msg.obj); + break; + } + case MSG_UPDATE_LOCALE: { + updateLocale(msg.obj); + break; + } + case MSG_NOTIFY_CONFIGURATION_CHANGED: { + notifyConfigurationChanged(msg.obj); + break; + } + case MSG_TERMINATE_EXECUTION: { + // TODO: Fix this. Terminate may cause unknown error. + terminateExecution(); + break; + } + case MSG_FOLD_CARD: { + onFoldCard(msg.obj); + break; + } + case MSG_ON_REQUEST_APPLICATION: { + onRequestApplication(msg.obj); + break; + } + case MSG_ON_SHOW_APPLICATION: { + onShowApplication(msg.obj); + break; + } + case MSG_ON_HIDE_APPLICATION: { + onHideApplication(msg.obj); + break; + } + case MSG_REGISTER_BUNDLE_CHUNKS: { + registerBundleChunks(msg.obj); + break; + } + case MSG_PAGE_SCROLL: { + onPageScroll(msg.obj); + break; + } + case MSG_PAGE_REACH_TOP: { + onPageReachTop(msg.obj); + break; + } + case MSG_PAGE_REACH_BOTTOM: { + onPageReachBottom(msg.obj); + break; + } + default: { + super.handleMessage(msg); + break; } - RuntimeLogManager.getDefault() - .logJsThreadTaskEnd(mContext.getPackageName(), - RuntimeLogManager.KEY_INFRAS_JS_LOAD); - mJsContext.getV8().executeVoidScript(script, "infras.js", 0); } - mApplicationState = STATE_RUNTIME_INITED; - } catch (V8RuntimeException ex) { - processV8Exception(ex); } } - private String readInfrasAsset() { - return JavascriptReader.get() - .read( - new AssetSource(mContext, "js/infras.js") { - @Override - public InputStream open() throws IOException { - try { - return super.open(); - } catch (IOException e) { - String host = mContext.getPackageName(); - String platform = ResourceConfig.getInstance().getPlatform(); - RuntimeLogManager.getDefault() - .logResourceNotFound(host, platform, "js/infras.js", e); - - throw e; - } - } - }); - } - - private String readInfrasAsset(String platform) { - Log.i(TAG, "try to load infras.js from " + platform); - try { - Context platformContext = mContext.createPackageContext(platform, 0); - return JavascriptReader.get().read(new AssetSource(platformContext, "js/infras.js")); - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "failed to createPackageContext for " + platform, e); - } - return null; - } - - public Context getPlatformContext(Context context) { - if (null == context) { - return null; - } - String platform = ResourceConfig.getInstance().getPlatform(); - try { - Context platformContext = context.createPackageContext(platform, 0); - return platformContext; - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "failed to getPlatformContext for " + platform, e); - } - return null; - } - - public void postNotifyConfigurationChanged(Page page, String type) { - Object[] params = new Object[] {page, type}; - mHandler.obtainMessage(H.MSG_NOTIFY_CONFIGURATION_CHANGED, params).sendToTarget(); - } - - private void notifyConfigurationChanged(Page page, String type) { - if (page == null) { - return; - } - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - int pageId = page.pageId; - args.push(pageId); - V8Object options = new V8Object(v8); - options.add("type", type); - args.push(options); - try { - v8.executeVoidFunction("notifyConfigurationChanged", args); - } catch (V8RuntimeException e) { - processV8Exception(e); - } finally { - JsUtils.release(args, options); - } - } - - public void postUpdateLocale(Locale locale, Map resources) { - Object[] params = new Object[] {locale, resources}; - mHandler.obtainMessage(H.MSG_UPDATE_LOCALE, params).sendToTarget(); - } - - private void updateLocale(Locale newLocale, Map resourcesJson) { - if (newLocale == null || resourcesJson == null) { - return; - } - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - // locale - V8Object locale = new V8Object(v8); - locale.add("language", newLocale.getLanguage()); - locale.add("countryOrRegion", newLocale.getCountry()); - args.push(locale); - // resources array - V8Array resources = new V8Array(v8); - for (JSONObject resJson : resourcesJson.values()) { - JavaSerializeObject object = new JavaSerializeObject(resJson); - V8Object res = V8ObjectHelper.toV8Object(v8, object.toMap()); - resources.push(res); - JsUtils.release(res); - } - args.push(resources); - try { - v8.executeVoidFunction("changeAppLocale", args); - } catch (V8RuntimeException e) { - processV8Exception(e); - } finally { - JsUtils.release(args, locale, resources); - } - } + private static final String INFRAS_SNAPSHOT_SO_NAME = "libinfrasjs_snapshot.so"; - private void registerManifest() { - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - try { - args.push(mAppInfo.getMetaInfo()); - v8.executeVoidFunction("registerManifest", args); - } catch (V8RuntimeException e) { - processV8Exception(e); - } finally { - JsUtils.release(args); - } - } + protected IJsEngine mEngine; + protected IJavaNative mNative; + protected final H mHandler; - public void postRegisterBundleChunks(String content) { - Object[] params = new Object[] {content}; - Message.obtain(mHandler, H.MSG_REGISTER_BUNDLE_CHUNKS, params).sendToTarget(); - } + protected JsThread(String name) { + super(name); - private void registerBundleChunks(String content) { - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - try { - args.push(content); - v8.executeVoidFunction("registerBundleChunks", args); - } catch (V8RuntimeException e) { - processV8Exception(e); - } finally { - JsUtils.release(args); - } - } + start(); - public void postCreateApplication(String jsContent, String css, HybridRequest request) { - Object[] params = new Object[] {jsContent, css, request}; - Message.obtain(mHandler, H.MSG_CREATE_APPLICATION, params).sendToTarget(); + mHandler = createHandler(); } - public void postInitInspectorJsContext() { - Message.obtain(mHandler, H.MSG_INIT_INSPECTOR_JSCONTEXT).sendToTarget(); + protected H createHandler() { + return new H(getLooper()); } - private void createApplication(String js, String css, HybridRequest request) { - registerManifest(); - V8 v8 = mJsContext.getV8(); - v8.executeVoidFunction("locateDsl", null); + protected abstract void onInit(); - V8Array args1 = new V8Array(v8); - try { - args1.push(mAppId); - args1.push(js); - args1.push(css); - v8.executeVoidFunction("createApplication", args1); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - return; - } finally { - JsUtils.release(args1); - } + protected abstract void onAttach(Object msgObj); - RuntimeLogManager.getDefault().logAppLoadEnd(mAppInfo.getPackage()); + public Handler getHandler() { + return mHandler; } - public void postOnRequestApplication() { - Message.obtain(mHandler, H.MSG_ON_REQUEST_APPLICATION).sendToTarget(); + public IJsEngine getEngine() { + return mEngine; } - private void onRequestApplication() { - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(mAppId); - try { - v8.executeVoidFunction("onRequestApplication", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - return; - } finally { - JsUtils.release(args); - } + public IJavaNative getNative() { + return mNative; } - public void postOnShowApplication() { - Message.obtain(mHandler, H.MSG_ON_SHOW_APPLICATION).sendToTarget(); + public void postExecuteScript(String script) { + Message.obtain(mHandler, H.MSG_EXECUTE_SCRIPT, new Object[] {script}).sendToTarget(); } - private void onShowApplication() { - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(mAppId); - try { - v8.executeVoidFunction("onShowApplication", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - return; - } finally { - JsUtils.release(args); - } + public void postExecuteVoidScript(String script, String scriptName, int lineNumber) { + Object[] obj = new Object[]{script, scriptName, lineNumber}; + mHandler.obtainMessage(H.MSG_EXECUTE_SCRIPT, obj).sendToTarget(); } - public void postOnHideApplication() { - Message.obtain(mHandler, H.MSG_ON_HIDE_APPLICATION).sendToTarget(); + protected void executeVoidScript(Object msgObj) { + Object[] args = (Object[]) msgObj; + String script = (String) args[0]; + mEngine.executeVoidScript(script, null, 0); } - private void onHideApplication() { - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(mAppId); - try { - v8.executeVoidFunction("onHideApplication", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - return; - } finally { - JsUtils.release(args); - } + public void postExecuteFunction(String name, Object... params) { + Object[] args = new Object[] {name, params}; + Message.obtain(mHandler, H.MSG_EXECUTE_FUNCTION, args).sendToTarget(); } - public void postRecreatePage(Page page) { - mHandler.obtainMessage(H.MSG_RECREATE_PAGE, page).sendToTarget(); + protected void executeVoidFunction(Object msgObj) { + Object[] args = (Object[]) msgObj; + String func = (String) args[0]; + Object[] params = (Object[]) args[1]; + mEngine.executeVoidFunction(func, params); } - public void postRefreshPage(Page page) { - Message.obtain(mHandler, H.MSG_REFRESH_PAGE, page).sendToTarget(); - } + protected void createApplication(Object msgObj) { + Object[] args = (Object[]) msgObj; + int appId = (int) args[0]; + String js = (String) args[1]; + String css = (String) args[2]; + String metaInfo = (String) args[3]; + mEngine.createApplication(appId, js, css, metaInfo); - public void postOnMenuButtonPress(Page page, HybridView.OnKeyUpListener onKeyUpIsConsumption) { - Object[] params = new Object[] {page, onKeyUpIsConsumption}; - mHandler.obtainMessage(H.MSG_ON_MENU_BUTTON_PRESS, params).sendToTarget(); + RuntimeLogManager.getDefault().logAppLoadEnd(mEngine.getQuickAppPkg()); } - public void postPageNotFound(Page page) { - Message.obtain(mHandler, H.MSG_PAGE_NOT_FOUND, page).sendToTarget(); + protected void destroyApplication(Object msgObj) { + Object[] args = (Object[]) msgObj; + int appId = (int) args[0]; + mEngine.destroyApplication(appId); } - public void postBackPress(Page page) { - mHandler.obtainMessage(H.MSG_BACK_PRESS, page).sendToTarget(); + protected void createPage(Object msgObj) { + Object[] args = (Object[]) msgObj; + int appId = (int) args[0]; + int pageId = (int) args[1]; + String js = (String) args[2]; + String css = (String) args[3]; + HashMap params = (HashMap) args[4]; + HashMap intent = (HashMap) args[5]; + HashMap meta = (HashMap) args[6]; + mEngine.createPage(appId, pageId, js, css, params, intent, meta); } - void backPress(Page page) { - boolean consumed = false; - if (page != null && page.getState() >= Page.STATE_CREATED) { - V8 v8 = mJsContext.getV8(); - try { - consumed = v8.executeBooleanScript("backPressPage(" + page.pageId + ");"); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - return; - } - } - if (!consumed && null != mMainHandler) { - mMainHandler.sendEmptyMessage(RootView.MSG_BACK_PRESS); - } + public void postRecreatePage(int pageId) { + mHandler.obtainMessage(H.MSG_RECREATE_PAGE, new Object[]{pageId}).sendToTarget(); } - void menuButtonPressPage(Page page, HybridView.OnKeyUpListener onKeyUpIsConsumption) { - boolean consumed = false; - if (page != null && page.getState() >= Page.STATE_CREATED) { - V8 v8 = mJsContext.getV8(); - try { - consumed = v8.executeBooleanScript("menuButtonPressPage(" + page.pageId + ");"); - } catch (V8RuntimeException ex) { - onKeyUpIsConsumption.consume(false); - processV8Exception(ex); - return; - } - } - onKeyUpIsConsumption.consume(consumed); + protected void recreatePage(Object msgObj) { + Object[] args = (Object[]) msgObj; + int pageId = (int) args[0]; + mEngine.recreatePage(pageId); } - private void firePageKeyEvent(JsEventCallbackData data) { - Page page = mPageManager.getPageById(data.pageId); - if (page == null) { - return; - } - - boolean consumed = false; - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(data.pageId); - V8Object paramsObj = JsUtils.mapToV8Object(v8, data.params); - args.push(paramsObj); - try { - consumed = v8.executeBooleanFunction("keyPressPage", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - JsUtils.release(args, paramsObj); - } - - if (null != mMainHandler) { - Object hashCode = data.params.get(KeyEventDelegate.KEY_HASHCODE); - if (hashCode instanceof Integer) { - KeyEventManager.getInstance() - .injectKeyEvent(consumed, mRootView, (Integer) hashCode); - } - } + public void postPageNotFound(int appId, String pageUri, int pageId) { + Message.obtain(mHandler, H.MSG_PAGE_NOT_FOUND, new Object[] {appId, pageUri, pageId}).sendToTarget(); } - public void postMenuPress(Page page) { - mHandler.obtainMessage(H.MSG_MENU_PRESS, page).sendToTarget(); + protected void notifyPageNotFound(Object msgObj) { + Object[] args = (Object[]) msgObj; + int appId = (int) args[0]; + String pageUri = (String) args[1]; + mEngine.notifyPageNotFound(appId, pageUri); } - private void onMenuPress(Page page) { - boolean consumed = false; - if (page != null && page.getState() >= Page.STATE_CREATED) { - V8 v8 = mJsContext.getV8(); - try { - consumed = v8.executeBooleanScript("menuPressPage(" + page.pageId + ");"); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - return; - } - } - - if (!consumed) { - mMainHandler.obtainMessage(RootView.MSG_MENU_PRESS, page).sendToTarget(); - } + public void postRefreshPage(int pageId, Map params, Map intent) { + Object[] obj = new Object[]{pageId, params, intent}; + Message.obtain(mHandler, H.MSG_REFRESH_PAGE, obj).sendToTarget(); } - public void postOrientationChange(Page page, ScreenOrientation screenOrientation) { - mHandler - .obtainMessage(H.MSG_ORIENTATION_CHANGE, new Pair<>(page, screenOrientation)) - .sendToTarget(); - } - - private void onOrientationChange(Page page, ScreenOrientation screenOrientation) { - if (page != null && page.getState() >= Page.STATE_CREATED) { - V8 v8 = mJsContext.getV8(); - V8Array array = new V8Array(v8); - array.push(page.pageId); - V8Object object = new V8Object(v8); - object.add("orientation", screenOrientation.getOrientation()); - object.add("angel", screenOrientation.getAngel()); - array.push(object); - try { - v8.executeVoidFunction("orientationChangePage", array); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - return; - } finally { - JsUtils.release(array, object); - } - } + protected void refreshPage(Object msgObj) { + Object[] args = (Object[]) msgObj; + int pageId = (int) args[0]; + Map params = (Map) args[1]; + Map intent = (Map) args[2]; + mEngine.refreshPage(pageId, params, intent); } - public void postChangeVisiblePage(Page page, boolean visible) { - if (page != null) { - // 当thread被block的时候,activity已经stop,不应该发送visible=true事件, - // 仅发送visible=false事件,在activity变为start时会重新发送visible=true事件 - if (visible && page.getState() == Page.STATE_INITIALIZED && !mBlocked) { - if (page.shouldReload() && page.getRequest() != null) { - if (MultiWindowManager.shouldApplyMultiWindowMode(mContext) && page.getIsMultiWindowLeftPage()) { - RouterUtils.replaceLeftPage(mPageManager, page.getRequest()); - } else { - RouterUtils.replace(mPageManager, page.getRequest()); - } - return; - } - requestFocus(); - page.setState(Page.STATE_VISIBLE); - postExecuteScript( - "changeVisiblePage(" + page.pageId + ", " + JsUtils.toJsBoolean(visible) - + ");"); - Log.d(TAG, "show page: " + page.getName()); - - String session = System.getProperty(RuntimeActivity.PROP_SESSION); - if (!TextUtils.equals(session, mSessionLastAppShow)) { - mSessionLastAppShow = session; - RuntimeLogManager.getDefault() - .logAppShow(mAppInfo.getPackage(), mAppInfo.getVersionCode()); - } - - Page referrer = page.getReferrer(); - String referrerName = referrer == null ? null : referrer.getName(); - RuntimeLogManager.getDefault() - .logPageViewStart(mAppInfo.getPackage(), page.getName(), referrerName); - } else if (!visible && page.getState() == Page.STATE_VISIBLE) { - page.setState(Page.STATE_INITIALIZED); - postExecuteScript( - "changeVisiblePage(" + page.pageId + ", " + JsUtils.toJsBoolean(visible) - + ");"); - Log.d(TAG, "hide page: " + page.getName()); - RuntimeLogManager.getDefault() - .logPageViewEnd(mAppInfo.getPackage(), page.getName()); - } else { - Log.i( - TAG, - "Skip page visible change: page=" - + page - + ", visible=" - + visible - + ", mBlocked=" - + mBlocked); - } - } + protected void destroyPage(Object msgObj) { + Object[] args = (Object[]) msgObj; + int pageId = (int) args[0]; + mEngine.destroyPage(pageId); } - private void requestFocus() { - RootView rootView = mRootView; - if (!BuildPlatform.isTV() || rootView == null || rootView.hasFocus()) { + public void postFireEvent(List data) { + if (data == null || data.isEmpty()) { return; } - rootView.post(rootView::requestFocus); + Message.obtain(mHandler, JsThread.H.MSG_FIRE_EVENT, new Object[] {data.get(0).pageId, data, null}).sendToTarget(); } - public void postInitializePage(int pageId) { - Page page = mPageManager.getPageById(pageId); - if (page != null) { - page.setState(Page.STATE_INITIALIZED); - Message.obtain(mMainHandler, RootView.MSG_PAGE_INITIALIZED, page).sendToTarget(); - } else { - Log.w(TAG, "postInitializePage: page is null"); - } + protected void fireEvent(Object msgObj) { + Object[] args = (Object[]) msgObj; + List data = (List) args[1]; + mEngine.fireEvent(data); } - public void postDestroyPage(Page page) { - if (page.getState() > Page.STATE_NONE) { - mHandler.obtainMessage(H.MSG_DESTROY_PAGE, 0, 0, page).sendToTarget(); - page.setState(Page.STATE_NONE); - } else { - Log.d(TAG, "skip page destroy: " + page.toString()); - } + public void postFireKeyEvent(JsEventCallbackData data) { + Message.obtain(mHandler, JsThread.H.MSG_FIRE_KEY_EVENT, new Object[] {data}).sendToTarget(); } - public void loadPage(final Page page) { - RuntimeLogManager.getDefault().logPageLoadStart(mAppInfo.getPackage(), page.getName()); - mMainHandler.obtainMessage(RootView.MSG_LOAD_PAGE_JS_START, page).sendToTarget(); - Executors.io() - .execute( - new AbsTask() { - @Override - protected String[] doInBackground() { - mJsChunksManager.registerPageChunks(page); - String pageJs = AppResourcesLoader.getPageJs(mContext, mAppInfo.getPackage(), page); - String pageCss = AppResourcesLoader.getPageCss(mContext, mAppInfo.getPackage(), page); - parseStyleSheets(pageCss, page); - return new String[]{pageJs, pageCss}; - } - - @Override - protected void onPostExecute(String[] contents) { - RuntimeLogManager.getDefault() - .logPageLoadEnd(mAppInfo.getPackage(), page.getName()); - int result = - TextUtils.isEmpty(contents[0]) - ? Page.JS_LOAD_RESULT_FAIL - : Page.JS_LOAD_RESULT_SUCC; - page.setLoadJsResult(result); - mMainHandler.obtainMessage(RootView.MSG_LOAD_PAGE_JS_FINISH, page) - .sendToTarget(); - - RoutableInfo routableInfo = page.getRoutableInfo(); - final String jsUri = routableInfo.getUri(); - postCreatePage(page, contents[0], jsUri, contents[1]); - Log.d(TAG, "loadPage onPostExecute uri=" + jsUri + " result=" - + result); - } - }); - } - - private void parseStyleSheets(String css, Page page) { - if (TextUtils.isEmpty(css)) { - return; - } - Executors.io() - .execute( - new AbsTask() { - @Override - protected Void doInBackground() { - RuntimeLogManager.getDefault() - .logAsyncThreadTaskStart(mAppInfo.getPackage(), - "parseStyleSheets"); - try { - org.hapjs.common.json.JSONObject styles = - new org.hapjs.common.json.JSONObject(css); - org.hapjs.common.json.JSONArray styleList = - styles.getJSONArray("list"); - int n = styleList.length(); - for (int i = 0; i < n; i++) { - org.hapjs.common.json.JSONObject styleSheetPlain = - styleList.getJSONObject(i); - CSSStyleSheet styleSheet = - CSSParser.parseCSSStyleSheet(styleSheetPlain); - - // 注册样式表 - RenderActionDocument document = - mRenderActionManager - .getOrCreateDocument(page.getPageId()); - document.registerStyleSheet(styleSheet.getStyleObjectId(), - styleSheet); - } - } catch (JSONException e) { - Log.e(TAG, "parse css failed: " + e.getMessage()); - } - RuntimeLogManager.getDefault() - .logAsyncThreadTaskEnd(mAppInfo.getPackage(), - "parseStyleSheets"); - return null; - } - }); - } - - private void postCreatePage(Page page, String js, String uri, String css) { - Object[] params = new Object[] {page, js, uri, css}; - Message.obtain(mHandler, H.MSG_CREATE_PAGE, params).sendToTarget(); - page.setState(Page.STATE_CREATED); - } - - private void createPage(Page page, String js, String uri, String css) { - preCreateSkeleton(page); - preCreateBody(page); - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(page.pageId); - args.push(mAppId); - args.push(js); - V8Object paramsObj = JsUtils.mapToV8Object(v8, page.params); - args.push(paramsObj); - V8Object intentObj = JsUtils.mapToV8Object(v8, page.intent); - args.push(intentObj); - V8Object metaObj = JsUtils.mapToV8Object(v8, page.meta); - args.push(metaObj); - args.push(css); - try { - v8.executeVoidFunction("createPage", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - JsUtils.release(args, paramsObj, intentObj, metaObj); + protected void fireKeyEvent(Object msgObj) { + Object[] args = (Object[]) msgObj; + AppJsThread.JsEventCallbackData data = (AppJsThread.JsEventCallbackData) args[0]; + if (Attributes.Event.KEY_EVENT.equals(data.eventName)) { + fireKeyEvent(data); + } else if (Attributes.Event.KEY_EVENT_PAGE.equals(data.eventName)) { + firePageKeyEvent(data); } - Message.obtain(mMainHandler, MSG_APP_LOAD_END).sendToTarget(); } - private void preCreateSkeleton(Page page) { - if (mAppInfo != null && mContext != null && page != null) { - SkeletonProvider skeletonProvider = - ProviderManager.getDefault().getProvider(SkeletonProvider.NAME); - if (skeletonProvider == null) { - // If you have not added a customized provider, use the default (enable function)) - skeletonProvider = new DefaultSkeletonProviderImpl(mContext); - } - if (skeletonProvider.isSkeletonEnable(mAppInfo.getPackage())) { - Executors.io() - .execute( - new AbsTask() { - @Override - protected JSONObject doInBackground() { - RpkSource skeletonConfigSource = - new RpkSource(mContext, mAppInfo.getPackage(), - "skeleton/config.json"); - String skeletonConfig = - TextReader.get().read(skeletonConfigSource); - JSONObject parseResult = null; - if (!TextUtils.isEmpty(skeletonConfig)) { - String skFileName = - SkeletonConfigParser.getSkeletonFileName(page, - skeletonConfig); - Log.i( - TAG, - "LOG_SKELETON parse skeleton config, current page is " - + page.getName()); - if (!TextUtils.isEmpty(skFileName)) { - // read and parse sk file - String skFilePathName = - "skeleton/page/" + skFileName; - RpkSource skFileSource = - new RpkSource(mContext, - mAppInfo.getPackage(), - skFilePathName); - InputStream inputStream = null; - try { - Log.i(TAG, - "LOG_SKELETON parse sk file, path = " - + skFilePathName); - inputStream = skFileSource.open(); - parseResult = - SkeletonDSLParser.parse(inputStream); - } catch (Exception e) { - Log.e(TAG, "LOG_SKELETON parse sk file fail, ", - e); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException ioe) { - Log.e(TAG, - "LOG_SKELETON close sk inputStream fail, ", - ioe); - } - } - } - } else { - Log.i(TAG, - "LOG_SKELETON no matching sk file for current page"); - } - } else { - Log.i(TAG, - "LOG_SKELETON skeleton config file is empty"); - } - return parseResult; - } - - @Override - protected void onPostExecute(JSONObject parseResult) { - if (mRenderActionManager != null) { - mRenderActionManager.showSkeleton(mAppInfo.getPackage(), - parseResult); - } - } - }); - } else { - Log.i(TAG, "LOG_SKELETON prevent render skeleton because not enable"); - } - } + protected void fireKeyEvent(JsEventCallbackData data) { + mEngine.fireKeyEvent(data); } - // Debugger: When paused at any breakpoint, v8.terminateExecution will terminate javascript - // execution and cause JsThread throwing exception which is no need for processing. - private boolean isTerminateExecution(String exceptionMessage) { - return mIsTerminateExecution && "null".equals(exceptionMessage); + protected boolean firePageKeyEvent(JsEventCallbackData data) { + return mEngine.keyPressPage(data.pageId, data.params); } - void recreatePage(Page page) { - // page VDoc is destroyed, apply the cached render actions will throw exception - mMainHandler.obtainMessage(RootView.MSG_PAGE_CLEAR_CACHE, page).sendToTarget(); - preCreateBody(page); - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(page.pageId); - String uri = page.getRoutableInfo().getUri(); - try { - v8.executeVoidFunction("recreatePage", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - JsUtils.release(args); - } + protected void post(Runnable runnable) { + mHandler.postAtFrontOfQueue(runnable); } - private void refreshPage(Page page) { - preCreateBody(page); - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(page.pageId); - V8Object paramsObj = JsUtils.mapToV8Object(v8, page.params); - args.push(paramsObj); - V8Object intentObj = JsUtils.mapToV8Object(v8, page.intent); - args.push(intentObj); - try { - v8.executeVoidFunction("refreshPage", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - JsUtils.release(args, paramsObj, intentObj); - } + public void postInJsThread(Runnable runnable) { + mHandler.post(runnable); } - private void notifyPageNotFound(Page page) { - preCreateBody(page); - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(mAppId); - V8Object uriObj = - JsUtils.mapToV8Object(v8, Collections.singletonMap("uri", page.getTargetPageUri())); - args.push(uriObj); - try { - v8.executeVoidFunction("notifyPageNotFound", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - JsUtils.release(args, uriObj); + public T postAndWait(Callable callable) throws Exception { + if (Looper.myLooper() == mHandler.getLooper()) { + return callable.call(); + } else { + SyncWaiter waiter = new SyncWaiter<>((T) null); + mHandler.post(() -> { + try { + waiter.setResult(callable.call()); + } catch (Exception e) { + waiter.setResult(e); + } + }); + Object result = waiter.waitAndGetResult(); + if (result instanceof Exception) { + throw (Exception) result; + } + return (T) result; } } - private void preCreateBody(Page page) { - VDomChangeAction action = new VDomChangeAction(); - action.action = VDomChangeAction.ACTION_PRE_CREATE_BODY; - action.pageId = page.pageId; - RenderActionPackage renderActionPackage = - new RenderActionPackage(page.pageId, RenderActionPackage.TYPE_PRE_CREATE_BODY); - renderActionPackage.renderActionList.add(action); - mJsBridge.sendRenderActions(renderActionPackage); + protected boolean backPress(Object msgObj) { + Object[] args = (Object[]) msgObj; + int pageId = (int) args[0]; + return mEngine.backPressPage(pageId); } - void destroyPage(Page page) { - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(page.pageId); - try { - v8.executeVoidFunction("destroyPage", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - JsUtils.release(args); - } - mJsTimer.clearTimers(page.pageId); - mRenderActionManager.destroyPage(page.pageId); + protected boolean menuButtonPressPage(Object msgObj) { + Object[] args = (Object[]) msgObj; + return mEngine.menuButtonPressPage((int) args[0]); } - private void postDestroyApplication() { - Message.obtain(mHandler, H.MSG_DESTROY_APPLICATION).sendToTarget(); + public void postFireCallback(JsThread.JsMethodCallbackData datas) { + Message.obtain(mHandler, H.MSG_FIRE_CALLBACK, new Object[] {datas}).sendToTarget(); } - private void destroyApplication() { - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - try { - args.push(mAppId); - v8.executeVoidFunction("destroyApplication", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - mApplicationState = STATE_DESTROYED; - JsUtils.release(args); - } + protected void fireCallback(Object msgObj) { + Object[] args = (Object[]) msgObj; + JsThread.JsMethodCallbackData data = (AppJsThread.JsMethodCallbackData) args[0]; + mEngine.fireCallback(data); } - public void postFoldCard(int id, boolean fold) { - Object[] params = new Object[] {id, fold}; - mHandler.obtainMessage(H.MSG_FOLD_CARD, params).sendToTarget(); + public void block(long delay) { + mHandler.sendEmptyMessageDelayed(H.MSG_BLOCK, delay); } - public void postFireKeyEvent(JsEventCallbackData data) { - Message.obtain(mHandler, JsThread.H.MSG_FIRE_KEY_EVENT, data).sendToTarget(); + protected void doBlock() { + mEngine.block(); } - public void postFireEvent(JsEventCallbackData data) { - Message.obtain(mHandler, JsThread.H.MSG_FIRE_EVENT, data).sendToTarget(); + public void unblock() { + mHandler.removeMessages(H.MSG_BLOCK); + if (mEngine != null) { + mEngine.unblock(); + } } - public void postFireEvent( - final int pageId, - final List datas, - final RenderEventCallback.EventPostListener listener) { - post( - new Runnable() { - @Override - public void run() { - fireEvent(pageId, datas, listener); - } - }); + public void postShutdown() { + mHandler.obtainMessage(H.MSG_SHUTDOWN).sendToTarget(); } - private void post(Runnable runnable) { - mHandler.postAtFrontOfQueue(runnable); + protected void doShutdown() { + mEngine.shutdown(); } - public void postInJsThread(Runnable runnable) { - mHandler.post(runnable); + protected boolean onMenuPress(Object msgObj) { + Object[] args = (Object[]) msgObj; + return mEngine.menuPressPage((int) args[0]); } - private void fireEvent( - int pageId, List datas, - RenderEventCallback.EventPostListener listener) { - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(pageId); // pageId - - V8Array events = new V8Array(v8); - List releaseObj = new ArrayList<>(); - for (JsEventCallbackData data : datas) { - V8Object event = new V8Object(v8); - event.add("action", 1); // action - - V8Array eventArg = new V8Array(v8); - eventArg.push(data.elementId); // ref - eventArg.push(data.eventName); // eventType - V8Object paramsObj = JsUtils.mapToV8Object(v8, data.params); - eventArg.push(paramsObj); // params - V8Object attributesObj = JsUtils.mapToV8Object(v8, data.attributes); - eventArg.push(attributesObj); // attributes - - event.add("args", eventArg); - events.push(event); - - releaseObj.add(event); - releaseObj.add(eventArg); - releaseObj.add(paramsObj); - releaseObj.add(attributesObj); - } - args.push(events); - releaseObj.add(events); - - try { - v8.executeVoidFunction("execJSBatch", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - if (listener != null) { - listener.finish(); - } - int size = releaseObj.size(); - V8Value[] temp = new V8Value[size]; - for (int i = 0; i < size; i++) { - temp[i] = releaseObj.get(i); - } - - JsUtils.release(args, temp); - } + public void postOrientationChange(int pageId, String orientation, float angel) { + Object[] obj = new Object[]{pageId, orientation, angel}; + mHandler.obtainMessage(H.MSG_ORIENTATION_CHANGE, obj).sendToTarget(); } - private void fireEvent(JsEventCallbackData data) { - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(data.pageId); - - V8Array events = new V8Array(v8); - V8Object event = new V8Object(v8); - - event.add("action", 1); - - V8Array eventArg = new V8Array(v8); - eventArg.push(data.elementId); - eventArg.push(data.eventName); - V8Object paramsObj = JsUtils.mapToV8Object(v8, data.params); - eventArg.push(paramsObj); - V8Object attributesObj = JsUtils.mapToV8Object(v8, data.attributes); - eventArg.push(attributesObj); - event.add("args", eventArg); - events.push(event); - args.push(events); - - try { - v8.executeVoidFunction("execJSBatch", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - JsUtils.release(args, events, event, eventArg, paramsObj, attributesObj); - } + protected void onOrientationChange(Object msgObj) { + Object[] args = (Object[]) msgObj; + int pageId = (int) args[0]; + String orientation = (String) args[1]; + float angel = (float) args[2]; + mEngine.orientationChangePage(pageId, orientation, angel); } - private void fireKeyEvent(JsEventCallbackData data) { - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(data.pageId); - V8Array events = new V8Array(v8); - V8Object event = new V8Object(v8); - event.add("action", 1); - V8Array eventArg = new V8Array(v8); - eventArg.push(data.elementId); - eventArg.push(data.eventName); - V8Object paramsObj = JsUtils.mapToV8Object(v8, data.params); - eventArg.push(paramsObj); - event.add("args", eventArg); - events.push(event); - args.push(events); - - try { - v8.executeVoidFunction("execJSBatch", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - JsUtils.release(args, events, event, eventArg, paramsObj); + public void postUpdateLocale(String language, String country, Map resourcesJson) { + if (resourcesJson == null) { + return; } - } - private void registerKeyEventCallback() { - mJsContext.getV8().registerJavaMethod(keyEventCallback, "callKeyEvent"); + Object[] params = new Object[]{language, country, resourcesJson}; + mHandler.obtainMessage(H.MSG_UPDATE_LOCALE, params).sendToTarget(); } - public void postFireCallback(JsMethodCallbackData data) { - Message.obtain(mHandler, H.MSG_FIRE_CALLBACK, data).sendToTarget(); + protected void updateLocale(Object msgObj) { + Object[] args = (Object[]) msgObj; + String language = (String) args[0]; + String country = (String) args[1]; + Map resourcesJson = (Map) args[2]; + mEngine.updateLocale(language, country, resourcesJson); } - void fireCallback(JsMethodCallbackData data) { - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - args.push(data.pageId); - - V8Array methodArray = new V8Array(v8); - V8Object methodObject = new V8Object(v8); - - methodObject.add("action", 2); - - V8Array callbackArgs = new V8Array(v8); - callbackArgs.push(data.callbackId); - V8Array callbackArgsParams = new V8Array(v8); - for (Object obj : data.params) { - JsUtils.push(callbackArgsParams, obj); - } - callbackArgs.push(callbackArgsParams); - - methodObject.add("args", callbackArgs); - methodArray.push(methodObject); - args.push(methodArray); - - try { - v8.executeVoidFunction("execJSBatch", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - JsUtils.release(args, methodArray, methodObject, callbackArgs, callbackArgsParams); - } - } - - private void onFoldCard(int s, boolean f) { - V8Array args = new V8Array(mJsContext.getV8()); - try { - args.push(s); - args.push(f); - mJsContext.getV8().executeVoidFunction("foldCard", args); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - JsUtils.release(args); - } + public void postNotifyConfigurationChanged(int pageId, String type) { + Object[] params = new Object[]{pageId, type}; + mHandler.obtainMessage(H.MSG_NOTIFY_CONFIGURATION_CHANGED, params).sendToTarget(); } - public void block(long delay) { - mHandler.sendEmptyMessageDelayed(H.MSG_BLOCK, delay); - mBlocked = true; + protected void notifyConfigurationChanged(Object msgObj) { + Object[] args = (Object[]) msgObj; + int pageId = (int) args[0]; + String type = (String) args[1]; + mEngine.notifyConfigurationChanged(pageId, type); } - private void doBlock() { - mBlocker.close(); - mBlocker.block(); + protected void terminateExecution() { + mEngine.terminateExecution(); } - public void unblock() { - mHandler.removeMessages(H.MSG_BLOCK); - mBlocker.open(); - mBlocked = false; + public void postFoldCard(int s, boolean f) { + Message.obtain(mHandler, H.MSG_FOLD_CARD, new Object[] {s, f}).sendToTarget(); } - public boolean isBlocked() { - return mBlocked; + protected void onFoldCard(Object msgObj) { + Object[] args = (Object[]) msgObj; + int s = (int) args[0]; + boolean f = (boolean) args[1]; + mEngine.onFoldCard(s, f); } - public void shutdown(long delay) { - Log.d(TAG, "shutdown: " + this); - unblock(); - if (mApplicationState == STATE_RUNTIME_INITED) { - mApplicationState = STATE_DESTROYING; - Page page = null; - if (null != mPageManager) { - page = mPageManager.getCurrPage(); - } - if (page != null) { - postChangeVisiblePage(page, false); - postDestroyPage(page); - } - postDestroyApplication(); - } - mHandler.sendEmptyMessageDelayed(H.MSG_SHUTDOWN, delay); + protected void onRequestApplication(Object msgObj) { + Object[] args = (Object[]) msgObj; + int appId = (int) args[0]; + mEngine.onRequestApplication(appId); } - private void doShutdown() { - InspectorManager.getInspector().onJsContextDispose(mJsContext.getV8()); - if (mCallback != null) { - mCallback.onRuntimeDestroy(); - } - mRenderActionManager.release(); - JsUtils.release(mJsTimer, mJsBridgeHistory, mProfiler); - mExtensionManager.dispose(); - mJsContext.dispose(); - quit(); - Log.d(TAG, "shutdown finish: " + this); + protected void onShowApplication(Object msgObj) { + Object[] args = (Object[]) msgObj; + int appId = (int) args[0]; + mEngine.onShowApplication(appId); } - public void processV8Exception(Exception ex) { - if (isTerminateExecution(ex.getMessage())) { - mIsTerminateExecution = false; - } else { - String msg = LogUtils.getStackTrace(ex); - Log.e(TAG, msg); - InspectorManager.getInspector().onConsoleMessage(InspectorProvider.CONSOLE_ERROR, msg); - Message.obtain(mMainHandler, RootView.MSG_USER_EXCEPTION, ex).sendToTarget(); - } - notifyAppError(ex); + protected void onHideApplication(Object msgObj) { + Object[] args = (Object[]) msgObj; + int appId = (int) args[0]; + mEngine.onHideApplication(appId); } - private void notifyAppError(Exception ex) { - String message = ex.getMessage(); - if (NotifyAppErrorHelper.isExceptionFromOnError(message)) { - Log.i(TAG, "Exception from onError()"); - return; - } - String stack = LogUtils.getStackTrace(ex); - String script = NotifyAppErrorHelper.generateScript(mAppId, message, stack); - postExecuteScript(script); + public void postRegisterBundleChunks(String content) { + Object[] params = new Object[]{content}; + Message.obtain(mHandler, H.MSG_REGISTER_BUNDLE_CHUNKS, params).sendToTarget(); } - public AppInfo getAppInfo() { - return mAppInfo; + protected void registerBundleChunks(Object msgObj) { + Object[] args = (Object[]) msgObj; + String content = (String) args[0]; + mEngine.registerBundleChunks(content); } - public synchronized JsChunksManager getJsChunksManager() { - if (mJsChunksManager == null) { - mJsChunksManager = new JsChunksManager(this); - } - return mJsChunksManager; + public void postPageScroll(int pageId, int scrollTop) { + Message.obtain(mHandler, H.MSG_PAGE_SCROLL, new Object[] {pageId, scrollTop}).sendToTarget(); } - public void postPageReachTop(Page page) { - mHandler.obtainMessage(H.MSG_PAGE_REACH_TOP, page).sendToTarget(); + protected void onPageScroll(Object msgObj) { + Object[] args = (Object[]) msgObj; + int pageId = (int) args[0]; + int scrollTop = (int) args[1]; + mEngine.pageScroll(pageId, scrollTop); } - public void postPageReachBottom(Page page) { - mHandler.obtainMessage(H.MSG_PAGE_REACH_BOTTOM, page).sendToTarget(); + public void postPageReachTop(int pageId) { + Message.obtain(mHandler, H.MSG_PAGE_REACH_TOP, new Object[] {pageId}).sendToTarget(); } - public void postPageScroll(Page page, int scrollTop) { - Object[] args = new Object[] {page, scrollTop}; - mHandler.obtainMessage(H.MSG_PAGE_SCROLL, args).sendToTarget(); + protected void onPageReachTop(Object msgObj) { + Object[] args = (Object[]) msgObj; + int pageId = (int) args[0]; + mEngine.reachPageTop(pageId); } - private void onPageReachTop(Page page) { - if (page == null) { - return; - } - V8 v8 = mJsContext.getV8(); - try { - v8.executeVoidScript("reachPageTop(" + page.pageId + ");"); - } catch (V8RuntimeException e) { - processV8Exception(e); - } + public void postPageReachBottom(int pageId) { + Message.obtain(mHandler, H.MSG_PAGE_REACH_BOTTOM, new Object[] {pageId}).sendToTarget(); } - private void onPageReachBottom(Page page) { - if (page == null) { - return; - } - V8 v8 = mJsContext.getV8(); - try { - v8.executeVoidScript("reachPageBottom(" + page.pageId + ");"); - } catch (V8RuntimeException e) { - processV8Exception(e); - } + protected void onPageReachBottom(Object msgObj) { + Object[] args = (Object[]) msgObj; + int pageId = (int) args[0]; + mEngine.reachPageBottom(pageId); } - private void onPageScroll(Page page, int scrollTop) { - if (page == null) { - return; + public static Context getPlatformContext(Context context) { + if (null == context) { + return null; } - V8 v8 = mJsContext.getV8(); - V8Array args = new V8Array(v8); - int pageId = page.pageId; - args.push(pageId); - V8Object options = new V8Object(v8); - options.add("scrollTop", scrollTop); - args.push(options); + String platform = ResourceConfig.getInstance().getPlatform(); try { - v8.executeVoidFunction("pageScroll", args); - } catch (V8RuntimeException e) { - processV8Exception(e); - } finally { - JsUtils.release(args, options); + Context platformContext = context.createPackageContext(platform, 0); + return platformContext; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "failed to getPlatformContext for " + platform, e); } + return null; } public interface LifecycleCallback { @@ -1379,313 +642,4 @@ public interface LifecycleCallback { */ void onRuntimeDestroy(); } - - public static class JsEventCallbackData { - - public final int pageId; - public final int elementId; - public final String eventName; - public final Map params; - public final Map attributes; - - public JsEventCallbackData( - int pageId, - int elementId, - String eventName, - Map params, - Map attributes) { - this.pageId = pageId; - this.elementId = elementId; - this.eventName = eventName; - this.params = params; - this.attributes = attributes; - } - } - - public static class JsMethodCallbackData { - - public final int pageId; - public final String callbackId; - public final Object[] params; - - public JsMethodCallbackData(int pageId, String callbackId, Object... params) { - this.pageId = pageId; - this.callbackId = callbackId; - this.params = params; - } - } - - public class H extends Handler { - - private static final int MSG_INIT = 1; - private static final int MSG_ATTACH = 2; - private static final int MSG_EXECUTE_SCRIPT = 3; - private static final int MSG_CREATE_APPLICATION = 4; - private static final int MSG_DESTROY_APPLICATION = 5; - private static final int MSG_CREATE_PAGE = 6; - private static final int MSG_RECREATE_PAGE = 7; - private static final int MSG_DESTROY_PAGE = 8; - private static final int MSG_FIRE_EVENT = 9; - private static final int MSG_BACK_PRESS = 10; - private static final int MSG_BLOCK = 11; - private static final int MSG_SHUTDOWN = 12; - private static final int MSG_MENU_PRESS = 13; - private static final int MSG_FIRE_CALLBACK = 14; - private static final int MSG_ORIENTATION_CHANGE = 15; - private static final int MSG_EXECUTE_FUNCTION = 16; - private static final int MSG_TERMINATE_EXECUTION = 17; - private static final int MSG_FOLD_CARD = 18; - private static final int MSG_INIT_INSPECTOR_JSCONTEXT = 19; - private static final int MSG_REFRESH_PAGE = 20; - private static final int MSG_UPDATE_LOCALE = 21; - private static final int MSG_NOTIFY_CONFIGURATION_CHANGED = 22; - private static final int MSG_PAGE_NOT_FOUND = 23; - private static final int MSG_ON_REQUEST_APPLICATION = 24; - private static final int MSG_ON_SHOW_APPLICATION = 25; - private static final int MSG_ON_HIDE_APPLICATION = 26; - private static final int MSG_REGISTER_BUNDLE_CHUNKS = 27; - private static final int MSG_PAGE_SCROLL = 28; - private static final int MSG_PAGE_REACH_TOP = 29; - private static final int MSG_PAGE_REACH_BOTTOM = 30; - private static final int MSG_FIRE_KEY_EVENT = 31; - private static final int MSG_ON_MENU_BUTTON_PRESS = 32; - - private final List mApplicationMessages = - Arrays.asList( - MSG_CREATE_APPLICATION, - MSG_DESTROY_APPLICATION, - MSG_CREATE_PAGE, - MSG_REFRESH_PAGE, - MSG_PAGE_NOT_FOUND, - MSG_RECREATE_PAGE, - MSG_DESTROY_PAGE, - MSG_FIRE_EVENT, - MSG_FIRE_KEY_EVENT, - MSG_BACK_PRESS, - MSG_MENU_PRESS, - MSG_FIRE_CALLBACK, - MSG_ORIENTATION_CHANGE); - - H(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - if ((mApplicationState == STATE_NONE || mApplicationState == STATE_DESTROYED) - && Collections.binarySearch(mApplicationMessages, msg.what) >= 0) { - // not handle application msg when runtime is not initialized or is destroying - return; - } - switch (msg.what) { - case MSG_INIT: - RuntimeLogManager.getDefault() - .logJsThreadTaskStart(mContext.getPackageName(), - RuntimeLogManager.KEY_JS_ENV_INIT); - onInit(); - RuntimeLogManager.getDefault() - .logJsThreadTaskEnd(mContext.getPackageName(), - RuntimeLogManager.KEY_JS_ENV_INIT); - break; - case MSG_ATTACH: - onAttach(); - break; - case MSG_EXECUTE_SCRIPT: { - if (DebugUtils.DBG) { - DebugUtils.startRecord("JsThreadExecuteScript"); - } - try { - mJsContext.getV8().executeVoidScript((String) msg.obj); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } - if (DebugUtils.DBG) { - DebugUtils.endRecord("JsThreadExecuteScript"); - } - break; - } - case MSG_EXECUTE_FUNCTION: { - if (DebugUtils.DBG) { - DebugUtils.startRecord("JsThreadExecuteFunction"); - } - V8 v8 = mJsContext.getV8(); - Pair pair = (Pair) msg.obj; - V8Array params = - pair.second == null - ? new V8Array(v8) - : V8ObjectHelper.toV8Array(v8, Arrays.asList(pair.second)); - try { - mJsContext.getV8().executeVoidFunction(pair.first, params); - } catch (V8RuntimeException ex) { - processV8Exception(ex); - } finally { - JsUtils.release(params); - } - if (DebugUtils.DBG) { - DebugUtils.endRecord("JsThreadExecuteFunction"); - } - break; - } - case MSG_CREATE_APPLICATION: { - if (DebugUtils.DBG) { - DebugUtils.startRecord("JsThreadExecuteApp"); - } - Object[] params = (Object[]) msg.obj; - createApplication((String) params[0], (String) params[1], - (HybridRequest) params[2]); - if (DebugUtils.DBG) { - DebugUtils.endRecord("JsThreadExecuteApp"); - } - break; - } - case MSG_DESTROY_APPLICATION: { - destroyApplication(); - break; - } - case MSG_CREATE_PAGE: { - Object[] params = (Object[]) msg.obj; - createPage( - (Page) params[0], (String) params[1], (String) params[2], - (String) params[3]); - break; - } - case MSG_RECREATE_PAGE: { - recreatePage((Page) msg.obj); - break; - } - case MSG_PAGE_NOT_FOUND: { - notifyPageNotFound((Page) msg.obj); - break; - } - case MSG_REFRESH_PAGE: { - refreshPage((Page) msg.obj); - break; - } - case MSG_DESTROY_PAGE: - destroyPage((Page) msg.obj); - break; - case MSG_FIRE_EVENT: { - if (DebugUtils.DBG) { - DebugUtils.startRecord("JsThreadFireEvent"); - } - fireEvent((JsEventCallbackData) msg.obj); - if (DebugUtils.DBG) { - DebugUtils.endRecord("JsThreadFireEvent"); - } - break; - } - case MSG_FIRE_KEY_EVENT: { - if (DebugUtils.DBG) { - DebugUtils.startRecord("JsThreadFireKeyEvent"); - } - JsEventCallbackData data = ((JsEventCallbackData) msg.obj); - if (Attributes.Event.KEY_EVENT.equals(data.eventName)) { - fireKeyEvent(data); - } else if (Attributes.Event.KEY_EVENT_PAGE.equals(data.eventName)) { - firePageKeyEvent(data); - } - if (DebugUtils.DBG) { - DebugUtils.endRecord("JsThreadFireKeyEvent"); - } - break; - } - case MSG_BACK_PRESS: { - backPress((Page) msg.obj); - break; - } - case MSG_ON_MENU_BUTTON_PRESS: { - Object[] params = (Object[]) msg.obj; - - menuButtonPressPage((Page) params[0], (HybridView.OnKeyUpListener) params[1]); - break; - } - case MSG_FIRE_CALLBACK: { - if (DebugUtils.DBG) { - DebugUtils.startRecord("JsThreadFireCallback"); - } - fireCallback((JsMethodCallbackData) msg.obj); - if (DebugUtils.DBG) { - DebugUtils.endRecord("JsThreadFireCallback"); - } - break; - } - case MSG_BLOCK: { - doBlock(); - break; - } - case MSG_SHUTDOWN: { - doShutdown(); - break; - } - case MSG_MENU_PRESS: { - onMenuPress((Page) msg.obj); - break; - } - case MSG_ORIENTATION_CHANGE: { - Pair pair = (Pair) msg.obj; - onOrientationChange(pair.first, pair.second); - break; - } - case MSG_UPDATE_LOCALE: { - Object[] params = (Object[]) msg.obj; - updateLocale(((Locale) params[0]), ((Map) params[1])); - break; - } - case MSG_NOTIFY_CONFIGURATION_CHANGED: { - Object[] params = ((Object[]) msg.obj); - notifyConfigurationChanged(((Page) params[0]), ((String) params[1])); - break; - } - case MSG_TERMINATE_EXECUTION: { - // TODO: Fix this. Terminate may cause unknown error. - mIsTerminateExecution = true; - mJsContext.getV8().terminateExecution(); - break; - } - case MSG_FOLD_CARD: { - Object[] params = (Object[]) msg.obj; - onFoldCard((int) params[0], (boolean) params[1]); - break; - } - case MSG_INIT_INSPECTOR_JSCONTEXT: { - InspectorManager.getInspector().onJsContextCreated(mJsContext.getV8()); - break; - } - case MSG_ON_REQUEST_APPLICATION: { - onRequestApplication(); - break; - } - case MSG_ON_SHOW_APPLICATION: { - onShowApplication(); - break; - } - case MSG_ON_HIDE_APPLICATION: { - onHideApplication(); - break; - } - case MSG_REGISTER_BUNDLE_CHUNKS: { - Object[] params = (Object[]) msg.obj; - registerBundleChunks((String) params[0]); - break; - } - case MSG_PAGE_SCROLL: { - Object[] args = (Object[]) msg.obj; - onPageScroll((Page) args[0], (int) args[1]); - break; - } - case MSG_PAGE_REACH_TOP: { - onPageReachTop((Page) msg.obj); - break; - } - case MSG_PAGE_REACH_BOTTOM: { - onPageReachBottom((Page) msg.obj); - break; - } - default: { - super.handleMessage(msg); - break; - } - } - } - } } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsThreadFactory.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsThreadFactory.java index 1f430f51..afb62c63 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsThreadFactory.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsThreadFactory.java @@ -44,9 +44,9 @@ public JsThread create(Context context) { private JsThread load(Context context) { Context applicationContext = context.getApplicationContext(); if (applicationContext != null) { - return new JsThread(applicationContext); + return new AppJsThread(applicationContext); } else { - return new JsThread(context); + return new AppJsThread(context); } } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsUtils.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsUtils.java index 46113851..eb6b9be2 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsUtils.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/JsUtils.java @@ -10,9 +10,11 @@ import com.eclipsesource.v8.V8ArrayBuffer; import com.eclipsesource.v8.V8Object; import com.eclipsesource.v8.V8Value; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; +import com.eclipsesource.v8.utils.ArrayBuffer; + import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -104,10 +106,21 @@ public static V8Object mapToV8Object(V8 v8, Map map) { } else if (val == null) { obj.addNull(key); } else if (val instanceof ArrayBuffer) { - V8ArrayBuffer nativeArrayBuffer = - new V8ArrayBuffer(v8, ((ArrayBuffer) val).getByteBuffer()); + V8ArrayBuffer nativeArrayBuffer = ((ArrayBuffer) val).getV8ArrayBuffer(); obj.add(key, nativeArrayBuffer); release(nativeArrayBuffer); + } else if (val instanceof ByteBuffer) { + V8ArrayBuffer v8ArrayBuffer = new V8ArrayBuffer(v8, (ByteBuffer) val); + obj.add(key, v8ArrayBuffer); + release(v8ArrayBuffer); + } else if (val instanceof byte[]) { + byte[] bytes = (byte[]) val; + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bytes.length); + byteBuffer.put(bytes); + byteBuffer.rewind(); + V8ArrayBuffer v8ArrayBuffer = new V8ArrayBuffer(v8, byteBuffer); + obj.add(key, v8ArrayBuffer); + release(v8ArrayBuffer); } else { obj.add(key, val.toString()); } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/Profiler.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/Profiler.java index ff54fa76..f9292d59 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/Profiler.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/Profiler.java @@ -12,9 +12,7 @@ import com.eclipsesource.v8.V8Array; import com.eclipsesource.v8.V8Object; import com.eclipsesource.v8.V8Value; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; + import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; @@ -31,20 +29,27 @@ public class Profiler extends V8Object { private static final String TAG = "Profiler"; - // 基于时间点和事件的 Profiler 文本文件 - private static final String FILE_NAME_TIMELINE = "profiler_log.txt"; - - // 基于火焰图的 Profile 火焰图文件 - private static final String FILE_NAME_FLAME = "framework.cpuprofile"; - - private static final String KEY_APP_START = "app_start"; - - private static ScheduledExecutor sExecutor = Executors.createSingleThreadExecutor(); private static Map mMsgMap = new HashMap<>(); private static boolean sIsAllowProfiler; private static boolean sIsCached; - public final JavaCallback isEnabled = (v8Object, v8Array) -> sIsAllowProfiler; + + private JsThread mJsThread; + private long mThreadId; + private IJavaNative mNative; + + public final JavaCallback isEnabled = + (v8Object, v8Array) -> { + if (!sIsCached) { + try { + sIsAllowProfiler = mJsThread.postAndWait(() -> mNative.profilerIsEnabled()); + } catch (Exception e) { + Log.e(TAG, "failed to check profilerIsEnabled"); + } + sIsCached = true; + } + return sIsAllowProfiler; + }; public final JavaVoidCallback saveProfilerData = (v8Object, args) -> { if (!sIsAllowProfiler) { @@ -52,9 +57,8 @@ public class Profiler extends V8Object { return; } String profilerData = toMsg(args); - sExecutor.execute(() -> writeFlameDataToFile(profilerData)); + mJsThread.post(() -> mNative.profilerSaveProfilerData(profilerData)); }; - private long mThreadId; public final JavaVoidCallback record = (v8Object, args) -> { if (!sIsAllowProfiler) { @@ -62,7 +66,7 @@ public class Profiler extends V8Object { return; } String msg = toMsg(args); - sExecutor.execute(() -> writeToFile(getContent("record", msg, mThreadId))); + mJsThread.post(() -> mNative.profilerRecord(msg, mThreadId)); }; public final JavaVoidCallback time = (v8Object, args) -> { @@ -72,7 +76,7 @@ public class Profiler extends V8Object { } long currentTime = System.nanoTime(); String msg = toMsg(args); - sExecutor.execute( + mJsThread.post( () -> { String key = mThreadId + "_" + msg; mMsgMap.put(key, currentTime); @@ -87,7 +91,7 @@ public class Profiler extends V8Object { } long currentTime = System.nanoTime(); String msg = toMsg(args); - sExecutor.execute( + mJsThread.post( () -> { String key = mThreadId + "_" + msg; String content; @@ -98,31 +102,15 @@ public class Profiler extends V8Object { content = getContent("timeEnd", msg, mThreadId) + ": don't match the time flag"; } - writeToFile(content); + mNative.profilerTimeEnd(content); }); }; - public Profiler(V8 v8, long threadId) { + public Profiler(V8 v8, long threadId, IJavaNative javaNative, JsThread jsThread) { super(v8); mThreadId = threadId; - } - - public static void recordAppStart(long time) { - mMsgMap.put(KEY_APP_START, time); - } - - public static void recordFirstFrameRendered(long time, long threadId) { - if (!sIsAllowProfiler) { - Log.d(TAG, "recordFirstFrameRendered: not allow profiler"); - return; - } - sExecutor.execute( - () -> { - if (mMsgMap.containsKey(KEY_APP_START)) { - writeToFile(getContent("first frame rendered", getCost(time, KEY_APP_START), - threadId)); - } - }); + mNative = javaNative; + mJsThread = jsThread; } private static String getCost(long time, String key) { @@ -134,52 +122,6 @@ private static String getCost(long time, String key) { return ""; } - private static void writeToFile(String content) { - try { - File logFile = - new File( - Runtime.getInstance().getContext().getExternalFilesDir(null), - FILE_NAME_TIMELINE); - if (!logFile.exists()) { - logFile.createNewFile(); - } - String finalContent = content + "\n"; - FileUtils.saveToFile(finalContent.getBytes(StandardCharsets.UTF_8), logFile, true); - } catch (IOException e) { - Log.e(TAG, "write to file failed", e); - } - } - - private static void writeFlameDataToFile(String content) { - try { - File logFile = - new File(Runtime.getInstance().getContext().getExternalFilesDir(null), - FILE_NAME_FLAME); - if (!logFile.exists()) { - logFile.createNewFile(); - } - String finalContent = content; - FileUtils.saveToFile(finalContent.getBytes(StandardCharsets.UTF_8), logFile, false); - } catch (IOException e) { - Log.e(TAG, "write to file failed", e); - } - } - - public static void checkProfilerState() { - if (sIsCached) { - return; - } - sExecutor.execute( - () -> { - SysOpProvider provider = - ProviderManager.getDefault().getProvider(SysOpProvider.NAME); - if (provider != null) { - sIsAllowProfiler = provider.isAllowProfiler(); - sIsCached = true; - } - }); - } - private static String getContent(String prefix, String msg, long threadId) { return getCurrentTime() + " " + threadId + " " + prefix + ": " + msg; } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/ProfilerHelper.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/ProfilerHelper.java new file mode 100644 index 00000000..39d9c041 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/ProfilerHelper.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ + +package org.hapjs.render.jsruntime; + +import android.util.Log; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import org.hapjs.common.executors.Executors; +import org.hapjs.common.executors.ScheduledExecutor; +import org.hapjs.common.utils.FileUtils; +import org.hapjs.runtime.ProviderManager; +import org.hapjs.runtime.Runtime; +import org.hapjs.system.SysOpProvider; + +public class ProfilerHelper { + + private static final String TAG = "ProfilerHelper"; + + // 基于时间点和事件的 Profiler 文本文件 + private static final String FILE_NAME_TIMELINE = "profiler_log.txt"; + + // 基于火焰图的 Profile 火焰图文件 + private static final String FILE_NAME_FLAME = "framework.cpuprofile"; + + private static final String KEY_APP_START = "app_start"; + + private static ScheduledExecutor sExecutor = Executors.createSingleThreadExecutor(); + private static Map mMsgMap = new HashMap<>(); + + private static boolean sIsAllowProfiler; + private static boolean sIsCached; + + public static void recordAppStart(long time) { + mMsgMap.put(KEY_APP_START, time); + } + + public static void recordFirstFrameRendered(long time, long threadId) { + if (!sIsAllowProfiler) { + Log.d(TAG, "recordFirstFrameRendered: not allow profiler"); + return; + } + sExecutor.execute(() -> { + if (mMsgMap.containsKey(KEY_APP_START)) { + writeToFile(getContent("first frame rendered", getCost(time, KEY_APP_START), threadId)); + } + }); + } + + public static boolean profilerIsEnabled() { + return sIsAllowProfiler; + } + + public static void profilerRecord(String msg, long threadId) { + sExecutor.execute(() -> writeToFile(getContent("record", msg, threadId))); + } + + public static void profilerSaveProfilerData(String data) { + sExecutor.execute(() -> writeFlameDataToFile(data)); + } + + public static void profilerTimeEnd(String msg) { + sExecutor.execute(() -> writeToFile(msg)); + } + + private static String getCost(long time, String key) { + Long value = mMsgMap.remove(key); + if (value != null) { + long cost = time - value; + return String.format(Locale.getDefault(), "%.1f", cost / 1000000f) + "ms"; + } + return ""; + } + + private static void writeToFile(String content) { + try { + File logFile = new File(Runtime.getInstance().getContext().getExternalFilesDir(null), FILE_NAME_TIMELINE); + if (!logFile.exists()) { + logFile.createNewFile(); + } + String finalContent = content + "\n"; + FileUtils.saveToFile(finalContent.getBytes(StandardCharsets.UTF_8), logFile, true); + } catch (IOException e) { + Log.e(TAG, "write to file failed", e); + } + } + + private static void writeFlameDataToFile(String content) { + try { + File logFile = new File(Runtime.getInstance().getContext().getExternalFilesDir(null), FILE_NAME_FLAME); + if (!logFile.exists()) { + logFile.createNewFile(); + } + String finalContent = content; + FileUtils.saveToFile(finalContent.getBytes(StandardCharsets.UTF_8), logFile, false); + } catch (IOException e) { + Log.e(TAG, "write to file failed", e); + } + } + + public static void checkProfilerState() { + if (sIsCached) { + return; + } + sExecutor.execute(() -> { + SysOpProvider provider = ProviderManager.getDefault().getProvider(SysOpProvider.NAME); + if (provider != null) { + sIsAllowProfiler = provider.isAllowProfiler(); + sIsCached = true; + } + }); + } + + private static String getContent(String prefix, String msg, long threadId) { + return getCurrentTime() + " " + threadId + " " + prefix + ": " + msg; + } + + private static String getCurrentTime() { + Date date = new Date(System.currentTimeMillis()); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + return sdf.format(date); + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxJsThread.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxJsThread.java new file mode 100644 index 00000000..11d22f5a --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxJsThread.java @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ + +package org.hapjs.render.jsruntime; + +import android.content.Context; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import com.eclipsesource.v8.V8; +import java.util.Map; +import org.hapjs.logging.RuntimeLogManager; +import org.hapjs.runtime.ProviderManager; +import org.hapjs.runtime.sandbox.SyncWaiter; + +public class SandboxJsThread extends JsThread { + private static final String TAG = "SandboxJsThread"; + + private Context mContext; + + private ParcelFileDescriptor[] mPositiveDescriptors; + private ParcelFileDescriptor[] mPassiveDescriptors; + + private JsContext mJsContext; + private JsBridgeRegisterHelper mJsBridgeRegisterHelper; + + public class H extends JsThread.H { + static final private int MSG_CODE_BASE = 100; + static final private int MSG_REGISTER_COMPONENTS = MSG_CODE_BASE + 1; + static final private int MSG_KEY_PRESS_PAGE = MSG_CODE_BASE + 2; + static final private int MSG_MENU_BUTTON_PRESS_PAGE = MSG_CODE_BASE + 3; + static final private int INSPECTOR_HANDLE_MESSAGE = MSG_CODE_BASE + 4; + static final private int INSPECTOR_INIT = MSG_CODE_BASE + 5; + static final private int INSPECTOR_SET_V8_CONTEXT = MSG_CODE_BASE + 6; + static final private int INSPECTOR_DISPOSE_V8_CONTEXT = MSG_CODE_BASE + 7; + static final private int INSPECTOR_DESTROY = MSG_CODE_BASE + 8; + static final private int INSPECTOR_BEGIN_LOAD_JS_CODE = MSG_CODE_BASE + 9; + static final private int INSPECTOR_END_LOAD_JS_CODE = MSG_CODE_BASE + 10; + static final private int INSPECTOR_EXECUTE_JS_CODE = MSG_CODE_BASE + 11; + static final private int INSPECTOR_FRONTEND_RELOAD = MSG_CODE_BASE + 12; + static final private int MSG_EXECUTE_OBJECT_SCRIPT_AND_STRINGIFY = MSG_CODE_BASE + 13; + static final private int ON_FRAME_CALLBACK = MSG_CODE_BASE + 14; + + H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REGISTER_COMPONENTS: { + registerComponents(msg.obj); + break; + } + case MSG_KEY_PRESS_PAGE: { + keyPressPage(msg.obj); + break; + } + case MSG_MENU_BUTTON_PRESS_PAGE: { + menuButtonPressPage(msg.obj); + break; + } + case INSPECTOR_HANDLE_MESSAGE: { + inspectorHandleMessage(msg.obj); + break; + } + case INSPECTOR_INIT: { + inspectorInit(msg.obj); + break; + } + case INSPECTOR_SET_V8_CONTEXT: { + inspectorSetV8Context(msg.obj); + break; + } + case INSPECTOR_DISPOSE_V8_CONTEXT: { + inspectorDisposeV8Context(msg.obj); + break; + } + case INSPECTOR_DESTROY: { + inspectorDestroy(msg.obj); + break; + } + case INSPECTOR_BEGIN_LOAD_JS_CODE: { + inspectorBeginLoadJsCode(msg.obj); + break; + } + case INSPECTOR_END_LOAD_JS_CODE: { + inspectorEndLoadJsCode(msg.obj); + break; + } + case INSPECTOR_EXECUTE_JS_CODE: { + inspectorExecuteJsCode(msg.obj); + break; + } + case INSPECTOR_FRONTEND_RELOAD: { + inspectorFrontendReload(msg.obj); + break; + } + case MSG_EXECUTE_OBJECT_SCRIPT_AND_STRINGIFY: { + executeObjectScriptAndStringify(msg.obj); + break; + } + case ON_FRAME_CALLBACK: { + onFrameCallback(msg.obj); + break; + } + default: { + super.handleMessage(msg); + break; + } + } + } + } + + public SandboxJsThread(Context context, ParcelFileDescriptor[] positiveDescriptors, ParcelFileDescriptor[] passiveDescriptors) { + super("SandboxJsThread"); + mContext = context; + mPositiveDescriptors = positiveDescriptors; + mPassiveDescriptors = passiveDescriptors; + + Message.obtain(mHandler, JsThread.H.MSG_INIT).sendToTarget(); + } + + @Override + protected H createHandler() { + return new H(getLooper()); + } + + public V8 getV8() { + return mJsContext.getV8(); + } + + @Override + protected void onInit() { + RuntimeLogManager.getDefault().logJsThreadTaskStart(mContext.getPackageName(), RuntimeLogManager.KEY_JS_ENV_INIT); + doInit(); + RuntimeLogManager.getDefault().logJsThreadTaskEnd(mContext.getPackageName(), RuntimeLogManager.KEY_JS_ENV_INIT); + } + + private void doInit() { + mJsContext = new JsContext(this); + SandboxProvider provider = ProviderManager.getDefault().getProvider(SandboxProvider.NAME); + provider.createSandboxChannelReceiver(mPassiveDescriptors[0], mPassiveDescriptors[1], this); + mNative = provider.createSandboxChannelSender(mPositiveDescriptors[0], mPositiveDescriptors[1], mHandler); + mJsBridgeRegisterHelper = new JsBridgeRegisterHelper(mContext, mJsContext, this, getId(), mNative); + mJsBridgeRegisterHelper.registerBridge(); + + mEngine = provider.createEngineImpl(mJsContext, new InspectorNativeCallback(), + e -> mNative.onV8Exception(e.getStackTrace(), e.getMessage()), + frameTimeNanos -> mJsBridgeRegisterHelper.onFrameCallback(frameTimeNanos)); + } + + public void postOnAttach(String environmentScript, String pkg) { + Object[] params = new Object[] {environmentScript, pkg}; + Message.obtain(mHandler, H.MSG_ATTACH, params).sendToTarget(); + } + + @Override + protected void onAttach(Object msgObj) { + Object[] params = (Object[]) msgObj; + String environmentScript = (String) params[0]; + String pkg = (String) params[1]; + mNative.setQuickAppPkg(pkg); + mEngine.setQuickAppPkg(pkg); + mEngine.onAttach(environmentScript, pkg); + mJsBridgeRegisterHelper.attach(pkg); + } + + public void postCreateApplication(int appId, String js, String css, String metaInfo) { + Object[] params = new Object[]{appId, js, css, metaInfo}; + mHandler.obtainMessage(H.MSG_CREATE_APPLICATION, params).sendToTarget(); + } + + public void postOnRequestApplication(int appId) { + mHandler.obtainMessage(H.MSG_ON_REQUEST_APPLICATION, new Object[] {appId}).sendToTarget(); + } + + public void postOnShowApplication(int appId) { + mHandler.obtainMessage(H.MSG_ON_SHOW_APPLICATION, new Object[] {appId}).sendToTarget(); + } + + public void postOnHideApplication(int appId) { + mHandler.obtainMessage(H.MSG_ON_HIDE_APPLICATION, new Object[] {appId}).sendToTarget(); + } + + public boolean postBackPressPage(int pageId) { + SyncWaiter waiter = new SyncWaiter<>(false); + mHandler.obtainMessage(H.MSG_BACK_PRESS, new Object[] {pageId, waiter}).sendToTarget(); + return waiter.waitAndGetResult(); + } + + @Override + protected boolean backPress(Object msgObj) { + Object[] params = (Object[]) msgObj; + SyncWaiter waiter = (SyncWaiter) params[1]; + boolean consumed = super.backPress(msgObj); + waiter.setResult(consumed); + return consumed; + } + + public boolean postMenuButtonPressPage(int pageId) { + SyncWaiter waiter = new SyncWaiter<>(false); + mHandler.obtainMessage(H.MSG_MENU_BUTTON_PRESS_PAGE, new Object[] {pageId, waiter}).sendToTarget(); + return waiter.waitAndGetResult(); + } + + @Override + protected boolean menuButtonPressPage(Object msgObj) { + Object[] params = (Object[]) msgObj; + SyncWaiter waiter = (SyncWaiter) params[1]; + boolean consumed = super.menuButtonPressPage(msgObj); + waiter.setResult(consumed); + return consumed; + } + + public boolean postKeyPressPage(int pageId, Map params) { + SyncWaiter waiter = new SyncWaiter<>(false); + Object[] obj = new Object[]{pageId, params, waiter}; + mHandler.obtainMessage(H.MSG_KEY_PRESS_PAGE, obj).sendToTarget(); + return waiter.waitAndGetResult(); + } + + private void keyPressPage(Object msgObj) { + int pageId = (int) ((Object[]) msgObj)[0]; + Map params = (Map) ((Object[]) msgObj)[1]; + SyncWaiter waiter = (SyncWaiter) ((Object[]) msgObj)[2]; + waiter.setResult(mEngine.keyPressPage(pageId, params)); + } + + public boolean postMenuPressPage(int pageId) { + SyncWaiter waiter = new SyncWaiter<>(false); + mHandler.obtainMessage(H.MSG_MENU_PRESS, new Object[] {pageId, waiter}).sendToTarget(); + return waiter.waitAndGetResult(); + } + + @Override + protected boolean onMenuPress(Object msgObj) { + Object[] params = (Object[]) msgObj; + int pageId = (int) params[0]; + SyncWaiter waiter = (SyncWaiter) params[1]; + boolean consumed = mEngine.menuPressPage(pageId); + waiter.setResult(consumed); + return consumed; + } + + public void postCreatePage(int appId, int pageId, String js, String css, Map params, + Map intent, Map meta) { + Object[] obj = new Object[]{appId, pageId, js, css, params, intent, meta}; + Message.obtain(mHandler, H.MSG_CREATE_PAGE, obj).sendToTarget(); + } + + public void postDestroyPage(int pageId) { + Message.obtain(mHandler, H.MSG_DESTROY_PAGE, new Object[] {pageId}).sendToTarget(); + } + + @Override + protected void destroyPage(Object msgObj) { + super.destroyPage(msgObj); + int pageId = (int) ((Object[]) msgObj)[0]; + mJsBridgeRegisterHelper.destroyPage(pageId); + } + + public void postDestroyApplication(int appId) { + Message.obtain(mHandler, H.MSG_DESTROY_APPLICATION, new Object[] {appId}).sendToTarget(); + } + + public void postRegisterComponents(String components) { + Message.obtain(mHandler, H.MSG_REGISTER_COMPONENTS, new Object[] {components}).sendToTarget(); + } + + private void registerComponents(Object msgObj) { + String components = (String) ((Object[]) msgObj)[0]; + mEngine.registerComponents(components); + } + + public void postTerminateExecution() { + Message.obtain(mHandler, H.MSG_TERMINATE_EXECUTION).sendToTarget(); + } + + public void postInspectorHandleMessage(Object[] args) { + Message.obtain(mHandler, H.INSPECTOR_HANDLE_MESSAGE, args).sendToTarget(); + } + + private void inspectorHandleMessage(Object msgObj) { + Object[] params = (Object[]) msgObj; + long ptr = (long) params[0]; + int sessionId = (int) params[1]; + String message = (String) params[2]; + mEngine.inspectorHandleMessage(ptr, sessionId, message); + } + + public long postInspectorInit(Object[] args) { + SyncWaiter waiter = new SyncWaiter(0l); + Object[] obj = new Object[]{args, waiter}; + Message.obtain(mHandler, H.INSPECTOR_INIT, obj).sendToTarget(); + return waiter.waitAndGetResult(); + } + + private void inspectorInit(Object msgObj) { + Object[] params = (Object[]) msgObj; + Object[] args = (Object[]) params[0]; + boolean autoEnable = (boolean) args[0]; + int sessionId = (int) args[1]; + SyncWaiter waiter = (SyncWaiter) params[1]; + waiter.setResult(mEngine.inspectorInit(autoEnable, sessionId)); + } + + public void postInspectorSetV8Context(Object[] args) { + Message.obtain(mHandler, H.INSPECTOR_SET_V8_CONTEXT, args).sendToTarget(); + } + + private void inspectorSetV8Context(Object msgObj) { + Object[] params = (Object[]) msgObj; + long ptr = (long) params[0]; + int isJsContextReCreated = (int) params[1]; + mEngine.inspectorSetV8Context(ptr, isJsContextReCreated); + } + + public void postInspectorDisposeV8Context(Object[] args) { + Message.obtain(mHandler, H.INSPECTOR_DISPOSE_V8_CONTEXT, args).sendToTarget(); + } + + private void inspectorDisposeV8Context(Object msgObj) { + Object[] params = (Object[]) msgObj; + long ptr = (long) params[0]; + mEngine.inspectorDisposeV8Context(ptr); + } + + public void postInspectorDestroy(Object[] args) { + Message.obtain(mHandler, H.INSPECTOR_DESTROY, args).sendToTarget(); + } + + private void inspectorDestroy(Object msgObj) { + Object[] params = (Object[]) msgObj; + long ptr = (long) params[0]; + mEngine.inspectorDestroy(ptr); + } + + public void postInspectorBeginLoadJsCode(Object[] args) { + Message.obtain(mHandler, H.INSPECTOR_BEGIN_LOAD_JS_CODE, args).sendToTarget(); + } + + private void inspectorBeginLoadJsCode(Object msgObj) { + Object[] params = (Object[]) msgObj; + String uri = (String) params[0]; + String content = (String) params[1]; + mEngine.inspectorBeginLoadJsCode(uri, content); + } + + public void postInspectorEndLoadJsCode(Object[] args) { + Message.obtain(mHandler, H.INSPECTOR_END_LOAD_JS_CODE, args).sendToTarget(); + } + + private void inspectorEndLoadJsCode(Object msgObj) { + Object[] params = (Object[]) msgObj; + String uri = (String) params[0]; + mEngine.inspectorEndLoadJsCode(uri); + } + + public String postInspectorExecuteJsCode(Object[] args) { + SyncWaiter waiter = new SyncWaiter<>(""); + Object[] obj = new Object[]{args, waiter}; + Message.obtain(mHandler, H.INSPECTOR_EXECUTE_JS_CODE, obj).sendToTarget(); + return waiter.waitAndGetResult(); + } + + private void inspectorExecuteJsCode(Object msgObj) { + Object[] params = (Object[]) msgObj; + Object[] args = (Object[]) params[0]; + long ptr = (long) args[0]; + String jsCode = (String) args[1]; + SyncWaiter waiter = (SyncWaiter) params[1]; + waiter.setResult(mEngine.inspectorExecuteJsCode(ptr, jsCode)); + } + + public void postInspectorFrontendReload(Object[] args) { + Message.obtain(mHandler, H.INSPECTOR_FRONTEND_RELOAD, args).sendToTarget(); + } + + private void inspectorFrontendReload(Object msgObj) { + Object[] params = (Object[]) msgObj; + long ptr = (long) params[0]; + mEngine.inspectorFrontendReload(ptr); + } + + public String postExecuteObjectScriptAndStringify(String script) { + SyncWaiter waiter = new SyncWaiter<>(""); + Object[] obj = new Object[]{script, waiter}; + Message.obtain(mHandler, H.MSG_EXECUTE_OBJECT_SCRIPT_AND_STRINGIFY, obj).sendToTarget(); + return waiter.waitAndGetResult(); + } + + protected void executeObjectScriptAndStringify(Object msgObj) { + Object[] params = (Object[]) msgObj; + String script = (String) params[0]; + SyncWaiter waiter = (SyncWaiter) params[1]; + waiter.setResult(mEngine.executeObjectScriptAndStringify(script)); + } + + public void onFrameCallback(Object[] args) { + Message.obtain(mHandler, H.ON_FRAME_CALLBACK, args).sendToTarget(); + } + + private void onFrameCallback(Object msgObj) { + Object[] params = (Object[]) msgObj; + long frameTimeNanos = (long) params[0]; + mEngine.onFrameCallback(frameTimeNanos); + } + + @Override + protected void doShutdown() { + mJsBridgeRegisterHelper.unregister(); + super.doShutdown(); + } + + private class InspectorNativeCallback implements V8InspectorNative.InspectorNativeCallback{ + @Override + public void inspectorResponse(int sessionId, int callId, String message) { + mNative.inspectorResponse(sessionId, callId, message); + } + + @Override + public void inspectorSendNotification(int sessionId, int callId, String message) { + mNative.inspectorSendNotification(sessionId, callId, message); + } + + @Override + public void inspectorRunMessageLoopOnPause(int contextGroupId) { + mNative.inspectorRunMessageLoopOnPause(contextGroupId); + } + + @Override + public void inspectorQuitMessageLoopOnPause() { + mNative.inspectorQuitMessageLoopOnPause(); + } + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxProcessLauncher.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxProcessLauncher.java new file mode 100644 index 00000000..176fab6d --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxProcessLauncher.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.render.jsruntime; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Build; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import org.hapjs.common.executors.Executors; +import org.hapjs.common.utils.ProcessUtils; +import org.hapjs.logging.LogProvider; +import org.hapjs.runtime.ProviderManager; +import org.hapjs.runtime.Runtime; +import org.hapjs.runtime.sandbox.ISandbox; +import org.hapjs.runtime.sandbox.ILogProvider; +import org.hapjs.runtime.sandbox.SandboxConfigs; + +public class SandboxProcessLauncher { + private static final String TAG = "SandboxProcessLauncher"; + + private static class SingletonHolder { + private static SandboxProcessLauncher sInstance = new SandboxProcessLauncher(); + } + + public static SandboxProcessLauncher getInstance() { + return SingletonHolder.sInstance; + } + + private ISandbox mSandbox; + private ParcelFileDescriptor[] mPositiveChannelDescriptors; + private ParcelFileDescriptor[] mPassiveChannelDescriptors; + + private SandboxProcessLauncher() { + } + + public void preStartSandboxProcess(String launcherId) { + Executors.io().execute(() -> startService(launcherId)); + } + + public void preBindSandboxProcess() { + Executors.io().execute(() -> ensureBind()); + } + + public synchronized ParcelFileDescriptor[][] getChannelDescriptor() { + ensureBind(); + ParcelFileDescriptor[][] channels = new ParcelFileDescriptor[][] {mPositiveChannelDescriptors, mPassiveChannelDescriptors}; + mPositiveChannelDescriptors = null; + mPassiveChannelDescriptors = null; + return channels; + } + + private synchronized void ensureBind() { + if (mSandbox != null) { + ensureChannel(mSandbox); + return; + } + + bindService(); + } + + private void startService(String launcherId) { + String sandboxName = "org.hapjs.runtime.sandbox.SandboxService$Sandbox" + launcherId; + Context context = Runtime.getInstance().getContext(); + Intent intent = new Intent(); + intent.setClassName(context.getPackageName(), sandboxName); + context.startService(intent); + } + + private void bindService() { + CountDownLatch bindingLatch = new CountDownLatch(1); + + String currentProcessName = ProcessUtils.getCurrentProcessName(); + String sandboxName = "org.hapjs.runtime.sandbox.SandboxService$Sandbox" + + currentProcessName.charAt(currentProcessName.length() - 1); + + Context context = Runtime.getInstance().getContext(); + Intent intent = new Intent(); + intent.setClassName(context.getPackageName(), sandboxName); + + ServiceConnection connection = new SandboxServiceConnection(bindingLatch); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Executor executor = java.util.concurrent.Executors.newSingleThreadExecutor(); + context.bindService(intent, Context.BIND_AUTO_CREATE, executor, connection); + } else { + context.bindService(intent, connection, Context.BIND_AUTO_CREATE); + } + + try { + bindingLatch.await(); + } catch (InterruptedException e) { + Log.e(TAG, "exception while mBindingLatch.await", e); + } + } + + private void ensureChannel(ISandbox iSandbox) { + if (mPositiveChannelDescriptors != null) { + return; + } + + try { + ParcelFileDescriptor[] forwardChannel = ParcelFileDescriptor.createReliablePipe(); + ParcelFileDescriptor[] backwardChannel = ParcelFileDescriptor.createReliablePipe(); + ParcelFileDescriptor[] readSides = iSandbox.createChannel(new ParcelFileDescriptor[] { + forwardChannel[0], backwardChannel[0] + }); + mPositiveChannelDescriptors = new ParcelFileDescriptor[] { + readSides[1], forwardChannel[1] + }; + mPassiveChannelDescriptors = new ParcelFileDescriptor[] { + readSides[0], backwardChannel[1] + }; + } catch (RemoteException | IOException e) { + throw new RuntimeException(e); + } + } + + private class SandboxServiceConnection implements ServiceConnection { + private CountDownLatch mBindingLatch; + + SandboxServiceConnection(CountDownLatch bindingLatch) { + mBindingLatch = bindingLatch; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + ISandbox iSandbox = ISandbox.Stub.asInterface(service); + mSandbox = iSandbox; + try { + SandboxProvider sandboxProvider = ProviderManager.getDefault().getProvider(SandboxProvider.NAME); + iSandbox.init(new SandboxConfigs.Builder() + .setDebugLogEnabled(sandboxProvider != null && sandboxProvider.isDebugLogEnabled()) + .setProfilerEnabled(ProfilerHelper.profilerIsEnabled()) + .build()); + iSandbox.setLogProvider(new SandboxLogProvider()); + iSandbox.asBinder().linkToDeath(() -> { + Log.e(TAG, "sandbox process has died. kill app process as well."); + Process.killProcess(Process.myPid()); + }, 0); + } catch (RemoteException e) { + Log.e(TAG, "failed to init or setStatProvider or linkToDeath", e); + } + ensureChannel(iSandbox); + mBindingLatch.countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + } + + private class SandboxLogProvider extends ILogProvider.Stub { + @Override + public void logCountEvent(String appPackage, String category, String key) { + LogProvider provider = ProviderManager.getDefault().getProvider(LogProvider.NAME); + if (provider != null) { + provider.logCountEvent(appPackage, category, key); + } + } + + @Override + public void logCountEventWithParams(String appPackage, String category, String key, Map params) { + LogProvider provider = ProviderManager.getDefault().getProvider(LogProvider.NAME); + if (provider != null) { + provider.logCountEvent(appPackage, category, key, params); + } + } + + @Override + public void logCalculateEvent(String appPackage, String category, String key, long value) { + LogProvider provider = ProviderManager.getDefault().getProvider(LogProvider.NAME); + if (provider != null) { + provider.logCalculateEvent(appPackage, category, key, value); + } + } + + @Override + public void logCalculateEventWithParams(String appPackage, String category, String key, long value, Map params) { + LogProvider provider = ProviderManager.getDefault().getProvider(LogProvider.NAME); + if (provider != null) { + provider.logCalculateEvent(appPackage, category, key, value, params); + } + } + + @Override + public void logNumericPropertyEvent(String appPackage, String category, String key, long value) { + LogProvider provider = ProviderManager.getDefault().getProvider(LogProvider.NAME); + if (provider != null) { + provider.logNumericPropertyEvent(appPackage, category, key, value); + } + } + + @Override + public void logNumericPropertyEventWithParams(String appPackage, String category, String key, long value, Map params) { + LogProvider provider = ProviderManager.getDefault().getProvider(LogProvider.NAME); + if (provider != null) { + provider.logNumericPropertyEvent(appPackage, category, key, value); + } + } + + @Override + public void logStringPropertyEvent(String appPackage, String category, String key, String value) { + LogProvider provider = ProviderManager.getDefault().getProvider(LogProvider.NAME); + if (provider != null) { + provider.logStringPropertyEvent(appPackage, category, key, value); + } + } + + @Override + public void logStringPropertyEventWithParams(String appPackage, String category, String key, String value, Map params) { + LogProvider provider = ProviderManager.getDefault().getProvider(LogProvider.NAME); + if (provider != null) { + provider.logStringPropertyEvent(appPackage, category, key, value, params); + } + } + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxProvider.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxProvider.java new file mode 100644 index 00000000..d05b6e86 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ + +package org.hapjs.render.jsruntime; + +import android.os.Handler; +import android.os.ParcelFileDescriptor; +import org.hapjs.render.action.RenderActionManager; +import org.hapjs.runtime.sandbox.AppChannelReceiver; +import org.hapjs.runtime.sandbox.AppChannelSender; +import org.hapjs.runtime.sandbox.SandboxChannelReceiver; +import org.hapjs.runtime.sandbox.SandboxChannelSender; + +public interface SandboxProvider { + String NAME = "SandboxProvider"; + boolean isSandboxEnabled(); + boolean isDebugLogEnabled(); + SandboxChannelSender createSandboxChannelSender(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, Handler handler); + SandboxChannelReceiver createSandboxChannelReceiver(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, SandboxJsThread jsThread); + AppChannelSender createAppChannelSender(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, Handler handler); + AppChannelReceiver createAppChannelReceiver(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, IJavaNative javaNative); + JsEngineImpl createEngineImpl(JsContext jsContext, V8InspectorNative.InspectorNativeCallback inspectorNativeCallback, + JsEngineImpl.V8ExceptionHandler v8ExceptionHandler, JsEngineImpl.FrameCallback frameCallback); + JavaNativeImpl createNativeImpl(RenderActionManager renderActionManager, JsEngineImpl.FrameCallback frameCallback); +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxProviderImpl.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxProviderImpl.java new file mode 100644 index 00000000..015b381c --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/SandboxProviderImpl.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ + +package org.hapjs.render.jsruntime; + +import android.os.Handler; +import android.os.ParcelFileDescriptor; +import org.hapjs.common.utils.ProcessUtils; +import org.hapjs.render.action.RenderActionManager; +import org.hapjs.runtime.Runtime; +import org.hapjs.runtime.sandbox.AppChannelReceiver; +import org.hapjs.runtime.sandbox.AppChannelSender; +import org.hapjs.runtime.sandbox.SandboxChannelReceiver; +import org.hapjs.runtime.sandbox.SandboxChannelSender; +import org.hapjs.runtime.sandbox.SandboxConfigs; + +public class SandboxProviderImpl implements SandboxProvider { + @Override + public boolean isSandboxEnabled() { + return false; + } + + @Override + public boolean isDebugLogEnabled() { + return ProcessUtils.isSandboxProcess(Runtime.getInstance().getContext()) && SandboxConfigs.isDebugLogEnabled(); + } + + @Override + public SandboxChannelSender createSandboxChannelSender(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, Handler handler) { + return new SandboxChannelSender(readSide, writeSide, handler); + } + + @Override + public SandboxChannelReceiver createSandboxChannelReceiver(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, + SandboxJsThread jsThread) { + return new SandboxChannelReceiver(readSide, writeSide, jsThread); + } + + @Override + public AppChannelSender createAppChannelSender(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, Handler handler) { + return new AppChannelSender(readSide, writeSide, handler); + } + + @Override + public AppChannelReceiver createAppChannelReceiver(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, IJavaNative javaNative) { + return new AppChannelReceiver(readSide, writeSide, javaNative); + } + + @Override + public JsEngineImpl createEngineImpl(JsContext jsContext, V8InspectorNative.InspectorNativeCallback inspectorNativeCallback, + JsEngineImpl.V8ExceptionHandler v8ExceptionHandler, JsEngineImpl.FrameCallback frameCallback) { + return new JsEngineImpl(jsContext, inspectorNativeCallback, v8ExceptionHandler, frameCallback); + } + + @Override + public JavaNativeImpl createNativeImpl(RenderActionManager renderActionManager, JsEngineImpl.FrameCallback frameCallback) { + return new JavaNativeImpl(renderActionManager, frameCallback); + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/V8InspectorNative.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/V8InspectorNative.java new file mode 100644 index 00000000..40ac7da5 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/V8InspectorNative.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.render.jsruntime; + +import androidx.annotation.Keep; + +import com.eclipsesource.v8.V8; + +@Keep +public class V8InspectorNative { + private static final String TAG = "V8InspectorNative"; + + public interface InspectorNativeCallback { + void inspectorResponse(int sessionId, int callId, String message); + void inspectorSendNotification(int sessionId, int callId, String message); + void inspectorRunMessageLoopOnPause(int contextGroupId); + void inspectorQuitMessageLoopOnPause(); + } + + static { + System.loadLibrary("inspector"); + } + + private InspectorNativeCallback mInspectorNativeCallback; + + public V8InspectorNative(InspectorNativeCallback inspectorNativeCallback) { + mInspectorNativeCallback = inspectorNativeCallback; + } + + //@callby native + @Keep + public void sendResponse(int sessionId, int callId, String message) { + mInspectorNativeCallback.inspectorResponse(sessionId, callId, message); + } + + //@callby native + @Keep + public void sendNotification(int sessionId, int callId, String message) { + mInspectorNativeCallback.inspectorSendNotification(sessionId, callId, message); + } + + //@callby native + @Keep + public void runMessageLoopOnPause(int contextGroupId) { + mInspectorNativeCallback.inspectorRunMessageLoopOnPause(contextGroupId); + } + + //@callby native + @Keep + public void quitMessageLoopOnPause() { + mInspectorNativeCallback.inspectorQuitMessageLoopOnPause(); + } + + public native void nativeHandleMessage(long ptr, int sessionId, String message); + public native long initNative(boolean autoEnable, int sessionId); + public native void nativeSetV8Context(long ptr, V8 v8, int isJsContextReCreated); + public native void nativeDisposeV8Context(long ptr); + public native void nativeDestroy(long ptr); + public native void nativeBeginLoadJsCode(String uri, String content); + public native void nativeEndLoadJsCode(String uri); + public native String nativeExecuteJsCode(long ptr, String jsCode); + public native void nativeFrontendReload(long ptr); +} + diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/V8ObjConverter.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/V8ObjConverter.java index cc5a905c..3efa7e00 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/V8ObjConverter.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/V8ObjConverter.java @@ -5,13 +5,14 @@ package org.hapjs.render.jsruntime; +import android.util.Pair; import com.eclipsesource.v8.V8Object; import java.util.Map; import org.hapjs.bridge.HybridRequest; public class V8ObjConverter { - static HybridRequest objToRequest(V8Object obj, String appId) { + static Pair> parseReqeustParams(V8Object obj) { String uri = obj.contains("uri") ? obj.getString("uri") : null; if (uri == null || uri.isEmpty()) { uri = obj.contains("path") ? obj.getString("path") : null; @@ -30,7 +31,7 @@ static HybridRequest objToRequest(V8Object obj, String appId) { } } - return new HybridRequest.Builder().pkg(appId).uri(uri).params(params).build(); + return new Pair<>(uri, params); } static String getPageUri(V8Object obj) { diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/AbstractSerializeArray.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/AbstractSerializeArray.java index ea49eb56..e37e509d 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/AbstractSerializeArray.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/AbstractSerializeArray.java @@ -7,11 +7,13 @@ import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; import org.json.JSONArray; +import java.nio.ByteBuffer; + abstract class AbstractSerializeArray implements SerializeArray { private static final String TAG = "AbstractSerializeArray"; @@ -88,6 +90,18 @@ public TypedArray getTypedArray(int index) throws SerializeException { return optTypedArray(index); } + @Override + public ByteBuffer getByteBuffer(int index) throws SerializeException { + ensureExists(index); + return optByteBuffer(index); + } + + @Override + public TypedArrayProxy getTypedArrayProxy(int index) throws SerializeException { + ensureExists(index); + return optTypedArrayProxy(index); + } + @Override public SerializeObject getSerializeObject(int index) throws SerializeException { ensureExists(index); diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/AbstractSerializeObject.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/AbstractSerializeObject.java index 75d76e0f..94ab9dea 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/AbstractSerializeObject.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/AbstractSerializeObject.java @@ -7,12 +7,14 @@ import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; + +import java.nio.ByteBuffer; import org.json.JSONObject; -abstract class AbstractSerializeObject implements SerializeObject { +public abstract class AbstractSerializeObject implements SerializeObject { private static final String TAG = "AbstractSerializeObject"; @Override @@ -88,6 +90,18 @@ public final TypedArray getTypedArray(String key) throws SerializeException { return optTypedArray(key); } + @Override + public final ByteBuffer getByteBuffer(String key) throws SerializeException { + ensureExists(key); + return optByteBuffer(key); + } + + @Override + public final TypedArrayProxy getTypedArrayProxy(String key) throws SerializeException { + ensureExists(key); + return optTypedArrayProxy(key); + } + @Override public final SerializeObject getSerializeObject(String key) throws SerializeException { ensureExists(key); diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JSONSerializeArray.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JSONSerializeArray.java index ee72b200..b5a428d9 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JSONSerializeArray.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JSONSerializeArray.java @@ -5,10 +5,13 @@ package org.hapjs.render.jsruntime.serialize; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; + +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -66,6 +69,16 @@ public TypedArray optTypedArray(int index) { return null; } + @Override + public ByteBuffer optByteBuffer(int index) { + return null; + } + + @Override + public TypedArrayProxy optTypedArrayProxy(int index) { + return null; + } + @Override public SerializeObject optSerializeObject(int index) { Object value = mJSONArray.opt(index); @@ -121,13 +134,13 @@ public SerializeArray put(String value) { } @Override - public SerializeArray put(ArrayBuffer value) { - throw new UnsupportedOperationException("Can't insert ArrayBuffer"); + public SerializeArray put(ByteBuffer value) { + throw new UnsupportedOperationException("Can't insert ByteBuffer"); } @Override - public SerializeArray put(TypedArray value) { - throw new UnsupportedOperationException("Can't insert TypedArray"); + public SerializeArray put(TypedArrayProxy value) { + throw new UnsupportedOperationException("Can't insert TypedArrayProxy"); } @Override diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JSONSerializeObject.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JSONSerializeObject.java index 6c78271f..89f2427c 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JSONSerializeObject.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JSONSerializeObject.java @@ -5,8 +5,10 @@ package org.hapjs.render.jsruntime.serialize; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; + +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -69,6 +71,16 @@ public TypedArray optTypedArray(String key) { return null; } + @Override + public ByteBuffer optByteBuffer(String key) { + return null; + } + + @Override + public TypedArrayProxy optTypedArrayProxy(String key) { + return null; + } + @Override public SerializeObject optSerializeObject(String key) { Object value = mJSONObject.opt(key); @@ -145,13 +157,13 @@ public SerializeObject put(String key, String value) { } @Override - public SerializeObject put(String key, ArrayBuffer value) { - throw new UnsupportedOperationException("Can't insert ArrayBuffer"); + public SerializeObject put(String key, ByteBuffer value) { + throw new UnsupportedOperationException("Can't insert ByteBuffer"); } @Override - public SerializeObject put(String key, TypedArray value) { - throw new UnsupportedOperationException("Can't insert TypedArray"); + public SerializeObject put(String key, TypedArrayProxy value) { + throw new UnsupportedOperationException("Can't insert TypedArrayProxy"); } @Override @@ -179,6 +191,11 @@ public SerializeObject put(String key, HandlerObject value) { throw new UnsupportedOperationException("Can't insert HandlerObject"); } + @Override + public SerializeObject put(String key, byte[] value) { + throw new UnsupportedOperationException("Can't insert byte array"); + } + @Override public Object remove(String key) { Object value = opt(key); diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JavaSerializeArray.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JavaSerializeArray.java index 43bd49b0..2e2e6142 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JavaSerializeArray.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JavaSerializeArray.java @@ -7,8 +7,11 @@ import android.util.Log; import android.util.SparseArray; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; + +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; + +import java.nio.ByteBuffer; import java.util.List; import org.json.JSONArray; import org.json.JSONException; @@ -151,6 +154,26 @@ public TypedArray optTypedArray(int index) { return mObject.optTypedArray(index); } + @Override + public ByteBuffer getByteBuffer(int index) throws SerializeException { + return mObject.getByteBuffer(index); + } + + @Override + public ByteBuffer optByteBuffer(int index) { + return mObject.optByteBuffer(index); + } + + @Override + public TypedArrayProxy getTypedArrayProxy(int index) throws SerializeException { + return mObject.getTypedArrayProxy(index); + } + + @Override + public TypedArrayProxy optTypedArrayProxy(int index) { + return mObject.optTypedArrayProxy(index); + } + @Override public SerializeObject getSerializeObject(int index) throws SerializeException { Object value = mOverlayMap.get(index); @@ -234,7 +257,7 @@ public SerializeArray put(String value) { } @Override - public SerializeArray put(ArrayBuffer value) { + public SerializeArray put(ByteBuffer value) { if (mObject instanceof JSONSerializeArray) { mObject = new V8SerializeArray(mObject.toList()); } @@ -243,7 +266,7 @@ public SerializeArray put(ArrayBuffer value) { } @Override - public SerializeArray put(TypedArray value) { + public SerializeArray put(TypedArrayProxy value) { if (mObject instanceof JSONSerializeArray) { mObject = new V8SerializeArray(mObject.toList()); } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JavaSerializeObject.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JavaSerializeObject.java index 87c9024f..32f23632 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JavaSerializeObject.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/JavaSerializeObject.java @@ -7,8 +7,10 @@ import android.util.Log; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; + +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -154,6 +156,26 @@ public TypedArray optTypedArray(String key) { return mObject.optTypedArray(key); } + @Override + public final ByteBuffer getByteBuffer(String key) throws SerializeException { + return mObject.getByteBuffer(key); + } + + @Override + public ByteBuffer optByteBuffer(String key) { + return mObject.optByteBuffer(key); + } + + @Override + public final TypedArrayProxy getTypedArrayProxy(String key) throws SerializeException { + return mObject.getTypedArrayProxy(key); + } + + @Override + public TypedArrayProxy optTypedArrayProxy(String key) { + return mObject.optTypedArrayProxy(key); + } + @Override public final SerializeObject getSerializeObject(String key) throws SerializeException { Object value = mOverlayMap.get(key); @@ -252,7 +274,7 @@ public final SerializeObject put(String key, String value) { } @Override - public final SerializeObject put(String key, ArrayBuffer value) { + public final SerializeObject put(String key, ByteBuffer value) { mOverlayMap.remove(key); if (mObject instanceof JSONSerializeObject) { mObject = new V8SerializeObject(mObject.toMap()); @@ -262,7 +284,7 @@ public final SerializeObject put(String key, ArrayBuffer value) { } @Override - public final SerializeObject put(String key, TypedArray value) { + public final SerializeObject put(String key, TypedArrayProxy value) { mOverlayMap.remove(key); if (mObject instanceof JSONSerializeObject) { mObject = new V8SerializeObject(mObject.toMap()); @@ -305,6 +327,16 @@ public SerializeObject put(String key, HandlerObject value) { return this; } + @Override + public SerializeObject put(String key, byte[] value) { + mOverlayMap.remove(key); + if (mObject instanceof JSONSerializeObject) { + mObject = new V8SerializeObject(mObject.toMap()); + } + mObject.put(key, value); + return this; + } + @Override public final Object remove(String key) { Object value = mOverlayMap.remove(key); diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/SerializeArray.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/SerializeArray.java index f840f190..1ea1dc48 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/SerializeArray.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/SerializeArray.java @@ -5,8 +5,11 @@ package org.hapjs.render.jsruntime.serialize; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; + + +import java.nio.ByteBuffer; import java.util.List; import org.json.JSONArray; @@ -45,13 +48,21 @@ public interface SerializeArray extends Serializable { String optString(int index, String defaultValue); - ArrayBuffer getArrayBuffer(int index) throws SerializeException; + ArrayBuffer getArrayBuffer(int key) throws SerializeException; + + ArrayBuffer optArrayBuffer(int key); + + TypedArray getTypedArray(int key) throws SerializeException; + + TypedArray optTypedArray(int key); + + ByteBuffer getByteBuffer(int index) throws SerializeException; - ArrayBuffer optArrayBuffer(int index); + ByteBuffer optByteBuffer(int index); - TypedArray getTypedArray(int index) throws SerializeException; + TypedArrayProxy getTypedArrayProxy(int index) throws SerializeException; - TypedArray optTypedArray(int index); + TypedArrayProxy optTypedArrayProxy(int index); SerializeObject getSerializeObject(int index) throws SerializeException; @@ -71,9 +82,9 @@ public interface SerializeArray extends Serializable { SerializeArray put(String value); - SerializeArray put(ArrayBuffer value); + SerializeArray put(ByteBuffer value); - SerializeArray put(TypedArray value); + SerializeArray put(TypedArrayProxy value); SerializeArray put(SerializeObject value); diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/SerializeObject.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/SerializeObject.java index fd4d1e25..5dfd9d7e 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/SerializeObject.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/SerializeObject.java @@ -5,8 +5,10 @@ package org.hapjs.render.jsruntime.serialize; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; + +import java.nio.ByteBuffer; import java.util.Map; import java.util.Set; import org.json.JSONObject; @@ -53,6 +55,11 @@ public interface SerializeObject extends Serializable { TypedArray getTypedArray(String key) throws SerializeException; TypedArray optTypedArray(String key); + ByteBuffer getByteBuffer(String key) throws SerializeException; + ByteBuffer optByteBuffer(String key) ; + + TypedArrayProxy getTypedArrayProxy(String key) throws SerializeException; + TypedArrayProxy optTypedArrayProxy(String key) ; SerializeObject getSerializeObject(String key) throws SerializeException; @@ -76,9 +83,9 @@ public interface SerializeObject extends Serializable { SerializeObject put(String key, String value); - SerializeObject put(String key, ArrayBuffer value); + SerializeObject put(String key, ByteBuffer value); - SerializeObject put(String key, TypedArray value); + SerializeObject put(String key, TypedArrayProxy value); SerializeObject put(String key, SerializeObject value); @@ -86,6 +93,8 @@ public interface SerializeObject extends Serializable { SerializeObject put(String key, HandlerObject value); + SerializeObject put(String key, byte[] value); + Object remove(String key); Map toMap(); diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/TypedArrayProxy.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/TypedArrayProxy.java new file mode 100644 index 00000000..2ca60008 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/TypedArrayProxy.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ + +package org.hapjs.render.jsruntime.serialize; + +import java.nio.ByteBuffer; + +public class TypedArrayProxy { + private int type; + private ByteBuffer buffer; + private byte[] bytes; + + private TypedArrayProxy() {} + + public TypedArrayProxy(int type, byte[] bytes) { + this.type = type; + this.bytes = bytes; + } + + public byte[] getBytes() { + return bytes; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public ByteBuffer getBuffer() { + if (buffer == null) { + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bytes.length); + byteBuffer.put(bytes); + byteBuffer.rewind(); + buffer = byteBuffer; + } + return buffer; + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/V8SerializeArray.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/V8SerializeArray.java index 3df81ad1..e91f32ba 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/V8SerializeArray.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/V8SerializeArray.java @@ -6,8 +6,10 @@ package org.hapjs.render.jsruntime.serialize; import com.eclipsesource.v8.V8; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; + +import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import org.json.JSONArray; @@ -86,6 +88,26 @@ public TypedArray optTypedArray(int index) { } } + @Override + public ByteBuffer optByteBuffer(int index) { + Object value = opt(index); + if (value instanceof ByteBuffer) { + return (ByteBuffer) value; + } else { + return null; + } + } + + @Override + public TypedArrayProxy optTypedArrayProxy(int index) { + Object value = opt(index); + if (value instanceof TypedArrayProxy) { + return (TypedArrayProxy) value; + } else { + return null; + } + } + @Override public SerializeObject optSerializeObject(int index) { Object value = mList.get(index); @@ -137,13 +159,13 @@ public SerializeArray put(String value) { } @Override - public SerializeArray put(ArrayBuffer value) { + public SerializeArray put(ByteBuffer value) { mList.add(value); return this; } @Override - public SerializeArray put(TypedArray value) { + public SerializeArray put(TypedArrayProxy value) { mList.add(value); return this; } @@ -193,6 +215,10 @@ public JSONArray toJSONArray() { // ignore } else if (value instanceof TypedArray) { // ignore + } else if (value instanceof ByteBuffer) { + // ignore + } else if (value instanceof TypedArrayProxy) { + // ignore } else if (value instanceof Map) { result.put(new V8SerializeObject((Map) value).toJSONObject()); } else if (value instanceof List) { diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/V8SerializeObject.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/V8SerializeObject.java index ba7a89c4..28e2f789 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/V8SerializeObject.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/jsruntime/serialize/V8SerializeObject.java @@ -6,8 +6,10 @@ package org.hapjs.render.jsruntime.serialize; import com.eclipsesource.v8.V8; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; -import com.eclipsesource.v8.utils.typedarrays.TypedArray; +import com.eclipsesource.v8.utils.ArrayBuffer; +import com.eclipsesource.v8.utils.TypedArray; + +import java.nio.ByteBuffer; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -15,9 +17,11 @@ import org.json.JSONException; import org.json.JSONObject; -class V8SerializeObject extends AbstractSerializeObject { +public class V8SerializeObject extends AbstractSerializeObject { private Map mMap; + private V8SerializeObject() {} + public V8SerializeObject(Map map) { mMap = map; removeUndefinedEntry(); @@ -84,6 +88,26 @@ public TypedArray optTypedArray(String key) { } } + @Override + public ByteBuffer optByteBuffer(String key) { + Object value = mMap.get(key); + if (value instanceof ByteBuffer) { + return (ByteBuffer) value; + } else { + return null; + } + } + + @Override + public TypedArrayProxy optTypedArrayProxy(String key) { + Object value = mMap.get(key); + if (value instanceof TypedArrayProxy) { + return (TypedArrayProxy) value; + } else { + return null; + } + } + @Override public SerializeObject optSerializeObject(String key) { Object value = mMap.get(key); @@ -145,13 +169,13 @@ public SerializeObject put(String key, String value) { } @Override - public SerializeObject put(String key, ArrayBuffer value) { + public SerializeObject put(String key, ByteBuffer value) { mMap.put(key, value); return this; } @Override - public SerializeObject put(String key, TypedArray value) { + public SerializeObject put(String key, TypedArrayProxy value) { mMap.put(key, value); return this; } @@ -174,6 +198,12 @@ public SerializeObject put(String key, HandlerObject value) { return this; } + @Override + public SerializeObject put(String key, byte[] value) { + mMap.put(key, value); + return this; + } + @Override public Object remove(String key) { Object value = opt(key); @@ -207,6 +237,12 @@ public JSONObject toJSONObject() { // ignore } else if (value instanceof TypedArray) { // ignore + } else if (value instanceof ByteBuffer) { + // ignore + } else if (value instanceof TypedArrayProxy) { + // ignore + } else if (value instanceof byte[]) { + // ignore } else if (value instanceof Map) { result.put(key, new V8SerializeObject((Map) value).toJSONObject()); diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/render/vdom/VDomActionApplier.java b/core/runtime/android/runtime/src/main/java/org/hapjs/render/vdom/VDomActionApplier.java index c79f2762..25a4908e 100755 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/render/vdom/VDomActionApplier.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/render/vdom/VDomActionApplier.java @@ -23,6 +23,7 @@ import org.hapjs.render.RootView; import org.hapjs.render.VDomChangeAction; import org.hapjs.render.css.Node; +import org.hapjs.render.jsruntime.AppJsThread; import org.hapjs.render.jsruntime.JsThread; import org.hapjs.render.skeleton.SkeletonProvider; import org.hapjs.runtime.HapEngine; @@ -74,7 +75,7 @@ private static Component generateComponent( public void applyChangeAction( HapEngine hapEngine, Context context, - JsThread jsThread, + AppJsThread jsThread, VDomChangeAction action, VDocument doc, RenderEventCallback renderEventCallback) { diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/Runtime.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/Runtime.java index f266a4f3..c0639144 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/Runtime.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/Runtime.java @@ -28,15 +28,18 @@ import org.hapjs.common.utils.DefaultStatusBarSizeProvider; import org.hapjs.common.utils.FrescoUtils; import org.hapjs.common.utils.ProcessUtils; -import org.hapjs.common.utils.SoLoaderHelper; import org.hapjs.common.utils.StatusBarSizeProvider; import org.hapjs.component.constants.DefaultFontSizeProvider; import org.hapjs.component.constants.FontSizeProvider; +import org.hapjs.logging.LogProvider; import org.hapjs.pm.DefaultNativePackageProviderImpl; import org.hapjs.pm.NativePackageProvider; import org.hapjs.render.DefaultFontFamilyProvider; import org.hapjs.render.FontFamilyProvider; -import org.hapjs.render.jsruntime.Profiler; +import org.hapjs.render.jsruntime.ProfilerHelper; +import org.hapjs.render.jsruntime.SandboxProvider; +import org.hapjs.render.jsruntime.SandboxProviderImpl; +import org.hapjs.runtime.sandbox.SandboxLogProviderImpl; import org.hapjs.system.DefaultSysOpProviderImpl; import org.hapjs.system.SysOpProvider; @@ -68,7 +71,7 @@ public static Runtime getInstance() { public final synchronized void onPreCreate(Context base) { if (ProcessUtils.isAppProcess(base)) { - Profiler.recordAppStart(System.nanoTime()); + ProfilerHelper.recordAppStart(System.nanoTime()); } if (mPreCreated) { Log.d(TAG, "already pre created! "); @@ -76,6 +79,7 @@ public final synchronized void onPreCreate(Context base) { } long preCreateStartTime = System.currentTimeMillis(); setContext(base); + ProviderManager.getDefault().addProvider(SandboxProvider.NAME, new SandboxProviderImpl()); doPreCreate(mContext); mPreCreated = true; long preCreateEndTime = System.currentTimeMillis(); @@ -91,7 +95,7 @@ public final synchronized void onCreate(Context context) { setContext(context); doCreate(context); if (ProcessUtils.isAppProcess(context)) { - Profiler.checkProfilerState(); + ProfilerHelper.checkProfilerState(); } synchronized (this) { mCreated = true; @@ -152,6 +156,10 @@ public String getVendor() { } protected void doPreCreate(Context context) { + if (ProcessUtils.isSandboxProcess(context)) { + ProviderManager pm = ProviderManager.getDefault(); + pm.addProvider(LogProvider.NAME, new SandboxLogProviderImpl()); + } } protected void doCreate(Context context) { diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/inspect/InspectorManager.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/inspect/InspectorManager.java index 80aa063f..dfa965a9 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/inspect/InspectorManager.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/inspect/InspectorManager.java @@ -9,17 +9,19 @@ import android.net.Uri; import android.util.Log; import android.view.View; -import com.eclipsesource.v8.V8; -import com.eclipsesource.v8.V8Value; + import java.io.FileNotFoundException; import java.io.IOException; import java.lang.ref.WeakReference; import okhttp3.Interceptor; import okhttp3.WebSocket; + import org.hapjs.common.net.HttpConfig; import org.hapjs.common.utils.FileUtils; import org.hapjs.render.Page; import org.hapjs.render.VDomChangeAction; +import org.hapjs.render.jsruntime.AppJsThread; +import org.hapjs.render.jsruntime.IJsEngine; import org.hapjs.render.jsruntime.JsThread; import org.hapjs.runtime.HapEngine; import org.hapjs.runtime.ProviderManager; @@ -27,7 +29,7 @@ public class InspectorManager { public static final String TAG = "InspectorManager"; - private WeakReference mJsthread; + private WeakReference mJsthread; private InspectorProvider mProviderImpl; private boolean mEnabled; @@ -51,17 +53,17 @@ public static InspectorManager getInstance() { return Holder.INSTANCE; } - public void notifyJsThreadReady(JsThread jsthread) { + public void notifyJsThreadReady(AppJsThread jsThread) { if (mEnabled) { - getInspector().onJsContextCreated(jsthread.getJsContext().getV8()); + getInspector().onJsContextCreated(jsThread.getEngine()); } else { - mJsthread = new WeakReference<>(jsthread); + mJsthread = new WeakReference<>(jsThread); } } private void sendJsthreadReadyMsg() { if (mJsthread != null) { - JsThread jsThread = mJsthread.get(); + AppJsThread jsThread = mJsthread.get(); if (jsThread != null) { jsThread.postInitInspectorJsContext(); } @@ -126,11 +128,11 @@ public WebSocket.Factory getWebSocketFactory() { } @Override - public void onJsContextCreated(V8 v8) { + public void onJsContextCreated(IJsEngine engine) { } @Override - public void onJsContextDispose(V8 v8) { + public void onJsContextDispose(IJsEngine engine) { } @Override @@ -155,11 +157,6 @@ public void onBeginLoadJsCode(String uri, String content) { public void onEndLoadJsCode(String uri) { } - @Override - public String handleConsoleMessage(final V8Value v8Array) { - return ""; - } - @Override public boolean isInspectorReady() { return true; @@ -168,5 +165,21 @@ public boolean isInspectorReady() { @Override public void setRootView(View view) { } + + @Override + public void inspectorResponse(int sessionId, int callId, String message) { + } + + @Override + public void inspectorSendNotification(int sessionId, int callId, String message) { + } + + @Override + public void inspectorRunMessageLoopOnPause(int contextGroupId) { + } + + @Override + public void inspectorQuitMessageLoopOnPause() { + } } } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/inspect/InspectorProvider.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/inspect/InspectorProvider.java index 73600fa7..9d9ed152 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/inspect/InspectorProvider.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/inspect/InspectorProvider.java @@ -7,12 +7,11 @@ import android.content.Context; import android.view.View; -import com.eclipsesource.v8.V8; -import com.eclipsesource.v8.V8Value; import okhttp3.Interceptor; import okhttp3.WebSocket; import org.hapjs.render.PageManager; import org.hapjs.render.VDomChangeAction; +import org.hapjs.render.jsruntime.IJsEngine; import org.hapjs.render.jsruntime.JsThread; public interface InspectorProvider extends PageManager.PageChangedListener { @@ -27,9 +26,9 @@ public interface InspectorProvider extends PageManager.PageChangedListener { WebSocket.Factory getWebSocketFactory(); - void onJsContextCreated(V8 v8); + void onJsContextCreated(IJsEngine engine); - void onJsContextDispose(V8 v8); + void onJsContextDispose(IJsEngine engine); void onAppliedChangeAction(Context context, JsThread jsThread, VDomChangeAction action); @@ -41,10 +40,15 @@ public interface InspectorProvider extends PageManager.PageChangedListener { void onBeginLoadJsCode(String uri, String content); void onEndLoadJsCode(String uri); - - String handleConsoleMessage(final V8Value v8Array); - boolean isInspectorReady(); void setRootView(View view); + + void inspectorResponse(int sessionId, int callId, String message); + + void inspectorSendNotification(int sessionId, int callId, String message); + + void inspectorRunMessageLoopOnPause(int contextGroupId); + + void inspectorQuitMessageLoopOnPause(); } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/AppChannelReceiver.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/AppChannelReceiver.java new file mode 100644 index 00000000..ff78a8d7 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/AppChannelReceiver.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import android.os.ParcelFileDescriptor; +import java.util.Map; +import org.hapjs.bridge.Response; +import org.hapjs.render.jsruntime.IJavaNative; + +// The channel end in app process receiving requests from sandbox process. +public class AppChannelReceiver extends ChannelReceiver { + private static final String TAG = "AppChannelReceiver"; + + protected IJavaNative mNative; + + public AppChannelReceiver(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, IJavaNative javaNative) { + super(readSide, writeSide, TAG); + mNative = javaNative; + } + + @Override + protected String getQuickAppPkg() { + return mNative.getQuickAppPkg(); + } + + @Override + protected Object onInvoke(String method, Object[] args) { + switch (method) { + case SandboxIpcMethods.CALL_NATIVE: + return callNative(args); + case SandboxIpcMethods.GET_VIEW_ID: + return getViewId(args); + case SandboxIpcMethods.READ_DEBUG_ASSET: + return readDebugAsset(args); + case SandboxIpcMethods.ON_KEY_EVENT_CALLBACK: + return onKeyEventCallback(args); + case SandboxIpcMethods.INVOKE_FEATURE: + return invokeFeature(args); + case SandboxIpcMethods.ROUTER_BACK: + return routerBack(args); + case SandboxIpcMethods.ROUTER_PUSH: + return routerPush(args); + case SandboxIpcMethods.ROUTER_CLEAR: + return routerClear(args); + case SandboxIpcMethods.ROUTER_REPLACE: + return routerReplace(args); + case SandboxIpcMethods.INSPECTOR_RESPONSE: + return inspectResponse(args); + case SandboxIpcMethods.INSPECTOR_SEND_NOTIFICATION: + return inspectorSendNotification(args); + case SandboxIpcMethods.INSPECTOR_RUN_MESSAGE_LOOP_ON_PAUSE: + return inspectorRunMessageLoopOnPause(args); + case SandboxIpcMethods.INSPECTOR_QUIT_MESSAGE_LOOP_ON_PAUSE: + return inspectorQuitMessageLoopOnPause(args); + case SandboxIpcMethods.PROFILER_IS_ENABLED: + return profilerIsEnabled(args); + case SandboxIpcMethods.PROFILER_RECORD: + return profilerRecord(args); + case SandboxIpcMethods.PROFILER_SAVE_PROFILER_DATA: + return profilerSaveProfilerData(args); + case SandboxIpcMethods.PROFILER_TIME_END: + return profilerTimeEnd(args); + case SandboxIpcMethods.ON_V8_EXCEPTION: + return onV8Exception(args); + case SandboxIpcMethods.REQUEST_ANIMATION_FRAME_NATIVE: + return requestAnimationFrameNative(args); + default: + throw new RuntimeException("unknown method: " + method); + } + } + + private Object callNative(Object[] args) { + int pageId = (Integer) args[0]; + String renderCommands = (String) args[1]; + mNative.callNative(pageId, renderCommands); + return null; + } + + private Object getViewId(Object[] args) { + int ref = (Integer) args[0]; + int viewId = mNative.getViewId(ref); + return viewId; + } + + private Object readDebugAsset(Object[] args) { + String path = (String) args[0]; + String asset = mNative.readDebugAsset(path); + return asset; + } + + private Object onKeyEventCallback(Object[] args) { + boolean consumed = (Boolean) args[0]; + int hash = (Integer) args[1]; + mNative.onKeyEventCallback(consumed, hash); + return null; + } + + private Object invokeFeature(Object[] args) { + String feature = (String) args[0]; + String action = (String) args[1]; + Object rawParams = (Object) args[2]; + String callback = (String) args[3]; + int instanceId = (Integer) args[4]; + Response response = mNative.invoke(feature, action, rawParams, callback, instanceId); + return response; + } + + private Object routerBack(Object[] args) { + mNative.routerBack(); + return null; + } + + private Object routerPush(Object[] args) { + String uri = (String) args[0]; + Map params = (Map) args[1]; + mNative.routerPush(uri, params); + return null; + } + + private Object routerReplace(Object[] args) { + String uri = (String) args[0]; + Map params = (Map) args[1]; + mNative.routerReplace(uri, params); + return null; + } + + private Object routerClear(Object[] args) { + mNative.routerClear(); + return null; + } + + private Object inspectResponse(Object[] args) { + int sessionId = (int) args[0]; + int callId = (int) args[1]; + String message = (String) args[2]; + mNative.inspectorResponse(sessionId, callId, message); + return null; + } + + private Object inspectorSendNotification(Object[] args) { + int sessionId = (int) args[0]; + int callId = (int) args[1]; + String message = (String) args[2]; + mNative.inspectorSendNotification(sessionId, callId, message); + return null; + } + + private Object inspectorRunMessageLoopOnPause(Object[] args) { + int contextGroupId = (int) args[0]; + mNative.inspectorRunMessageLoopOnPause(contextGroupId); + return null; + } + + private Object inspectorQuitMessageLoopOnPause(Object[] args) { + mNative.inspectorQuitMessageLoopOnPause(); + return null; + } + + private Object profilerIsEnabled(Object[] args) { + return mNative.profilerIsEnabled(); + } + + private Object profilerRecord(Object[] args) { + String msg = (String) args[0]; + long threadId = (long) args[1]; + mNative.profilerRecord(msg, threadId); + return null; + } + + private Object profilerSaveProfilerData(Object[] args) { + String data = (String) args[0]; + mNative.profilerSaveProfilerData(data); + return null; + } + + private Object profilerTimeEnd(Object[] args) { + String msg = (String) args[0]; + mNative.profilerTimeEnd(msg); + return null; + } + + private Object onV8Exception(Object[] args) { + StackTraceElement[] stack = (StackTraceElement[]) args[0]; + String msg = (String) args[1]; + mNative.onV8Exception(stack, msg); + return null; + } + + private Object requestAnimationFrameNative(Object[] args) { + mNative.requestAnimationFrameNative(); + return null; + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/AppChannelSender.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/AppChannelSender.java new file mode 100644 index 00000000..5ab45ea2 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/AppChannelSender.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import android.os.Handler; +import android.os.Looper; +import android.os.ParcelFileDescriptor; +import java.util.List; +import java.util.Map; +import org.hapjs.render.jsruntime.IJsEngine; +import org.hapjs.render.jsruntime.JsThread; +import org.json.JSONObject; + +// The channel end in app process sending requests to sandbox process. +public class AppChannelSender extends ChannelSender implements IJsEngine { + private Handler mHandler; + + public AppChannelSender(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, Handler handler) { + super(readSide, writeSide, handler); + mHandler = handler; + } + + @Override + public void block() { + invokeAsync(SandboxIpcMethods.BLOCK); + } + + @Override + public void unblock() { + if (Looper.myLooper() != mHandler.getLooper()) { + mHandler.post(() -> unblock()); + return; + } + invokeAsync(SandboxIpcMethods.UNBLOCK); + } + + @Override + public void onAttach(String environmentScript, String pkg) { + invokeAsync(SandboxIpcMethods.ON_ATTACH, environmentScript, pkg); + } + + @Override + public void notifyConfigurationChanged(int pageId, String type) { + invokeAsync(SandboxIpcMethods.NOTIFY_CONFIGURATION_CHANGED, pageId, type); + } + + @Override + public void updateLocale(String language, String country, Map resourcesJson) { + invokeAsync(SandboxIpcMethods.UPDATE_LOCALE, language, country, resourcesJson); + } + + @Override + public void registerBundleChunks(String content) { + invokeAsync(SandboxIpcMethods.REGISTER_BUNDLE_CHUNKS, content); + } + + @Override + public void createApplication(int appId, String js, String css, String metaInfo) { + invokeAsync(SandboxIpcMethods.CREATE_APPLICATION, appId, js, css, metaInfo); + } + + @Override + public void onRequestApplication(int appId) { + invokeAsync(SandboxIpcMethods.REQUEST_APPLICATION, appId); + } + + @Override + public void onShowApplication(int appId) { + invokeAsync(SandboxIpcMethods.ON_SHOW_APPLICATION, appId); + } + + @Override + public void onHideApplication(int appId) { + invokeAsync(SandboxIpcMethods.ON_HIDE_APPLICATION, appId); + } + + @Override + public boolean backPressPage(int pageId) { + return invokeSync(SandboxIpcMethods.BACK_PRESS_PAGE, boolean.class, pageId); + } + + @Override + public boolean menuButtonPressPage(int pageId) { + return invokeSync(SandboxIpcMethods.MENU_BUTTON_PRESS_PAGE, boolean.class, pageId); + } + + @Override + public boolean keyPressPage(int pageId, Map params) { + return invokeSync(SandboxIpcMethods.KEY_PRESS_PAGE, boolean.class, pageId, params); + } + + @Override + public boolean menuPressPage(int pageId) { + return invokeSync(SandboxIpcMethods.MENU_PRESS_PAGE, boolean.class, pageId); + } + + @Override + public void orientationChangePage(int pageId, String orientation, float angle) { + invokeAsync(SandboxIpcMethods.ORIENTATION_CHANGE_PAGE, pageId, orientation, angle); + } + + @Override + public void executeVoidScript(String script, String scriptName, int lineNumber) { // TODO: SYNC OR ASYNC + invokeAsync(SandboxIpcMethods.EXECUTE_VOID_SCRIPT, script, scriptName, lineNumber); + } + + @Override + public void executeScript(String script, String scriptName, int lineNumber) {// TODO: SYNC OR ASYNC + invokeAsync(SandboxIpcMethods.EXECUTE_SCRIPT, script, scriptName, lineNumber); + } + + @Override + public void executeVoidFunction(String func, Object[] params) { // TODO: sync or async + invokeAsync(SandboxIpcMethods.EXECUTE_VOID_FUNCTION, func, params); + } + + @Override + public String executeObjectScriptAndStringify(String script) { + return invokeSync(SandboxIpcMethods.EXECUTE_OBJECT_SCRIPT_AND_STRINGIFY, String.class, script); + } + + @Override + public void createPage(int appId, int pageId, String js, String css, Map params, Map intent, + Map meta) { + invokeAsync(SandboxIpcMethods.CREATE_PAGE, appId, pageId, js, css, params, intent, meta); + } + + @Override + public void recreatePage(int pageId) { + invokeAsync(SandboxIpcMethods.RECREATE_PAGE, pageId); + } + + @Override + public void refreshPage(int pageId, Map params, Map intent) { + invokeAsync(SandboxIpcMethods.REFRESH_PAGE, pageId, params, intent); + } + + @Override + public void notifyPageNotFound(int appId, String pageUri) { + invokeAsync(SandboxIpcMethods.NOTIFY_PAGE_NOT_FOUND, appId, pageUri); + } + + @Override + public void destroyPage(int pageId) { + invokeAsync(SandboxIpcMethods.DESTROY_PAGE, pageId); + } + + @Override + public void destroyApplication(int appId) { + invokeAsync(SandboxIpcMethods.DESTROY_APPLICATION, appId); + } + + @Override + public void fireEvent(List datas) { + invokeAsync(SandboxIpcMethods.FIRE_EVENT, datas); + } + + @Override + public void fireKeyEvent(JsThread.JsEventCallbackData data) { + invokeAsync(SandboxIpcMethods.FIRE_KEY_EVENT, data); + } + + @Override + public void fireCallback(JsThread.JsMethodCallbackData data) { + invokeAsync(SandboxIpcMethods.FIRE_CALLBACK, data); + } + + @Override + public void onFoldCard(int s, boolean f) { + invokeAsync(SandboxIpcMethods.ON_FOLD_CARD, s, f); + } + + @Override + public void reachPageTop(int pageId) { + invokeAsync(SandboxIpcMethods.REACH_PAGE_TOP, pageId); + } + + @Override + public void reachPageBottom(int pageId) { + invokeAsync(SandboxIpcMethods.REACH_PAGE_BOTTOM, pageId); + } + + @Override + public void pageScroll(int pageId, int scrollTop) { + invokeAsync(SandboxIpcMethods.PAGE_SCROLL, pageId, scrollTop); + } + + @Override + public void registerComponents(String builtInComponents) { + invokeAsync(SandboxIpcMethods.REGISTER_COMPONENTS, builtInComponents); + } + + @Override + public void terminateExecution() { + invokeAsync(SandboxIpcMethods.TERMINATE_EXECUTION); + } + + @Override + public void shutdown() { + invokeAsync(SandboxIpcMethods.SHUTDOWN); + } + + @Override + public void inspectorHandleMessage(long ptr, int sessionId, String message) { + invokeAsync(SandboxIpcMethods.INSPECTOR_HANDLE_MESSAGE, ptr, sessionId, message); + } + + @Override + public long inspectorInit(boolean autoEnable, int sessionId) { + return invokeSync(SandboxIpcMethods.INSPECTOR_INIT, long.class, autoEnable, sessionId); + } + + @Override + public void inspectorSetV8Context(long ptr, int isJsContextReCreated) { + invokeAsync(SandboxIpcMethods.INSPECTOR_SET_V8_CONTEXT, ptr, isJsContextReCreated); + } + + @Override + public void inspectorDisposeV8Context(long ptr) { + invokeAsync(SandboxIpcMethods.INSPECTOR_DISPOSE_V8_CONTEXT, ptr); + } + + @Override + public void inspectorDestroy(long ptr) { + invokeAsync(SandboxIpcMethods.INSPECTOR_DESTROY, ptr); + } + + @Override + public void inspectorBeginLoadJsCode(String uri, String content) { + invokeAsync(SandboxIpcMethods.INSPECTOR_BEGIN_LOAD_JS_CODE, uri, content); + } + + @Override + public void inspectorEndLoadJsCode(String uri) { + invokeAsync(SandboxIpcMethods.INSPECTOR_END_LOAD_JS_CODE, uri); + } + + @Override + public String inspectorExecuteJsCode(long ptr, String jsCode) { + return invokeSync(SandboxIpcMethods.INSPECTOR_EXECUTE_JS_CODE, String.class, ptr, jsCode); + } + + @Override + public void inspectorFrontendReload(long ptr) { + invokeAsync(SandboxIpcMethods.INSPECTOR_FRONTEND_RELOAD, ptr); + } + + @Override + public void onFrameCallback(long frameTimeNanos) { + invokeAsync(SandboxIpcMethods.ON_FRAME_CALLBACK, frameTimeNanos); + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/ChannelReceiver.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/ChannelReceiver.java new file mode 100644 index 00000000..a8aebed1 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/ChannelReceiver.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import android.os.ParcelFileDescriptor; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +// The channel end for receiving request from the corresponding sender end. +public abstract class ChannelReceiver extends SandboxChannel { + private static final String TAG = "ChannelReceiver"; + + public ChannelReceiver(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, String name) { + super(readSide, writeSide); + + new Thread(() -> startListen(), name).start(); + } + + private void startListen() { + try { + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + while (true) { + buff.reset(); + + debugLog(TAG, "start to read channel"); + read(buff); + + debugLog(TAG, "start to deserialize"); + SerializeHelper.Deserializer deserializer = SerializeHelper.getInstance().createDeserializer(buff.toByteArray()); + boolean sync = deserializer.readObject(boolean.class); + String method = deserializer.readObject(String.class); + Object[] args = deserializer.readObject(Object[].class); + long startTimeStamp = deserializer.readObject(long.class); + deserializer.close(); + debugLog(TAG, "start to deserialize, sync=" + sync + ", method=" + method + ", size=" + buff.size() + ", timeCosts=" + (System.currentTimeMillis() - startTimeStamp)); + + SandboxLogHelper.onChannelReceive(getQuickAppPkg(), method, buff.size(), startTimeStamp); + + Object response; + if (SandboxIpcMethods.HEART_BEAT.equals(method)) { + response = true; + } else { + response = onInvoke(method, args); + } + debugLog(TAG, "end to invoke, response=" + response); + + if (sync) { + SerializeHelper.Serializer serializer = SerializeHelper.getInstance().createSerializer(); + serializer.writeObject(response == null); + if (response != null) { + serializer.writeObject(response); + } + byte[] bytes = serializer.closeAndGetBytes(); + write(bytes); + debugLog(TAG, "end to write response"); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected abstract String getQuickAppPkg(); + protected abstract Object onInvoke(String method, Object[] args); +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/ChannelSender.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/ChannelSender.java new file mode 100644 index 00000000..198cb0db --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/ChannelSender.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import android.os.Handler; +import android.os.Looper; +import android.os.ParcelFileDescriptor; +import android.util.Log; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +// The channel end for sending request from the corresponding receiver end. +public class ChannelSender extends SandboxChannel { + private static final String TAG = "ChannelSender"; + + private SandboxLogHelper.PositiveChannelStatHelper mSandboxStatHelper; + private Handler mHandler; + private String mQuickAppPkg; + + public ChannelSender(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, Handler handler) { + super(readSide, writeSide); + mHandler = handler; + mSandboxStatHelper = new SandboxLogHelper.PositiveChannelStatHelper(this, handler); + } + + public void setQuickAppPkg(String pkg) { + mQuickAppPkg = pkg; + } + + public String getQuickAppPkg() { + return mQuickAppPkg; + } + + protected T invokeSync(String method, Class retClazz, Object... args) { + if (Looper.myLooper() != mHandler.getLooper()) { + throw new RuntimeException("channel can only be accessed from one single thread"); + } + + try { + debugLog(TAG, "start to scheduleHeartBeat"); + mSandboxStatHelper.scheduleHeartBeat(getQuickAppPkg()); + + debugLog(TAG, "start to write sync, method=" + method); + write(serialize(true, method, args)); + + debugLog(TAG, "start to read response, method=" + method); + ByteArrayOutputStream responseBuffer = new ByteArrayOutputStream(); + read(responseBuffer); + + debugLog(TAG, "end to read response, method=" + method); + SerializeHelper.Deserializer deserializer = SerializeHelper.getInstance().createDeserializer(responseBuffer.toByteArray()); + boolean isNull = deserializer.readObject(boolean.class); + T response = isNull ? null : deserializer.readObject(retClazz); + + debugLog(TAG, "end to write sync, method=" + method); + return response; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected void invokeAsync(String method, Object... args) { + if (Looper.myLooper() != mHandler.getLooper()) { + throw new RuntimeException("channel can only be accessed from one single thread"); + } + + try { + debugLog(TAG, "start to scheduleHeartBeat"); + mSandboxStatHelper.scheduleHeartBeat(getQuickAppPkg()); + + debugLog(TAG, "start to write async, method=" + method); + write(serialize(false, method, args)); + Log.d(TAG, "end to write response, method=" + method); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private byte[] serialize(boolean sync, String method, Object... args) throws IOException { + debugLog(TAG, "start to serialize, method=" + method); + + SerializeHelper.Serializer serializer = SerializeHelper.getInstance().createSerializer(); + serializer.writeObject(sync); + serializer.writeObject(method); + serializer.writeObject(args); + serializer.writeObject(System.currentTimeMillis()); + byte[] data = serializer.closeAndGetBytes(); + + debugLog(TAG, "end to serialize, dataSize=" + data.length); + return data; + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxChannel.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxChannel.java new file mode 100644 index 00000000..a5fa7500 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxChannel.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.locks.ReentrantLock; + +import org.hapjs.render.jsruntime.SandboxProvider; +import org.hapjs.runtime.ProviderManager; + +// A channel has two ends. One is for sending requests, the other is for receiving request and may send back response. +public class SandboxChannel { + private static final String TAG = "SandboxChannel"; + private ParcelFileDescriptor.AutoCloseInputStream mInput; + private ParcelFileDescriptor.AutoCloseOutputStream mOutput; + private byte[] mBuffer = new byte[64 * 1024]; + private ReentrantLock mLock = new ReentrantLock(true); + + public SandboxChannel(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide) { + mInput = new ParcelFileDescriptor.AutoCloseInputStream(readSide); + mOutput = new ParcelFileDescriptor.AutoCloseOutputStream(writeSide); + } + + protected void read(ByteArrayOutputStream buff) throws IOException { + int lenNeeded = readInt(); + read(mInput, buff, lenNeeded); + } + + private int readInt() throws IOException { + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + read(mInput, buff, 4); + byte[] array = buff.toByteArray(); + return ((array[0] & 0xff) << 24) | ((array[1] & 0xff) << 16) | ((array[2] & 0xff) << 8) | (array[3] & 0xff); + } + + private void read(InputStream input, OutputStream output, int lenNeeded) throws IOException { + int len, lenLeft = lenNeeded; + while ((len = input.read(mBuffer, 0, Math.min(mBuffer.length, lenLeft))) > 0) { + output.write(mBuffer, 0, len); + lenLeft -= len; + } + + if (len == -1) { + throw new IOException("stream closed."); + } else if (lenLeft != 0) { + throw new IOException("want " + lenNeeded + ", but get " + (lenNeeded - lenLeft)); + } + } + + protected void write(byte[] data) throws IOException { + writeInt(data.length); + mOutput.write(data, 0, data.length); + } + + private void writeInt(int val) throws IOException { + mBuffer[0] = (byte) ((val >> 24) & 0xff); + mBuffer[1] = (byte) ((val >> 16) & 0xff); + mBuffer[2] = (byte) ((val >> 8) & 0xff); + mBuffer[3] = (byte) (val & 0xff); + + mOutput.write(mBuffer, 0, 4); + } + + protected static void debugLog(String tag, String msg) { + SandboxProvider sandboxProvider = ProviderManager.getDefault().getProvider(SandboxProvider.NAME); + if (sandboxProvider != null && sandboxProvider.isDebugLogEnabled()) { + Log.d(tag, msg); + } + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxChannelReceiver.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxChannelReceiver.java new file mode 100644 index 00000000..0e769552 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxChannelReceiver.java @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import android.os.ParcelFileDescriptor; +import java.util.List; +import java.util.Map; +import org.hapjs.render.jsruntime.JsThread; +import org.hapjs.render.jsruntime.SandboxJsThread; +import org.json.JSONObject; + +// The channel end in sandbox process receiving requests from app process. +public class SandboxChannelReceiver extends ChannelReceiver { + private static final String TAG = "SandboxChannelReceiver"; + + protected SandboxJsThread mJsThread; + + public SandboxChannelReceiver(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, SandboxJsThread jsThread) { + super(readSide, writeSide, TAG); + mJsThread = jsThread; + } + + @Override + protected String getQuickAppPkg() { + return mJsThread.getNative() == null ? null : mJsThread.getNative().getQuickAppPkg(); + } + + @Override + protected Object onInvoke(String method, Object[] args) { + switch (method) { + case SandboxIpcMethods.BLOCK: + block(); + break; + case SandboxIpcMethods.UNBLOCK: + unblock(); + break; + case SandboxIpcMethods.ON_ATTACH: + onAttach(args); + break; + case SandboxIpcMethods.NOTIFY_CONFIGURATION_CHANGED: + notifyConfigurationChanged(args); + break; + case SandboxIpcMethods.UPDATE_LOCALE: + updateLocale(args); + break; + case SandboxIpcMethods.REGISTER_BUNDLE_CHUNKS: + registerBundleChunks(args); + break; + case SandboxIpcMethods.CREATE_APPLICATION: + createApplication(args); + break; + case SandboxIpcMethods.REQUEST_APPLICATION: + onRequestApplication(args); + break; + case SandboxIpcMethods.ON_SHOW_APPLICATION: + onShowApplication(args); + break; + case SandboxIpcMethods.ON_HIDE_APPLICATION: + onHideApplication(args); + break; + case SandboxIpcMethods.BACK_PRESS_PAGE: + return backPressPage(args); + case SandboxIpcMethods.MENU_BUTTON_PRESS_PAGE: + return menuButtonPressPage(args); + case SandboxIpcMethods.KEY_PRESS_PAGE: + return keyPressPage(args); + case SandboxIpcMethods.MENU_PRESS_PAGE: + return menuPressPage(args); + case SandboxIpcMethods.ORIENTATION_CHANGE_PAGE: + orientationChangePage(args); + break; + case SandboxIpcMethods.EXECUTE_VOID_SCRIPT: + executeVoidScript(args); + break; + case SandboxIpcMethods.EXECUTE_SCRIPT: + executeScript(args); + break; + case SandboxIpcMethods.EXECUTE_VOID_FUNCTION: + executeVoidFunction(args); + break; + case SandboxIpcMethods.EXECUTE_OBJECT_SCRIPT_AND_STRINGIFY: + return executeObjectScriptAndStringify(args); + case SandboxIpcMethods.CREATE_PAGE: + createPage(args); + break; + case SandboxIpcMethods.RECREATE_PAGE: + recreatePage(args); + break; + case SandboxIpcMethods.REFRESH_PAGE: + refreshPage(args); + break; + case SandboxIpcMethods.NOTIFY_PAGE_NOT_FOUND: + notifyPageNotFound(args); + break; + case SandboxIpcMethods.DESTROY_PAGE: + destroyPage(args); + break; + case SandboxIpcMethods.DESTROY_APPLICATION: + destroyApplication(args); + break; + case SandboxIpcMethods.FIRE_EVENT: + fireEvent(args); + break; + case SandboxIpcMethods.FIRE_KEY_EVENT: + fireKeyEvent(args); + break; + case SandboxIpcMethods.FIRE_CALLBACK: + fireCallback(args); + break; + case SandboxIpcMethods.ON_FOLD_CARD: + onFoldCard(args); + break; + case SandboxIpcMethods.REACH_PAGE_TOP: + reachPageTop(args); + break; + case SandboxIpcMethods.REACH_PAGE_BOTTOM: + reachPageBottom(args); + break; + case SandboxIpcMethods.PAGE_SCROLL: + pageScroll(args); + break; + case SandboxIpcMethods.REGISTER_COMPONENTS: + registerComponents(args); + break; + case SandboxIpcMethods.TERMINATE_EXECUTION: + terminateExecution(args); + break; + case SandboxIpcMethods.SHUTDOWN: + postShutdown(args); + break; + case SandboxIpcMethods.INSPECTOR_HANDLE_MESSAGE: + inspectorHandleMessage(args); + break; + case SandboxIpcMethods.INSPECTOR_INIT: + return inspectorInit(args); + case SandboxIpcMethods.INSPECTOR_SET_V8_CONTEXT: + inspectorSetV8Context(args); + break; + case SandboxIpcMethods.INSPECTOR_DISPOSE_V8_CONTEXT: + inspectorDisposeV8Context(args); + break; + case SandboxIpcMethods.INSPECTOR_DESTROY: + inspectorDestroy(args); + break; + case SandboxIpcMethods.INSPECTOR_BEGIN_LOAD_JS_CODE: + inspectorBeginLoadJsCode(args); + break; + case SandboxIpcMethods.INSPECTOR_END_LOAD_JS_CODE: + inspectorEndLoadJsCode(args); + break; + case SandboxIpcMethods.INSPECTOR_EXECUTE_JS_CODE: + return inspectorExecuteJsCode(args); + case SandboxIpcMethods.INSPECTOR_FRONTEND_RELOAD: + inspectorFrontendReload(args); + break; + case SandboxIpcMethods.ON_FRAME_CALLBACK: + onFrameCallback(args); + break; + } + return null; + } + + private void block() { + mJsThread.block(0); + } + + private void unblock() { + mJsThread.unblock(); + } + + private void onAttach(Object[] args) { + String script = (String) args[0]; + String pkg = (String) args[1]; + mJsThread.postOnAttach(script, pkg); + } + + private void notifyConfigurationChanged(Object[] args) { + int pageId = (Integer) args[0]; + String type = (String) args[1]; + mJsThread.postNotifyConfigurationChanged(pageId, type); + } + + private void updateLocale(Object[] args) { + String language = (String) args[0]; + String country = (String) args[1]; + Map resourcesJson = (Map) args[2]; + mJsThread.postUpdateLocale(language, country, resourcesJson); + } + + public void registerBundleChunks(Object[] args) { + String content = (String) args[0]; + mJsThread.postRegisterBundleChunks(content); + } + + public void createApplication(Object[] args) { + int appId = (Integer) args[0]; + String js = (String) args[1]; + String css = (String) args[2]; + String metaInfo = (String) args[3]; + mJsThread.postCreateApplication(appId, js, css, metaInfo); + } + + public void onRequestApplication(Object[] args) { + int appId = (Integer) args[0]; + mJsThread.postOnRequestApplication(appId); + } + + public void onShowApplication(Object[] args) { + int appId = (Integer) args[0]; + mJsThread.postOnShowApplication(appId); + } + + public void onHideApplication(Object[] args) { + int appId = (Integer) args[0]; + mJsThread.postOnHideApplication(appId); + } + + public Object backPressPage(Object[] args) { + int pageId = (Integer) args[0]; + return mJsThread.postBackPressPage(pageId); + } + + public Object menuButtonPressPage(Object[] args) { + int pageId = (Integer) args[0]; + + return mJsThread.postMenuButtonPressPage(pageId); + } + + public Object keyPressPage(Object[] args) { + int pageId = (Integer) args[0]; + Map params = (Map) args[1]; + return mJsThread.postKeyPressPage(pageId, params); + } + + public Object menuPressPage(Object[] args) { + int pageId = (Integer) args[0]; + return mJsThread.postMenuPressPage(pageId); + } + + private void orientationChangePage(Object[] args) { + int pageId = (Integer) args[0]; + String orientation = (String) args[1]; + float angel = (Float) args[2]; + mJsThread.postOrientationChange(pageId, orientation, angel); + } + + private void executeVoidScript(Object[] args) { + String script = (String) args[0]; + String scriptName = (String) args[1]; + int lineNumber = (Integer) args[2]; + mJsThread.postExecuteVoidScript(script, scriptName, lineNumber); + } + + private void executeScript(Object[] args) { + String script = (String) args[0]; + String scriptName = (String) args[1]; + int lineNumber = (Integer) args[2]; + mJsThread.postExecuteVoidScript(script, scriptName, lineNumber); + } + + private void executeVoidFunction(Object[] args) { + String function = (String) args[0]; + Object[] params = (Object[]) args[1]; + mJsThread.postExecuteFunction(function, params); + } + + private String executeObjectScriptAndStringify(Object[] args) { + String script = (String) args[0]; + return mJsThread.postExecuteObjectScriptAndStringify(script); + } + + private void createPage(Object[] args) { + int appId = (Integer) args[0]; + int pageId = (Integer) args[1]; + String js = (String) args[2]; + String css = (String) args[3]; + Map params = (Map) args[4]; + Map intent = (Map) args[5]; + Map meta = (Map) args[6]; + mJsThread.postCreatePage(appId, pageId, js, css, params, intent, meta); + } + + private void recreatePage(Object[] args) { + int pageId = (Integer) args[0]; + mJsThread.postRecreatePage(pageId); + } + + private void refreshPage(Object[] args) { + int pageId = (Integer) args[0]; + Map params = (Map) args[1]; + Map intent = (Map) args[2]; + mJsThread.postRefreshPage(pageId, params, intent); + } + + private void notifyPageNotFound(Object[] args) { + int appId = (Integer) args[0]; + String pageUri = (String) args[1]; + int pageId = (Integer) args[2]; + mJsThread.postPageNotFound(appId, pageUri, pageId); + } + + private void destroyPage(Object[] args) { + int pageId = (Integer) args[0]; + mJsThread.postDestroyPage(pageId); + } + + private void destroyApplication(Object[] args) { + int appId = (Integer) args[0]; + mJsThread.postDestroyApplication(appId); + } + + private void fireEvent(Object[] args) { + List data = (List) args[0]; + mJsThread.postFireEvent(data); + } + + private void fireKeyEvent(Object[] args) { + JsThread.JsEventCallbackData data = (JsThread.JsEventCallbackData) args[0]; + mJsThread.postFireKeyEvent(data); + } + + private void fireCallback(Object[] args) { + JsThread.JsMethodCallbackData data = (JsThread.JsMethodCallbackData) args[0]; + mJsThread.postFireCallback(data); + } + + private void onFoldCard(Object[] args) { + int s = (Integer) args[0]; + boolean f = (Boolean) args[1]; + mJsThread.postFoldCard(s, f); + } + + private void reachPageTop(Object[] args) { + int pageId = (Integer) args[0]; + mJsThread.postPageReachTop(pageId); + } + + private void reachPageBottom(Object[] args) { + int pageId = (Integer) args[0]; + mJsThread.postPageReachBottom(pageId); + } + + private void pageScroll(Object[] args) { + int pageId = (Integer) args[0]; + int scrollTop = (Integer) args[1]; + mJsThread.postPageScroll(pageId, scrollTop); + } + + private void registerComponents(Object[] args) { + String components = (String) args[0]; + mJsThread.postRegisterComponents(components); + } + + private void terminateExecution(Object[] args) { + mJsThread.postTerminateExecution(); + } + + private void postShutdown(Object[] args) { + mJsThread.postShutdown(); + } + + private void inspectorHandleMessage(Object[] args) { + mJsThread.postInspectorHandleMessage(args); + } + + private long inspectorInit(Object[] args) { + return mJsThread.postInspectorInit(args); + } + + private void inspectorSetV8Context(Object[] args) { + mJsThread.postInspectorSetV8Context(args); + } + + private void inspectorDisposeV8Context(Object[] args) { + mJsThread.postInspectorDisposeV8Context(args); + } + + private void inspectorDestroy(Object[] args) { + mJsThread.postInspectorDestroy(args); + } + + private void inspectorBeginLoadJsCode(Object[] args) { + mJsThread.postInspectorBeginLoadJsCode(args); + } + + private void inspectorEndLoadJsCode(Object[] args) { + mJsThread.postInspectorEndLoadJsCode(args); + } + + private String inspectorExecuteJsCode(Object[] args) { + return mJsThread.postInspectorExecuteJsCode(args); + } + + private void inspectorFrontendReload(Object[] args) { + mJsThread.postInspectorFrontendReload(args); + } + + private void onFrameCallback(Object[] args) { + mJsThread.onFrameCallback(args); + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxChannelSender.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxChannelSender.java new file mode 100644 index 00000000..8fe99510 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxChannelSender.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import android.os.Handler; +import android.os.ParcelFileDescriptor; +import java.util.Map; +import org.hapjs.bridge.Response; +import org.hapjs.render.jsruntime.IJavaNative; + +// The channel end in sandbox process sending requests to app process. +public class SandboxChannelSender extends ChannelSender implements IJavaNative { + public SandboxChannelSender(ParcelFileDescriptor readSide, ParcelFileDescriptor writeSide, Handler handler) { + super(readSide, writeSide, handler); + } + + @Override + public void callNative(int pageId, String argsString) { + invokeAsync(SandboxIpcMethods.CALL_NATIVE, pageId, argsString); + } + + @Override + public int getViewId(int ref) { + return invokeSync(SandboxIpcMethods.GET_VIEW_ID, int.class, ref); + } + + @Override + public String readDebugAsset(String path) { + return invokeSync(SandboxIpcMethods.READ_DEBUG_ASSET, String.class, path); + } + + @Override + public void onKeyEventCallback(boolean consumed, int hash) { + invokeAsync(SandboxIpcMethods.ON_KEY_EVENT_CALLBACK, consumed, hash); + } + + @Override + public Response invoke(String feature, String action, Object rawParams, String callback, int instanceId) { + return invokeSync(SandboxIpcMethods.INVOKE_FEATURE, Response.class, feature, action, rawParams, callback, instanceId); + } + + @Override + public void routerBack() { + invokeAsync(SandboxIpcMethods.ROUTER_BACK); + } + + @Override + public void routerClear() { + invokeAsync(SandboxIpcMethods.ROUTER_CLEAR); + } + + @Override + public void routerReplace(String uri, Map params) { + invokeAsync(SandboxIpcMethods.ROUTER_REPLACE, uri, params); + } + + @Override + public void routerPush(String uri, Map params) { + invokeAsync(SandboxIpcMethods.ROUTER_PUSH, uri, params); + } + + @Override + public void inspectorResponse(int sessionId, int callId, String message) { + invokeAsync(SandboxIpcMethods.INSPECTOR_RESPONSE, sessionId, callId, message); + } + + @Override + public void inspectorSendNotification(int sessionId, int callId, String message) { + invokeAsync(SandboxIpcMethods.INSPECTOR_SEND_NOTIFICATION, sessionId, callId, message); + } + + @Override + public void inspectorRunMessageLoopOnPause(int contextGroupId) { + invokeAsync(SandboxIpcMethods.INSPECTOR_RUN_MESSAGE_LOOP_ON_PAUSE, contextGroupId); + } + + @Override + public void inspectorQuitMessageLoopOnPause() { + invokeAsync(SandboxIpcMethods.INSPECTOR_QUIT_MESSAGE_LOOP_ON_PAUSE); + } + + @Override + public boolean profilerIsEnabled() { + return invokeSync(SandboxIpcMethods.PROFILER_IS_ENABLED, boolean.class); + } + + @Override + public void profilerRecord(String msg, long threadId) { + invokeAsync(SandboxIpcMethods.PROFILER_RECORD, msg, threadId); + } + + @Override + public void profilerSaveProfilerData(String data) { + invokeAsync(SandboxIpcMethods.PROFILER_SAVE_PROFILER_DATA, data); + } + + @Override + public void profilerTimeEnd(String msg) { + invokeAsync(SandboxIpcMethods.PROFILER_TIME_END, msg); + } + + @Override + public void onV8Exception(StackTraceElement[] stack, String msg) { + invokeAsync(SandboxIpcMethods.ON_V8_EXCEPTION, stack, msg); + } + + @Override + public void requestAnimationFrameNative() { + invokeAsync(SandboxIpcMethods.REQUEST_ANIMATION_FRAME_NATIVE); + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxConfigs.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxConfigs.java new file mode 100644 index 00000000..56a16610 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxConfigs.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import java.util.HashMap; +import java.util.Map; + +public class SandboxConfigs { + private static Map sConfigs; + private static final String KEY_DEBUG_LOG_ENABLED = "debugLogEnabled"; + private static final String KEY_PROFILER_ENABLED = "profilerEnabled"; + + public static void setConfigs(Map config) { + sConfigs = config; + } + + public static boolean isDebugLogEnabled() { + Map config = sConfigs; + return config != null && config.get(KEY_DEBUG_LOG_ENABLED) != null + && Boolean.valueOf(config.get(KEY_DEBUG_LOG_ENABLED)); + } + + public static boolean isProfilerEnabled() { + Map config = sConfigs; + return config != null && config.get(KEY_PROFILER_ENABLED) != null + && Boolean.valueOf(config.get(KEY_PROFILER_ENABLED)); + } + + public static class Builder { + private Map mConfigs = new HashMap<>(); + + public Builder() {} + + public Builder setDebugLogEnabled(boolean enabled) { + mConfigs.put(KEY_DEBUG_LOG_ENABLED, String.valueOf(enabled)); + return this; + } + + public Builder setProfilerEnabled(boolean enabled) { + mConfigs.put(KEY_PROFILER_ENABLED, String.valueOf(enabled)); + return this; + } + + public Map build() { + return mConfigs; + } + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxIpcMethods.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxIpcMethods.java new file mode 100644 index 00000000..8f71e709 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxIpcMethods.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +public interface SandboxIpcMethods { + String HEART_BEAT = "heartBeat"; + String BLOCK = "block"; + String UNBLOCK = "unblock"; + String ON_ATTACH = "onAttach"; + String NOTIFY_CONFIGURATION_CHANGED = "notifyConfigurationChanged"; + String UPDATE_LOCALE = "updateLocale"; + String REGISTER_BUNDLE_CHUNKS = "registerBundleChunks"; + String CREATE_APPLICATION = "createApplication"; + String REQUEST_APPLICATION = "requestApplication"; + String ON_SHOW_APPLICATION = "onShowApplication"; + String ON_HIDE_APPLICATION = "onHideApplication"; + String BACK_PRESS_PAGE = "backPressPage"; + String MENU_BUTTON_PRESS_PAGE = "menuButtonPressPage"; + String KEY_PRESS_PAGE = "keyPressPage"; + String MENU_PRESS_PAGE = "menuPressPage"; + String ORIENTATION_CHANGE_PAGE = "orientationChangePage"; + String EXECUTE_VOID_SCRIPT = "executeVoidScript"; + String EXECUTE_SCRIPT = "executeScript"; + String EXECUTE_VOID_FUNCTION = "executeVoidFunction"; + String EXECUTE_OBJECT_SCRIPT_AND_STRINGIFY = "executeObjectScriptAndStringify"; + String CREATE_PAGE = "createPage"; + String RECREATE_PAGE = "recreatePage"; + String REFRESH_PAGE = "refreshPage"; + String NOTIFY_PAGE_NOT_FOUND = "notifyPageNotFound"; + String DESTROY_PAGE = "destroyPage"; + String DESTROY_APPLICATION = "destroyApplication"; + String FIRE_EVENT = "fireEvent"; + String FIRE_KEY_EVENT = "fireKeyEvent"; + String FIRE_CALLBACK = "fireCallback"; + String ON_FOLD_CARD = "onFoldCard"; + String REACH_PAGE_TOP = "reachPageTop"; + String REACH_PAGE_BOTTOM = "reachPageBottom"; + String PAGE_SCROLL = "pageScroll"; + String REGISTER_COMPONENTS = "registerComponents"; + String TERMINATE_EXECUTION = "terminateExecution"; + String SHUTDOWN = "shutdown"; + String INSPECTOR_HANDLE_MESSAGE = "inspectorHandleMessage"; + String INSPECTOR_INIT = "inspectorInit"; + String INSPECTOR_SET_V8_CONTEXT = "inspectorSetV8Context"; + String INSPECTOR_DISPOSE_V8_CONTEXT = "inspectorDisposeV8Context"; + String INSPECTOR_DESTROY = "inspectorDestroy"; + String INSPECTOR_BEGIN_LOAD_JS_CODE = "inspectorBeginLoadJsCode"; + String INSPECTOR_END_LOAD_JS_CODE = "inspectorEndLoadJsCode"; + String INSPECTOR_EXECUTE_JS_CODE = "inspectorExecuteJsCode"; + String INSPECTOR_FRONTEND_RELOAD = "inspectorFrontendReload"; + String ON_FRAME_CALLBACK = "onFrameCallback"; + + String CALL_NATIVE = "callNative"; + String GET_VIEW_ID = "getViewId"; + String READ_DEBUG_ASSET = "readDebugAsset"; + String ON_KEY_EVENT_CALLBACK = "onKeyEventCallback"; + String INVOKE_FEATURE = "invokeFeature"; + String ROUTER_BACK = "routerBack"; + String ROUTER_PUSH = "routerPush"; + String ROUTER_CLEAR = "routerClear"; + String ROUTER_REPLACE = "routerReplace"; + String INSPECTOR_RESPONSE = "inspectorResponse"; + String INSPECTOR_SEND_NOTIFICATION = "sendNotification"; + String INSPECTOR_RUN_MESSAGE_LOOP_ON_PAUSE = "runMessageLoopOnPause"; + String INSPECTOR_QUIT_MESSAGE_LOOP_ON_PAUSE = "quitMessageLoopOnPause"; + String PROFILER_IS_ENABLED = "profilerIsEnabled"; + String PROFILER_RECORD = "profilerRecord"; + String PROFILER_SAVE_PROFILER_DATA = "profilerSaveProfilerData"; + String PROFILER_TIME_END = "profilerTimeEnd"; + String ON_V8_EXCEPTION = "onV8Exception"; + String REQUEST_ANIMATION_FRAME_NATIVE = "requestAnimationFrameNative"; +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxLogHelper.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxLogHelper.java new file mode 100644 index 00000000..5d8695d3 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxLogHelper.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import org.hapjs.logging.RuntimeLogManager; +import org.hapjs.render.jsruntime.SandboxProvider; +import org.hapjs.runtime.ProviderManager; + +public class SandboxLogHelper { + private static final String TAG = "SandboxLogHelper"; + + private static final long SLOW_MESSAGE_THRESHOLD = 100; + private static final long HEART_BEAT_DELAY = 1000; + private static final long HEART_BEAT_TIMEOUT_THRESHOLD = 1000; + + public static class PositiveChannelStatHelper { + private ChannelSender mChannel; + private Handler mSenderHandler; + private Handler mMainHandler; + private boolean mHeartBeatWaiting; + + public PositiveChannelStatHelper(ChannelSender channel, Handler handler) { + mChannel = channel; + mSenderHandler = handler; + mMainHandler = new Handler(Looper.getMainLooper()); + } + + public void scheduleHeartBeat(String pkg) { + if (mHeartBeatWaiting) { + return; + } + + mHeartBeatWaiting = true; + mSenderHandler.postDelayed(() -> { + long start = System.currentTimeMillis(); + Runnable recordSlowHeartbeat = () -> { + Log.w(TAG, "heartbeat response too slow"); + RuntimeLogManager.getDefault().recordSandboxMessageSlow(pkg, SandboxIpcMethods.HEART_BEAT, 0, + System.currentTimeMillis() - start); + }; + mMainHandler.postDelayed(recordSlowHeartbeat, HEART_BEAT_TIMEOUT_THRESHOLD); + + debugLog(TAG, "heartbeat starts"); + mChannel.invokeSync(SandboxIpcMethods.HEART_BEAT, boolean.class); + debugLog(TAG, "heartbeat ends"); + + mHeartBeatWaiting = false; + mMainHandler.removeCallbacks(recordSlowHeartbeat); + if (System.currentTimeMillis() - start > HEART_BEAT_TIMEOUT_THRESHOLD) { + Log.w(TAG, "heartbeat response two slow: " + (System.currentTimeMillis() - start)); + RuntimeLogManager.getDefault().recordSandboxMessageSlow(pkg, SandboxIpcMethods.HEART_BEAT, 0, + System.currentTimeMillis() - start); + } + }, HEART_BEAT_DELAY); + } + } + + public static void onChannelReceive(String pkg, String method, int dataSize, long startStamp) { + long now = System.currentTimeMillis(); + long timeCost = now - startStamp; + if (timeCost > SLOW_MESSAGE_THRESHOLD) { + RuntimeLogManager.getDefault().recordSandboxMessageSlow(pkg, method, dataSize, timeCost); + } + } + + private static void debugLog(String tag, String msg) { + SandboxProvider sandboxProvider = ProviderManager.getDefault().getProvider(SandboxProvider.NAME); + if (sandboxProvider != null && sandboxProvider.isDebugLogEnabled()) { + Log.d(tag, msg); + } + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxLogProviderImpl.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxLogProviderImpl.java new file mode 100644 index 00000000..fbd6abfd --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxLogProviderImpl.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import android.os.RemoteException; +import android.util.Log; +import java.util.Map; +import org.hapjs.common.executors.Executor; +import org.hapjs.common.executors.Executors; +import org.hapjs.logging.LogProvider; + +public class SandboxLogProviderImpl implements LogProvider { + private static final String TAG = "SandboxLogProviderImpl"; + + private ILogProvider mLogProviderImpl; + private Executor mExecutor = Executors.createSingleThreadExecutor(); + + public void setLogProvider(ILogProvider logProviderImpl) { + mLogProviderImpl = logProviderImpl; + } + + @Override + public void logCountEvent(String appPackage, String category, String key) { + mExecutor.execute(() -> doLogCountEvent(appPackage, category, key)); + } + + protected void doLogCountEvent(String appPackage, String category, String key) { + ILogProvider logProviderImpl = mLogProviderImpl; + if (logProviderImpl == null) { + return; + } + + try { + logProviderImpl.logCountEvent(appPackage, category, key); + } catch (RemoteException e) { + Log.e(TAG, "failed to stat", e); + } + } + + @Override + public void logCountEvent(String appPackage, String category, String key, Map params) { + mExecutor.execute(() -> doLogCountEvent(appPackage, category, key, params)); + } + + protected void doLogCountEvent(String appPackage, String category, String key, Map params) { + ILogProvider logProviderImpl = mLogProviderImpl; + if (logProviderImpl == null) { + return; + } + + try { + logProviderImpl.logCountEventWithParams(appPackage, category, key, params); + } catch (RemoteException e) { + Log.e(TAG, "failed to stat", e); + } + } + + @Override + public void logCalculateEvent(String appPackage, String category, String key, long value) { + mExecutor.execute(() -> doLogCalculateEvent(appPackage, category, key, value)); + } + + private void doLogCalculateEvent(String appPackage, String category, String key, long value) { + ILogProvider logProviderImpl = mLogProviderImpl; + if (logProviderImpl == null) { + return; + } + + try { + logProviderImpl.logCalculateEvent(appPackage, category, key, value); + } catch (RemoteException e) { + Log.e(TAG, "failed to stat", e); + } + } + + @Override + public void logCalculateEvent(String appPackage, String category, String key, long value, Map params) { + mExecutor.execute(() -> doLogCalculateEvent(appPackage, category, key, value, params)); + } + + private void doLogCalculateEvent(String appPackage, String category, String key, long value, Map params) { + ILogProvider logProviderImpl = mLogProviderImpl; + if (logProviderImpl == null) { + return; + } + + try { + logProviderImpl.logCalculateEventWithParams(appPackage, category, key, value, params); + } catch (RemoteException e) { + Log.e(TAG, "failed to stat", e); + } + } + + @Override + public void logNumericPropertyEvent(String appPackage, String category, String key, long value) { + mExecutor.execute(() -> doLogNumericPropertyEvent(appPackage, category, key, value)); + } + + private void doLogNumericPropertyEvent(String appPackage, String category, String key, long value) { + ILogProvider logProviderImpl = mLogProviderImpl; + if (logProviderImpl == null) { + return; + } + + try { + logProviderImpl.logNumericPropertyEvent(appPackage, category, key, value); + } catch (RemoteException e) { + Log.e(TAG, "failed to stat", e); + } + } + + @Override + public void logNumericPropertyEvent(String appPackage, String category, String key, long value, Map params) { + mExecutor.execute(() -> doLogNumericPropertyEvent(appPackage, category, key, value, params)); + } + + private void doLogNumericPropertyEvent(String appPackage, String category, String key, long value, Map params) { + ILogProvider logProviderImpl = mLogProviderImpl; + if (logProviderImpl == null) { + return; + } + + try { + logProviderImpl.logNumericPropertyEventWithParams(appPackage, category, key, value, params); + } catch (RemoteException e) { + Log.e(TAG, "failed to stat", e); + } + } + + @Override + public void logStringPropertyEvent(String appPackage, String category, String key, String value) { + mExecutor.execute(() -> doLogStringPropertyEvent(appPackage, category, key, value)); + } + + private void doLogStringPropertyEvent(String appPackage, String category, String key, String value) { + ILogProvider logProviderImpl = mLogProviderImpl; + if (logProviderImpl == null) { + return; + } + + try { + logProviderImpl.logStringPropertyEvent(appPackage, category, key, value); + } catch (RemoteException e) { + Log.e(TAG, "failed to stat", e); + } + } + + @Override + public void logStringPropertyEvent(String appPackage, String category, String key, String value, Map params) { + mExecutor.execute(() -> doLogStringPropertyEvent(appPackage, category, key, value, params)); + } + + private void doLogStringPropertyEvent(String appPackage, String category, String key, String value, Map params) { + ILogProvider logProviderImpl = mLogProviderImpl; + if (logProviderImpl == null) { + return; + } + + try { + logProviderImpl.logStringPropertyEventWithParams(appPackage, category, key, value, params); + } catch (RemoteException e) { + Log.e(TAG, "failed to stat", e); + } + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxService.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxService.java new file mode 100644 index 00000000..d3418131 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SandboxService.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import android.app.Service; +import android.content.Intent; +import android.os.Build; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import org.hapjs.analyzer.model.LogData; +import org.hapjs.analyzer.monitors.AbsLogDumper; +import org.hapjs.common.executors.Executors; +import org.hapjs.logging.LogProvider; +import org.hapjs.render.jsruntime.SandboxJsThread; +import org.hapjs.runtime.ProviderManager; + +public class SandboxService extends Service { + private static final String TAG = "SandboxService"; + + private ILogListener mLogListener; + private Dumper mDumper; + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return new ISandbox.Stub() { + @Override + public void init(Map configs) { + SandboxConfigs.setConfigs(configs); + } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + @Override + public ParcelFileDescriptor[] createChannel(ParcelFileDescriptor[] readSides) throws RemoteException { + try { + ParcelFileDescriptor[] positiveDescriptors = ParcelFileDescriptor.createReliablePipe(); + ParcelFileDescriptor[] passiveDescriptors = ParcelFileDescriptor.createReliablePipe(); + new SandboxJsThread(SandboxService.this, + new ParcelFileDescriptor[] {readSides[1], positiveDescriptors[1]}, + new ParcelFileDescriptor[] {readSides[0], passiveDescriptors[1]}); + return new ParcelFileDescriptor[] { + positiveDescriptors[0], passiveDescriptors[0] + }; + } catch (IOException e) { + throw new RemoteException(e.getMessage()); + } + } + + @Override + public void setLogProvider(ILogProvider logProviderImpl) { + LogProvider logProvider = ProviderManager.getDefault().getProvider(LogProvider.NAME); + if (logProvider instanceof SandboxLogProviderImpl) { + ((SandboxLogProviderImpl) logProvider).setLogProvider(logProviderImpl); + } + + try { + logProviderImpl.asBinder().linkToDeath(() -> { + Log.e(TAG, "app process has died. kill sandbox process as well."); + Process.killProcess(Process.myPid()); + }, 0); + } catch (RemoteException e) { + Log.e(TAG, "failed to linkToDeath", e); + } + } + + @Override + public void setLogListener(ILogListener listener) { + mLogListener = listener; + if (listener != null) { + if (mDumper == null) { + mDumper = new Dumper(); + Executors.io().execute(mDumper); + } + } else { + if (mDumper != null) { + mDumper.close(); + mDumper = null; + } + } + } + }; + } + + @Override + public void onDestroy() { + super.onDestroy(); + LogProvider logProvider = ProviderManager.getDefault().getProvider(LogProvider.NAME); + if (logProvider instanceof SandboxLogProviderImpl) { + ((SandboxLogProviderImpl) logProvider).setLogProvider(null); + } + + mLogListener = null; + Dumper dumper = mDumper; + mDumper = null; + if (dumper != null) { + dumper.close(); + } + } + + public static class Sandbox0 extends SandboxService {} + public static class Sandbox1 extends SandboxService {} + public static class Sandbox2 extends SandboxService {} + public static class Sandbox3 extends SandboxService {} + public static class Sandbox4 extends SandboxService {} + + private class Dumper extends AbsLogDumper { + @Override + protected void doDumpLog(List logs) { + ILogListener logListener = mLogListener; + if (logListener != null) { + try { + logListener.onLog(logs); + } catch (RemoteException e) { + Log.e(TAG, "failed to onLog", e); + } + } + } + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SerializeHelper.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SerializeHelper.java new file mode 100644 index 00000000..18c4be27 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SerializeHelper.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ + +package org.hapjs.runtime.sandbox; + +import android.util.ArrayMap; +import android.util.Log; + +import com.eclipsesource.v8.V8; +import com.eclipsesource.v8.V8Object; +import com.eclipsesource.v8.V8Value; +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.serializers.FieldSerializer; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.hapjs.bridge.InstanceManager; +import org.hapjs.bridge.Response; +import org.hapjs.render.jsruntime.JsThread; +import org.hapjs.render.jsruntime.serialize.AbstractSerializeObject; +import org.hapjs.render.jsruntime.serialize.JavaSerializeObject; +import org.hapjs.render.jsruntime.serialize.TypedArrayProxy; +import org.hapjs.render.jsruntime.serialize.V8SerializeObject; +import org.json.JSONArray; +import org.json.JSONObject; + +public class SerializeHelper { + private static final String TAG = "SerializeHelper"; + + private static class SingletonHolder { + private static final SerializeHelper sInstance = new SerializeHelper(); + } + + public static SerializeHelper getInstance() { + return SingletonHolder.sInstance; + } + + private ThreadLocal mKryo = new ThreadLocal<>(); + + private SerializeHelper() { + } + + private void ensureRegister() { + Kryo kryo = mKryo.get(); + if (kryo != null) { + return; + } + + long start = System.currentTimeMillis(); + kryo = new Kryo(); + mKryo.set(kryo); + + kryo.register(Map.class); + kryo.register(HashMap.class); + kryo.register(LinkedHashMap.class); + kryo.register(ArrayMap.class); + kryo.register(JSONObject.class); + kryo.register(JSONArray.class); + kryo.register(Object[].class); + kryo.register(List.class); + kryo.register(ArrayList.class); + kryo.register(JsThread.JsEventCallbackData.class); + kryo.register(JsThread.JsMethodCallbackData.class); + kryo.register(Response.class); + kryo.register(JavaSerializeObject.class); + kryo.register(StackTraceElement[].class); + kryo.register(V8SerializeObject.class); + kryo.register(AbstractSerializeObject.class); + kryo.register(InstanceManager.InstanceHandler.class); + kryo.register(byte[].class); + kryo.register(org.hapjs.common.json.JSONObject.class); + kryo.register(org.hapjs.common.json.JSONArray.class); + + V8Value undefined = V8.getUndefined(); + kryo.register(undefined.getClass(), new FieldSerializer(kryo, undefined.getClass()) { + @Override + public void write(Kryo kryo, Output output, Object object) { + // 不序列化 Undefined 数据 + } + + @Override + public Object read(Kryo kryo, Input input, Class type) { + return V8.getUndefined(); + } + }); + + kryo.register(StackTraceElement.class, new FieldSerializer(kryo, StackTraceElement.class) { + public void write(Kryo kryo, Output output, Object object) { + StackTraceElement stack = (StackTraceElement) object; + output.writeString(stack.getClassName()); + output.writeString(stack.getMethodName()); + output.writeString(stack.getFileName()); + output.writeInt(stack.getLineNumber()); + } + + public Object read(Kryo kryo, Input input, Class type) { + String className = input.readString(); + String methodName = input.readString(); + String fileName = input.readString(); + int lineNumber = input.readInt(); + return new StackTraceElement(className, methodName, fileName, lineNumber); + } + }); + + Class clazz = ByteBuffer.wrap(new byte[1]).getClass(); + kryo.register(clazz, new FieldSerializer(kryo, clazz) { + public void write(Kryo kryo, Output output, Object object) { + byte[] bytes = ((ByteBuffer) object).array(); + output.writeInt(bytes.length); + output.write(bytes); + } + + public Object read(Kryo kryo, Input input, Class type) { + int len = input.readInt(); + byte[] bytes = input.readBytes(len); + return ByteBuffer.wrap(bytes); + } + }); + + kryo.register(TypedArrayProxy.class, new FieldSerializer(kryo, TypedArrayProxy.class) { + public void write(Kryo kryo, Output output, Object object) { + byte[] bytes = ((TypedArrayProxy) object).getBytes(); + output.writeInt(bytes.length); + output.write(bytes); + output.writeInt(((TypedArrayProxy) object).getType()); + } + + public Object read(Kryo kryo, Input input, Class type) { + int len = input.readInt(); + byte[] bytes = input.readBytes(len); + int dataType = input.readInt(); + return new TypedArrayProxy(dataType, bytes); + } + }); + + Log.i(TAG, "kryo register costs=" + (System.currentTimeMillis() - start)); + } + + private Kryo getKryo() { + ensureRegister(); + return mKryo.get(); + } + + public Serializer createSerializer() { + return new Serializer(); + } + + public Deserializer createDeserializer(byte[] data) { + return new Deserializer(data); + } + + public class Serializer { + private ByteArrayOutputStream mByteArrayOutputStream; + private Output mOutput; + + private Serializer() { + mByteArrayOutputStream = new ByteArrayOutputStream(); + mOutput = new Output(mByteArrayOutputStream); + } + + public void writeObject(Object arg) { + getKryo().writeObject(mOutput, arg); + } + + public void writeObjectOrNull(Object arg, Class clazz) { + getKryo().writeObjectOrNull(mOutput, arg, clazz); + } + + public byte[] closeAndGetBytes() { + mOutput.close(); + return mByteArrayOutputStream.toByteArray(); + } + } + + public class Deserializer { + private Input mInput; + + private Deserializer(byte[] data) { + mInput = new Input(new ByteArrayInputStream(data)); + } + + public T readObject(Class clazz) { + return getKryo().readObject(mInput, clazz); + } + + public void close() { + mInput.close(); + } + } +} diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SyncWaiter.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SyncWaiter.java new file mode 100644 index 00000000..d7cac128 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/sandbox/SyncWaiter.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023, hapjs.org. All rights reserved. + */ +package org.hapjs.runtime.sandbox; + +import android.util.Log; +import java.util.concurrent.CountDownLatch; + +public class SyncWaiter { + private static final String TAG = "SyncWaiter"; + + private CountDownLatch mLatch = new CountDownLatch(1); + private T mResult; + private T mDefault; + + public SyncWaiter(T def) { + mDefault = def; + } + + public void setResult(T result) { + mResult = result; + mLatch.countDown(); + } + + public T waitAndGetResult() { + try { + mLatch.await(); + } catch (InterruptedException e) { + Log.e(TAG, "interrupted while waiting for result", e); + return mDefault; + } + return mResult; + } +} diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Camera.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Camera.java index ed08d95e..aeab569e 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Camera.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Camera.java @@ -9,9 +9,13 @@ import android.text.TextUtils; import android.util.Log; import android.view.View; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; + +import com.eclipsesource.v8.utils.ArrayBuffer; + +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; + import org.hapjs.bridge.ApplicationContext; import org.hapjs.bridge.annotation.WidgetAnnotation; import org.hapjs.component.Component; @@ -158,8 +162,7 @@ public void onCameraFrameListener(int width, int height, byte[] bytes, params.put("width", width); params.put("height", height); params.put("time", time); - ArrayBuffer frameBuffer = new ArrayBuffer(bytes); - params.put("frame", frameBuffer); + params.put("frame", bytes); mCallback.onJsEventCallback( getPageId(), mRef, CAMERA_FRAME, Camera.this, params, null); } diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/canvas/canvas2d/Parser.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/canvas/canvas2d/Parser.java index 44944a2e..f1521cee 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/canvas/canvas2d/Parser.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/canvas/canvas2d/Parser.java @@ -14,7 +14,7 @@ import android.util.Base64; import android.util.Log; import androidx.annotation.NonNull; -import com.eclipsesource.v8.utils.typedarrays.ArrayBuffer; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; @@ -1597,7 +1597,7 @@ public void render( } result.put("width", imageData.width); result.put("height", imageData.height); - result.put("data", new ArrayBuffer(imageData.data)); + result.put("data", imageData.data); } }; } diff --git a/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/CDPHttpSocketLikeHandler.java b/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/CDPHttpSocketLikeHandler.java index 589a4b26..a000dad6 100644 --- a/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/CDPHttpSocketLikeHandler.java +++ b/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/CDPHttpSocketLikeHandler.java @@ -31,7 +31,7 @@ import org.hapjs.render.Page; import org.hapjs.render.PageManager; import org.hapjs.render.RootView; -import org.hapjs.render.jsruntime.JsThread; +import org.hapjs.render.jsruntime.AppJsThread; import org.hapjs.render.vdom.DocComponent; import org.hapjs.render.vdom.VDocument; import org.json.JSONArray; @@ -109,42 +109,23 @@ public boolean handleRequest( } else if (PATH_RUNTIME.equals(path)) { RootView rootView = V8Inspector.getInstance().getRootView(); if (rootView != null) { - final JsThread mJsThread = rootView.getJsThread(); + final AppJsThread jsThread = rootView.getJsThread(); // Stetho doesn't has request body, so here we are Runnable r = new Runnable() { @Override public void run() { - V8 v8 = mJsThread.getJsContext().getV8(); String message = request.getFirstHeaderValue("message"); Log.d(TAG, "message = " + message); - V8Object object = v8.executeObjectScript("v = " + message); - if (object == null) { - throw new IllegalStateException( - "related value not exists."); - } - - V8Object json = v8.getObject("JSON"); - // INSPECTOR MOD - if (json == null) { - throw new IllegalStateException( - "V8Object which key is JSON not exists"); - } - // END - V8Array parameters = new V8Array(v8).push(object); - String result = - json.executeStringFunction("stringify", parameters); + String result = jsThread.getEngine().executeObjectScriptAndStringify("v = " + message); Log.d(TAG, "result = " + result); setSuccessfulResponse( response, LightHttpBody.create(result, "application/json")); - parameters.release(); - object.release(); - json.release(); } }; - HandlerUtil.postAndWait(mJsThread.getHandler(), r); + HandlerUtil.postAndWait(jsThread.getHandler(), r); } else { Log.e(TAG, "can't get pages."); diff --git a/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/V8Inspector.java b/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/V8Inspector.java index d8ed5cca..d7d9af49 100644 --- a/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/V8Inspector.java +++ b/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/V8Inspector.java @@ -45,6 +45,7 @@ import org.hapjs.render.Page; import org.hapjs.render.RootView; import org.hapjs.render.VDomChangeAction; +import org.hapjs.render.jsruntime.IJsEngine; import org.hapjs.render.jsruntime.JsThread; import org.hapjs.runtime.HapEngine; import org.hapjs.runtime.ProviderManager; @@ -75,10 +76,6 @@ public class V8Inspector implements InspectorProvider { ? new Method("android/os/Message", "recycleUnchecked", "()V") : null; - static { - System.loadLibrary("inspector"); - } - private final List mCachedVDomActionMessages = new ArrayList<>(); private Map mPeerMaps = new ConcurrentHashMap(); private MessageHandler mHandler; @@ -300,21 +297,6 @@ public boolean processInspectRequest(String url, Context context) { return url.startsWith(INSPECTOR_HEAD); } - public V8Object executeObjectScript(final String script) { - if (mHandler == null) { - return null; - } - final MessageHandler h = mHandler; - return HandlerUtil.postAndWait( - h, - new UncheckedCallable() { - @Override - public V8Object call() { - return h.mV8.executeObjectScript(script); - } - }); - } - public String executeJsCode(final String jsCode) { if (mHandler == null) { return null; @@ -325,23 +307,7 @@ public String executeJsCode(final String jsCode) { new UncheckedCallable() { @Override public String call() { - return nativeExecuteJsCode(h.mNativePtr, jsCode); - } - }); - } - - @Override - public String handleConsoleMessage(final V8Value v8Array) { - if (mHandler == null) { - return null; - } - final MessageHandler h = mHandler; - return HandlerUtil.postAndWait( - h, - new UncheckedCallable() { - @Override - public String call() { - return nativeHandleConsoleMessage(h.mNativePtr, v8Array); + return h.mEngine.inspectorExecuteJsCode(h.mNativePtr, jsCode); } }); } @@ -376,49 +342,6 @@ private V8Array createV8Paramters(V8 v8, Object[] args) { return paramters; } - public String executeStringFunction(final String name, final Object... args) { - if (mHandler == null) { - return null; - } - final MessageHandler h = mHandler; - return HandlerUtil.postAndWait( - h, - new UncheckedCallable() { - @Override - public String call() { - V8Array paramters = createV8Paramters(h.mV8, args); - try { - return h.mV8.executeStringFunction(name, paramters); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - }); - } - - public String executeJSONFunction( - final String name, final V8ObjectCheck checker, final Object... args) { - if (mHandler == null) { - return null; - } - final MessageHandler h = mHandler; - return HandlerUtil.postAndWait( - h, - new UncheckedCallable() { - @Override - public String call() { - try { - Object obj = h.mV8.executeJSFunction(name, args); - return toJSON(obj, checker); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - }); - } - public void handleMessage(JsonRpcPeer peer, String message) { int hashcode = peer.hashCode(); mLastSessionId = hashcode; @@ -434,17 +357,15 @@ public void handleMessage(JsonRpcPeer peer, String message) { } } - // @callby native - public void sendResponse(int sessionId, int callId, String message) { - // JsonRpcPeer peer = mPeerMaps.get(sessionId); - // if (peer != null) { - // peer.getWebSocket().sendText(message); - // } + + @Override + public void inspectorResponse(int sessionId, int callId, String message) { mSendHandler.obtainMessage(H.SEND_MESSAGE, sessionId, 0, message).sendToTarget(); } - public void sendNotification(int sessionId, int callId, String message) { - sendResponse(sessionId, callId, message); + @Override + public void inspectorSendNotification(int sessionId, int callId, String message) { + inspectorResponse(sessionId, callId, message); } @Override @@ -526,22 +447,22 @@ public void run() { } @Override - public synchronized void onJsContextCreated(V8 v8) { - if (mHandler != null && mHandler.mV8 == v8) { + public synchronized void onJsContextCreated(IJsEngine engine) { + if (mHandler != null && mHandler.mEngine == engine) { return; } - if (mHandler != null && mHandler.mNativePtr != 0 && mHandler.mV8 != null) { - mDestroyNatives.add(new DestroyNativeInfo(mHandler.mV8, mHandler.mNativePtr)); + if (mHandler != null && mHandler.mNativePtr != 0 && mHandler.mEngine != null) { + mDestroyNatives.add(new DestroyNativeInfo(mHandler.mEngine, mHandler.mNativePtr)); } - long nativePtr = initNative(mAutoEnable, mLastSessionId); + long nativePtr = engine.inspectorInit(mAutoEnable, mLastSessionId); if (mIsJsContextFirstCreated) { - nativeSetV8Context(nativePtr, v8, 0); + engine.inspectorSetV8Context(nativePtr, 0); mIsJsContextFirstCreated = false; } else { - nativeSetV8Context(nativePtr, v8, 1); + engine.inspectorSetV8Context(nativePtr, 1); } - // start the thread handler - mHandler = new MessageHandler(nativePtr, v8, Looper.myLooper()); + //start the thread handler + mHandler = new MessageHandler(nativePtr, engine, Looper.myLooper()); for (ProtocolMessage pm : mCachedProtocolMessages) { mHandler .obtainMessage(MessageHandler.HANDLE_MESSAGE, pm.peerHasCode, 0, pm.message) @@ -558,12 +479,12 @@ private void onPageVisibleChanged(boolean visible) { } @Override - public synchronized void onJsContextDispose(V8 v8) { - if (mHandler != null && mHandler.mV8 == v8) { - nativeDisposeV8Context(mHandler.mNativePtr); - nativeDestroy(mHandler.mNativePtr); + public synchronized void onJsContextDispose(IJsEngine engine) { + if (mHandler != null && mHandler.mEngine == engine) { + engine.inspectorDisposeV8Context(mHandler.mNativePtr); + engine.inspectorDestroy(mHandler.mNativePtr); mHandler.mNativePtr = 0; - mHandler.mV8 = null; + mHandler.mEngine = null; mHandler = null; mAutoEnable = true; } else { @@ -571,7 +492,7 @@ public synchronized void onJsContextDispose(V8 v8) { DestroyNativeInfo info = null; for (i = 0; i < mDestroyNatives.size(); i++) { info = mDestroyNatives.get(i); - if (info.v8 == v8) { + if (info.engine == engine) { break; } } @@ -581,8 +502,8 @@ public synchronized void onJsContextDispose(V8 v8) { } mDestroyNatives.remove(i); if (info.nativePtr != 0) { - nativeDisposeV8Context(info.nativePtr); - nativeDestroy(info.nativePtr); + engine.inspectorDisposeV8Context(info.nativePtr); + engine.inspectorDestroy(info.nativePtr); } } } @@ -612,12 +533,12 @@ public void onConsoleMessage(int level, String msg) { @Override public void onBeginLoadJsCode(String uri, String content) { - nativeBeginLoadJsCode(uri, content); + mHandler.mEngine.inspectorBeginLoadJsCode(uri, content); } @Override public void onEndLoadJsCode(String uri) { - nativeEndLoadJsCode(uri); + mHandler.mEngine.inspectorEndLoadJsCode(uri); } public void domEnabled() { @@ -658,8 +579,8 @@ public void consoleDisabled(JsonRpcPeer peer) { * 从而让v8虚拟机继续执行。 * */ - // @Called by native - void runMessageLoopOnPause(int contextGroupId) { + //@Called by native + public void inspectorRunMessageLoopOnPause(int contextGroupId) { mPausedLooper = Looper.myLooper(); if (mPausedLooper == null) { return; @@ -716,7 +637,8 @@ void runMessageLoopOnPause(int contextGroupId) { } } - void quitMessageLoopOnPause() { + @Override + public void inspectorQuitMessageLoopOnPause() { if (mPausedLooper != null) { mHandler.obtainMessage(MessageHandler.PAUSE_QUIT, 0, 0, null).sendToTarget(); } @@ -731,7 +653,7 @@ private void sendPageVisibilityChangedMessage(JsonRpcPeer peer, boolean visible) final void destroy() { if (mHandler != null && mHandler.mNativePtr != 0) { - nativeDestroy(mHandler.mNativePtr); + mHandler.mEngine.inspectorDestroy(mHandler.mNativePtr); mHandler.mNativePtr = 0; } } @@ -838,11 +760,11 @@ private static class PageChangeMessage { } private static class DestroyNativeInfo { - V8 v8; + IJsEngine engine; long nativePtr; - DestroyNativeInfo(V8 v8, long nptr) { - this.v8 = v8; + DestroyNativeInfo(IJsEngine engine, long nptr) { + this.engine = engine; this.nativePtr = nptr; } } @@ -864,18 +786,18 @@ private class MessageHandler extends Handler { static final int HANDLE_MESSAGE = 1; static final int PAUSE_QUIT = -1; long mNativePtr; - V8 mV8; + IJsEngine mEngine; - MessageHandler(long nativeptr, V8 v8, Looper looper) { + MessageHandler (long nativeptr, IJsEngine engine, Looper looper) { super(looper); - this.mV8 = v8; + this.mEngine = engine; this.mNativePtr = nativeptr; } @Override public void handleMessage(Message msg) { if (msg.what == HANDLE_MESSAGE) { - nativeHandleMessage(mNativePtr, msg.arg1, (String) (msg.obj)); + mEngine.inspectorHandleMessage(mNativePtr, msg.arg1, (String) (msg.obj)); } } } @@ -926,7 +848,7 @@ public void run() { // END MessageHandler h = mHandler; rootView.reloadCurrentPage(); - nativeFrontendReload(h.mNativePtr); + h.mEngine.inspectorFrontendReload(h.mNativePtr); } catch (NoSuchMethodError e) { Log.e(TAG, "Reload current page error", e); } diff --git a/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/VElementDescriptorBase.java b/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/VElementDescriptorBase.java index 3b2f8570..6f584b2a 100644 --- a/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/VElementDescriptorBase.java +++ b/debug/engine/android/inspector/src/main/java/org/hapjs/inspector/VElementDescriptorBase.java @@ -36,6 +36,7 @@ import org.hapjs.render.css.CSSStyleDeclaration; import org.hapjs.render.css.CSSStyleRule; import org.hapjs.render.css.MatchedCSSRuleList; +import org.hapjs.render.jsruntime.AppJsThread; import org.hapjs.render.jsruntime.JsThread; import org.json.JSONArray; import org.json.JSONException; @@ -381,7 +382,7 @@ public void setStyle(E element, final String ruleName, final CSSStyleDeclaration Log.w("VDOM setStyle", "rootView is null"); return; } - JsThread jsThread = rootView.getJsThread(); + AppJsThread jsThread = rootView.getJsThread(); if (jsThread == null) { Log.w("VDOM setStyle", "jsThread is null"); return; diff --git a/debug/engine/android/inspector/src/main/jni/inspector/java_native_reflect.cpp b/debug/engine/android/inspector/src/main/jni/inspector/java_native_reflect.cpp index f6e5b361..225f8fa7 100644 --- a/debug/engine/android/inspector/src/main/jni/inspector/java_native_reflect.cpp +++ b/debug/engine/android/inspector/src/main/jni/inspector/java_native_reflect.cpp @@ -117,12 +117,12 @@ struct JAccessWrapper { jlong value_; JAccessWrapper(T v, bool isStatic) { - value_ = reinterpret_cast(v) | (isStatic ? 1 : 0); + value_ = (reinterpret_cast(v) << 2)| (isStatic ? 1 : 0); } JAccessWrapper(jlong v) : value_(v) {} - T id() const { return reinterpret_cast(value_ & ~1); } + T id() const { return reinterpret_cast(value_ >> 2); } bool isStatic() const { return value_ & 1; } diff --git a/debug/engine/android/inspector/src/main/jni/inspector/java_v8_inspector.cpp b/debug/engine/android/inspector/src/main/jni/inspector/java_v8_inspector.cpp index 0ae364ca..e043386c 100644 --- a/debug/engine/android/inspector/src/main/jni/inspector/java_v8_inspector.cpp +++ b/debug/engine/android/inspector/src/main/jni/inspector/java_v8_inspector.cpp @@ -27,7 +27,7 @@ static inline JNIEnv *AttachCurrentThread() { namespace inspector { ///////////////////////////////////////////////////////////// -#define V8InspectorClassName "org/hapjs/inspector/V8Inspector" +#define V8InspectorClassName "org/hapjs/render/jsruntime/V8InspectorNative" static jstring toJavaString(JNIEnv *env, const hybrid::jschar_t *str, size_t size) { @@ -289,9 +289,6 @@ namespace inspector { {"nativeDestroy", "(J)V", (void *) HybridInspector::nativeDestroy}, {"nativeExecuteJsCode", "(JLjava/lang/String;)Ljava/lang/String;", (void *) HybridInspector::nativeExecuteJsCode}, - {"nativeHandleConsoleMessage", - "(JLcom/eclipsesource/v8/V8Value;)Ljava/lang/String;", - (void *) HybridInspector::nativeHandleConsoleMessage}, {"nativeFrontendReload", "(J)V", (void *) HybridInspector::nativeFrontendReload}}; diff --git a/mockup/platform/android/app-impl/src/main/java/org/hapjs/mockup/impl/LogProviderImpl.java b/mockup/platform/android/app-impl/src/main/java/org/hapjs/mockup/impl/LogProviderImpl.java index fc948b0d..ea5136b6 100644 --- a/mockup/platform/android/app-impl/src/main/java/org/hapjs/mockup/impl/LogProviderImpl.java +++ b/mockup/platform/android/app-impl/src/main/java/org/hapjs/mockup/impl/LogProviderImpl.java @@ -19,7 +19,7 @@ public void logCountEvent(String appPackage, String category, String key) { @Override public void logCountEvent( - String appPackage, String category, String key, Map params) { + String appPackage, String category, String key, Map params) { Log.i( TAG, "recordCountEvent: appPackage=" @@ -40,7 +40,7 @@ public void logCalculateEvent(String appPackage, String category, String key, lo @Override public void logCalculateEvent( String appPackage, String category, String key, long value, - Map params) { + Map params) { Log.i( TAG, "recordCalculateEvent: appPackage=" @@ -64,7 +64,7 @@ public void logNumericPropertyEvent(String appPackage, String category, String k @Override public void logNumericPropertyEvent( String appPackage, String category, String key, long value, - Map params) { + Map params) { Log.i( TAG, "recordNumericPropertyEvent: appPackage=" @@ -88,7 +88,7 @@ public void logStringPropertyEvent(String appPackage, String category, String ke @Override public void logStringPropertyEvent( String appPackage, String category, String key, String value, - Map params) { + Map params) { Log.i( TAG, "recordStringPropertyEvent: appPackage=" diff --git a/mockup/platform/android/app/src/car/java/org/hapjs/mockup/MockupRuntime.java b/mockup/platform/android/app/src/car/java/org/hapjs/mockup/MockupRuntime.java index 37998296..bccc5675 100644 --- a/mockup/platform/android/app/src/car/java/org/hapjs/mockup/MockupRuntime.java +++ b/mockup/platform/android/app/src/car/java/org/hapjs/mockup/MockupRuntime.java @@ -25,8 +25,8 @@ public class MockupRuntime extends PlatformRuntime { @Override - protected void onAllProcessInit() { - super.onAllProcessInit(); + protected void onAllProcessInitOtherThenSandbox() { + super.onAllProcessInitOtherThenSandbox(); ProviderManager pm = ProviderManager.getDefault(); pm.addProvider(DistributionProvider.NAME, new DistributionProviderImpl(mContext)); diff --git a/mockup/platform/android/app/src/phone/java/org/hapjs/mockup/MockupRuntime.java b/mockup/platform/android/app/src/phone/java/org/hapjs/mockup/MockupRuntime.java index 1380a710..13657d34 100644 --- a/mockup/platform/android/app/src/phone/java/org/hapjs/mockup/MockupRuntime.java +++ b/mockup/platform/android/app/src/phone/java/org/hapjs/mockup/MockupRuntime.java @@ -25,8 +25,8 @@ public class MockupRuntime extends PlatformRuntime { @Override - protected void onAllProcessInit() { - super.onAllProcessInit(); + protected void onAllProcessInitOtherThenSandbox() { + super.onAllProcessInitOtherThenSandbox(); // add service must be called before any other code ProviderManager pm = ProviderManager.getDefault(); diff --git a/mockup/platform/android/app/src/tv/java/org/hapjs/mockup/MockupRuntime.java b/mockup/platform/android/app/src/tv/java/org/hapjs/mockup/MockupRuntime.java index 1380a710..13657d34 100644 --- a/mockup/platform/android/app/src/tv/java/org/hapjs/mockup/MockupRuntime.java +++ b/mockup/platform/android/app/src/tv/java/org/hapjs/mockup/MockupRuntime.java @@ -25,8 +25,8 @@ public class MockupRuntime extends PlatformRuntime { @Override - protected void onAllProcessInit() { - super.onAllProcessInit(); + protected void onAllProcessInitOtherThenSandbox() { + super.onAllProcessInitOtherThenSandbox(); // add service must be called before any other code ProviderManager pm = ProviderManager.getDefault(); diff --git a/mockup/platform/android/build.gradle b/mockup/platform/android/build.gradle index 0d0bc3a5..9541c36a 100755 --- a/mockup/platform/android/build.gradle +++ b/mockup/platform/android/build.gradle @@ -192,7 +192,7 @@ subprojects { apply plugin: "com.github.spotbugs" android { - compileSdkVersion rootProject.compileSdkVersion + compileSdkVersion 30 group "org.hapjs" version rootProject.appVersionName defaultConfig { diff --git a/platform/platform/android/platform/src/main/java/org/hapjs/LauncherActivity.java b/platform/platform/android/platform/src/main/java/org/hapjs/LauncherActivity.java index 4b603efa..a5532e0d 100644 --- a/platform/platform/android/platform/src/main/java/org/hapjs/LauncherActivity.java +++ b/platform/platform/android/platform/src/main/java/org/hapjs/LauncherActivity.java @@ -10,6 +10,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.Dialog; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -78,11 +79,14 @@ import org.hapjs.render.PageManager; import org.hapjs.render.PageNotFoundException; import org.hapjs.render.RootView; +import org.hapjs.render.jsruntime.SandboxProcessLauncher; +import org.hapjs.render.jsruntime.SandboxProvider; import org.hapjs.render.vdom.VDocument; import org.hapjs.runtime.Checkable; import org.hapjs.runtime.CheckableAlertDialog; import org.hapjs.runtime.DarkThemeUtil; import org.hapjs.runtime.GrayModeManager; +import org.hapjs.runtime.ProviderManager; import org.hapjs.runtime.Runtime; import org.hapjs.runtime.RuntimeActivity; import org.hapjs.utils.ActivityUtils; @@ -1590,6 +1594,15 @@ public void launch(Context context, Intent intent) { System.currentTimeMillis() + SESSION_EXPIRE_SPAN); PlatformLogManager.getDefault().logAppPreLaunch(pkg, path, status, source); context.startActivity(intent, options); + + SandboxProvider sandboxProvider = ProviderManager.getDefault().getProvider(SandboxProvider.NAME); + if (sandboxProvider.isSandboxEnabled()) { + ComponentName componentName = intent.getComponent(); + if (componentName != null && !TextUtils.isEmpty(componentName.getClassName())) { + String className = componentName.getClassName(); + SandboxProcessLauncher.getInstance().preStartSandboxProcess(className.substring(className.length() - 1)); + } + } } } diff --git a/platform/platform/android/platform/src/main/java/org/hapjs/PlatformRuntime.java b/platform/platform/android/platform/src/main/java/org/hapjs/PlatformRuntime.java index e84c30b5..4b8e87a6 100644 --- a/platform/platform/android/platform/src/main/java/org/hapjs/PlatformRuntime.java +++ b/platform/platform/android/platform/src/main/java/org/hapjs/PlatformRuntime.java @@ -80,11 +80,15 @@ protected void doCreate(Context context) { super.doCreate(context); Log.i(TAG, "Hybrid Application onCreate"); Cache.setDefaultFileNotFoundHandler(new DefaultFileNotFoundHandler()); - onAllProcessInit(); + if (!ProcessUtils.isSandboxProcess(context)) { + onAllProcessInitOtherThenSandbox(); + } if (ProcessUtils.isMainProcess(context)) { onMainProcessInit(); } else if (ProcessUtils.isAppProcess(context)) { onAppProcessInit(); + } else if (ProcessUtils.isSandboxProcess(context)) { + onSandboxProcessInit(); } else { onOtherProcessInit(); } @@ -113,7 +117,7 @@ protected List onCreateDatabase() { return databases; } - protected void onAllProcessInit() { + protected void onAllProcessInitOtherThenSandbox() { Context applicationContext = Runtime.getInstance().getContext().getApplicationContext(); if (applicationContext instanceof Application) { ((Application) applicationContext).registerActivityLifecycleCallbacks(this); @@ -227,6 +231,10 @@ protected void onAppProcessInit() { protected void onOtherProcessInit() { } + protected void onSandboxProcessInit() { + Thread.setDefaultUncaughtExceptionHandler(new CrashHandler()); + } + @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { GrayModeManager.getInstance().init(activity.getApplicationContext());