debugger:complete android part

This commit is contained in:
pengfeizhou
2021-02-21 01:51:59 +08:00
committed by osborn
parent 4c0237aec6
commit af31014e2e
16 changed files with 526 additions and 561 deletions

View File

@@ -2,6 +2,8 @@ package pub.doric.devkit;
import android.widget.Toast;
import com.github.pengfeizhou.jscore.JSONBuilder;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
@@ -15,13 +17,12 @@ import pub.doric.devkit.event.ConnectExceptionEvent;
import pub.doric.devkit.event.EOFExceptionEvent;
import pub.doric.devkit.event.OpenEvent;
import pub.doric.devkit.event.StopDebugEvent;
import pub.doric.utils.DoricLog;
public class DevKit implements IDevKit {
public static boolean isRunningInEmulator = false;
public static String ip = "";
private static class Inner {
private static final DevKit sInstance = new DevKit();
}
@@ -38,6 +39,10 @@ public class DevKit implements IDevKit {
private WSClient wsClient;
boolean devKitConnected = false;
private DoricContextDebuggable debuggable;
@Override
public void connectDevKit(String url) {
wsClient = new WSClient(url);
@@ -54,9 +59,39 @@ public class DevKit implements IDevKit {
wsClient = null;
}
boolean devKitConnected = false;
@Override
public void startDebugging(String source) {
if (debuggable != null) {
debuggable.stopDebug(true);
}
DoricContext context = matchContext(source);
if (context == null) {
DoricLog.d("Cannot find context source %s for debugging", source);
wsClient.sendToDebugger("DEBUG_STOP", new JSONBuilder()
.put("msg", "Cannot find suitable alive context for debugging")
.toJSONObject());
} else {
wsClient.sendToDebugger(
"DEBUG_RES",
new JSONBuilder()
.put("contextId", context.getContextId())
.toJSONObject());
debuggable = new DoricContextDebuggable(wsClient, context);
debuggable.startDebug();
}
}
@Override
public void stopDebugging(boolean resume) {
wsClient.sendToDebugger("DEBUG_STOP", new JSONBuilder()
.put("msg", "Stop debugging")
.toJSONObject());
if (debuggable != null) {
debuggable.stopDebug(resume);
debuggable = null;
}
}
private DoricContextDebuggable doricContextDebuggable;
@Subscribe(threadMode = ThreadMode.MAIN)
public void onOpenEvent(OpenEvent openEvent) {
@@ -78,10 +113,10 @@ public class DevKit implements IDevKit {
@Subscribe(threadMode = ThreadMode.MAIN)
public void onQuitDebugEvent(StopDebugEvent quitDebugEvent) {
doricContextDebuggable.stopDebug();
stopDebugging(true);
}
public DoricContext requestDebugContext(String source) {
public DoricContext matchContext(String source) {
for (DoricContext context : DoricContextManager.aliveContexts()) {
if (source.contains(context.getSource()) || context.getSource().equals("__dev__")) {
return context;
@@ -91,16 +126,14 @@ public class DevKit implements IDevKit {
}
public void reload(String source, String script) {
for (DoricContext context : DoricContextManager.aliveContexts()) {
if (doricContextDebuggable != null &&
doricContextDebuggable.isDebugging &&
doricContextDebuggable.getContext().getContextId().equals(context.getContextId())) {
System.out.println("is debugging context id: " + context.getContextId());
} else {
if (source.contains(context.getSource()) || context.getSource().equals("__dev__")) {
context.reload(script);
}
}
DoricContext context = matchContext(source);
if (context == null) {
DoricLog.d("Cannot find context source %s for reload", source);
} else if (context.getDriver() instanceof DoricDebugDriver) {
DoricLog.d("Context source %s in debugging,skip reload", source);
} else {
DoricLog.d("Context reload :id %s,source %s ", context.getContextId(), source);
context.reload(script);
}
}
}

View File

@@ -5,34 +5,28 @@ import pub.doric.IDoricDriver;
public class DoricContextDebuggable {
private final DoricContext doricContext;
private DoricDebugDriver debugDriver;
private final IDoricDriver nativeDriver;
public boolean isDebugging = false;
private final WSClient wsClient;
public DoricContextDebuggable(DoricContext doricContext) {
public DoricContextDebuggable(WSClient wsClient, DoricContext doricContext) {
this.wsClient = wsClient;
this.doricContext = doricContext;
this.isDebugging = true;
this.nativeDriver = this.doricContext.getDriver();
}
public void startDebug() {
debugDriver = new DoricDebugDriver(new IStatusCallback() {
@Override
public void start() {
doricContext.setDriver(debugDriver);
doricContext.reload(doricContext.getScript());
}
});
}
public void stopDebug() {
isDebugging = false;
doricContext.setDriver(nativeDriver);
debugDriver.destroy();
doricContext.setDriver(new DoricDebugDriver(this.wsClient));
doricContext.reload(doricContext.getScript());
}
public DoricContext getContext() {
return doricContext;
public void stopDebug(boolean resume) {
IDoricDriver doricDriver = doricContext.getDriver();
if (doricDriver instanceof DoricDebugDriver) {
((DoricDebugDriver) doricDriver).destroy();
}
if (resume) {
doricContext.setDriver(nativeDriver);
doricContext.reload(doricContext.getScript());
}
}
}

View File

@@ -28,7 +28,6 @@ import pub.doric.DoricRegistry;
import pub.doric.IDoricDriver;
import pub.doric.async.AsyncCall;
import pub.doric.async.AsyncResult;
import pub.doric.engine.DoricJSEngine;
import pub.doric.utils.DoricConstant;
import pub.doric.utils.DoricLog;
import pub.doric.utils.ThreadMode;
@@ -39,17 +38,17 @@ import pub.doric.utils.ThreadMode;
* @CreateDate: 2019-07-18
*/
public class DoricDebugDriver implements IDoricDriver {
private final DoricJSEngine doricJSEngine;
private final DoricDebugJSEngine doricDebugJSEngine;
private final ExecutorService mBridgeExecutor;
private final Handler mUIHandler;
private final Handler mJSHandler;
private String theContextId = null;
public DoricDebugDriver(IStatusCallback statusCallback) {
doricJSEngine = new DoricDebugJSEngine(statusCallback);
public DoricDebugDriver(WSClient wsClient) {
doricDebugJSEngine = new DoricDebugJSEngine(wsClient);
mBridgeExecutor = Executors.newCachedThreadPool();
mUIHandler = new Handler(Looper.getMainLooper());
mJSHandler = doricJSEngine.getJSHandler();
mJSHandler = doricDebugJSEngine.getJSHandler();
}
@Override
@@ -69,7 +68,7 @@ public class DoricDebugDriver implements IDoricDriver {
@Override
public JSDecoder call() {
try {
return doricJSEngine.invokeDoricMethod(method, args);
return doricDebugJSEngine.invokeDoricMethod(method, args);
} catch (Exception e) {
DoricLog.e("invokeDoricMethod(%s,...),error is %s", method, e.getLocalizedMessage());
return new JSDecoder(null);
@@ -97,7 +96,7 @@ public class DoricDebugDriver implements IDoricDriver {
@Override
public Boolean call() {
try {
doricJSEngine.prepareContext(contextId, script, source);
theContextId = contextId;
return true;
} catch (Exception e) {
DoricLog.e("createContext %s error is %s", source, e.getLocalizedMessage());
@@ -113,7 +112,9 @@ public class DoricDebugDriver implements IDoricDriver {
@Override
public Boolean call() {
try {
doricJSEngine.destroyContext(contextId);
if (contextId.equals(theContextId)) {
DevKit.getInstance().stopDebugging(false);
}
return true;
} catch (Exception e) {
DoricLog.e("destroyContext %s error is %s", contextId, e.getLocalizedMessage());
@@ -125,11 +126,11 @@ public class DoricDebugDriver implements IDoricDriver {
@Override
public DoricRegistry getRegistry() {
return doricJSEngine.getRegistry();
return doricDebugJSEngine.getRegistry();
}
public void destroy() {
doricJSEngine.teardown();
doricDebugJSEngine.teardown();
mBridgeExecutor.shutdown();
}
}

View File

@@ -4,16 +4,15 @@ import pub.doric.devkit.remote.DoricRemoteJSExecutor;
import pub.doric.engine.DoricJSEngine;
public class DoricDebugJSEngine extends DoricJSEngine {
private final WSClient wsClient;
private IStatusCallback statusCallback;
public DoricDebugJSEngine(IStatusCallback statusCallback) {
public DoricDebugJSEngine(WSClient wsClient) {
super();
this.statusCallback = statusCallback;
this.wsClient = wsClient;
}
@Override
protected void initJSEngine() {
mDoricJSE = new DoricRemoteJSExecutor(statusCallback);
mDoricJSE = new DoricRemoteJSExecutor(this.wsClient);
}
}

View File

@@ -14,4 +14,8 @@ public interface IDevKit {
void sendDevCommand(IDevKit.Command command, JSONObject jsonObject);
void disconnectDevKit();
void startDebugging(String source);
void stopDebugging(boolean resume);
}

View File

@@ -1,5 +0,0 @@
package pub.doric.devkit;
public interface IStatusCallback {
void start();
}

View File

@@ -23,6 +23,8 @@ import org.json.JSONObject;
import java.io.EOFException;
import java.net.ConnectException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
@@ -43,6 +45,12 @@ import pub.doric.devkit.event.OpenEvent;
public class WSClient extends WebSocketListener {
private final WebSocket webSocket;
public interface Interceptor {
boolean intercept(String type, String command, JSONObject payload) throws JSONException;
}
private Set<Interceptor> interceptors = new HashSet<>();
public WSClient(String url) {
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
@@ -53,6 +61,15 @@ public class WSClient extends WebSocketListener {
webSocket = okHttpClient.newWebSocket(request, this);
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public void removeInterceptor(Interceptor interceptor) {
interceptors.remove(interceptor);
}
public void close() {
webSocket.close(1000, "Close");
}
@@ -72,23 +89,21 @@ public class WSClient extends WebSocketListener {
String type = jsonObject.optString("type");
String cmd = jsonObject.optString("cmd");
JSONObject payload = jsonObject.optJSONObject("payload");
if ("D2C".equals(type)) {
if ("DEBUG_REQ".equals(cmd)) {
String source = payload.optString("source");
DoricContext context = DevKit.getInstance().requestDebugContext(source);
sendToDebugger("DEBUG_RES", new JSONBuilder()
.put("contextId", context == null ? "" : context.getContextId())
.toJSONObject());
}
} else if ("S2C".equals(type)) {
if ("RELOAD".equals(cmd)) {
String source = payload.optString("source");
String script = payload.optString("script");
DevKit.getInstance().reload(source, script);
for (Interceptor interceptor : interceptors) {
if (interceptor.intercept(type, cmd, payload)) {
return;
}
}
if ("DEBUG_REQ".equals(cmd)) {
String source = payload.optString("source");
DevKit.getInstance().startDebugging(source);
} else if ("DEBUG_STOP".equals(cmd)) {
DevKit.getInstance().stopDebugging(true);
} else if ("RELOAD".equals(cmd)) {
String source = payload.optString("source");
String script = payload.optString("script");
DevKit.getInstance().reload(source, script);
}
} catch (JSONException e) {
e.printStackTrace();
}

View File

@@ -20,15 +20,15 @@ import com.github.pengfeizhou.jscore.JSRuntimeException;
import com.github.pengfeizhou.jscore.JavaFunction;
import com.github.pengfeizhou.jscore.JavaValue;
import pub.doric.devkit.IStatusCallback;
import pub.doric.devkit.WSClient;
import pub.doric.engine.IDoricJSE;
public class DoricRemoteJSExecutor implements IDoricJSE {
private final RemoteJSExecutor mRemoteJSExecutor;
public DoricRemoteJSExecutor(IStatusCallback statusCallback) {
this.mRemoteJSExecutor = new RemoteJSExecutor(statusCallback);
public DoricRemoteJSExecutor(WSClient wsClient) {
this.mRemoteJSExecutor = new RemoteJSExecutor(wsClient);
}
@Override

View File

@@ -5,102 +5,26 @@ import com.github.pengfeizhou.jscore.JSONBuilder;
import com.github.pengfeizhou.jscore.JavaFunction;
import com.github.pengfeizhou.jscore.JavaValue;
import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.EOFException;
import java.net.ConnectException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import pub.doric.devkit.DevKit;
import pub.doric.devkit.IStatusCallback;
import pub.doric.devkit.event.StopDebugEvent;
import pub.doric.devkit.WSClient;
public class RemoteJSExecutor {
private final WebSocket webSocket;
public class RemoteJSExecutor implements WSClient.Interceptor {
private final Map<String, JavaFunction> globalFunctions = new HashMap<>();
private JSDecoder temp;
private final WSClient wsClient;
private final Thread currentThread;
public RemoteJSExecutor(final IStatusCallback statusCallback) {
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
final Request request = new Request.Builder().url("ws://" + DevKit.ip + ":2080").build();
final Thread current = Thread.currentThread();
webSocket = okHttpClient.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
LockSupport.unpark(current);
statusCallback.start();
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
if (t instanceof ConnectException) {
// 连接remote异常
LockSupport.unpark(current);
throw new RuntimeException("remote js executor cannot connect");
} else if (t instanceof EOFException) {
// 被远端强制断开
System.out.println("remote js executor eof");
LockSupport.unpark(current);
EventBus.getDefault().post(new StopDebugEvent());
}
}
@Override
public void onMessage(WebSocket webSocket, String text) {
try {
JSONObject jsonObject = new JSONObject(text);
String cmd = jsonObject.optString("cmd");
switch (cmd) {
case "injectGlobalJSFunction": {
String name = jsonObject.optString("name");
JSONArray arguments = jsonObject.optJSONArray("arguments");
assert arguments != null;
JSDecoder[] decoders = new JSDecoder[arguments.length()];
for (int i = 0; i < arguments.length(); i++) {
Object o = arguments.get(i);
decoders[i] = new JSDecoder(new ValueBuilder(o).build());
}
globalFunctions.get(name).exec(decoders);
}
break;
case "invokeMethod": {
try {
Object result = jsonObject.opt("result");
ValueBuilder vb = new ValueBuilder(result);
temp = new JSDecoder(vb.build());
System.out.println(result);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
LockSupport.unpark(current);
}
}
break;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
LockSupport.park(current);
public RemoteJSExecutor(WSClient wsClient) {
this.wsClient = wsClient;
this.wsClient.addInterceptor(this);
currentThread = Thread.currentThread();
}
public String loadJS(String script, String source) {
@@ -113,17 +37,22 @@ public class RemoteJSExecutor {
public void injectGlobalJSFunction(String name, JavaFunction javaFunction) {
globalFunctions.put(name, javaFunction);
webSocket.send(new JSONBuilder().put("cmd", "injectGlobalJSFunction")
.put("name", name).toString()
);
wsClient.sendToDebugger(
"injectGlobalJSFunction",
new JSONBuilder()
.put("cmd", "injectGlobalJSFunction")
.put("name", name)
.toJSONObject());
}
public void injectGlobalJSObject(String name, JavaValue javaValue) {
webSocket.send(new JSONBuilder().put("cmd", "injectGlobalJSObject")
.put("name", name)
.put("type", javaValue.getType())
.put("value", javaValue.getValue()).toString()
);
wsClient.sendToDebugger(
"injectGlobalJSObject",
new JSONBuilder()
.put("name", name)
.put("type", javaValue.getType())
.put("value", javaValue.getValue())
.toJSONObject());
}
public JSDecoder invokeMethod(String objectName, String functionName, JavaValue[] javaValues, boolean hashKey) {
@@ -134,19 +63,60 @@ public class RemoteJSExecutor {
.put("value", javaValue.getValue())
.toJSONObject());
}
webSocket.send(new JSONBuilder()
.put("cmd", "invokeMethod")
.put("objectName", objectName)
.put("functionName", functionName)
.put("values", jsonArray)
.put("hashKey", hashKey)
.toString());
wsClient.sendToDebugger(
"invokeMethod",
new JSONBuilder()
.put("cmd", "invokeMethod")
.put("objectName", objectName)
.put("functionName", functionName)
.put("values", jsonArray)
.put("hashKey", hashKey)
.toJSONObject());
LockSupport.park(Thread.currentThread());
return temp;
}
public void destroy() {
webSocket.close(1000, "destroy");
wsClient.sendToDebugger("DEBUG_STOP", null);
wsClient.removeInterceptor(this);
}
@Override
public boolean intercept(String type, String cmd, JSONObject payload) throws JSONException {
if ("D2C".equals(type)) {
switch (cmd) {
case "injectGlobalJSFunction": {
String name = payload.optString("name");
JSONArray arguments = payload.optJSONArray("arguments");
assert arguments != null;
JSDecoder[] decoders = new JSDecoder[arguments.length()];
for (int i = 0; i < arguments.length(); i++) {
Object o = arguments.get(i);
decoders[i] = new JSDecoder(new ValueBuilder(o).build());
}
globalFunctions.get(name).exec(decoders);
}
break;
case "invokeMethod": {
try {
Object result = payload.opt("result");
ValueBuilder vb = new ValueBuilder(result);
temp = new JSDecoder(vb.build());
System.out.println(result);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
LockSupport.unpark(currentThread);
}
}
break;
default:
break;
}
return true;
}
return false;
}
}