Java 使用Android的Google云频道和隐藏的WebView

Java 使用Android的Google云频道和隐藏的WebView,java,javascript,android,webview,channel-api,Java,Javascript,Android,Webview,Channel Api,关于谷歌不提供Java(和其他)API来访问其GAE频道的讨论很多,目前只能从JavaScript访问GAE频道,如下所述: 基本上,有两种可能的方法: 编写“模拟”访问的Java类,如下所示: 使用隐藏的WebView并将JavaScript连接到Java,如下所述: 我尝试了选项1,并使其正常工作(因此我假设我的服务器代码很好),但我拒绝了它,因为不能保证Google将来不会更改他们未记录的API 我继续并实现了以下代码: 创建WebView并使用JavaScript事件的Java类:

关于谷歌不提供Java(和其他)API来访问其GAE频道的讨论很多,目前只能从JavaScript访问GAE频道,如下所述:

基本上,有两种可能的方法:

  • 编写“模拟”访问的Java类,如下所示:
  • 使用隐藏的WebView并将JavaScript连接到Java,如下所述:
  • 我尝试了选项1,并使其正常工作(因此我假设我的服务器代码很好),但我拒绝了它,因为不能保证Google将来不会更改他们未记录的API

    我继续并实现了以下代码:

    创建WebView并使用JavaScript事件的Java类:

    public class ChannelService {
    
        class ChannelListener {
            @JavascriptInterface public void onOpen() {
                System.out.println("open"); // PROBLEM: these lines are never called
            }
            @JavascriptInterface public void onMessage(String message) {
                System.out.println("message");
            }
            @JavascriptInterface public void onError(Integer errorCode, String description) {
                System.out.println("error");
            }
            @JavascriptInterface public void onClose() {
                System.out.println("close");
            }
        }
    
        public ChannelService(Context context) throws IOException {
            final WebView webView = new WebView(context);
            webView.getSettings().setJavaScriptEnabled(true);
    
            webView.addJavascriptInterface(new ChannelListener(), "channelListener"); // the connection between Java and JavaScript
    
            webView.setWebViewClient(new WebViewClient() {
                @Override public void onPageFinished(WebView view, String url) {
                    super.onPageFinished(view, url); // this line is called after the page loads
                }
                @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                    super.onReceivedError(view, errorCode, description, failingUrl);
                }
            });
    
            String html = AssetsHelper.assetToString(context, "channels.html"); // loads the html file below
    
            html = html.replaceAll("\\{\\{ channelurl \\}\\}", ApiService.channelUrl()); // channelurl and token are replaced fine and should be correct
            html = html.replaceAll("\\{\\{ token \\}\\}", Session.getToken().getId());
    
            webView.loadData(html, "text/html", null);
        }
    
    }
    
    包含JavaScript代码的channels.html文件:

    <html>
        <head>
            <script src='{{ channelurl }}jsapi'></script>
        </head>
        <body>
            <script type='text/javascript'>
    
                onOpen = function() {
                    channelListener.onOpen();
                }
                onMessage = function(message) {
                    channelListener.onMessage(message);
                }
                onError = function(errorcode, description) {
                    channelListener.onError(errorcode, description);
                }
                onClose = function() {
                    channelListener.onClose();
                }
    
                openChannel = function() {
                    var token = '{{ token }}';
                    var channel = new goog.appengine.Channel(token);
    
                    var handler = {
                        'onopen': onOpen,
                        'onmessage': onMessage,
                        'onerror': onError,
                        'onclose': onClose
                    };
    
                    var socket = channel.open(handler);
    
                    socket.onopen = onOpen;
                    socket.onmessage = onMessage;
                    socket.onerror = onError;
                    socket.onclose = onClose;
                }
    
            </script>
        </body>
    </html>
    
    
    onOpen=函数(){
    channelListener.onOpen();
    }
    onMessage=函数(消息){
    onMessage(消息);
    }
    onError=功能(错误代码、说明){
    channelListener.onError(错误代码、描述);
    }
    onClose=函数(){
    channelListener.onClose();
    }
    openChannel=function(){
    var-token='{{token}}';
    var通道=新的goog.appengine.channel(令牌);
    变量处理程序={
    “onopen”:onopen,
    “onmessage”:onmessage,
    “onerror”:onerror,
    “onclose”:onclose
    };
    var socket=channel.open(处理程序);
    socket.onopen=onopen;
    socket.onmessage=onmessage;
    socket.onerror=onerror;
    socket.onclose=onclose;
    }
    
    感谢Google,WebView不会报告我的代码中的任何错误,这使得调试非常困难。多亏了我自己,我从来没有主动编写过JavaScript代码,所以我在这里非常愚蠢,你可能会笑,并立即发现我的错误

    我已经在代码中记录了这一点,但让我在另一个时间说明什么是有效的,什么是无效的:

    • 95%的人确信服务器端代码可以正常工作,因为我的选项1可以正常工作。因此,我几乎可以肯定,我的信息是发送给客户的
    • 当客户端登录并通过REST端点将令牌传递给客户端时,我正在预创建通道。它完好无损地到达那里(与之相比),而且它毕竟也嵌入得很好
    • 当我收到带有正确url的onPageFinished()时,我的网站似乎已加载。(修正:我确实收到过好几次这个事件,有一些看起来像params的东西,但我猜这是安卓和普通的。)
    • 然后什么都不会发生,没有异常,Java事件也不会被调用
    • 我的Java代码仍在继续,当我设置断点时,可以检索WebView并且它是活动的,我在活动中为它使用了一个公共变量,这应该很好;我确保它在UI线程上

    欢迎任何想法,无论是我做错了什么,还是我如何调试它。谢谢

    我发现了两个问题:

  • JavaScript代码永远不会被触发,很明显不会发生任何事情
  • 您可以从onPageFinished调用openChannel:

    webView.loadUrl("javascript:openChannel()")
    
    或者完全删除该函数,并将部分脚本留在主体中(请参见下文)

  • 功能

        onSomething = function() {
            ChannelListener.onSomething();
        };
    
  • 需要使用分号终止;正如这里所讨论的,Android对此很挑剔。如果您没有正确地关闭它,WebView将一事无成

    以下是使用选项2的完整工作代码:

    <html>
        <head>
            <script src='{{ channelurl }}jsapi'></script>
        </head>
        <body>
            <script type="text/javascript">
    
                onOpen = function() {
                    ChannelListener.onOpen();
                };
                onMessage = function(message) {
                    ChannelListener.onMessage(message.data);
                };
                onError = function(error) {
                    ChannelListener.onError(error.code, error.description);
                };
                onClose = function() {
                    ChannelListener.onClose();
                };
    
                var token = '{{ token }}';
                var channel = new goog.appengine.Channel(token);
    
                var handler = {
                    'onopen': onOpen,
                    'onmessage': onMessage,
                    'onerror': onError,
                    'onclose': onClose
                };
    
                var socket = channel.open(handler);
    
                socket.onopen = onOpen;
                socket.onmessage = onMessage;
                socket.onerror = onError;
                socket.onclose = onClose;
    
            </script>
        </body>
    </html>
    
    
    onOpen=函数(){
    ChannelListener.onOpen();
    };
    onMessage=函数(消息){
    ChannelListener.onMessage(message.data);
    };
    onError=函数(错误){
    ChannelListener.onError(error.code,error.description);
    };
    onClose=函数(){
    ChannelListener.onClose();
    };
    var-token='{{token}}';
    var通道=新的goog.appengine.channel(令牌);
    变量处理程序={
    “onopen”:onopen,
    “onmessage”:onmessage,
    “onerror”:onerror,
    “onclose”:onclose
    };
    var socket=channel.open(处理程序);
    socket.onopen=onopen;
    socket.onmessage=onmessage;
    socket.onerror=onerror;
    socket.onclose=onclose;
    
    现在只剩下一个问题,但情况不同了:onMessage(message)不会在处理程序中传递message参数,可能是因为它是在不同的范围内执行的。我把这个问题作为一个新问题发布在这里:

    --

    我发现了为什么我没有收到来自服务器的消息的问题,并编辑了上面的代码。要接收消息,必须将message.data而不仅仅是消息传递给Java,以接收服务器发送的字符串消息

    --


    我也根据这里定义的谷歌规范盲目地调整了onError函数,但由于谷歌在这里的示例代码中没有实现错误处理,我不能肯定它是否能像这样工作,但我假设它能做到。如果您遇到此问题并能确认其工作正常,请发表评论。

    对不起,我的代码出错了。而不是webView.loadData(html,“text/html”,null);我将数据加载为url webView.loadUrl(html);-OnPageFinished的原因被调用了两次。我已经在上面的代码中修复了这个问题。问题依然存在。