Geeks With Blogs
Josh Reuben

Overview

Native activities – create an application based only on native code, without a single line of Java.

Use cases:

  • Android sensor polling – more accurate than Java events
  • Cross platform 3D – include a common OpenGL static lib that can also be linked in say an ObjectiveC app. (If writing purely for Android, just use the Java API and import android.opengl.GLES20.* package, adapting a TextureView to perform EGL init like a GLSurfaceView - the performance leverages the same OpenGL ES calls via JNI wrappers, and shaders run on the GPU anyway, yielding same performance. Or you could use LibGDX...)
  • Image processing – eg leverage OpenCV C++ libs for Augmented Reality apps (see my next post).

You can pretty much call any POSIX API or included C/C++ lib that is POSIX compliant !


The native_app_glue Module API

NativeActivity class leverages JNI to handle application lifecycle events from the ART JVM and pass them to native code in a separate thread.

${ANDROID_NDK}/sources/android/native_app_glue – hides details of synchronization with mutexes, IPC pipes

different events set android_app.activityState with the appropriate APP_CMD_* value:

  • onStart, onResume, onPause, onStop
  • onSaveInstance - wait for native application callback to save state. 
  • OnDestroy - notify native thread that destruction is pending, and free memory when acknowledged.
  • onConfigChanged, onWindowFocusedChanged, onLowMemory
  • onNativeWindowCreated / onNativeWindowDestroyed - call android_app_set_window() which provides and requests the native thread to change its display window.
  • onInputQueueCreated / onInputQueueDestoyed - call android_app_set_input() to register an input queue
  • Note: screen orientation changes require activity recreation, and raises an APP_CMD_ SAVE_STATE event

ANativeActivity_onCreate()

  • application entry point - forks the native thread
  • Executed on the UI thread, communicates to native thread via callback event wrappers over Linux pipes and synchronization mutexes
  • allocates memory and initializes android_app application context
  • android_app_entry() - executed on the native thread - reads data incoming from the pipe and passes it to ALooper event queue processor
  • ALooper_prepare() - creates ALooper
  • Alooper_addFd() - attaches ALooper to the pipe (as a POSIX file descriptor)
android_poll_source::process – called from custom event loop. Calls process_cmd() / process_input()
  • process_cmd() - process command queue and calls back to custom android_app::onAppCmd callback pointer
  • process_input() - process input queue and calls back to custom android_app::onAppCmd callback pointer

thread entry point attaches the Command Queue to the ALooper; From my understanding, the Input Queue communicates to the ALooper via a pipe but can be manually attached to the ALooper via AInputQueue_attach/detachLooper()

Custom user queue can also be attached to the ALooper to listen to additional file-descriptors.

app_dummy() - ensures native glue is not stripped by the linker.

android_app structure - contains native activity context info: state, window, event queue, etc

  • onAppCmd: callback pointer - triggered each time an activity event occurs from command queue.
  • onInputEvent: callback pointer - triggered each time an input event occurs from input queue.
  • void* userData: pointer to data structure accessible to callback function
  • int destroyRequested flag to notify custom event loop that an application is being terminated, and that is should gracefully save state & free resources
  • int activityState - current activity state APP_CMD_*
  • ‹ANativeActivity* activity - the JNI NativeActivity
  • ‹  AConfiguration* config - info about host hardware and system state: locale, screen orientation & size etc
  • void* savedState, size_t savedStateSize - save data when an activity thread is destroyed for later restoration
  • ‹AInputQueue* inputQueue handles input events 
  • ‹ALooper* looper – manually attach /detach event listeners against custom event sources exposed via POSIX file descriptors
  • ‹  ANativeWindow* window, ARect contentRect: - graphics drawable area, declared in native_window.h

Alooper_pollAll() – custom native event loop should poll the queue for pending android_poll_source events.

ANativeActivity_finish() - request activity termination


Create a native Android project:

  • create a new project > Android project and set settings: project name, Build target, Application name, Package name (com.domain.MyApp), Min SDK Version. Uncheck Create Activity.
  • remove res/layout/main.xml, src directory (its for Java code).
  • add NativeActivity to the AndroidManifest.xml file: Instead of a new Java Activity child class
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.MyNS.MyApp" android:versionCode="1" android:versionName="1.0">
<uses-sdk android:minSdkVersion="21"/>
<application android:icon="@drawable/icon" android:label="@string/app_name">
	<activity android:name="android.app.NativeActivity" android:label="@string/app_name">
		<meta-data android:name="android.app.lib_name"   android:value="MyApp"/>
         	<intent-filter>
            	<action android:name="android.intent.action.MAIN"/>
            	<category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
	</activity>
</application>
</manifest>

set project to compile native code:

  • Convert the project to a hybrid C++ project (not C) using Convert C/C++ Project wizard.
  • Project >Properties > C/C++ Build - set default build command to ndk-build.
  • Project >Properties > C/C++ Build > Path and Symbols/Includes - add Android NDK & native app glue include directories:
${env_var:ANDROID_NDK}/platforms/android-21/arch-arm/usr/include
${env_var:ANDROID_NDK}/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/lib/gcc/arm-linux-androideabi/4.9/include
${env_var:ANDROID_NDK}/sources/android/native_app_glue

Create directory jni under project root – add all hpp, cpp , mk files here.

Make File

Build script – I'll cover the details of Make (and Cmake) in a future post …

define a Make macro LS_CPP that automatically lists all .cpp files in jni directory

Android.mk:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LS_CPP=$(subst $(1)/,,$(wildcard $(1)/*.cpp))
LOCAL_MODULE    := MyApp
LOCAL_SRC_FILES := $(call LS_CPP,$(LOCAL_PATH))
LOCAL_LDLIBS    := -landroid -llog
LOCAL_STATIC_LIBRARIES := android_native_app_glue
include $(BUILD_SHARED_LIBRARY)
$(call import-module, android/native_app_glue)

Main

app entry point android_main() method runs the event loop

Main.cpp:

#include “Timer.hpp”

#include "EventHandlerImpl.hpp"
#include "EventLoop.hpp"
using namespace MyNS;
void android_main (android_app* pApp){
    EventLoop eventLoop(pApp);
    Timer timer;
    EventHandlerImpl eventHandler (timer, pApp);
    eventLoop.run(eventHandler);
}


EventHandler

Abstract base – interface to observe and handle all native activity events passed from EventLoop - each event has its own handler method. it is important to handle 3 core events in the activity lifecycle:

  • ‰ onActivate(): activity is resumed and window is available and focused.

  • ‰ onDeactivate(): activity is paused or display window loses focus or is destroyed.

  • ‰  onStep(): - idle: computations can take place.

EventHandler.hpp:

#ifndef _EVENTHANDLER_HPP_
#define _EVENTHANDLER_HPP_
namespace MyNS {
    class EventHandler {
        public:
            virtual int32_t onActivate() = 0;
            virtual void onDeactivate() = 0;
            virtual int32_t onStep() = 0;

            virtual void onStart() {};
            virtual void onResume() {};
            virtual void onPause() {};
            virtual void onStop() {};
            virtual void onDestroy() {};
            virtual void onSaveState(void** pData, int32_t* pSize) {};
            virtual void onConfigChanged() {};
            virtual void onLowMemory() {};
            virtual void onInitWindow() {};
            virtual void onTerminateWindow() {};
            virtual void onGainFocus() {};
            virtual void onLostFocus() {};
		};
	}
#endif


EventLoop


EventLoop.hpp:

#ifndef _EVENTLOOP_HPP_
#define _EVENTLOOP_HPP_

#include <android_native_app_glue.h>
#include "EventHandler.hpp"

namespace MyNS {
    class EventLoop {
    public:
        EventLoop(android_app* pApp);

void run(EventHandler& pEventHandler);

protected:

        void activate();
        void deactivate();
        void processEvent(int32_t appCmdCode);

private:

const int32_t SUCCESS;

        static void eventCallback(android_app* pApp, int32_t appCmdCode);
        int isBlocking() const;

bool isEnabled_;

bool isTerminating_;

EventHandler* eventHandler_;

android_app* app_;

};

}

#endif

EventLoop.cpp

#include "EventLoop.hpp"
#include "Log.hpp"
namespace MyNS {
    EventLoop::EventLoop(android_app* pApp) :
        SUCCESS(0),

isEnabled_(false), isTerminating_(false), app_(pApp), eventHandler_(nullptr) {

app_->onAppCmd = eventCallback;

app_->userData = this;

    }

run() method polls application events in an event loop - during activity lifetime, loops continuously over events until it is requested to terminate.

    void EventLoop::run() {
        int32_t events;
        android_poll_source* pPollSource;
        app_dummy();
        while (true) {
            while (Alooper_pollAll( isBlocking(), nullptr, &events,  (void**) &pPollSource) >= 0) {
                if (pPollSource) {
                    Log::info("Processing an event");
                    pPollSource->process(app_, pPollSource);
                }
                if (app_->destroyRequested) {
                    Log::info("Exiting event loop"); 
                    return;

}

}

if ((isEnabled_) && (!isTerminating_)) {

                if (eventHandler_->onStep() != SUCCESS) {
                    isTerminating_ = true;
                    ANativeActivity_finish(app_->activity);

}

}

}

}

int EventLoop::isBlocking() {

// return blocking timeout param:

// -1 block until event is received - avoid consuming battery / CPU time uselessly

// 0 non-blocking: empty event queue → inner while loop will be terminated

// note: > 0 → block until event is received or timeout duration elapsed

return isEnabled_ ? 0 : -1;

}

eventCallback() calls processEvent()

Parameter appCmdCode contains an enumeration value (APP_CMD_*) which describes the occurring event.

    void EventLoop::eventCallback(android_app* pApp,  int32_t appCmdCode) { 
        EventLoop& eventLoop = *(EventLoop*) pApp->userData;
        eventLoop.processEvent(appCmdCode);

}








    void EventLoop::processEvent(int32_t appCmdCode) {
        switch (appCmdCode) {
            case APP_CMD_CONFIG_CHANGED:
                eventHandler_->onConfigChanged();
                break;
            case APP_CMD_INIT_WINDOW:
                eventHandler_->onInitWindow();
                break;
            case APP_CMD_START:
                eventHandler_->onStart();
                break;
            case APP_CMD_STOP:
                eventHandler_->onStop();
                break;
            case APP_CMD_TERM_WINDOW:
                eventHandler_->onTerminateWindow();
                deactivate();
                break;
            case APP_CMD_DESTROY:
                eventHandler_->onDestroy();
                break;
            case APP_CMD_GAINED_FOCUS:

activate();

                eventHandler_->onGainFocus();
                break;
            case APP_CMD_LOST_FOCUS:
                eventHandler_->onLostFocus();
                deactivate();
                break;
            case APP_CMD_LOW_MEMORY:
                eventHandler_->onLowMemory();
                break;
            case APP_CMD_PAUSE:
                eventHandler_->onPause();
                deactivate();
                break;
            case APP_CMD_RESUME:
                eventHandler_->onResume();
                break;
            case APP_CMD_SAVE_STATE:
                eventHandler_->onSaveState(&app_->savedState,  &app_->savedStateSize);
                break;

default:

                break;

}

}

activate() / deactivate() - check activity state & notify the observer

Activation occurs when activity gains focus - can receive input events.

Deactivation occurs when window loses focus , application is paused or window is destroyed - cannot receive input events.

    void EventLoop::activate() {
        if ((!isEnabled_) && (app_->window != nullptr)) {
            isTerminating_ = false; 
            isEnabled_ = true;
            if (eventHandler_->onActivate() != SUCCESS) {

isTerminating_ = true;

                ANativeActivity_finish(app_->activity);
            }

}

}

    void EventLoop::deactivate() {
        if (isEnabled_) {
            eventHandler_->onDeactivate();
            isEnabled_ = false;
        }
    }
}


EventHandlerImpl

implements EventHandler interface - place application-specific code here.
EventHandlerImpl.hpp:

 #ifndef _EVENTHANDLERIMPL_HPP_
 #define _EVENTHANDLERIMPL_HPP_

#include <android_native_app_glue.h>
#include "EventHandler.hpp"
#include “Timer.hpp”

namespace MyNS {
    class EventHandlerImpl : public EventHandler {
    public:

EventHandlerImpl(Timer& pTimer, android_app* pApp);

    protected:
        int32_t onActivate();
        void onDeactivate();
        int32_t onStep();
        void onStart();
        void onResume();
        void onPause();
        void onStop();
        void onDestroy();
        void onSaveState(void** pData; int32_t* pSize);
        void onConfigChanged();
        void onLowMemory();
        void onInitWindow();
        void onTerminateWindow();
        void onGainFocus();
        void onLostFocus();
    private:
        const int32_t SUCCESS;
        const int32_t FAIL;
        void clear();
        void draw();
        android_app* app_;
        ANativeWindow_Buffer windowBuffer_;
        Timer* timer_;

};

}

#endif

EventHandlerImpl.cpp
#include "EventHandlerImpl.hpp"
#include "Log.hpp"
#include <math.h>


namespace MyNS {

EventHandlerImpl::EventHandlerImpl(Timer& pTimer, android_app* pApp) :

timer_(pTimer), app_(pApp), SUCCESS(0), FAIL(-1) {}

the ANativeWindow_* API gives native access to the display window and allow manipulating its surface like a bitmap - requires locking and unlocking before and after processing.

set window format as 32-bit with ANativeWindow_setBuffersGeometry().

Query window information in an ANativeWindow_Buffer structure

    int32_t EventHandlerImpl::onActivate() {
        timer_->reset();
        if (ANativeWindow_setBuffersGeometry(
            app_->window, 0,0, WINDOW_FORMAT_RGBX_8888 /* 32bit */ ) < 0) 
        {
            return FAIL;
        }
        // Need to lock the window buffer to get its properties.
        if (ANativeWindow_lock (app_->window, &windowBuffer_, nullptr) >= 0) {
            ANativeWindow_unlockAndPost(app_->window);
        } else {
            return FAIL;
        }
        return SUCCESS;
    }

step the application by moving the cursor at a constant rate –

call ANativeWindow_lock() to lock window buffer for drawing and call ANativeWindow_unlockAndPost() to unlock it when drawing is finished

    int32_t EventHandlerImpl::onStep() {
        timer_->update();
        if (ANativeWindow_lock(app_->window, &windowBuffer_, nullptr) >= 0) {
            clear();
            draw();
            ANativeWindow_unlockAndPost(app_->window);
            return SUCCESS;
        } else {
            return FAIL;

}

}

implement clear and draw methods

The display window surface which is a continuous memory buffer - directly accessible via the bits field and can be modified pixel by pixel

Clear the screen with a brute-force approach using memset().

    void EventHandlerImpl::clear() {
        memset( windowBuffer_.bits,  0, windowBuffer_.stride * windowBuffer_.height * sizeof(uint32_t*));
}
    void EventHandlerImpl::draw() {
        // todo: transform windowBuffer_.bits
    }
}


Log

Log.hpp

define _Log_debug macro - activates debug messages if NDEBUG flag is set:

#ifndef _LOG_HPP
#define _LOG_HPP

namespace MyNS {
    class Log {
    public:
        static void error(const char* pMessage, ...);
        static void warn(const char* pMessage, ...);
        static void info(const char* pMessage, ...);
        static void debug(const char* pMessage, ...);
};
}
#ifndef NDEBUG
    #define _Log_debug(...) Log::debug(__VA_ARGS__)
#else
    #define _Log_debug(...)
#endif

#endif

Log.cpp file - implement method info().

NDK provides a dedicated logging API in header android/log.h to write messages to Android logs.

log methods differ in their level macro: ANDROID_LOG_INFO/ERROR,/WARN/DEBUG .

NDEBUG macro is defined by the NDK compilation toolchain. To undefined it, in the manifest, set:<application android:debuggable="true" ...>

run adb logcat to see emitted log messages.

#include <stdarg.h>
#include <android/log.h>
#include "Log.hpp"

namespace MyNS {
    void Log::info(const char* pMessage, ...) {

va_list varargs;
va_start(varargs, pMessage);

__android_log_vprint(ANDROID_LOG_INFO, "MyNS", pMessage, varargs);
__android_log_print(ANDROID_LOG_INFO, "MyNS", "\n");

va_end(varargs);

}

}


Timer

adapt FPS according to device speed.

Use POSIX time primitives from time.h

clock_gettime() - retrieve current time from monotonic system clock

work with doubles when manipulating absolute time to avoid losing accuracy - resulting delay can be converted back to float

Timer.hpp
#ifndef _TIMer_HPP_
#define _TIMer_HPP_

#include <time.h>
       
namespace MyNS {
    class Timer {
    public:
        Timer();
        void reset();
        void update();
        double now();
        float elapsed();
    private:
        float elapsed_;
        double lastTime_;
    };
}
#endif

Timer.cpp

#include "Timer.hpp"
namespace MyNS {
    Timer::Timer() : elapsed_(0.0f), lastTime_(0.0f) {}

    void Timer::reset() {
        elapsed_ = 0.0f;
        lastTime_ = now();
}
    void Timer::update() {
        double currentTime = now();
        elapsed_ = (currentTime - lastTime_);
        lastTime_ = currentTime;
}
    double Timer::now() {
        timespec timeVal;
        clock_gettime(CLOCK_MONOTONIC, &timeVal);
        return timeVal.tv_sec + (timeVal.tv_nsec * 1.0e-9);
}
    float Timer::elapsed() {
        return elapsed_;
}
}

		        
		
			Posted on Tuesday, February 17, 2015 6:12 PM


		Java
	, 
		Android
	
 | Back to top
		
		


Comments on this post: Native Android Apps

# re: Native Android Apps
Requesting Gravatar...
I have used the app and it really worked well. - Mark Zokle
Left by George Thomas on Dec 20, 2016 6:03 PM

Your comment:
 (will show your gravatar)


Copyright © JoshReuben | Powered by: GeeksWithBlogs.net