android: webview shell mode
This commit is contained in:
		@@ -25,7 +25,7 @@ android {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
afterEvaluate {
 | 
			
		||||
    buildJSBundle.exec()
 | 
			
		||||
    //buildJSBundle.exec()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
task buildJSBundle(type: Exec) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user