
Использование возможностей веб-приложений в родном Android
Когда дело доходит до разработки мобильных приложений для Android или iOS, существует 3 разных подхода: разработка собственных приложений, разработка мобильных веб-приложений, более известных как прогрессивные веб-приложения (PWA), и разработка гибридных приложений. Среди этих трех вариантов разработка собственных приложений по-прежнему остается на первом месте, поскольку она обеспечивает полный доступ к аппаратному обеспечению устройства и его функциям, таким как камера, GPS, Bluetooth и т. Д., Тогда как прогрессивные веб-приложения - это веб-приложения, которые предоставляют нативное приложение, подобное приложению. пользовательский интерфейс для кроссплатформенных веб-приложений. В наши дни PWA становятся довольно распространенными. Они относительно просты в развертывании, обслуживании и обратной совместимости не требуется. Но есть и недостатки. Например: PWA не имеют полного доступа к оборудованию на вашем устройстве. Выбор между Progressive Web Apps и Native Android в конечном итоге зависит от требований приложения.
Однако может наступить время, когда вы захотите использовать существующее веб-приложение, чтобы избежать переписывания представлений на нативном языке. Интеграция веб-приложений в native осуществляется через класс Android Webview. Требование может варьироваться от простого рендеринга веб-приложения внутри нативного до двусторонней связи между ними. Для двусторонней связи поддержка Webview для Javascript и JavaScriptInterface становится очевидной, и это основная тема этого блога.

В этом блоге рассказывается больше о разработке собственных приложений для Android и о том, как мы можем подключать веб-компоненты внутри родных приложений для более быстрой итерации разработки. Он предназначен для людей, которые разрабатывают приложения в экосистеме Android
Следующий код описывает, как достичь двусторонней связи. Давайте разберемся с простыми сценариями и разберем каждый из них на примере.
Следующий код описывает, как достичь двусторонней связи. Давайте разберемся с простыми сценариями и разберем каждый из них на примере.
Сценарий 1. Загрузка нового URL-адреса в Webview при нажатии кнопки HTML с помощью прямого вызова JavascriptInterface.
index.html
Давайте сначала создадим образец веб-страницы. Вы можете добавить это в папку с активами в main.
<html>
<body>
<input type="button" onclick="NativeHandler.callApi()" style="height:100px; white-space: normal;text-align:left;" value="[JavaScript -> Java] Call api and load url" /><br/>
<p id="test" style="overflow-wrap:break-word">Demo App</p>
</body>
</html>
XML-макет
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.sequoia.android.activity.
WebviewActivity">
<WebView
android:id="@+id/common_web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
JavascriptInterface
class DemoJavaScriptInterface(val activity: BaseActivity) {
companion object {
private val TAG = DemoJavaScriptInterface::class.java.simpleName
}
@JavascriptInterface
fun callApi() {
Log.v(TAG, "call to native")
activity.runOnUiThread({ activity.callApi() })
}
}
WebviewActivity
class WebviewActivity : BaseActivity() {
companion object {
private val TAG = WebviewActivity::class.java.simpleName
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.webview_activity)
common_web_view.addJavascriptInterface(DemoJavaScriptInterface(this), "NativeHandler")
initWebViewSettingsAndClient("file:///android_asset/index.html")
}
@SuppressLint("SetJavaScriptEnabled")
private fun initWebViewSettingsAndClient(url: String) {
if (isDestroyed || isFinishing) return
val settings: WebSettings = common_web_view.getSettings()
settings.javaScriptEnabled = true
settings.javaScriptCanOpenWindowsAutomatically = true
common_web_view.setWebChromeClient(object : WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
Log.d(TAG, consoleMessage.message() + " -- From line "
+ consoleMessage.lineNumber() + " of "
+ consoleMessage.sourceId())
return super.onConsoleMessage(consoleMessage)
}
})
common_web_view.loadUrl(url)
}
override fun callApi() {
val url = "https://github.com"
loadHtmlInWebView(url)
}
@SuppressLint("SetJavaScriptEnabled")
private fun loadHtmlInWebView(url: String) {
common_web_view.loadUrl(url)
}
}
Здесь при нажатии кнопки в html создается вызов функции callAPi в JavascriptInterface, которую мы установили с помощью addJavascriptInterface для активного веб-просмотра. Нам также необходимо явно установить для javaScriptEnabled значение true в Webview.
Сценарий 2: загрузка нового URL-адреса в Webview при нажатии кнопки HTML с использованием косвенного вызова JavascriptInterface через функцию сценария в HTML.
index.html
<html>
<head>
<script type="text/javascript">
function testSync() {
NativeHandler.callApi();
}
</script>
</head>
<body>
<input type="button" onclick="testSync()" style="height:100px; white-space: normal;text-align:left;" value="[Sync JavaScript -> Java] Call api and load url" /><br/>
<p id="test" style="overflow-wrap:break-word">Demo App</p>
</body>
</html>
Здесь единственное, что изменилось, - это обработка кликов в html. Теперь мы вызываем функцию сценария для вызова метода callApi вместо того, чтобы вызывать его напрямую.
Сценарий 3: изменение данных в веб-просмотре - изменение текста при нажатии кнопки HTML.
index.html
К приведенному выше html просто добавьте следующую функцию скрипта
function setUrlInText(url) {
if(typeof(url) == 'string') {
document.getElementById("test").innerHTML = url;
console.log("setUrlInText function entered");
}
}
Эта функция будет вызываться Webview для установки нового текста в html.
WebviewActivity
Измените метод loadHtmlInWebView, чтобы вызвать новую функцию скрипта setUrlInText и передать новый URL-адрес в качестве параметра.
@SuppressLint("SetJavaScriptEnabled")
private fun loadHtmlInWebView(url: String) {
common_web_view.evaluateJavascript("setUrlInText('"+ url+"')", null)
}
Это установит новый текст в конкретный elementId. Здесь мы передаем null в обратном вызове. Сценарий 5 демонстрирует, как обрабатывать обратные вызовы javascript.
Сценарий 4: установите данные в собственном формате, передайте объект в Webview и отобразите его свойства в формате html.
UserDetails
Давайте создадим простой класс данных UserDetails со свойством name, email и phoneNumber.
data class UserDetails(val name: String, val email: String, val phoneNumber: String)
index.html
К приведенному выше html просто добавьте следующую функцию скрипта
function getData(userDetails) {
if(typeof(userDetails) == 'object') {
var name = userDetails.name || '';
var phoneNumber = userDetails.phoneNumber || '';
var email = userDetails.email || '';
document.getElementById("test").innerHTML = "name = " + name + ", " +
"email = " + email + ", " +
"phoneNumber = " + phoneNumber;
console.log("getData function exiting " + name + ", " + email + " , " + phoneNumber);
}
}
Это установит UserDetails в конкретный elementId. Здесь мы передаем null в обратном вызове.
XML-макет
В макете xml добавьте 3 поля для ввода данных и кнопку для отправки данных в Webview.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.sequoia.android.activity.
WebviewActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/etName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Enter Name"
android:inputType="textPersonName" />
<EditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Enter Email"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/etPhone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Enter Phone Number"
android:inputType="phone" />
<Button
android:id="@+id/sendData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="sendDataToWebview"
android:text="Send data to webview" />
</LinearLayout>
<WebView
android:id="@+id/common_web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</RelativeLayout>
WebviewActivity
В действии добавьте следующую функцию для обработки при нажатии собственной кнопки.
fun sendDataToWebview(view: View) {
val name = etName.text.toString()
val email = etEmail.text.toString()
val phone = etPhone.text.toString()
val userDetails = UserDetails(name, email, phone)
val strUserDetails: String = Gson().toJson(userDetails)
common_web_view.evaluateJavascript(
"getData($strUserDetails)", null
)
}
AssessmentJavascript используется для вызова функции html-скрипта getData и передает строковый объект в его параметры.
Сценарий 5. Измените данные, полученные в формате html, отправьте их обратно в собственный формат и отобразите.
index.html
Добавьте следующий код в функцию скрипта getData. Теперь мы вернем измененный объект UserDetails.
userDetails.name = 'Demo App'; userDetails.email = '[email protected]'; userDetails.phoneNumber = 789; return userDetails;
WebviewActivity
Добавьте следующий код в функцию sendDataToWebview.
common_web_view.evaluateJavascript(
"getData($strUserDetails)", object : ValueCallback<String> {
override fun onReceiveValue(value: String?) {
val userDetails = Gson().fromJson(value, UserDetails::class.java)
etName.setText(userDetails.name)
etEmail.setText(userDetails.email)
etPhone.setText(userDetails.phoneNumber)
Log.e(TAG, value)
}
})
Теперь мы передаем значение обратного вызова методу evalJavascript, и данные из функции скрипта принимаются в onReceiveValue. Мы используем этот объект для установки новых данных в собственном представлении.
В этом блоге мы рассмотрели несколько примеров, которые охватывают большую часть деталей реализации взаимодействия нативных приложений и веб-приложений. Рассмотренные здесь варианты использования были ограничены, но вы можете расширить их, чтобы охватить любые сложные требования, которые могут у вас возникнуть.
Вы можете просмотреть полный код этой статьи здесь.
Не стесняйтесь обращаться ко мне, если у вас есть какие-либо вопросы или отзывы об этом посте - [email protected]
Вот мой профиль в LinkedIn.