Использование облачных каналов Google с Android со скрытым WebView

Ведется много дискуссий о том, что Google не предоставляет Java (и другой) API для доступа к своим каналам GAE, доступ к которым в настоящее время возможен только из JavaScript, как описано здесь:

https://cloud.google.com/appengine/docs/java/channel/ https://cloud.google.com/appengine/docs/java/channel/javascript

По сути, есть 2 возможных подхода:

  1. Напишите классы Java, которые «симулируют» доступ, как это сделано здесь: https://github.com/gvsumasl/jacc
  2. Используйте скрытый WebView и подключите JavaScript к Java, как описано здесь: http://developer.android.com/guide/webapps/webview.html

Я пробовал вариант 1, и он работал (поэтому я предполагаю, что код моего сервера в порядке), но я отклонил его, потому что нет гарантии, что Google не изменит свой недокументированный API в будущем.

Я пошел дальше и реализовал следующий код:

Класс Java, который создал WebView и использует события JavaScript:

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);
    }

}

Файл channels.html с кодом JavaScript:

<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>

Благодаря Google, WebView не сообщает об ошибках в моем коде, что очень затрудняет отладку. Благодаря себе, я никогда активно не писал код JavaScript, так что здесь я довольно глуп, и вы можете посмеяться и сразу заметить мою ошибку.

Я уже задокументировал это в коде, но позвольте мне указать в другой раз, что работает, а что нет:

  • На 95% уверен, что код на стороне сервера работает, так как у меня работал вариант 1. Так что я почти уверен, что мои сообщения отправляются клиенту.
  • Я предварительно создаю каналы, когда клиент входит в систему, и передаю токен клиенту через конечные точки REST. Он попадает туда целым (сравнивал), и после replaceAll встраивается тоже нормально.
  • Кажется, мой веб-сайт загружается, когда я получаю onPageFinished() с правильным URL-адресом. (ИСПРАВЛЕНО: на самом деле я получаю это событие несколько раз с чем-то, похожим на параметры, но я думаю, что это Android и нормально.)
  • Тогда ничего не происходит, никаких исключений, и события Java не вызываются.
  • Мой код Java продолжается, и когда я устанавливаю точку останова, WebView может быть получен и жив. Я использовал для него общедоступную переменную в своей деятельности, что должно быть хорошо; и я убедился, что это в потоке пользовательского интерфейса.

Любые идеи приветствуются, либо что я сделал неправильно, либо как я могу отладить это. Спасибо!


person Oliver Hausler    schedule 04.01.2015    source источник
comment
Извините, я допустил ошибку в своем коде. Вместо webView.loadData(html, text/html, null); Я загрузил данные как URL-адрес webView.loadUrl(html); - причина, по которой OnPageFinished вызывался дважды. Я исправил это в коде выше. Проблема сохраняется.   -  person Oliver Hausler    schedule 04.01.2015


Ответы (1)


Две проблемы, которые я обнаружил:

  1. Код JavaScript никогда не срабатывает, понятно, что ничего не произойдет.

Либо вы вызываете openChannel из onPageFinished:

webView.loadUrl("javascript:openChannel()")

или вы полностью удаляете эту функцию и оставляете часть скрипта в основном теле (см. ниже).

  1. Функции

        onSomething = function() {
            ChannelListener.onSomething();
        };
    

необходимо завершать точкой с запятой; как обсуждалось здесь var functionName = function() {} vs function functionName() {} и 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>

Теперь осталась только одна проблема, но это уже другая история: onMessage(message) не передает аргумент сообщения через обработчик, вероятно, потому, что он выполняется в другой области видимости. Я разместил эту проблему как новый вопрос здесь: Аргумент JavaScript не передается через обработчик

--

Я нашел проблему, почему я не получил сообщение от сервера и отредактировал код выше. Чтобы получить сообщение, вы должны передать message.data вместо просто сообщения Java, чтобы получить строковое сообщение, отправленное сервером.

--

Я также слепо настроил функцию onError в соответствии со спецификациями Google, как указано здесь https://cloud.google.com/appengine/docs/java/channel/javascript, но поскольку Google не реализовал обработку ошибок в своем примере кода здесь http://code.google.com/p/channel-tac-toe/source/browse/trunk/index.html Я не могу точно сказать, что это так работает, но предполагаю, что работает. Если вы сталкиваетесь с этим и можете подтвердить, что он работает хорошо, пожалуйста, прокомментируйте.

person Oliver Hausler    schedule 04.01.2015