Uruchomienie kodu JavaScript w aplikacji na system Android

Javascript to potężny język skryptowy, który jest bardzo popularny. Istnieje wiele świetnych bibliotek, które nie są przepisane do Javy lub Kotlina. Oczywiście możemy tworzyć aplikacje w xHTML, React Native, ale jest to bardzo ograniczona forma. Zastanawiałeś się jak możemy uruchomić kod JavaScript w aplikacji na system Android? Jeśli tak to ten wpis da Ci odpowiedź.

Silniki JavaScript dla Javy i Androida

Używanie kodu JavaScript na platformach, które natywnie używają języków kompilowanych, takich jak iOS i Android, może być kłopotliwe. Deweloperzy z nadgryzionym jabłkiem mają łatwiej, ponieważ posiadają silnik JavaScriptCore. W przypadku platformy Android nie jest tak kolorowo. Może kiedyś…

Na szczęście istnieje kilka projektów open-source, które próbują rozwiązać ten problem. Możemy wyróżnić takie biblioteki jak:

  • duktape
  • QuickJS
  • quack
  • Rhino
  • JS Evaluator for Android
  • AndroidJSCore
  • J2V8
  • LiquidCore

Część z nich jest już nie wspierana, ale działają. Drugą wadą jest to, że niektóre, mogą mieć problem z obsługą dużej ilości kodu. Warto też wspomnieć, ze to silniki języka ECMAScript, nie pełnoprawnego JavaScript, jakiego znamy z przeglądarek. I tak na przykład, Duktape jest zgodny z ES5.0 / ES5.1, a dla Rhino możesz sprawdzić kompatybilność tutaj. Dlatego nie uświadczysz takich dodatków jak: obiektu window lub parsera DOM, które są wbudowane w przeglądarkę. Ciekawym rozwiązaniem jest projekt LiquidCore, który może uruchomić kod z node.js. Brzmi ciekawie? Niestety muszę Cię zmartwić, nie wszystko działa tak jak powinno i niektóre biblioteki node powodują błędy. Warto wspomnieć o tym, że takie biblioteki mają też swoją wagę.

Nie będę opisywał każdej biblioteki osobno. Nie jest to celem artykułu. Natomiast tutaj możesz zobaczyć porównanie niektórych projektów z powyższej listy. Tekst nie jest nowy, ale można wyciągnąć pewne wnioski.

Oczywiście powyższa lista nie jest zamknięta, aby uruchomić kod JavaScript w aplikacji na system Android. Jeżeli powyższe biblioteki nie spełniają naszych wymagań, czy mamy coś innego?

Uruchomienie kodu JavaScript za pomoca Webview

Android posiada WebView, który pozwala przeglądać strony internetowe. Webview jest oparty na projekcie Chromium i jest aktualizowany niezależnie od systemu. Użytkownik powinien mieć zawsze aktualną wersję przeglądarki oraz wersję silnika JavaScript V8.

Klasa WebView jest rozszerzeniem klasy View, Skoro może otwierać strony internetowe to i może wykonać kod JavaScript w aplikacji Androida. Po raz kolejny muszę Cię zmartwić. Uruchomienie kodu js nie stanowi problemu, ale nie w usłudze, workmanagerze – czyli w zadaniach, które działają w tle.

JavaScript w WebView jest domyślnie wyłączony. Możesz go włączyć za pomocą metody setJavaScriptEnabled().

val webView = WebView(this)
webView.settings.javaScriptEnabled = true

WebSettings zapewnia dostęp do wielu innych ustawień, które mogą okazać się przydatne. Nie ma potrzeby dodawania widzetu to layoutu. Nie musisz wyświetlać strony internetowej, aby uruchomić kod js. Wykonanie kodu JavaScript w Androidzie możesz wykonać na dwa sposoby. Pierwszy z nich to załadownie pliku HTML.

webView.loadUrl("file:///android_asset/runjs.html");
<!DOCTYPE html>
<html>
<head>
<script>
function hello()
{
    console.log("Hello JS");
}
</script>
<script src="fun.js"></script>
</head>
<body>
</body>
</html>

Plik z kodem strony możemy umieścić w folderze assets. Kod JavaScript możemy umieścić bezpośrednio w kodzie HTML lub dołączyć plik js lokalnie albo z zewnętrznego źródła. Oczywiście stronę www możemy załadować z internetu, z własnej strony. Innym sposobem dostarczenia strony internetowej lub kodu JavaScript do WebView jest skorzystanie z metody loadDataWithBaseURL() lub loadData().

Komunikacja między JavaScriptem a Androidem w WebView

Powyższy kod zwróci nam oczywiście „Hello JS” w konsoli, a to raczej nam się nie przyda. Możesz skorzystać z klasy WebChromeClient, aby słuchać wywołań JavaScript, takich jak: powiadomień o bieżącej stronie, komunikaty konsoli, ostrzeżenia, postęp odświeżania strony i inne wywołania JavaScript.

Drugi sposób na wykonanie kodu JavaScript, a jednocześnie na komunikację z Androidem to skorzystanie z interfejsów. Spójrz na kod.

 val listen = ValueCallback<String> { result ->
            Log.d("TAG response from js", result)
        } 
webView.evaluateJavascript("javascript:getHello();", listen)
function getHello()
{
    return "Hello JS";
}
info

Pamiętaj, aby kod JavaScript wykonać dopiero po załadowaniu strony. W tym celu skorzystaj z metody onPageFinished() interfejsu WebViewClient, aby mieć pewność, że treść strony została załadowana i jest gotowa do użycia.

webView.webViewClient = object : WebViewClient() {
    override fun onPageFinished(view: WebView, url: String) {
        webView.evaluateJavascript("javascript:getHello();", listen)               
 }
}

WebViewClient umożliwia na słuchanie zdarzeń ze strony internetowej, na przykład, gdy zaczyna się ładować, wystąpił błąd związany z ładowaniem strony, przesyłaniem formularzy, linkami i innymi zdarzeniami. W powyższym przykładzie nie trudno się domyślić, że zostanie zwrócone „Hello JS”. Interfejs zawsze będzie nam zwracał Stringa. Jeżeli będziesz potrzebował zwrócić obiekt, to czytaj dalej.

Oczywiście możemy stworzyć interfejs, w którym to kod JavaScript wywoła odpowiednią funkcję w kodzie aplikacji.

class WebAppInterface(private val mContext: Context) {
    @JavascriptInterface
    fun showToast(toast: String) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
    }
}
webView.addJavascriptInterface(WebAppInterface(this), "Android")
function showAndroidToast(toast) {
    Android.showToast(toast);
}

Aby powiązać interfejs między kodem JavaScript a kodem systemu Android, wystarczy skorzystać z addJavascriptInterface(), Pierwszy parametr tej funkcji to klasa, którą chcesz powiązać z JavaScriptem, a drugi to nazwa interfejsu dla JavaScript, aby móc komunikować się z aplikacji. 

W powyższym przykładzie klasa WebAppInterface umożliwia kodu js wyświetlenie wiadomości przy użyciu metody showToast(). Nazwa interfejsu (Android) może być dowolna.

info

Pamiętaj dodać adnotację @JavascriptInterface do dowolnej metody, która ma być dostępna dla JavaScript. Metoda musi być publiczna. Jeśli nie podasz adnotacji, funkcja nie będzie dostępna dla kodu js.

info

Użycie addJavascriptInterface() może prowadzić do problemu z bezpieczeństwem. Gdy kod HTML w WebView jest niewiarygodny (na przykład, kodu HTML jest dostarczana przez nieznaną osobę lub proces), osoba atakująca może dołączyć niebezpieczny kod, który wykona się po stronie klienta. W związku z tym nie powinieneś używać, addJavascriptInterface(), chyba że napisałeś sam cały kod HTML i JavaScript, który pojawia się w pliku WebView.

Uzupełnij wiedzę o: Szyfrowanie danych w Androidzie

Przekazywanie parametrów i zwracanie obiektów między JavaScript, a Androidem

Wiemy już jak uruchomić kod JavaScript w aplikacji na system Android. Zobaczmy jak teraz możemy przekazać parametry lub zwracać obiekty.

val listen = ValueCallback<String> { result ->
    val base64Str = Base64.decode(result, Base64.NO_WRAP)
    val text = String(base64Str)
    Log.d("TAG receive js  ", text)
}

webView.webViewClient = object : WebViewClient() {
    override fun onPageFinished(view: WebView, url: String) {
        val jo = JSONObject()
        jo.put("id", 1)
        jo.put("text", "hello")
        val base64Str = Base64.encodeToString(jo.toString().toByteArray(), Base64.NO_WRAP)
        webView.evaluateJavascript("javascript:funWithJson(' $base64Str ');", listen)
    }
}
function b64EncodeUnicode(str) {
    // first we use encodeURIComponent to get percent-encoded UTF-8,
    // then we convert the percent encodings into raw bytes which
    // can be fed into btoa.
    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
        function toSolidBytes(match, p1) {
            return String.fromCharCode('0x' + p1);
    }));
}

function b64DecodeUnicode(str) {
    // Going backwards: from bytestream, to percent-encoding, to original string.
    return decodeURIComponent(atob(str).split('').map(function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
}

function funWithJson(data) {
    var base64 = b64DecodeUnicode(data);
    var json = JSON.parse(base64);
    var id = json.id;
    var text = json.text;
    var object= {
            id: id,
            text: "Hello again JS"
        }
    var json = JSON.stringify(article);
    var content = b64EncodeUnicode(json);
    return content;
}

Sporo kodu! Jest on wymagany. Czasami zdarza się tak, że jeżeli przekazujemy coś do funkcji js, silnik JavaScript może ten parametr zinterpretować, co za tym idzie wystąpi błąd. Dlatego dane musimy zakodować, w tym przykładzie skorzystaliśmy z Base64. Dodatkowo pokazałem jak przekazać kilka parametrów za pomocą JSON. Podobnie robimy w drugą stronę. Tworzymy obiekt typu JSON i przekazujemy go do zakodowania, a następnie zwracamy tę wartość. I oto w ten sposób możemy przekazywać obiekty z i do funkcji JavaScript.

Czytając ten krótki, ale konkretny artykuł można wywnioskować, że uruchomienie kodu JavaScript w aplikacji na system Android nie jest łatwe, ale wykonalne. Dlatego musisz sam dopasować rozwiązanie do swojego projektu. Nie ma idealnego rozwiązania w tym przypadku, WebView daje programistom wiele możliwości i mocy, ale wymaga dużej odpowiedzialności. Mam nadzieję, że w przyszłości powstanie rozwiązanie, które nie będzie powodowało problemów z wykonaniem kodu JS w aplikacji na Androida. A może masz lepszy pomysł na to, jak wykonać funkcję JS? Daj znać!