android: webview shell mode
This commit is contained in:
parent
b7935e48c7
commit
c7252613a2
@ -25,7 +25,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
buildJSBundle.exec()
|
//buildJSBundle.exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
task buildJSBundle(type: Exec) {
|
task buildJSBundle(type: Exec) {
|
||||||
|
@ -83,7 +83,8 @@ public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.Time
|
|||||||
initJSEngine();
|
initJSEngine();
|
||||||
injectGlobal();
|
injectGlobal();
|
||||||
initDoricRuntime();
|
initDoricRuntime();
|
||||||
if (mDoricJSE instanceof DoricWebViewJSExecutor) {
|
if (mDoricJSE instanceof DoricWebViewJSExecutor
|
||||||
|
|| mDoricJSE instanceof DoricWebShellJSExecutor) {
|
||||||
mDoricJSE.loadJS("_prepared();", "");
|
mDoricJSE.loadJS("_prepared();", "");
|
||||||
}
|
}
|
||||||
initialized = true;
|
initialized = true;
|
||||||
@ -103,7 +104,7 @@ public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.Time
|
|||||||
mDoricJSE = new DoricNativeJSExecutor();
|
mDoricJSE = new DoricNativeJSExecutor();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
mDoricJSE = new DoricWebShellJSExecutor(Doric.application());
|
mDoricJSE = new DoricWebShellJSExecutor(Doric.application());
|
||||||
loadBuiltinJS("doric-web.js");
|
//loadBuiltinJS("doric-web.js");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,8 +25,11 @@ import android.webkit.ConsoleMessage;
|
|||||||
import android.webkit.JavascriptInterface;
|
import android.webkit.JavascriptInterface;
|
||||||
import android.webkit.JsResult;
|
import android.webkit.JsResult;
|
||||||
import android.webkit.WebChromeClient;
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebResourceRequest;
|
||||||
|
import android.webkit.WebResourceResponse;
|
||||||
import android.webkit.WebSettings;
|
import android.webkit.WebSettings;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
import com.github.pengfeizhou.jscore.JSDecoder;
|
import com.github.pengfeizhou.jscore.JSDecoder;
|
||||||
import com.github.pengfeizhou.jscore.JSONBuilder;
|
import com.github.pengfeizhou.jscore.JSONBuilder;
|
||||||
@ -38,12 +41,20 @@ import org.json.JSONArray;
|
|||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
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.BuildConfig;
|
||||||
import pub.doric.async.SettableFuture;
|
import pub.doric.async.SettableFuture;
|
||||||
import pub.doric.utils.DoricLog;
|
import pub.doric.utils.DoricLog;
|
||||||
|
import pub.doric.utils.DoricUtils;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,6 +139,7 @@ public class DoricWebShellJSExecutor implements IDoricJSE {
|
|||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
public void ready() {
|
public void ready() {
|
||||||
DoricLog.d("Ready");
|
DoricLog.d("Ready");
|
||||||
|
readyFuture.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
@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) {
|
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");
|
HandlerThread webViewHandlerThread = new HandlerThread("DoricWebViewJSExecutor");
|
||||||
webViewHandlerThread.start();
|
webViewHandlerThread.start();
|
||||||
this.handler = new Handler(webViewHandlerThread.getLooper());
|
this.handler = new Handler(webViewHandlerThread.getLooper());
|
||||||
handler.post(new Runnable() {
|
this.handler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
webView = new WebView(context.getApplicationContext());
|
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) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && BuildConfig.DEBUG) {
|
||||||
WebView.setWebContentsDebuggingEnabled(true);
|
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
|
@Override
|
||||||
public String loadJS(final String script, String source) {
|
public String loadJS(final String script, String source) {
|
||||||
handler.post(new Runnable() {
|
String uniqueId = String.valueOf(scriptId.incrementAndGet());
|
||||||
@Override
|
String url = shellUrl + "script/" + uniqueId;
|
||||||
public void run() {
|
loadingScripts.put(url, script);
|
||||||
webView.evaluateJavascript(script, null);
|
execJS(String.format("javascript:addScriptElement('%s','%s')", uniqueId, url));
|
||||||
}
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JSDecoder evaluateJS(String script, String source, boolean hashKey) throws JSRuntimeException {
|
public JSDecoder evaluateJS(final String script, String source, boolean hashKey) throws JSRuntimeException {
|
||||||
loadJS(script, source);
|
execJS(script);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectGlobalJSFunction(String name, JavaFunction javaFunction) {
|
public void injectGlobalJSFunction(String name, JavaFunction javaFunction) {
|
||||||
globalFunctions.put(name, javaFunction);
|
globalFunctions.put(name, javaFunction);
|
||||||
loadJS(String.format("__injectGlobalFunction('%s')", name), "");
|
execJS(String.format("__injectGlobalFunction('%s')", name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectGlobalJSObject(String name, JavaValue javaValue) {
|
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
|
@Override
|
||||||
@ -251,7 +363,7 @@ public class DoricWebShellJSExecutor implements IDoricJSE {
|
|||||||
objectName,
|
objectName,
|
||||||
functionName,
|
functionName,
|
||||||
jsonArray.toString());
|
jsonArray.toString());
|
||||||
loadJS(script, "");
|
execJS(script);
|
||||||
String result = returnFuture.get();
|
String result = returnFuture.get();
|
||||||
returnFuture = null;
|
returnFuture = null;
|
||||||
if (!TextUtils.isEmpty(result)) {
|
if (!TextUtils.isEmpty(result)) {
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>这是一个HTML5的网页</title>
|
<title>Web shell for doric</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>Hello HTML5</p>
|
<p>Hello HTML5</p>
|
||||||
</body>
|
</body>
|
||||||
|
<script type="text/javascript" src="http://shell.doric/doric-web.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function addScriptElement(scriptId, source) {
|
function addScriptElement(scriptId, source) {
|
||||||
const scriptElement = document.createElement("script");
|
const scriptElement = document.createElement("script");
|
||||||
@ -15,9 +16,12 @@
|
|||||||
scriptElement.src = source;
|
scriptElement.src = source;
|
||||||
document.body.appendChild(scriptElement);
|
document.body.appendChild(scriptElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeScriptElement(scriptId) {
|
function removeScriptElement(scriptId) {
|
||||||
const scriptElement = document.getElementById(scriptId);
|
const scriptElement = document.getElementById(scriptId);
|
||||||
document.body.removeChild(scriptElement);
|
document.body.removeChild(scriptElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NativeClient.ready();
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
@ -2,9 +2,25 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>这是一个HTML5的网页</title>
|
<title>Web shell for doric</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>Hello HTML5</p>
|
<p>Hello HTML5</p>
|
||||||
</body>
|
</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>
|
</html>
|
||||||
|
Reference in New Issue
Block a user