我有一个Web服务,正在尝试使用WebView在后台进行身份验证。当我最初发送请求时,它将正常运行(基于凭据的失败/成功),但是在接收到缓存的响应之后。

这是我的webview设置代码:

WebView browser = new WebView(this);
WebSettings settings = browser.getSettings();
settings.setJavaScriptEnabled(true);
settings.setSavePassword(false);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setAppCacheEnabled(false);
browser.setWebChromeClient(new WebChromeClient() {
    public void onProgressChanged(WebView view, int progress) {
    Log.d("BROWSERPROGRESS", Integer.toString(progress));
}
});
jsInterface = new AddAccountJSInterface();
browser.addJavascriptInterface(jsInterface, "ADDACCOUNTJSINTERFACE");
browser.setWebViewClient(new AddAccountClient(this));

如您所见,我还有两个控制我的webView的类:
  • 为javascript(AddAccountJSInterface)提供接口(interface)的对象
  • WebViewClient

  • 另外,我确实有一个WebChromeClient,但它仅用于调试,我很确定它不会干扰任何东西。

    JS接口(interface)只是提供了一种获取正文HTML进行执行分析的简单方法,因此我相信这也不是问题。

    WebViewClient中包含以下代码,该代码根据来自Web服务的各种响应完成路由的大多数“自定义”工作。
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if(url.contains(INSTALL_PREFIX)) {
                HashMap<String, String> params = extractParameters(url);
                verificationComplete(params);
                return true;
            }
            return false;
        }
    
        @Override
        public void onPageFinished(WebView view, String url){
            if(invalidShop(view)) {
                Toast.makeText(context, context.getString(R.string.no_find_shop), Toast.LENGTH_SHORT).show();
                shopAddressField.requestFocus();
                replaceUiElements(loadingBar, addAccountButton);
            } else if(url.contains(ADMIN_AUTH_LOGIN)) {
                if(invalidLogin(view)) {
                    Toast.makeText(context, context.getString(R.string.invalid_login),Toast.LENGTH_SHORT).show();
                    emailField.requestFocus();
                    replaceUiElements(loadingBar, addAccountButton);
                } else {
                    String email = emailField.getText().toString();
                    String password = passwordField.getText().toString();
                    String submitJS = String.format(FORM_SUBMISSION_JS, email, password);
    
                    jsInterface.setInnerHTML("");
    
                    browser.loadUrl(submitJS);
                }
            }
        }
    

    在我的 Activity 中,我需要填写3个文本字段,然后单击一个按钮将其提交。然后,该 Activity 从3个文本字段(shopAddressField,usernameField,passwordField)中获取数据,然后执行一些JavaScript以填充一些表单数据(已加载到不可见的webView中),然后单击Submit按钮。

    这是困惑的最后一部分,它似乎是在缓存来自服务器的响应(也许正在使用cookie?)并返回该值,而不是询问服务器数据是否正确。

    一点澄清:

    JSInterface只是一个Java对象,允许我在Webview上执行与该对象内的函数绑定(bind)的javascript。在我的情况下,我的JSInterface有一个函数是setInnerHtml(String html)。

    这是在webview上执行的javascript:
    javascript:window.ADDACOUNTJSINTERFACE.setInnerHTML(document.body.innerHTML)
    

    这是setInnerHtml函数:
    public void setInnerHtml(String innerHtml) {
        this.innerHtml = innerHtml;
    }
    

    因此,当我实际执行jsInterface.setInnerHtml(“”)时,我只是在重写被拉入的HTML(以确保出于某种原因我没有从那里得到旧数据)。

    至于我的SubmitJS,再次在我的webView上执行一些Javascript,如下所示:
    // submitJS will be something like this once all the credentials have been set
    // Note: I know that the server will make jQuery available
    // Note: Much of the Java string formatting has been removed to help clarify
    // the code.
    String submitJS =
        "javascript:(function() {
            $('login-input').value='username';
            $('password').value='password';
            $('sign-in-form').up().submit();
        })()"
    // I then simply get the webview to execute the javascript above
    webView.loadData(submitJS);
    

    最佳答案

    因此,事实证明问题并非基于缓存,也可能不是cookie。

    在webView上执行javascript时,它会在单独的线程中执行此操作,并且可能会很慢。这导致竞争状态,导致代码以错误的顺序执行。

    我已经通过使用信号量作为互斥体解决了这个问题。这使我可以防止getter在webView上的Javascript执行之前返回。

    我现在创建的界面如下所示:

    private class AddAccountJSInterface {
        private final String TAG = getClass().getName().toUpperCase();
        private Semaphore mutex = new Semaphore(1, false);
        private String innerHTML;
    
        public void aquireSemaphore() {
            Log.d(TAG, "Attempting to lock semaphore");
            try {
                mutex.acquire();
            } catch(InterruptedException e) {
                Log.d(TAG, "Oh snap, we got interrupted.  Just going to abort.");
                return;
            }
            Log.d(TAG, "Semaphore has been aquired");
        }
    
        @SuppressWarnings("unused")
        public void setInnerHTML(String html) {
                this.innerHTML = html;
                Log.d(TAG, "setInnerHTML is now releasing semaphore.");
                mutex.release();
                Log.d(TAG, "setInnerHTML has successfully released the semaphore.");
        }
    
        public synchronized String getInnerHTML() {
            Log.d(TAG, "getInnerHTML attempting to aquire semaphore, may block...");
            String innerHTML = "";
            try {
                mutex.acquire();
    
                Log.d(TAG, "getInnerHTML has aquired the semaphore, grabbing data.");
                innerHTML = this.innerHTML;
    
                Log.d(TAG, "getInnerHTML no longer needs semaphore, releasing");
                mutex.release();
            } catch (InterruptedException e) {
                Log.d(TAG, "Something has gone wrong while attempting to aquire semaphore, aborting");
            }
    
            return innerHTML;
        }
    }
    

    现在,我在代码中使用它的方式如下:
    // I have access to the jsInterface object which is an instance of the class above as well as a webView which I will be executing the javascript on.
    String getInnerHtmlJS = "javascript:window.MYJSINTERFACE.setInnerHTML(document.body.innerHTML);"
    jsInterface.aquireSemaphore()
    // Execute my JS on the webview
    jsInterface.loadUrl(getInnerHtmlJS)
    // Now we get our inner HTML
    // Note: getInnerHTML will block since it must wait for the setInnerHTML (executed via the JS) function to release the semaphore
    String theInnerHTML = jsInterface.getInnerHTML();
    

    07-24 09:49
    查看更多