Мы живем в эру мобильных устройств. Однажды нам в голову приходит идея создать супер-мега приложение для мобильных устройств и разместить его в магазине приложений. Однако, тут возникает одна небольшая проблема: мы всего лишь веб-разработчики. Мы не разбираемся в языках Java и Objective-C, и не располагаем лишним временем для их изучения.
Сегодня у нас появилась возможность воплотить свои мечты в реальность и улучшить мир, в котором мы живем, с помощью наших супер-мега приложений.
В этой статье мы опишем процесс создания гибридного приложения для операционной системы Андроид.
Гибридное приложение сочетает в себе свойства как нативных, так и веб-приложений. Как нативное приложение, оно может распространяться среди пользователей через магазин приложений, а также пользоваться преимуществами многочисленных функций мобильных устройств. Как веб-приложение, оно состоит из HTML, CSS и Javascript файлов.
Преимущества данного типа приложений заключаются в следующем:
- почти все приложения могут быть написаны на языке JavaScript для всех мобильных платформ;
- они могут использовать такие функции мобильного устройства, как камера, акселерометр, и др.;
- все HTML, CSS и JavaScript файлы можно обновить, не ожидая утверждения новой версии приложения.
Однако, гибридное приложение все же работает медленнее нативного. Но это не такая большая проблема, потому что самую медленную часть приложения можно написать на Java или Objective-C и добавить при следующем обновлении.
Давайте приступим к созданию приложения, а заодно и к изменению мира к лучшему!
Шаг первый. Готовимся захватить мир
Итак, давайте приступим к воплощению нашей идеи и создадим наше первое приложение для андроид. Прежде всего, необходимо скачать Android SDK и другие инструменты для эмуляции различных Android-устройств либо драйверы для тестирования приложения на реальном устройстве. Это совсем несложно: просто загрузите пакет . Здесь вы найдете IDE Eclipse с уже встроенным расширением ADT, Android SDK, SDK Manager и другие.
Теперь, когда у вас есть все эти файлы, запустите файл SDK Manager.exe (для ОС Mac или Linux откройте в командной строке директорию «tools/», которая находится в скачанном пакете Android SDK, затем выполните команду android sdk) и установите следующие инструменты:
- SDK Tools. Этот пакет уже должен быть установлен, если же нет, то вы знаете, что делать;
- SDK Platform-tools;
- SDK для одной из версий Android. Ваше новое Android-приложение должно быть скомпилировано под какую-либо версию ОС Android. Мы использовали самую последнюю версию Android на момент написания статьи, чтобы иметь возможность использовать новые функции;
- Драйверы. Если вы хотите протестировать свое приложение на андроид-устройстве, необходимо установить Web и USB-драйверы (если у вас возникают проблемы с отладкой приложений на реальных устройствах, можете поискать подсказки в этой статье или задать нам вопрос в комментариях).
Отлично! Мы закончили с самой скучной частью создания приложений для андроид. Давайте запустим Eclipse IDE, загруженную вместе с SDK, и начнем изменять мир. Кроме того, если хотите, можете загрузить Android Studio (IDE на базе IntelliJ IDEA). Должны вас предупредить, что она все еще работает в бета-версии и может доставить дополнительные неудобства.
Шаг второй. Создаем приложение
Основой всех гибридных приложений является элемент WebView. Данный элемент отображает веб-страницы с помощью WebKit-движка. Это значит, что вы можете создать обычный HTML-файл, содержащий <style> или <link> теги для подключения CSS, и <script> теги для добавления JS-кода, а затем передать его в элемент WebView. В результате получится практически такое же изображение, как если бы вы открыли данную страницу в браузере (сравните изображения ниже).
Как вы уже догадались, самый простой способ создать гибридное приложение для андроид — просто открыть существующий веб-сайт в элементе WebView. Нам просто нужно его создать, развернуть на весь экран, и передать ему URL веб-сайта. Давайте так и сделаем.
Для начала, откройте уже загруженный Eclipse и создайте новый Android Application Project. Задайте ему имя и выберите версию Android, для которой хотите скомпилировать свое приложение. Кроме того, вы можете создать иконку для своего нового приложения, а также activity. В данном случае под activity понимается набор элементов пользовательского интерфейса, занимающих весь экран и необходимых для выполнения какого-либо действия (но, для этого примера вам не нужно что-либо здесь менять). Когда вы кликните на кнопку Finish, ваше первое приложение для Android будет создано.
Opened in Chrome Browser | Opened in WebView |
IDE автоматически создает множество файлов. Давайте посмотрим на некоторые из них:
1) AndroidManifest.xml – этот файл содержит информацию о вашем приложении и обо всем, что оно умеет делать. ОС Android считывает этот файл при установке и запуске приложения. Необходимо добавить в этот файл всю информацию о функциях андроид-устройств, которые вы бы хотели использовать (камера, список контактов и т.д.). Кроме того, в данном манифесте также указываются все пользовательские (UI) темы, контроллеры, а также фоновые сервисы. Получить детальную информацию можно здесь;
2) res/layout{qualifier}/{name_of_your_activity}.xml — этот файл содержит описание всех UI-элементов, которые вы собираетесь использовать для того или иного activity. Для нашего гибридного приложения необходимо всего одно activity и один UI-элемент (WebView). Если вы хотите добавить какие-либо нативные кнопки Android, они должны быть описаны в данном файле;
3) src — в этой папке размещаются все ваши Java-файлы;
4) res/drawable{qualifier} — содержит все ваши графические файлы;
5) bin — здесь находятся все ваши скомпилированные файлы;
6) assets — содержит все типы файлов, к которым вы хотите иметь доступ. Это могут быть статичные HTML-файлы или какие-либо CSS или JS-файлы, которые будут добавлены в .apk вашего приложения.
Существуют некоторые сложности добавления всех файлов в папку assets. Одна из них состоит в том, что когда нужно внести какие либо-изменения или исправить ошибки, необходимо обновить свое приложение через магазин приложений, поскольку вам придется компилировать новый .apk файл. Это значит, что нужно будет ожидать подтверждения, и пр. Однако, если вы получаете все файлы со своего веб-сервера, все ресурсы (HTML,CSS, JS) можно изменить сразу же, если они протестированы должным образом.
Теперь мы знаем все, что нам нужно для создания простого гибридного приложения. Прежде всего, мы должны изменить файл лeйаута (res/layout/{name_of_your_activity}.xml). Давайте добавим WebView элемент и растянем его на весь экран:
1 2 3 4 5 6 7 8 9 10 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <WebView android:id="@+id/WebView" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout> |
Нам не нужен относительный лeйаут, потому что у нас есть только один элемент. Поэтому мы используем простой линейный лейаут. Как видите, мы объявили элемент WebView с id — “WebView”. Мы будем использовать этот id, чтобы иметь возможность обратиться к элементу WebView в дальнейшем. Символ “+” означает, что данный id будет задан в пространстве имен вашего приложения.
Следующий шаг — изменение файла AndroidManifest.xml. Нам необходим доступ в интернет для того, чтобы загрузить файлы с нашего веб-сервера. Добавьте следующую строку в файл манифеста перед тегом “application“:
1 | <uses-permission android:name="android.permission.INTERNET" /> |
А теперь самая приятная часть: пишем код. Откройте Java-файл, который содержит класс вашего activity (путь должен быть указан как “src/com.example.your_project_name}/{your_main_activity_name}.java”) и внесите в код некоторые изменения. Он должен выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package com.example.xbsoftware; import android.os.Bundle; import android.app.Activity; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.webkit.*; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* разворачиваем приложение на весь экран */ requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); /* применяем наш лейаут к текущему экрану */ setContentView(R.layout.activity_main); /* находим WebView элемент по его id */ WebView webView = (WebView) findViewById(R.id.WebView); /* создаем новые настройки для нашего WebView элемента */ WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); webView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); /* здесь вы можете поместить URL вашего сайта */ webView.loadUrl("http://xbsoftware.com/"); } } |
Будьте внимательны, копируя данный код! Имя пакета (“com.example.xbsoftware”) и имя класса (“MainActivity”) для вашего приложения могут отличаться.
Отлично! Работа завершена, и мы можем запустить наше первое приложение для ОС Android. Просто нажмите “Run”. Здорово, правда?
Улучшаем приложение. Классы WebViewClient и WebChromeClient
Существуют два класса, о которых вам нужно знать. Первый — это WebViewClient. Данный класс отвечает за все, что связано с отрисовкой страницы. У него есть методы для обработки ошибок, получения http-запросов, загрузки страницы и др. Все эти методы можно перегрузить. Это значит, что можно добавить какую-то логику на каждое из этих событий. Полный список поддерживаемых методов для данного класса вы можете найти здесь.
Как видите, если кликнуть на какую-либо внешнюю ссылку в вашем приложении, откроется браузер Google Chrome. Для того, чтобы это исправить, мы перегрузим два метода класса WebViewClient: метод shouldOverrideUrlLoading и метод onReceivedError. Первый метод будет вызываться при изменении URL. Второй — когда вместо контента страницы вы получите сообщение об ошибке. Нам нужно создать новый класс, который расширит WebViewClient, и пометить все перегруженные методы атрибутом ”@Override“.
Мы должны перезагрузить второй метод, поскольку нам может понадобиться показать нашу собственную страницу с сообщением об ошибке, если какие-то ресурсы невозможно загрузить. Поэтому, когда мы находим ошибку, то прекращаем дальнейшую загрузку, и открываем нашу страницу для обработки ошибок из папки “assets”.
Если вы хотите открывать внутренние страницы в WebView, необходимо изменить параметр webSettings. Просто добавьте эту строку туда, где вы создали объект webSettings:
1 | webSettings.setAllowFileAccess(true); |
Теперь можно добавить файлы в папку “assets” и использовать их на своей странице. Мы создали простой файл для того, чтобы показать ошибки, и расположили его прямо в папке “assets”:
А к уже существующему элементу WebView необходимо добавить новый объект WebViewClient, содержащий следующий код:
1 | webView.setWebViewClient(new MyClient()); |
Обратимся к примеру:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class MyClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { /* откроем новую веб-страницу в webview */ view.loadUrl(url); return true; } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { /*остановка загрузки и отображение страницы error.html из папки “assets”*/ view.stopLoading(); view.loadUrl(String.format("file:///android_asset/error.html?code=%s&description=%s&url=%s", Uri.encode(String.valueOf(errorCode)), Uri.encode(description), Uri.encode(failingUrl))); } } |
error.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <!DOCTYPE html> <html> <head> <title>Achtung!</title> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <div id="error"> <div><span>Code:</span><span id="code"></span></div> <div><span>Description:</span><span id="description"></span></div> <div><span>Url:</span><span id="url"></span></div> </div> </body> <script type="text/javascript"> var query = window.location.search.substring(1); query.split("&").forEach(function(param){ var params = param.split("="); document.getElementById(params[0]).textContent = params[1]; }); </script> </html> |
Если вы замените URL в параметре loadUrl на неверный, откроется страница, сообщающая об ошибке.
Второй класс — WebChromeClient — отвечает за все, что связано с UI браузера. С его помощью можно управлять историей посещений, создавать новые окна, работать с сообщениями и иконками. Все методы данного класса, которые можно перезагрузить, описаны здесь.
Давайте для примера заменим все диалоговые окна, содержащие сообщения, нашими собственными. Нам необходимо расширить класс WebChromeClient и перезагрузить метод onJsAlert. Посмотрим на пример:
1 2 3 4 5 6 7 | class MyWebChromeClient extends WebChromeClient { @Override public boolean onJsAlert(WebView webView, String url, String message, JsResult result) { new AlertDialog.Builder(MainActivity.this).setTitle("hello").setMessage("hello").create().show(); return true; } } |
Так же, как и в случае с WebViewClient, вам нужно добавить объект MyWebChromeClient к существующему элементу WebView:
1 | webView.setWebChromeClient(new MyWebChromeClient()); |
Шаг два с половиной. Связывание Java и JavaScript
Процесс разработки гибридных приложений не был бы таким увлекательным, если бы не могли вызывать Java-методы из JavaScript. Для связывания методов некоторых объектов Java с JavaScript нам необходимо использовать модуль JavascriptInterface. Чтобы показать его использование на примере, мы создадим класс “MyJavaScriptInterface”, у которого будет один метод под названием “make“. Кроме того, если версией андроид, для которой собирается проект, является версия SDK от 17 и выше, все методы, доступные через JavaScript, должны иметь примечание @JavaScriptInterface.
Посмотрим на пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package com.example.xbsoftware; import android.content.Context; import android.webkit.JavascriptInterface; import android.webkit.WebView; public class MyJavaScriptInterface { private WebView webView; private Context context; public MyJavaScriptInterface(WebView currentWebView, Context currentContext){ webView = currentWebView; context = currentContext; } @JavascriptInterface public String make() throws Exception { java.lang.Thread.sleep(10000); return "Hello from Java"; } } |
Кроме того, вам нужно использовать следующий код, чтобы добавить данный метод в существующий элемент WebView:
1 2 3 | MyJavaScriptInterface myInterface = new MyJavaScriptInterface(webView, MainActivity.this); /*новое свойство объекта окна будет называться MyInterface, и будет включать в себя функцию “make” */ webView.addJavascriptInterface(myInterface, "MyInterface"); |
Теперь метод “make” будет доступен через JavaScript:
1 | window.MyInterface.make(); |
Это достаточно простой пример использования такого многофункционального инструмента. Вы также можете сделать возможным взаимодействие JavaScript из WebView со всеми модулями мобильных устройств, такими как камера, список контактов, WiFi и другими.
Если вы запустите данный пример, то увидите, что метод “make” — это синхронный метод. Как вы знаете, JavaScript — однопоточный язык. Это значит, что если вы выполняете блок JS кода на странице, никакой другой JS код на данной странице в настоящий момент выполняться не будет. Для нашего примера это означает, что другой JS код не может быть вызван, пока не закончится выполнение функции “make” (он будет ожидать в течение десяти секунд, поскольку Java-поток, который выполняет код, связанный с данной функцией, будет находится в спящем режиме в течение этого времени). Чтобы решить эту проблему, вам нужно создать AsyncTask в Java, и выполнить там данный код. Когда все будет готово, вызовите какую-нибудь глобальную функцию в JavaScript, и передайте ей результаты. Но будьте внимательны: все методы WebView могут быть вызваны только в UI потоке! Это означает, что вы не можете вызвать метод loadUrl WebView в методе doInBackground, потому что это — другой поток.
Посмотрим на пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | package com.example.xbsoftware; import android.content.Context; import android.os.AsyncTask; import android.webkit.JavascriptInterface; import android.webkit.WebView; public class MyJavaScriptInterface { private WebView webView; /*класс для обработки данных в другом потоке*/ private class MyTask extends AsyncTask<Void, Void, String> { private String callback; private WebView webView; public MyTask(String callbackMethod, WebView newVebView){ callback = callbackMethod; webView = newVebView; } /* ЗДЕСЬ вы можете добавить свои вычисления*/ @Override protected String doInBackground(Void... smth) { String response = "Hello from Java"; try { java.lang.Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return response; } @Override protected void onProgressUpdate(Void... progress) { } /* данный метод будет вызван, когда все вычисления в методе doInBackground будут завершены */ @Override protected void onPostExecute(String response) { try { webView.loadUrl("javascript:window." + callback + "('" + response + "')"); } catch (Exception e) { e.printStackTrace(); } } } public MyJavaScriptInterface(WebView currentWebView, Context currentContext){ webView = currentWebView; } /* метод, доступный через JavaScript*/ @JavascriptInterface public void make(final String callbackName) throws Exception { new MyTask(callbackName, webView).execute(); } } |
HTML файл для вызова метода из Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <html> <head> <title>hello world</title> </head> <body> <div id="parent">do smth</div> <button id="ClickIt">Click me</button> </body> <script type="text/javascript"> var parent = document.getElementById("parent"); document.getElementById("ClickIt").addEventListener("click", function(){ var funcName = "globalFunction" + Math.floor(Math.random()*100); window[funcName] = function(response){ delete window[funcName]; parent.textContent = response; }; window.MyInterface.make(funcName); parent.textContent = "waiting..........."; }, false); </script> </html> |
Как видите, для вызова каких-либо JS функций из Java не существует прямого метода. Поэтому, нам придется создать временную глобальную функцию для получения результатов. Нам нужно использовать метод webView.loadUrl(“javascript:{your js code here}”) для вызова некоторых JS функций.
Отладка
Как вы уже заметили, весь Java код можно легко отладить в IDE. Но как же поступить с JS кодом? Конечно, его можно отладить отдельно. Просто откройте страницу в браузере Chrome своего устройства, и включите удаленную отладку в том же браузере своего компьютера. Но иногда возникает необходимость отладить все эти части вместе.
Сейчас самое время сказать спасибо за новый Android 4.4 KitKat с его новым WebView. Если у вас он есть, все что нужно сделать, это добавить следующую строку кода:
1 | WebView.setWebContentsDebuggingEnabled(true); |
После запуска вашего приложения (конечно, если вы тестируете его на реальном устройстве, оно должно быть подключено к компьютеру, на котором приложение разрабатывается с помощью USB-кабеля) вам нужно ввести в адресной строке следующий URL: chrome://inspect/#devices. Затем, выберите ваше устройство и кликните по ссылке “inspect”:
Вы можете найти дополнительную информацию на сайте разработчиков Google Android, а также почитать интересную и полезную книгу, которая вдохновила нас на создание данной статьи.
Благодарим за внимание и ждем ваших комментариев.