从 JavaFXPorts Webview 访问本机 android.webkit.WebView 实例

Access to native android.webkit.WebView instance from JavaFXPorts Webview

我想通过

实现 java-to-java 脚本方法调用

WebView.getEngine().executeScript("browserMessagingServicePush('Push data from Java to JS')");

在 Android 使用 javafxports 8.60.9,但得到

java.lang.UnsupportedOperationException: Not supported yet.
...
at javafx.scene.web.WebEngine.executeScript(WebEngine.java:860)

例外。

如何获取对 Android 的本机 WebView 的引用以实现类似的功能?

WebView.addJavascriptInterface(new JSInterface(), "JSInterface");

或者有其他解决方法?

解决方案

我找到了从 JavaFXPorts 应用程序使用 Android 本机 WebView(而不是使用 JavaFX WebView)执行 JavaScript 的解决方案:

public class AndroidNativeService extends Activity implements NativeService {
    private Stage stage;

    public void myWebview(Stage stage) {
        this.stage = stage;

        FXActivity.getInstance().runOnUiThread(new Runnable() {     
            @Override
            public void run() {             
                WebView webView = new WebView(FXActivity.getInstance().getApplicationContext());
                FXActivity.getInstance().setContentView(webView);
                webView.getSettings().setJavaScriptEnabled(true);
                webView.getSettings().setDomStorageEnabled(true);
                webView.setWebViewClient(webClient);
                webView.loadUrl("http://www.example.com/index.html");
            }
        });
    }

    WebViewClient webClient = new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            // super.onPageFinished(view, url);          
            System.out.println(">>>>>>>>>>>>>>>>>>>>> onpagefinished title = "+view.getTitle());

            view.evaluateJavascript("(function(){return document.documentElement.outerHTML})();", new ValueCallback<String>() {
                @Override
                public void onReceiveValue(String html) {
                    System.out.println(">>>>>>>>>>>>>>>>>>>>> evaluateJavascript html = "+html);
                    // view.goBack();
                    // stage.show(); # get error java.lang.IllegalStateException: Not on FX application thread; currentThread = main
                }
            });
        }
    };

}

我调用此 class 使用:

Task<Void> task = new Task<Void>() {
    @Override 
    protected Void call() throws Exception {
        Platform.runLater( () ->  {
            GoNativePlatform goNativePlatform =  GoNativePlatformFactory.getPlatform();
            Looper.prepare(); // if don't use this then get error Looper.prepare() not set
            goNativePlatform.getNativeService().myWebView(stage);   
        });
        return null;
    }
};

Thread th = new Thread(task);
th.start();

我从 Handling Android Native Activities like a Pro 的 class 声明中复制了 "Implements NativeService",尽管这对于完成这项工作并不是绝对必要的。 当尝试使用此 JavaFX WebView 代码实现 JavaFX WebView(而不是 Android Native WebView)时:

Document doc = webEngine.getDocument();
getTitle(webView.getEngine());
String html = (String) webView.getEngine().executeScript("document.documentElement.outerHTML");

当 Android 上的 运行 时,我得到了空值或 "not supported yet",尽管代码在桌面上运行正常。 (还没有在 IOS 上尝试过,但我也想在这里实现)。 我想检索网页标题并执行 javascript 从网页中检索其他数据。 因此,我的解决方案是在 Android 上 运行 时使用 Android 本机 WebView 而不是 JavaFX WebView。 这是我的完整代码:

public class AndroidNativeService extends Activity implements NativeService {
    private Stage stage;

    public void myWebview(Stage stage) {
        this.stage = stage;

        FXActivity.getInstance().runOnUiThread(new Runnable() {     
            @Override
            public void run() {             
                WebView webView = new WebView(FXActivity.getInstance().getApplicationContext());
                FXActivity.getInstance().setContentView(webView);
                webView.getSettings().setJavaScriptEnabled(true);
                webView.getSettings().setDomStorageEnabled(true);
                webView.setWebViewClient(webClient);
                webView.loadUrl("http://www.example.com/index.html");

                webView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        System.out.println(">>>>>>>>>>>>>>>>>>>>> onClick()");
                        // webView.goBack();
                    }
                }); 
            }
        });
    }

    WebViewClient webClient = new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            // super.onPageFinished(view, url);          
            System.out.println(">>>>>>>>>>>>>>>>>>>>> onpagefinished title = "+view.getTitle());

            view.evaluateJavascript("(function(){return document.documentElement.outerHTML})();", new ValueCallback<String>() {
                @Override
                public void onReceiveValue(String html) {
                    System.out.println(">>>>>>>>>>>>>>>>>>>>> evaluateJavascript html = "+html);
                    // view.goBack();
                    // stage.show(); # get error java.lang.IllegalStateException: Not on FX application thread; currentThread = main
                }
            });
        }
    };

    @Override
    public void onBackPressed() {
        System.out.println(">>>>>>>>>>>>>>>>>>>>> Back Button Pressed");
        setResult(RESULT_CANCELED);
        super.onBackPressed();
        // FXActivity.getInstance().finish();
    }

}

虽然我已经成功显示了想要的网页,检索了标题并执行了一些JavaScript,但我正在努力关闭网页并return到JavaFX阶段。 我在 onBackPressed()OnClickListener 上面的 2 个回调不起作用,也没有被调用(看不到 System.out.println)。你可以从上面看到我正在尝试并努力使用这些替代方案:

  • webView.goBack();
  • stage.show(); // get error java.lang.IllegalStateException: Not on FX application thread; currentThread = main
  • FXActivity.getInstance().finish();

但我只是设法终止整个应用程序,而不是 return 到 JavaFX 阶段(即使我将它的值传递给此 class)。有什么想法吗?

PSLooper.prepare(); 是调用此代码所必需的,尽管我不明白我在这里做什么?

解决方案更新

我找到了从 JavaFXPorts 应用程序使用 Android 本机 WebView(而不是使用 JavaFX WebView)执行 JavaScript 的解决方案 - 请参阅下面的代码。 我通过使用 2 个活动解决了往返 JavaFX 阶段和 Android WebView(在我 2018 年 2 月 25 日的第一个答案中)的导航问题:我的主要 activity(JavaFX 阶段)调用第二个 Activity (Android WebView) 并传递我要显示的 URL,当第二个 Activity 完成时,它会传回一个结果字符串。您可以通过执行 finish() 或使用后退按钮退出第二个 Activity。您需要在 AndroidManifest.xml 中为第二个 Activity 添加一个附加条目。

这是我的主要内容 activity:

import android.content.Intent;
import javafx.application.Platform;
import javafxports.android.FXActivity;

/*****************************************
   This is the main activity
******************************************/
public class MainActivity extends FXActivity {

    private static final int SECOND_ACTIVITY_REQUEST_CODE = 0;

    protected void webView() {

        Platform.runLater( () -> {
                String url ="http://www.example.com";
                Intent intent = new Intent(FXActivity.getInstance().getApplicationContext(), Test.class);
                intent.putExtra("url", url);

                // Add result handler
                FXActivity.getInstance().setOnActivityResultHandler((requestCode, resultCode, data) ->
                    switch (requestCode) {
                        case SECOND_ACTIVITY_REQUEST_CODE:
                             //if (resultCode == RESULT_OK) {}

                             // get String data from Intent
                             String result = data.getStringExtra("result");
                             System.out.println("result = " + result);
                             break;

                        default:                             
                             super.onActivityResult(requestCode, resultCode, data);
                             break;
                     }
                });

                FXActivity.getInstance().startActivityForResult(intent, SECOND_ACTIVITY_REQUEST_CODE);
        });

    }
}

这是我的第二个 Activity(Android WebView):

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import android.webkit.WebViewClient;

/*****************************************
    This is the 2nd activity
******************************************/
public class Test extends Activity {
    private String url;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle bundle = getIntent().getExtras();
        if (bundle != null) {
            url = bundle.getString("url");
        }
        WebView webView = new WebView(this);
        setContentView(webView);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setDomStorageEnabled(true);
        webView.setWebViewClient(webClient);
        webView.loadUrl(url);
    }


    WebViewClient webClient = new WebViewClient() {

    @Override
    public void onPageFinished(WebView view, String url) {
         String title = view.getTitle();
         System.out.println("onpagefinished title = "+view.getTitle());

         view.evaluateJavascript("(function(){return document.documentElement.outerHTML})();",
                  new ValueCallback<String>() {

                      @Override
                      public void onReceiveValue(String html) {
                        System.out.println("evaluateJavascript html = "+html);

                        if (<exit 2nd activity condition>) {
                            // put the String to pass back into an Intent and close this activity
                            Intent intent = new Intent();
                            intent.putExtra("result", "this is the result string");
                            setResult(RESULT_OK, intent);
                            finishAndRemoveTask();                  // takes you back to main activity
                        }
                      }
             });
    }
    };

}

这是我的 AndroidManifest.xml,我手动更改为 Test.class 添加第二个 Activity:

<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example" android:versionCode="1" android:versionName="1.0">
    <supports-screens android:xlargeScreens="true"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21"/>
    <application android:label="APP" android:name="android.support.multidex.MultiDexApplication" android:icon="@mipmap/ic_launcher">
            <activity android:name="javafxports.android.FXActivity" android:label="Main" android:configChanges="orientation|screenSize">
                    <meta-data android:name="main.class" android:value="com.example.Launcher"/>
                    <meta-data android:name="debug.port" android:value="0"/>
                    <intent-filter>
                            <action android:name="android.intent.action.MAIN"/>
                            <category android:name="android.intent.category.LAUNCHER"/>
                    </intent-filter>
            </activity>
            <activity
                android:name="com.example.Test"
                android:label="Test"
                android:configChanges="orientation|screenSize">
            </activity>
    </application>
</manifest>

在实例化 MainActivity 时崩溃 "can't create a handle without a Looper.prepare()"。句柄未明确创建,因此不清楚引用了哪个句柄。解决方案:创建一个循环器然后 实例化销毁它。您只能发出 Looper.prepare() 一次,否则它会崩溃。对 MainActivity.webView() 的后续调用在没有 Looper 的情况下工作。这是我的代码:

Looper.prepare();
new MainActivity();
Looper.myLooper().quitSafely();