Complete Communications Engineering

This requires adding the shared object files and associated headers to the project, adding some declarations to the CMake file, and creating a JNI wrapper interface for the methods in the shared library.  This article will illustrate the process with an example application.  The example will use the nodejs-mobile library which can be downloaded pre-built for Android.

To start, create a new Android Studio project with an empty activity.  Call it ‘NodeJSMobileDemo’, set the language to ‘Kotlin’ and the minimum SDK to ‘Android 6.0 (Marshmallow)’.  Once the project loads, right-click on ‘app’ in the project explorer and select ‘Add C++ to Module’.  Select ‘Create CMakeLists.txt …’ and click OK.  This will create some default files, folders and declarations that will help with bringing native libraries into the project.

The next step is to copy the nodejs-mobile files into the project.  The pre-built package contains a ‘bin’ folder and an ‘include’ folder.  The ‘bin’ folder contains the pre-built shared object files for various platforms.  The ‘include’ folder contains the header files that will be included by the application.  Create a folder called ‘libnode’ under the ‘app/src/main/cpp’ folder of the Android Studio application.  Copy the ‘bin’ and ‘include’ folders from the nodejs-mobile package into the new folder.

Next, edit the file ‘app/src/main/cpp/CMakeLists.txt’.  Use the following contents:

cmake_minimum_required(VERSION 3.18.1)

 

project(“nodejsmobiledemo”)

 

include_directories(${CMAKE_SOURCE_DIR}/libnode/include/node)

 

# node library

add_library (node SHARED IMPORTED)

set_target_properties (node

        PROPERTIES IMPORTED_LOCATION

        ${CMAKE_SOURCE_DIR}/libnode/bin/${ANDROID_ABI}/libnode.so)

 

# android logging

find_library (log-lib log)

 

# nodejsmobiledemo library

add_library (nodejsmobiledemo SHARED nodejsmobiledemo.cpp)

target_link_libraries (nodejsmobiledemo node ${log-lib})

This file controls how the native code is compiled and linked.  The ‘include_directories’ line adds the include folder for libnode to the search path so the project’s native source code (nodejsmobiledemo.cpp) can find it.  The next two lines add the libnode library to the project as an imported shared library, and tell CMake where to find it.  The find_library line is added by Android Studio for logging support.  The last two lines tell how to build the application’s library and which libraries are required to link it.

Next, edit the file ‘app/src/main/cpp/ nodejsmobiledemo.cpp’.  Use the following contents:

#include <jni.h>

#include <node.h>

 

static const char *node_source =

    “var http = require(‘http’);\n”

    “var versions_server = http.createServer( (request, response) => {\n”

    ”  response.end(‘Versions: ‘ + JSON.stringify(process.versions));\n”

    “});\n”

    “versions_server.listen(3000);\n”;

 

static int node_is_running = 0;

 

extern “C”

JNIEXPORT jint JNICALL

Java_com_example_nodejsmobiledemo_MainActivity_startNode(

        JNIEnv *env,

        jobject /* this */)

{

    int node_result;

    char *argv[] = {

            (char *)“node”,

            (char *)“-e”,

            (char*)node_source

    };

 

    /* Only run once */

    if (node_is_running) {

        return 0;

    }

    node_is_running = 1;

 

    /* Start the NodeJS server */

    node_result = node::Start(3, argv);

 

    return node_result;

}

This code defines a function that can be called for the main Kotlin application.  The function has a long name here, but it will be called as just ‘startNode()’ in the application.  The function starts a Node.JS server by calling into the nodejs-mobile library.  The server runs some static source code that is defined inline (node_source).  There is also a static variable to ensure the server is only started once.

Next, edit the file ‘app/src/main/java/com/example/nodejsmobiledemo/MainActivity.kt’.  Use the following contents:

package com.example.nodejsmobiledemo

 

import androidx.appcompat.app.AppCompatActivity

import android.os.Bundle

import kotlin.concurrent.thread

 

class MainActivity : AppCompatActivity() {

    init {

        System.loadLibrary(“nodejsmobiledemo”)

    }

 

    external fun startNode(): Int

 

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

        thread() {startNode()}

    }

}

There are a few additions to the auto-generated version here.  The init declaration tells the application which shared libraries to load.  In this case, only the application’s library needs to be loaded.  The next line declares the function from the shared library so it can be called from Kotlin.  Last, after the normal application startup, a thread is created and the startNode() function is called from the thread.

There are two more details to take care of before the application will work.  First, open the file ‘app/src/main/AndroidMainfest.xml’ and add the following line inside the <manifest> section:

<uses-permission android:name=“android.permission.INTERNET”/>

This will allow the application to send and receive packets on the network, which is required for connecting to the Node.JS server.  Last, open the file ‘app/build.gradle’ and add the following line to the cmake section of externalNativeBuild in defaultConfig:

arguments “-DANDROID_STL=c++_shared”

This line should make sure that the required library ‘libc++_shared.so’ is included in the APK.

After all of these changes are made it should be possible to build and run the application on an Android device or simulator.  When it runs, it will display the pre-defined ‘hello world’ screen.  It will also startup a Node.JS server that should be accessible from any device on the same local network through port 3000.  For example, if the Android phone has the IP address ‘192.168.1.50’, then you should be able to navigate to ‘http://192.168.1.50:3000’ and connect to the webpage.  The webpage will show some Node.JS version information.