android: webview shell mode

This commit is contained in:
pengfei.zhou 2021-11-08 15:19:55 +08:00 committed by osborn
parent b7935e48c7
commit c7252613a2
5 changed files with 157 additions and 24 deletions

View File

@ -25,7 +25,7 @@ android {
}
afterEvaluate {
buildJSBundle.exec()
//buildJSBundle.exec()
}
task buildJSBundle(type: Exec) {

View File

@ -83,7 +83,8 @@ public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.Time
initJSEngine();
injectGlobal();
initDoricRuntime();
if (mDoricJSE instanceof DoricWebViewJSExecutor) {
if (mDoricJSE instanceof DoricWebViewJSExecutor
|| mDoricJSE instanceof DoricWebShellJSExecutor) {
mDoricJSE.loadJS("_prepared();", "");
}
initialized = true;
@ -103,7 +104,7 @@ public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.Time
mDoricJSE = new DoricNativeJSExecutor();
} catch (Throwable e) {
mDoricJSE = new DoricWebShellJSExecutor(Doric.application());
loadBuiltinJS("doric-web.js");
//loadBuiltinJS("doric-web.js");
}
}

View File

@ -25,8 +25,11 @@ import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.github.pengfeizhou.jscore.JSDecoder;
import com.github.pengfeizhou.jscore.JSONBuilder;
@ -38,12 +41,20 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import pub.doric.BuildConfig;
import pub.doric.async.SettableFuture;
import pub.doric.utils.DoricLog;
import pub.doric.utils.DoricUtils;
/**
@ -128,6 +139,7 @@ public class DoricWebShellJSExecutor implements IDoricJSE {
@JavascriptInterface
public void ready() {
DoricLog.d("Ready");
readyFuture.set(true);
}
@JavascriptInterface
@ -188,54 +200,154 @@ public class DoricWebShellJSExecutor implements IDoricJSE {
}
}
@SuppressLint({"JavascriptInterface", "SetJavaScriptEnabled"})
private interface ResourceInterceptor {
boolean filter(String url);
WebResourceResponse onIntercept(String url);
}
private final SettableFuture<Boolean> readyFuture;
private final Set<ResourceInterceptor> resourceInterceptors = new HashSet<>();
private class DoricWebViewClient extends WebViewClient {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
for (ResourceInterceptor interceptor : resourceInterceptors) {
if (interceptor.filter(url)) {
WebResourceResponse webResourceResponse = interceptor.onIntercept(url);
if (webResourceResponse != null) {
return webResourceResponse;
}
}
}
return null;
}
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
for (ResourceInterceptor interceptor : resourceInterceptors) {
if (interceptor.filter(url)) {
WebResourceResponse webResourceResponse = interceptor.onIntercept(url);
if (webResourceResponse != null) {
return webResourceResponse;
}
}
}
return null;
}
}
private final String shellUrl = "http://shell.doric/";
private final Map<String, String> loadingScripts = new HashMap<>();
private final AtomicInteger scriptId = new AtomicInteger(0);
@SuppressLint({"JavascriptInterface", "SetJavaScriptEnabled", "AddJavascriptInterface"})
public DoricWebShellJSExecutor(final Context context) {
resourceInterceptors.add(new ResourceInterceptor() {
@Override
public boolean filter(String url) {
return url.startsWith(shellUrl + "doric-web.html");
}
@Override
public WebResourceResponse onIntercept(String url) {
String content = DoricUtils.readAssetFile("doric-web.html");
InputStream inputStream = new ByteArrayInputStream(content.getBytes());
return new WebResourceResponse("text/html", "utf-8", inputStream);
}
});
resourceInterceptors.add(new ResourceInterceptor() {
@Override
public boolean filter(String url) {
return url.startsWith(shellUrl + "doric-web.js");
}
@Override
public WebResourceResponse onIntercept(String url) {
String content = DoricUtils.readAssetFile("doric-web.js");
InputStream inputStream = new ByteArrayInputStream(content.getBytes());
return new WebResourceResponse("text/javascript", "utf-8", inputStream);
}
});
resourceInterceptors.add(new ResourceInterceptor() {
@Override
public boolean filter(String url) {
return url.startsWith(shellUrl + "script/");
}
@Override
public WebResourceResponse onIntercept(String url) {
String script = loadingScripts.remove(url);
if (TextUtils.isEmpty(script)) {
return null;
}
assert script != null;
InputStream inputStream = new ByteArrayInputStream(script.getBytes());
return new WebResourceResponse("text/javascript", "utf-8", inputStream);
}
});
readyFuture = new SettableFuture<>();
HandlerThread webViewHandlerThread = new HandlerThread("DoricWebViewJSExecutor");
webViewHandlerThread.start();
this.handler = new Handler(webViewHandlerThread.getLooper());
handler.post(new Runnable() {
this.handler.post(new Runnable() {
@Override
public void run() {
webView = new WebView(context.getApplicationContext());
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.setWebChromeClient(new DoricWebChromeClient());
webView.loadUrl("about:blank");
WebViewCallback webViewCallback = new WebViewCallback();
webView.addJavascriptInterface(webViewCallback, "NativeClient");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && BuildConfig.DEBUG) {
WebView.setWebContentsDebuggingEnabled(true);
}
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.setWebChromeClient(new DoricWebChromeClient());
webView.setWebViewClient(new DoricWebViewClient());
WebViewCallback webViewCallback = new WebViewCallback();
webView.addJavascriptInterface(webViewCallback, "NativeClient");
webView.loadUrl(shellUrl + "doric-web.html");
}
});
readyFuture.get();
}
private void execJS(final String script) {
this.handler.post(new Runnable() {
@Override
public void run() {
webView.loadUrl(String.format("javascript:%s", script));
}
});
}
@Override
public String loadJS(final String script, String source) {
handler.post(new Runnable() {
@Override
public void run() {
webView.evaluateJavascript(script, null);
}
});
String uniqueId = String.valueOf(scriptId.incrementAndGet());
String url = shellUrl + "script/" + uniqueId;
loadingScripts.put(url, script);
execJS(String.format("javascript:addScriptElement('%s','%s')", uniqueId, url));
return null;
}
@Override
public JSDecoder evaluateJS(String script, String source, boolean hashKey) throws JSRuntimeException {
loadJS(script, source);
public JSDecoder evaluateJS(final String script, String source, boolean hashKey) throws JSRuntimeException {
execJS(script);
return null;
}
@Override
public void injectGlobalJSFunction(String name, JavaFunction javaFunction) {
globalFunctions.put(name, javaFunction);
loadJS(String.format("__injectGlobalFunction('%s')", name), "");
execJS(String.format("__injectGlobalFunction('%s')", name));
}
@Override
public void injectGlobalJSObject(String name, JavaValue javaValue) {
loadJS(String.format("__injectGlobalObject('%s','%s')", name, javaValue.getValue()), "");
execJS(String.format("__injectGlobalObject('%s','%s')", name, javaValue.getValue()));
}
@Override
@ -251,7 +363,7 @@ public class DoricWebShellJSExecutor implements IDoricJSE {
objectName,
functionName,
jsonArray.toString());
loadJS(script, "");
execJS(script);
String result = returnFuture.get();
returnFuture = null;
if (!TextUtils.isEmpty(result)) {

View File

@ -2,11 +2,12 @@
<html>
<head>
<meta charset="UTF-8" />
<title>这是一个HTML5的网页</title>
<title>Web shell for doric</title>
</head>
<body>
<p>Hello HTML5</p>
</body>
<script type="text/javascript" src="http://shell.doric/doric-web.js"></script>
<script type="text/javascript">
function addScriptElement(scriptId, source) {
const scriptElement = document.createElement("script");
@ -15,9 +16,12 @@
scriptElement.src = source;
document.body.appendChild(scriptElement);
}
function removeScriptElement(scriptId) {
const scriptElement = document.getElementById(scriptId);
document.body.removeChild(scriptElement);
}
NativeClient.ready();
</script>
</html>

View File

@ -2,9 +2,25 @@
<html>
<head>
<meta charset="UTF-8" />
<title>这是一个HTML5的网页</title>
<title>Web shell for doric</title>
</head>
<body>
<p>Hello HTML5</p>
</body>
<script type="text/javascript">
function addScriptElement(scriptId, source) {
const scriptElement = document.createElement("script");
scriptElement.type = "text/javascript";
scriptElement.id = scriptId;
scriptElement.src = source;
document.body.appendChild(scriptElement);
}
function removeScriptElement(scriptId) {
const scriptElement = document.getElementById(scriptId);
document.body.removeChild(scriptElement);
}
NativeClient.ready();
</script>
</html>