Pull to refresh

Crosswalk Project — замена Android WebView. Проблемы интеграции

Reading time7 min
Views11K
CrosswalkProject

В этой статье я закончу свой рассказ о проекте Crosswalk Project (первую часть вы можете найти здесь). Расскажу более детально о своем опыте интеграции Crosswalk, некоторых тонкостях при работе с ним, встреченных проблемах и их возможных решениях.

Первое и, возможно, самое очевидно решение всех вопросов — это изменение кода Crosswalk под свои нужды. Это возможно, т.к. проект открытый и даже можно помочь его авторам. Однако, нужно это не всегда и может вызвать дополнительные затруднения. В описываемых примерах такой вариант я не рассматриваю, но изучить код базовых классов полезно.

Контроль загрузки страницы в XWalkView.


Пожалуй второй наиболее важной вещью в Crosswalk, после скорости его работы, является предсказуемость вызова калбэков. Возможность ориентироваться на логику вызовов и иметь единый алгоритм для всех версий Android — это просто отлично. Сложности же с контролем загрузки в системном WebView хорошо описаны в этой статье.

Также как и системный класс, XWalkView имеет два вспомогательных класса, которые могут получать вызовы во время загрузки и работы с XWalkView — это XWalkUIClient и XWalkResourceClient. Как я писал ранее, они не имеют прямого соответствия системным WebChromeClient и WebViewClient, методы в них распределены несколько иначе. Однако, для себя я установил примерное соответствие как WebChromeClient == XWalkUIClient и WebViewClient == XWalkResourceClient.

XWalkUIClient в том числе содержит методы:
  • onPageLoadStarted — информирует о начале загрузки основного фрэйма.
  • onPageLoadStopped — информирует об окончании загрузки основного фрэйма.

а также:
  • onConsoleMessage
  • onReceivedTitle
  • onConsoleMessage
  • onRequestFocus
  • shouldOverrideKeyEvent

Кака видно, здесь присутствуют методы входящие как в интерфейс WebViewClient, так и WebChromeClient, но, в целом, XWalkUIClient содержит методы имеющие важность для UI.

XWalkResourceClient в том числе содержит:
  • onDocumentLoadedInFrame — информирует о том, что HTML документ получен и обработан, вызывается не дожидаясь загрузки css, изображений и т.д. NB! Замечу, что метод описан в Javadoc и реализован в master ветке, но отсутствует сейчас в классе при интеграции.
  • onLoadStarted — информирует о том, что началась загрузка ресурса по указанному url.
  • onLoadFinished — информирует о том, что завершилась загрузка ресурса по указанному url.
  • onProgressChanged — информирует о проценте загрузки страницы.
  • shouldInterceptLoadRequest — аналогично системному калбэку, позволяет клиенту вернуть данные для казанного url.
  • shouldOverrideUrlLoading — аналогично системному калбэку, позволяет клиенту перехватить контроль перед загрузкой ресурса.

Последовательность вызова основных калбэков выглядит так:
  • shouldOverrideUrlLoading — для указанного url;
  • onPageLoadStarted;
  • shouldInterceptLoadRequest — загрузка основной страницы;
  • onLoadStarted — загрузка основной страницы (дублирует shouldInterceptLoadRequest);
  • shouldInterceptLoadRequest в паре с onLoadStarted — загрузка ресурсов, для каждого ресурса;
  • onPageLoadStopped;
  • onLoadFinished.

Данная последовательность будет аналогична как для url загруженного с помощью метода WXalkView, так и для перехода по ссылке на странице и даже при остановке загрузки с помощью stopLoading().

Практически аналогично выглядит последовательность для загрузки страницы с редиректом:
  • shouldOverrideUrlLoading — для указанного url;
  • onPageLoadStarted;
  • shouldInterceptLoadRequest — загрузка основной страницы;
  • onLoadStarted — загрузка основной страницы;
  • shouldOverrideUrlLoading — для url, указанного в редиректе;
  • onPageLoadStarted;
  • shouldInterceptLoadRequest в паре с onLoadStarted — загрузка ресурсов;
  • onPageLoadStopped;
  • onLoadFinished.

Как видно, при переходе по редиректу мы получили дополнительные вызовы тех же стартовых калбэков и, по сути, логика в этом случае тоже не нарушилась.

Дополнительно можно указать, что метод onPageLoadStopped имеет следующую сигнатуру:
onPageLoadStopped(XWalkView view, java.lang.String url, XWalkUIClient.LoadStatus status);

Дополнительный параметр типа XWalkUIClient.LoadStatus дает достаточно важную информацию о том, как закончилась загрузка и имеет 3 варианта:
  • CANCELLED — в случае вызова stopLoading();
  • FAILED — в случае ошибки загрузки;
  • FINISHED — нормальное завершение загрузки.

Необошлось и без пары странностей в поведении Crosswalk. Например, метод onLoadFinished вызывается только один раз после загрузки всей страницы и даже после onPageLoadStopped. Логика предполагает, что вызов onLoadFinished должен соответствовать вызову onLoadStarted, который происходит для каждого ресурса, но сейчас это не так.

Также возможна ситуация когда произойдет двукратный вызов пары финальных методов onPageLoadStopped и onLoadFinished. Упоминание о таком поведении я нашел еще для Crosswalk 11ой версии, но очевидно с этой проблемой не справились до сих пор.

Обработка событий от экрана и кнопок.


Одной из первых проблем с которой вы столкнетесь при интеграции XWalkView будет обработка событий экрана и клавиатуры (конкретно кнопки Back). В XWalkView, также как и методы onActivityResult и onNewIntent, реализован метод: dispatchKeyEvent(KeyEvent event). В нем происходит перехват события от кнопки Back для перехода из полноэкранного режима и перехода по истории браузера. Соответственно вы не сможете получить это событие, если вы используете методы:
public boolean onKeyUp(int keyCode, KeyEvent event);
public void onBackPressed();

Вариантом решения может быть обработка этого события также в dispatchKeyEvent(KeyEvent event). Это также полезно знать, если вы хотите реализовать собственный переход по истории навигации.

Аналогичная проблема ожидает вас, если вы хотите использовать onTouchEvent listener для вашего XWalkView. Решение аналогично приведенному выше — реализуйте обработку события в dispatchTouchEvent(MotionEvent event).

Примеры добавлены в тестовый проект, доступный в GitHub.

Работа с хранилищем cookie.


Важным моментом в работе браузера и WebView является обработка cookies. Например, вы хотите загрузить какую-то страницу не через XWalkView, но отобразить ее в нем и предоставить пользователю возможность с ней работать. Страница может устанавливать свои cookies и для нормальной работы они понадобятся в XWalkView.

В Crosswalk существует специальный компонент XWalkCookieManager, который собственно и занимается хранением и работой с cookies. Очень странно, но он не описан в Javadoc, хотя и является публичным и доступным для использования. Реализацию и комментарии к методам класса можно найти здесь.

XWalkCookieManager, как и кэш Crosswalk, един для всех инстансов XWalkView в вашем приложении. Класс достаточно прост в использовании и позволяет добавлять и получать cookie для определенного адреса, очистить хранилище и пр. Пример инициализации:
mXCookieManager = new XWalkCookieManager();
mXCookieManager.setAcceptCookie(true);
mXCookieManager.setAcceptFileSchemeCookies(true);

Однако простота его реализации имеет и некоторые проблемы. Например, метод для установки cookie выглядит так:
public void setCookie(String url, String value);

Соответсвенно вы не сможете указать время жизни для cookie, а domain и path для cookie будут получены из указанного url, но с некоторыми неприятными нюансами.

Например, вы хотите установить cookie для адреса m.vk.com и предполагаете, что установленная cookie будет действительна и для адреса login.vk.com, но в нашем случае это не так. Для того, чтобы XWalkCookieManager адекватно отработал этот момент необходимо устанавливать cookie для адреса .vk.com.

Также заявлено, что XWalkCookieManager должен обновить cookie, если вы устанавливаете уже имеющуюся в хранилище, но я наблюдал, что иногда он дублирует значения. Очевидно какие-то проблемы в разборе value при установке. Поэтому стоит лишний раз проверить корректность работы этого классы, если вы пользуетесь им.

В качестве простого примера работы с XWalkCookieManager, в тестовом проекте реализован метод синхронизации с CookieManager.

Получение WebSettings и их установка.


Как я уже писал ранее, WebSettings не доступны при работе с XWalkView и имеют заранее предустановленные параметры. Точнее в Crosswalk имеется аналог этого класса — XWalkSettings, его методы вы можете изучить здесь. Для некоторых параметров было сделано исключение и они вынесены в интерфейс самого класса XWalkView. Например, сейчас вы можете установить нужный вам User-Agent.

Однако, может возникнуть необходимость более тонкой настройки. Также может возникнуть необходимость, как в моем случае, получить User-Agent, используемый самим XWalkView. В таком случае можно воспользоваться возможностью получить его через reflection.

Собственно сам класс XWalkView является надстройкой над реализацией XWalkViewInternal и соединяется с ней посредством bridge через reflection. XWalkViewInternal же имеет публичный метод getSettings(), что мы и можем использовать:
Method ___getBridge = XWalkView.class.getDeclaredMethod("getBridge");
___getBridge.setAccessible(true);
XWalkViewBridge xWalkViewBridge = null;
xWalkViewBridge = (XWalkViewBridge) ___getBridge.invoke(webView);
XWalkSettings xWalkSettings = xWalkViewBridge.getSettings();

В качестве примера получения XWalkSettings и работы с ним, в тестовом проекте реализован метод получения User-Agent.

Получение изображения XWalkView.


Поскольку XWalkView использует для отрисовки SurfaceView или TextureView, то нет возможности получить его изображение стандартными методами. Например, такой вариант работает для системного WebView, но не работает для XWalkView:
View view = mWebView.getRootView();
view.setDrawingCacheEnabled(true);
Bitmap b = Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);

Происходит это т.к. стандартные методы view, типа getDrawingCache() или draw(), работают с software layer, а в XWalkView используется hardware layer. Именно поэтому разработчики просят установить флаг android:hardwareAccelerated=«true» для приложений использующих Crosswalk. К слову, сейчас он устанавливается по дефолту и отдельно его прописывать нет необходимости.

Получить изображение XWalkView возможно при использовании в качестве основы TextureView, где имеется метод getBitmap(). В кратце, для этого необходимо найти в дереве целевой TextureView и уже у него вызвать этот метод. Пример реализации также доступен в тестовом проекте.

Мелкие нюансы.


В довесок несколько мелких моментов, которые вам также могут встретиться в процессе интеграции:
  • Если вы заменяете системный WebView в старом проекте и используете аннотацию JavascriptInterface, не забудьте исправить импорт. Иначе получите ошибки во время выполнения JavaScript кода. Соответственно:
    import android.webkit.JavascriptInterface; // remove
    import org.xwalk.core.JavascriptInterface; // add
    

  • Если вы используете несколько XWalkView, используете его во фрагментах или в каких-то других, более сложных чем в примере, условиях. В этом случае может возникнуть ситуация когда необходимо будет прямо вызывать методы для пробуждения таймеров XWalkView, после возобновления работы с ним:
    public void resumeTimers();
    public void onShow();
    



Выводы


Использовать Crosswalk в разработке достаточно просто и удобно, особенно учитывая открытый исходный код. Неплохое комьюнити и достаточно большое распространение проекта позволят решить большинство проблем.

Если вы поддерживаете Android 4.0+ и хотите более предсказуемой работы вашего кода на всех версиях Android, то я определенно рекомендую Crosswalk.

Если же вы поддерживаете Android с версии 4.4 и тем более 5.0, то я бы задумался о применении Crosswalk в своих проектах. Отсутствие некоторых новых возможностей системного WebView может осложнить вам жизнь.

Надеюсь мой опыт поможет вам определиться, что же использовать в качестве WebView для вашего проекта :).
Tags:
Hubs:
Total votes 12: ↑10 and ↓2+8
Comments2

Articles