Migrate from NativeActivity   Part of Android Game Development Kit.

This page describes how to migrate from NativeActivity to GameActivity in your Android game project.

GameActivity is based on NativeActivity from the Android framework, with enhancements and new features:

  • Supports Fragment from Jetpack.
  • Adds TextInput support to facilitate soft keyboard integration.
  • Handles touch and key events in the GameActivity Java class rather than the NativeActivity onInputEvent interface.

Before migrating, we recommend reading the get started guide, which describes how to set up and integrate GameActivity in your project.

Java build script updates

GameActivity is distributed as a Jetpack library. Make sure to apply the Gradle script updating steps described in the get started guide:

  1. Enable Jetpack library in your project’s gradle.properties file:

    android.useAndroidX=true
    
  2. Optionally, specify a Prefab version, in the same gradle.properties file, for example:

    android.prefabVersion=2.0.0
    
  3. Enable Prefab feature in your app’s build.gradle file:

    android {
        ... // other configurations
        buildFeatures.prefab true
    }
    
  4. Add the GameActivity dependency to your application:

    1. Add the core and games-activity libraries.
    2. If your current minimum supported API level is less than 16, update it to at least 16.
    3. Update the compiled SDK version to the one that the games-activity library requires. Jetpack typically requires the latest SDK version at the release build time.

    Your updated build.gradle file might look something like this:

    android {
        compiledSdkVersion 33
        ... // other configurations.
        defaultConfig {
            minSdkVersion 16
        }
        ... // other configurations.
    
        buildFeatures.prefab true
    }
    dependencies {
        implementation 'androidx.core:core:1.9.0'
        implementation 'androidx.games:games-activity:1.2.2'
    }
    

Kotlin or Java code updates

NativeActivity can be used as a startup activity and creates a full screen application. At present, GameActivity cannot be used as the startup activity. Apps must derive a class from GameActivity and use that as the startup activity. You must also make additional configuration changes to create a full screen app.

The following steps assume your application uses NativeActivity as the startup activity. If that is not the case, you can skip most of them.

  1. Create a Kotlin or Java file to host the new startup activity. For example, the following code creates the MainActivity as the startup activity and loads the application’s main native library, libAndroidGame.so:

    Kotlin

    class MainActivity : GameActivity() {
       override fun onResume() {
           super.onResume()
           // Use the function recommended from the following page:
           // https://d.android.com/training/system-ui/immersive
           hideSystemBars()
       }
       companion object {
           init {
               System.loadLibrary("AndroidGame")
           }
       }
    }

    Java

      public class MainActivity extends GameActivity {
          protected void onResume() {
              super.onResume();
              // Use the function recommended from
              // https://d.android.com/training/system-ui/immersive
              hideSystemBars();
          }
          static {
              System.loadLibrary("AndroidGame");
          }
      }
  2. Create a full screen app theme in the res\values\themes.xml file:

    <resources xmlns:tools="http://schemas.android.com/tools">
        <!-- Base application theme. -->
        <style name="Application.Fullscreen" parent="Theme.AppCompat.Light.NoActionBar">
            <item name="android:windowFullscreen">true</item>
            <item name="android:windowContentOverlay">@null</item>"
        </style>
    </resources>
    
  3. Apply the theme to the application in the AndroidManifest.xml file:

    <application  android:theme=”@style/Application.Fullscreen”>
         <!-- other configurations not listed here. -->
    </application>
    

    For detailed instructions for full screen mode, see to the immersive guide and example implementation in the games-samples repo.

This migration guide does not change the native library name. If you do change it, ensure the native library names are consistent in the following three locations:

  • Kotlin or Java code:

    System.loadLibrary(AndroidGame)
    
  • AndroidManifest.xml:

    <meta-data android:name="android.app.lib_name"
            android:value="AndroidGame" />
    
  • Inside the C/C++ build script file, for example CMakeLists.txt:

    add_library(AndroidGame ...)
    

C/C++ build script updates

The instructions in this section use cmake as the example. If your application uses ndk-build, you need to map them to the equivalent commands described in ndk-build documentation page.

GameActivity’s C/C++ implementation has been providing a source code release. For version 1.2.2 a later, a static library release is provided. The static library is the recommended release type.

The release is packed inside the AAR with the prefab utility. The native code includes GameActivity’s C/C++ sources and the native_app_glue code. They need to be built together with your application's C/C++ code.

NativeActivity applications already use the native_app_glue code shipped inside NDK. You must replace it with GameActivity’s version of native_app_glue. Other than that, all cmake steps documented inside the getting started guide apply:

  • Import either the C/C++ static library or the C/++ source code into your project as follows.

    Static library

    In your project's CMakeLists.txt file, import the game-activity static library into the game-activity_static prefab module:

    find_package(game-activity REQUIRED CONFIG)
    target_link_libraries(${PROJECT_NAME} PUBLIC log android
    game-activity::game-activity_static)
    

    Source code

    In your project's CMakeLists.txt file, import the game-activity package and add it to your target. The game-activity package requires libandroid.so, so if it's missing, you must also import it.

    find_package(game-activity REQUIRED CONFIG)
    ...
    target_link_libraries(... android game-activity::game-activity)
    
  • Remove all references to NDK’s native_app_glue code, such as:

    ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
        ...
    set(CMAKE_SHARED_LINKER_FLAGS
        "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
    
  • If you are using the source code release, include the GameActivity source files. Otherwise, skip this step.

    get_target_property(game-activity-include
                        game-activity::game-activity
                        INTERFACE_INCLUDE_DIRECTORIES)
    add_library(${PROJECT_NAME} SHARED
        main.cpp
        ${game-activity-include}/game-activity/native_app_glue/android_native_app_glue.c
        ${game-activity-include}/game-activity/GameActivity.cpp
        ${game-activity-include}/game-text-input/gametextinput.cpp)
    

Work around the UnsatisfiedLinkError issue

If you encounter an UnsatsifiedLinkError for the com.google.androidgamesdk.GameActivity.initializeNativeCode() function, add this code to your CMakeLists.txt file:

set(CMAKE_SHARED_LINKER_FLAGS
    "${CMAKE_SHARED_LINKER_FLAGS} -u \
    Java_com_google_androidgamesdk_GameActivity_initializeNativeCode")

C/C++ source code updates

Follow these steps to replace NativeActivity references in your application with GameActivity:

  • Use the native_app_glue released with GameActivity. Search and replace all android_native_app_glue.h usage with:

    #include <game-activity/native_app_glue/android_native_app_glue.h>
    
  • Set both motion event filter and key event filter to NULL so your app can receive input events from all input devices. You typically do this inside android_main() function:

    void android_main(android_app* app) {
        ... // other init code.
    
        android_app_set_key_event_filter(app, NULL);
        android_app_set_motion_event_filter(app, NULL);
    
        ... // additional init code, and game loop code.
    }
    
  • Remove AInputEvent related code, and replace it with GameActivity’s InputBuffer implementation:

    while (true) {
        // Read all pending events.
        int events;
        struct android_poll_source* source;
    
        // If not animating, block forever waiting for events.
        // If animating, loop until all events are read, then continue
        // to draw the next frame of animation.
        while ((ALooper_pollAll(engine.animating ? 0 : -1, nullptr, &events,
                                (void**)&source)) >= 0) {
           // Process this app cycle or inset change event.
           if (source) {
               source->process(source->app, source);
           }
    
              ... // Other processing.
    
           // Check if app is exiting.
           if (state->destroyRequested) {
               engine_term_display(&engine);
               return;
           }
        }
        // Process input events if there are any.
        engine_handle_input(state);
    
       if (engine.animating) {
           // Draw a game frame.
       }
    }
    
    // Implement input event handling function.
    static int32_t engine_handle_input(struct android_app* app) {
       auto* engine = (struct engine*)app->userData;
       auto ib = android_app_swap_input_buffers(app);
       if (ib && ib->motionEventsCount) {
           for (int i = 0; i < ib->motionEventsCount; i++) {
               auto *event = &ib->motionEvents[i];
               int32_t ptrIdx = 0;
               switch (event->action & AMOTION_EVENT_ACTION_MASK) {
                   case AMOTION_EVENT_ACTION_POINTER_DOWN:
                   case AMOTION_EVENT_ACTION_POINTER_UP:
                       // Retrieve the index for the starting and the ending of any secondary pointers
                       ptrIdx = (event->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
                                AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
                   case AMOTION_EVENT_ACTION_DOWN:
                   case AMOTION_EVENT_ACTION_UP:
                       engine->state.x = GameActivityPointerAxes_getAxisValue(
                           &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_X);
                       engine->state.y = GameActivityPointerAxes_getAxisValue(
                           &event->pointers[ptrIdx], AMOTION_EVENT_AXIS_Y);
                       break;
                    case AMOTION_EVENT_ACTION_MOVE:
                    // Process the move action: the new coordinates for all active touch pointers
                    // are inside the event->pointers[]. Compare with our internally saved
                    // coordinates to find out which pointers are actually moved. Note that there is
                    // no index embedded inside event->action for AMOTION_EVENT_ACTION_MOVE (there
                    // might be multiple pointers moved at the same time).
                        ...
                       break;
               }
           }
           android_app_clear_motion_events(ib);
       }
    
       // Process the KeyEvent in a similar way.
           ...
    
       return 0;
    }
    
  • Review and update logic that attached to NativeActivity’s AInputEvent. As shown in the previous step, GameActivity’s InputBuffer processing is outside the ALooper_pollAll() loop.

  • Replace android_app::activity->clazz usage with android_app:: activity->javaGameActivity. GameActivity renames the Java GameActivity instance.

Additional steps

The previous steps cover NativeActivity's functionality, but GameActivity has additional features that you might want to use:

We recommend exploring these features and adopting them as appropriate for your games.

If you have any questions or recommendations for GameActivity or other AGDK libraries, create a bug to let us know.