【问题标题】:Android: How can I know CSS injection is complete?Android:我怎么知道 CSS 注入完成了?
【发布时间】:2017-07-17 06:05:57
【问题描述】:

我仅在将 CSS 文件注入 HTML 后才尝试显示我的 webview。 我曾尝试将其放在 onPageCommitVisible 函数上,但它仅适用于以上 23 个 api 和 d。有人知道只有在 CSS 完成加载后我才能显示 webview 吗?现在它“跳跃”了,在替换新 CSS 之前,我会在第一秒看到原始 CSS。

@Override
        public void onPageFinished(WebView view, String url) {
            Utils.logDebug(this, "Page finished");
            if (android.os.Build.VERSION.SDK_INT < 23) {
                injectCSS(view);
            }
            super.onPageFinished(view, url);
            showWebView(true);
           onPageChange();
        }

这是我的 InjestCSS 函数:

private void injectCSS(WebView webView) {
            try {
                webView.loadUrl("javascript:(function() {" +
                        "var css = document.createElement(\"style\");\n" +
                        "css.type = \"text/css\";\n" +
                        "css.innerHTML = \"" + readFileAsString() + "\";\n" +
                        "document.body.appendChild(css);" +
                        "})()");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

此函数将 CSS 代码注入 HTML,如您在函数中所见。

【问题讨论】:

  • 你的injectCSS在做什么?
  • @j2ko 刚刚更新了我的问题。

标签: java android html css


【解决方案1】:

很少有地方可以处理这个问题。

  • 您可以使用evaluateJavaScript 代替loadUrl(API 级别19)并传递回调,您将在其中设置webview 可见。
  • 您可以使用addJavaScriptInterface 注册自己的javascript 接口并在脚本末尾调用它
  • 您可以设置 WebChromeClient 并覆盖 onJsAlert,然后在脚本中使用特定消息引发警报。
  • 更新:此外,这可以通过拦截“css”请求之一,并将加载的文件附加所需内容来实现。这将允许您在 onPageFinished 之前注入您的样式。检查此this 线程。

我在以下示例中结合了所有方法:

    package com.j2ko.webviewapp;

    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Base64;
    import android.util.Log;
    import android.view.View;
    import android.webkit.JavascriptInterface;
    import android.webkit.JsResult;
    import android.webkit.ValueCallback;
    import android.webkit.WebChromeClient;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;

    import java.io.InputStream;
    import java.io.StringReader;
    import java.io.StringWriter;

    public class MainActivity extends AppCompatActivity {
        private static final String MAIN_FUNC_FMT = "(function() { %s })()";
        private static final String FUNC_BODY_FMT =
                "var parent = document.loadedgetElementsByTagName('head').item(0);" +
                        "var css = document.createElement('style');" +
                        "css.type = 'text/css';" +
                        "css.innerHTML = %s;" +
                        "parent.appendChild(css);";

        private static final String BASE64_DECODE_FMT = "window.atob('%s')";

        WebView mWebView;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            mWebView = (WebView) findViewById(R.id.webview);
            mWebView.getSettings().setJavaScriptEnabled(true);
            mWebView.setWebViewClient(new WebViewClient() {
                @Override
                public void onPageFinished(WebView view, String url) {
                    super.onPageFinished(view, url);
                    //Change it to whatever
                    injectWithEvaluateAndInterface(view);
                }

                @Override
                public void onLoadResource(WebView view, String url) {
                    super.onLoadResource(view, url);
                }
            });
            mWebView.setVisibility(View.INVISIBLE);
            mWebView.loadUrl("http://wiki.org");
        }


        private static class CSSInjectBuilder {
            private final String mOrigin;
            private String mAtEnd = null;
            private boolean mUseBase64 = false;

            public CSSInjectBuilder(String css) {
                mOrigin = css;
            }

            public CSSInjectBuilder withBase64() {
                mUseBase64 = true;
                return this;
            }

            public CSSInjectBuilder withExpressionAtEnd(String expression){
                mAtEnd = expression;
                return this;
            }

            String build() {
                String func_body = FUNC_BODY_FMT;

                if (mAtEnd != null) {
                    func_body += mAtEnd;
                }

                final String css;
                if (mUseBase64) {
                    byte[] buffer = mOrigin.getBytes();
                    css = String.format(BASE64_DECODE_FMT, Base64.encodeToString(buffer, Base64.NO_WRAP));
                } else {
                    css = "'" + mOrigin + "'";
                }

                func_body = String.format(func_body, css);

                return String.format(MAIN_FUNC_FMT, func_body);
            }
        }

        byte[] loadAsset() {
            try {
                InputStream inputStream = getAssets().open("style.css");
                byte[] buffer = new byte[inputStream.available()];
                inputStream.read(buffer);
                inputStream.close();
                return buffer;
            } catch (Exception e) {
            }

            return null;
        }

        String loadCSS() {
            return new String(loadAsset());
        }

        void injectWithEvaluate(final WebView view) {
            view.evaluateJavascript(new CSSInjectBuilder(loadCSS()).withBase64().build(), new ValueCallback<String>() {
                @Override
                public void onReceiveValue(String value) {
                    view.setVisibility(View.VISIBLE);
                }
            });
        }

        void injectWithEvaluateAndInterface(WebView view) {
            view.addJavascriptInterface(new WebViewInterface(), "WebViewBackEnd");
            final String injector = new CSSInjectBuilder(loadCSS())
                    .withBase64()
                    .withExpressionAtEnd("window.WebViewBackEnd.CSSInjectionComplete();")
                    .build();

            view.evaluateJavascript(injector, null);
        }

        void injectWithLoadUrlSimple(WebView view) {
            view.loadUrl("javascript:" + loadCSS());
            view.setVisibility(View.VISIBLE);
        }

        void injectWithLoadUrlAndCheckAlert(final WebView view) {
            view.setWebChromeClient(new WebChromeClient() {
                @Override
                public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                    if (message.equals("CSSInjectionComplete")) {
                        view.setVisibility(View.VISIBLE);
                        return true;
                    }
                    return super.onJsAlert(view, url, message, result);
                }
            });

            //alert could hang aplying scripts so put it on timeout
            final String injector = new CSSInjectBuilder(loadCSS())
                    .withBase64()
                    .withExpressionAtEnd("setTimeout(function(){alert('CSSInjectionComplete');}, 1);")
                    .build();

            view.loadUrl("javascript:"  + injector);
        }

        private class WebViewInterface {

            @JavascriptInterface
            public void CSSInjectionComplete(){

                mWebView.post(new Runnable() {
                    @Override
                    public void run() {
                        mWebView.setVisibility(View.VISIBLE);
                    }
                });
            }
        }

    }

【讨论】:

  • 我必须实施所有方法还是只实施一种?
  • 我发布了所有这些只是为了说明目的 - 选择哪个更适合
  • 所以我现在只尝试了一个,但是使用 WebViewInterface 我在可见 webview 和注入发生之间仍然存在延迟。
  • 尝试延迟呼叫WebViewInterface,就像我为警报所做的那样
  • 你的意思是这样的:.withExpresionAtEnd("setTimeout(function(){window.WebViewBackEnd.CSSInjectionComplete();}, 1);") ?
猜你喜欢
  • 1970-01-01
  • 2016-09-28
  • 1970-01-01
  • 1970-01-01
  • 2017-01-31
  • 2014-05-02
  • 2018-09-24
  • 1970-01-01
  • 2015-12-31
相关资源
最近更新 更多