Html javascript to apk

JavaScript to APK. Подводные камни разработки под Android для тех, кого задолбал PhoneGap. Построение мостов из Java в JavaScript

Я люблю игры на JavaScript и стараюсь сделать их код пуленепробиваемыми для портирования на все платформы. Полгода назад я уже писал о сборке Android приложений и сегодня хотел бы раскрыть тему более подробно.

Сразу предупрежу, что мне пришлось отказаться от PhoneGap, т.к. опыт использования его в двух проектах меня огорчил. Он отлично справляется с «Hello World» приложениями, но при конвейерной сборке всего подряд всплывают нюансы.

Почему PhoneGap не пошел:
1. Он изначально пустой. Постоянно приходится подключать все новые и новые модули.
2. Многие модули написаны криво. Они либо берут много лишнего, либо ведут себя неожиданно. Например, из двух модулей под Android для отправки SMS, один не работал, второй — отправлял true при любых условиях.
3. Не решены элементарные вещи, вроде получения EMEI телефона. Нужно постоянно допиливать.

Я так и не понял сути PhoneGap. Изначально ожидал одну кнопку «сделать хорошо», а не деле он ничего не делает. Под каждую платформу мне все равно надо ставить SDK. Под каждую задачу — искать и ставить модуль. Сами модули тоже ограничены. Они могут делать что-то только под часть платформ, а если нужно под другие — то приходится снова искать модули, которые смогу сделать это на других девайсах. Много мусора и ненужных вещей, а ведь хочется собирать билды с минимумом затрат. Все эти факторы заставляют писать нативно. И вот тут начинают вылезать подводные камни.

Читайте также:  Non printable characters python

Почему CocoonJS не пошел:
С CocoonJS работал мало, поэтому никаких особых вопросов не возникло. Билды с canvas действительно работают быстрее. Но в общем — смысла работать с CocoonJS не увидел, т.к. он платный.

Что касается сборки под другие платформы и прочие нюансы — про это будет отдельная статья, и дальнейшее обсуждение по этой теме или теме PhoneGap выходит за рамки этой.

Перейдем к сути

Для начала начнем с основы — WebView с запущенной HTML страничкой на весь экран. В onCreate MainActivity пишем:

vw = (WebView) findViewById(R.id.webview); vw.setVerticalScrollBarEnabled(false); // отключили прокрутку vw.setHorizontalScrollBarEnabled(false); // отключили прокрутку vw.getSettings().setJavaScriptEnabled(true); // включили JavaScript vw.getSettings().setDomStorageEnabled(true); // включили localStorage и т.п. vw.getSettings().setSupportZoom(false); // отключили зум, т.к. нормальные приложения подобным функционалом не обладают vw.getSettings().setSupportMultipleWindows(false); // отключили поддержку вкладок. // Т.к. пользователь должен сидеть в SPA приложении vw.addJavascriptInterface(new WebAppInterface(this), "API"); // прокидываем объект в JavaScript. // Это будет наш мост в мир Java. В JavaScript`е создается объект API vw.loadUrl("file:///android_asset/index.html"); // загрузили нашу страничку vw.setWebViewClient(new WebViewClient()); 

Все спорные ситуации будем решать в Java. Помните, пишите вы для Bada или SmartTV — всегда есть какой-то стандартный функционал, который позволяет кидать мосты в JavaScript. В нашем случае для Android`а мы кинули экземпляр класса WebAppInterface, а сам класс будет выглядеть так:

public class WebAppInterface < Context mContext; /** Instantiate the interface and set the context */ WebAppInterface(Context c) < mContext = c; >/** Далее идут методы, которые появятся в JavaScript */ @JavascriptInterface public void sendSms(String phoneNumber, String message) < . какой-то нативный код >> 

Подводный камень: Работа с такими мостами обычно может быть асинхронна или непредсказуема и полна сюрпризов.

Если у вас возникла необходимость из Java сообщить JavaScript`у какое-либо событие на ровном месте, самый простой способ достучаться до него — это стучать в URL:

vw.loadUrl("javascript: . какой-либо код на JavaScript"); 

Подводный камень: В Android`ах > 4 от Samsung`а при тач-событии DOM элементы могут подсвечиваться синим цветом.

Обратите внимание на этот нюанс. Типичная «защита» вам не поможет:

Чтобы обойти багу, надо добавить:

if (document.addEventListener) < document.addEventListener("touchstart", function () < >, true); > 

Но это не всегда решает проблему. Возможно, на проблему влияет сама верстка. Например, возьмем два приложения: Судоку и Тест. В судоку доска сверстана таблицей, и для таблицы такое решение помогло. В Тесте же кнопки это. Вроде бы все по стандартам семантики HTML5, и все должно быть более чем прекрасно, но на деле приходиться добивать таким CSS комбо:

.some_button:focus, .some_button:focus:active

Так же заметил, что синее выделение не появляется, если тач-событие пришлось точно на текст кнопки (текст при этом должен быть очень большим).

Способ борьбы с багом — либо делать проверки, либо отключить шрифты. Хотя, с другой стороны, возможно, у меня были сами шрифты кривые.

Подводный камень: Android чувствителен к регистру.

Если у вас среди кучи ресурсов .jpg затерялась картинка с .JPG — вы вряд ли когда-либо заметите разницу в браузере, а вот в WebView картинка не загрузится.

Подводный камень: Android чувствителен к зарезервированным словам.

Например, у меня была в assets`ах папка с именем classijizm. Android отказывался собирать проект и не мог внятно объяснить ошибку. Переименовал в klassijizm — заработало. Опять же, в обычном браузере того-же Android`а таких проблем не было.

Подводный камень: Тег audio на Android не работает.

Точнее, он работает в браузере, но не работает, когда вы используете его внутри WebView. Чтобы обойти это ограничение, можно прокинуть мост и переписать на нативном коде. В onCreate добавляем:

А для WebView расширяем JavaScript интерфейс:

@JavascriptInterface public void audio(String url) < try < soundClick = getAssets().openFd(url); mp.reset(); mp.setDataSource(soundClick.getFileDescriptor(), soundClick.getStartOffset(), soundClick.getLength()); mp.prepare(); mp.start(); >catch (IOException e) < e.printStackTrace(); >> 

Подводный камень: Вместо закрытия Android сворачивает приложения. Поэтому, если вы используете Аудио — вам нужно как-минимум отключить звук.

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

@Override public void onBackPressed() < vw.loadUrl("javascript: windowClose();"); MainActivity.this.finish(); >@Override public void onPause() < super.onPause(); vw.loadUrl("javascript: windowClose();"); MainActivity.this.finish(); >@Override public void onResume() < super.onResume(); vw.loadUrl("javascript: windowOpen();"); >@Override public void onDestroy()

Командой MainActivity.this.finish(); я пытаюсь закрыть приложение при каждом удобном случае. Так можно быть более уверенным, что Android в следующий раз просто начнет все с начала, а не будет пытаться что-либо восстановить. Понятно, что в играх типа Судоку так делать нельзя, но в большинстве игр — можно, т.к. они достаточно просты (тот же FlappyBirds или Тесты). Советую опасаться попыток Android вернуть все как было, т.к. появляются другие баги.

Подводный камень: Android при onResume не всегда удачно восстанавливает приложения. Да и вообще на некоторых девайсах есть проблемы с повторным запуском.

Например, он может остановить таймеры или чудным образом не среагировать на resize. Поэтому в любой непонятной ситуации вызывайте resize и перепроверяйте таймеры.

Подводный камень: при сворачивание/открытии приложения может возникнуть несколько WebView, которые будут работать параллельно и мешать друг другу.

Чтобы наверняка уйти от такой проблемы, допишем в манифест:

Чтобы наше приложение на JavaScript выглядело ещё лучше, его можно запустить во весь экран, убрав черную полоску сверху. Для этого в манифест нужно добавить:

Подводный камень: localStorage, в который вы сохраняете данные будет уничтожен после закрытия приложения.

Чтобы сохранить данные между двумя вызовами, нужно сохранить данные в нативном SharedPreferences. Прокинем два моста на сохранение и загрузку:

@JavascriptInterface public void saveSomeThing(String message, String id) < if(numberDataForSave >Integer.parseInt(id)) return; numberDataForSave = Integer.parseInt(id); SharedPreferences preferences = getSharedPreferences("com.example.something", MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putString("somethingID", message); editor.commit(); > @JavascriptInterface public String loadSomeThing()

Подводный камень: Методы работают асинхронно (или мне показалось?!). Если вызывать сохранение очень часто, то данные могут прийти не в том порядке.

На деле бага будет выглядеть так — сохранилась не последняя строка, а предыдущая. Чтобы решить проблему, номеруем данные в JavaScript и нативном коде. В методе для сохранения есть переменная numberDataForSave, которая проверяет индекс сохраняемых данных. Если индекс меньше, чем последний сохраненный, данные игнорируются.

Подводный камень: В layout`е главного activity обычно много лишнего.

Для одного WebView во весь экран нам столько не надо. Можно сократить, оставив:

Подводный камень: Отправили смс без сим-карты. СМСка не ушла, а callback вернул true.

Если вы используете PhoneGap, возможно, модуль отправки СМС под Android написан криво (во всяком случае лично я столкнулся с такой проблемой). Он возвращает true при любом исходе. Чтобы реализовать это нормально, прокинем мост для отправки СМС с Java в JavaScript:

@JavascriptInterface public void sendSms(String phoneNumber, String message)

И добавим метод в класс MainActivity:

private PendingIntent registerSentSmsReceiver() < String SENT = "SMS_SENT"; PendingIntent sentPI = PendingIntent.getBroadcast(MainActivity.this, 0, new Intent(SENT), 0); sendSmsReceiver = new BroadcastReceiver() < public void onReceive(Context arg0, Intent arg1) < switch (getResultCode()) < case Activity.RESULT_OK: vw.loadUrl("javascript: smsSend(true);"); break; default: vw.loadUrl("javascript: smsSend(false);"); break; >> >; registerReceiver(sendSmsReceiver, new IntentFilter(SENT)); return sentPI; > 

Чтобы приложение не вылетало с новым классом, нужно обновить onDestroy:

@Override public void onDestroy() < super.onDestroy(); if (sendSmsReceiver != null) < unregisterReceiver(sendSmsReceiver); >> 

Помните, sendSmsReceiver всегда нужно разрегистрировать при дестрое.

Подводный камень: На Android`ах от Samsung`а WebView тормозит на тач-событиях. Более того, он может вообще остановить отрисовку при зажатом пальце.

От этой баги никуда не уйти. Так уж его сделали. Это стало одной из причин для продвижения CocoonJS. При желании вы можете переписать элемент canvas на нативный (используя все те же мосты), но это уже трэш. Лучше писать сразу все на Java. Но, тем не менее, собрать APK файл все-таки имеет смысл, т.к. кроме Samsung`а есть ещё куча телефонов от других производителей, и там все может быть не так плохо. Ну а в том же Tizen`е в этом плане вообще сказка.

Заранее прошу прощения за кривые моменты на Java, т.к. все-таки мой профиль JavaScript. Ну, и демка русская и английская из статьи, если кому интересно будет.

Источник

HTML TO APP

Convert your HTML / JS / CSS files into a mobile App for Android &iOS.

Google Play Store Ready

Publish your App to the Google Play Store and to any other APK store.

Detect Installs

Web To App - App Maker

The App Maker of WebIntoApp.com also allows you to convert your HTML / Javascript / CSS project files into a mobile App for Android and iOS, online.

Any App that made with HTML / JS / CSS that can work on your local device can be used as a stand alone App for Android & iOS. Just upload your project files under a zip file and hit the Make App button, the App Maker will add your files to a WebView App with all the Extra Features.

You can use the Extra Features section of our App Maker in order to set the Icon, the Ownership and the look and the behavior of your App.

The App Maker supports the Firebase SDK by default, you can easily add your JSON / PLIST settings then use the Messaging (Push Notifications) and the Analytics services of Firebase from your App.

You can convert your HTML / JS / CSS project file into a mobile App for Android for Free and test the results, upgrading to a dedicated App can be done at any time in the future.

Follow the next steps in order to convert your HTML / JS / CSS into a mobile App:

HTML To App ZIP

The HTML/CSS/JS project files should be under the main directory of a ZIP file, while the index.html file is the entry point of your App.

HTML To App Details

Set the App details, such as the Icon, the Ownership, the Splash Screen and more, then click on the Next Button.

HTML To App Maker

Make sure all the details of the App are correct, set the mode of your App (Free or Dedicated) then click on the Make App button. The App Maker will build your App online, this process may take up to 1 minute.

Your App is ready! You can download and install it on your local device and publish it to the Google Play Store (Free App) and to the Apple App Store (Dedicated App).

Источник

Оцените статью