Использование возможностей веб-приложений в родном 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.