Verwenden von Vala für nativen Android Code

…am Beispiel einer GStreamer Sample App. Wolltest du nicht schon immer mal Android JNI Code in einer höheren Programmiersprache entwickeln? In diesem Tutorial werden wir C Code (zumindest als Proof of Concept) von einem bestehendem Android-GStreamer Codebeispiel durch Vala Code ersetzen.

coding-924920_960_720

Dieser Artikel richtet sich, wie man es sich bei dieser Vielfalt an (scheinbar) inkompatiblen Technologien schon denken kann, an versierte Entwickler mit Kenntnissen zu Android und dem Java Native Interface (JNI). Programmierkenntnisse in der Sprache Vala werden nicht vorausgesetzt, allerdings sollte man diesen Artikel mit den Features von Vala überfliegen.

Warum?

Als erstes stellt sich bei so einem Thema einmal die Sinnfrage: Warum, um alles in der Welt, will ich Vala in meinem Android Projekt verwenden? Warum will ich überhaupt nativen code verwenden? Nun ja…

Warum Nativen Code?

Im NDK developers‘ Guide wird  darauf hingewiesen, dass dieses Feature für viele Entwickler nicht geeignet sein wird. Schließlich ist die Einbindung von nativem Code mit einer Menge Arbeit verbunden:

– Erweiterung des Projektsetups
– Erstellen von „Glue-Code“ für die Schnittstelle zwischen Java und C/C++
– Schwierigeres Debuggen

Im NDK developers‘ Guide findet sich aber auch der Hauptgrund für die Benutzung des NDKs:  „Reuse your own or other developers‘ C or C++ libraries.“

Warum Vala Code?

Der vorhin genannte Grund trifft nicht nur auf C/C++, sondern auch auf Vala zu. Nun könnte man hier einwenden, dass diese Programmiersprache ja nur ein „Frontend“ zu C ist, dass glib verwendet. Da könnte man ja gleich C code verwenden.

Vala ist eine höhere Programmiersprache. Obwohl auch große Softwaresysteme in C implementiert werden, bevorzugen viele Entwickler Höhere Programmiersprachen mit umfangreicher Funktionalität. So wird zum Beispiel Ubuntus Unity Oberfläche zum Teil in Vala implementiert. Unter diesem Link sind die zahlreichen Features der Sprache beschrieben. Vor allem das implizite Speichermanagement per reference counting erspart einem oft Ärger und eine Menge Codezeilen.

Wie mache ich das?

Die Grundidee besteht darin, dass wir aus dem Vala Code C Code generieren, und diesen dann mit dem bestehenden C Code mitkompilieren. Dazu muss natürlich auch das build System dementsprechend konfiguriert werden. Zuletzt werden die Dateien wie folgt verarbeitet:

blog_vala

Setup

Um den Umfang dieses Blogeintrages auf das Einbinden von dieser Programmiersprache zu beschränken, werden wir von der hier  zu findenden, bestehenden Android Gstreamer Sample App ausgehen. Zuerst holen wir uns also die besagte sample App:

# git clone https://github.com/jaroslavas/Gstreamer-Android-example
# cd Gstreamer-Android-example

Nun downloaden wir, der Anweisungen in der readme.md Datei folgend, die Binaries für GStreamer und passen die local.properties und src/main/jni/Android.mk an. Im GStreamer package sind bereits alle Abhängigkeiten, wie etwa glib, enthalten. Weiters wird der Vala compiler installiert.

Erstellen und einbinden eines Vala source files in den C Code

Wir lagern nun Testweise das Erstellen der GStreamer Pipeline in eine Vala Datei Namens test.vala im jni Verzeichnis aus:


using Gst;
namespace codefluegel {
    public Element create_pipeline() throws Error {
       return parse_launch("playbin");
    }
} 

Nun können wir das File in ein .c file kompilieren, indem wir folgendes Kommando im jni Ordner ausführen:

# valac -H vala_exports.h -C *.vala  --pkg gstreamer-1.0

Der Vala Compiler erzeugt hiermit zwei Dateien. Die Header Datei vala_exports.h enthält alle mit public gekennzeichneten Funktionen und Klassen des Vala Codes. In unserem Fall ist dies folgende Funktion:

GstElement* codefluegel_create_pipeline (GError** error);

Wie wir hier sehen, wird der namespace zum Funktionsnamen hinzugefügt. Das throws statement wird in einen error Parameter umgewandelt.

Die zweite erzeugte Datei ist die C Source Datei, die wir später mitkompilieren werden.

Anpassen des ursprünglichen C Codes

Nun können wir den generierten Header in tutorial-5.c einfügen, sowie die Funktion aufrufen:

#include "vala_exports.h"
…
// replacing call (somewhere around line 343)
// data->pipeline = gst_parse_launch("playbin", &error);
data->pipeline = codefluegel_create_pipeline (&error);

Für Produktivcode würde so ein Austauschen einzelner Codezeilen natürlich keinen Sinn machen. Dies hier soll nur exemplarisch zeigen, wie man generell Vala Code einbindet. Ansonsten würde man die gesamte Logik des nativen Appteils in Vala schreiben. Dann würde nur noch der Gluecode in C übrig bleiben. Um diesen loszuwerden, gibt es aber auch Tools. Mehr dazu weiter unten.

Buildprozess anpassen

Der native Teil der App wird hier mit ndk-build gebaut, dass von gradle aufgerufen wird. Wir müssen daher das Android.mk file anpassen, und um die Vala Kompilierung ergänzen:

LOCAL_PATH := $(call my-dir)
# Edit this line
GSTREAMER_ROOT_ANDROID := some/path/to/gstreamer/libs
include $(CLEAR_VARS)

# add vala to C compilation
VALAC := valac
VALA_PACKAGE_PARAMS := --pkg gstreamer-1.0
VALA_SOURCE_FILES := $(LOCAL_PATH)/test.vala
VALA_COMPILE_RESULT := $(shell ($(VALAC) -C -H vala_exports.h $(VALA_SOURCE_FILES) $(VALA_PACKAGE_PARAMS)))



# assure that build fails if valac fails
ifneq (,$(findstring succeed,$(VALA_COMPILE_RESULT)))
  # vala compilation successful
else
  $(error vala compilation failed)
endif
LOCAL_MODULE    := tutorial-5
# add the vala file here
LOCAL_SRC_FILES := tutorial-5.c test.c
# HERE FOLLOWS THE GSTREAMER PART
# […]

Somit sollte bei jedem build die test.c Datei generiert und mitkompiliert werden.

Verbesserungen

Jeder, der in Java bereits das JNI verwendet hat weiß, wie umständlich der Datenaustausch zwischen Java und native Code ist. Hier bietet Swig Abhilfe, indem es den Schnittstellencode aus einer Interfacedefinition heraus generiert. Glücklicherweise gibt es mit Valabind einen Generator, der aus Vala Dateien eine solche Interfacedefinition generiert. Dieser unterstützt zwar nicht alle Features von Vala, ist aber dennoch sehr hilfreich.

Mit dem Verbinden beider Tools können wir nun das Vala Interface über generierte Java Klassen ansprechen.

Lust auf mehr? Melde dich jetzt für unseren monatlichen Newsletter an!