Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions native-activity/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,69 @@
# Native Activity

> [!WARNING]
> **Most apps should not use the app development model shown in this sample**.
> Instead, use a Java or Kotlin `AppCompatActivity` and connect your native code
> using JNI like the other samples in this repository. `NativeActivity` and
> `GameActivity` attempt to translate the Android [activity lifecycle] into a
> desktop style `main()` function with a polled event loop. That is not how
> Android apps work, and while it may help you get your prototype running more
> quickly, as your app matures you will likely end up retranslating the
> `native_app_glue` model to again look like `Activity`.

This is an Android sample that uses [NativeActivity] with `native_app_glue`,
which enables building NDK apps without having to write any Java code. In
practice most apps, even games which are predominantly native code, will need to
call some Java APIs or customize their app's activity further.
call some Java APIs or customize their app's activity further. While it may save
you a small amount of effort during prototyping, it may result in a difficult
migration later. It's also worth noting that some of the code in this sample is
spent undoing the work of `native_app_glue` to create a class very similar to
`Activity`.

The more modern approach to this is to use [GameActivity], which has all the
same benefits as `NativeActivity` and `native_app_glue`, while also making it
easier to include Java code in your app without a rewrite later. It's also
source compatible. This sample will likely migrate to `GameActivity` in the
future.
source compatible. However, it still has all the problems explained in the
warning above, and in practice neither `NativeActivity` nor `GameActivity` is
the recommended app development model.

The app here is intentionally quite simple, aiming to show the core event and
draw loop necessary for an app using `native_app_glue` without any extra
clutter. It uses `AChoreographer` to manage the update/render loop, and uses
`ANativeWindow` and `AHardwareBuffer` to update the screen with a simple color
clear.

[activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
[GameActivity]: https://developer.android.com/games/agdk/game-activity
[NativeActivity]: http://developer.android.com/reference/android/app/NativeActivity.html

## Screenshots
## Walkthrough

The interesting sections of code in this sample are in three files:
[AndroidManifest.xml], [CMakeLists.txt], and [main.cpp]. Each of those files has
code comments explaining the portions relevant to using `NativeActivity`, but
the high level details of the app are explained here.

This app uses `NativeActivity` rather than its own child class of `Activity` or
`AppCompatActivity`. This is specified in the `<activity />` declaration in [the
manifest].

Apps which use `NativeActivity` are typically written using `native_app_glue`,
which adapts the Android activity lifecycle code to look more like a desktop
program with a `main()` function and an event loop. This is set up in the app's
[CMakeLists.txt file].

When using `native_app_glue` with a [version script], you must export
`ANativeActivity_onCreate`. This sample does this in
[libnative-activity.map.txt].

This is a fairly simple application, so all of the code is in a single file,
[main.cpp]. The entry point for an app using `native_app_glue` is
`android_main()`. That function is the best place to start reading in this file
to learn how the sample works, then follow through to the definition of
`engine_handle_cmd` and `Engine`.

![screenshot](screenshot.png)
[CMakeLists.txt file]: app/src/main/cpp/CMakeLists.txt
[libnative-activity.map.txt]: app/src/main/cpp/libnative-activity.map.txt
[main.cpp]: app/src/main/cpp/main.cpp
[the manifest]: app/src/main/AndroidManifest.xml
[version script]: https://developer.android.com/ndk/guides/symbol-visibility
68 changes: 37 additions & 31 deletions native-activity/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0">
android:versionName="1.0">

<!--
This .apk has no Java/Kotlin code, so set hasCode to false.
<!--
This .apk has no Java/Kotlin code, so set hasCode to false.
If you copy from this sample and later add Java/Kotlin code, or add a
dependency on a library that does (such as androidx), be sure to set
`android:hasCode` to `true` (or just remove it, since that's the default).
-->
<application
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:hasCode="false">

<!-- Our activity is the built-in NativeActivity framework class.
This will take care of integrating with our NDK code. -->
<activity android:name="android.app.NativeActivity"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:exported="true">
<!-- Tell NativeActivity the name of our .so -->
<meta-data android:name="android.app.lib_name"
android:value="native-activity" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
If you copy from this sample and later add Java/Kotlin code, or add a
dependency on a library that does (such as androidx), be sure to set
`android:hasCode` to `true` (or just remove it, since that's the default).
-->
<application
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:hasCode="false">

<!--
This app uses android.app.NativeActivity rather than its own child class of
Activity or AppCompatActivity. The advantage of this is that we do not have
to write any Java code of our own. The Java portion of the app is provided
by the OS.
-->
<activity
android:name="android.app.NativeActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="true">
<!--
This property tells NativeActivity which of our app's libraries
provide the definition of ANativeActivity_onCreate, which is the
entry point for apps using ANativeActivity.
-->
<meta-data
android:name="android.app.lib_name"
android:value="native-activity" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<!-- END_INCLUDE(manifest) -->
37 changes: 20 additions & 17 deletions native-activity/app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
#
# Copyright (C) The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# Copyright (C) 2010 The Android Open Source Project
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 4.1.0)
project(NativeActivity LANGUAGES C CXX)

include(AppLibrary)
include(AndroidNdkModules)

# This includes the AndroidNdkModules.cmake file, which is shipped with the
# NDK's CMake distribution. Including this file defines
# android_ndk_import_module_native_app_glue(), which defines the
# native_app_glue target when called.
include(AndroidNdkModules)
android_ndk_import_module_native_app_glue()

add_app_library(native-activity SHARED main.cpp)

# Linking the native_app_glue target with our native-activity target (the
# library which contains the main app code) includes native_app_glue in the app.
target_link_libraries(native-activity
android
# We have to use $<LINK_LIBRARY:WHOLE_ARCHIVE,native_app_glue> rather than
# the simpler native_app_glue spelling to instruct the linker that the
# entire native_app_glue static library should be included in
# libnative-activity.so, even if the linker does not find any calls in our
# code to native_app_glue. This is because native_app_glue is a static
# library rather than a shared library, and normally the linker will only
# include code from static libraries when it has found a call to that code.
# This is usually a good thing because it reduces the size of the app, but
# in this case the calls to native_app_glue, specifically the
# ANativeActivity_onCreate function, don't come from us, but instead come
# from ANativeActivity, which the linker cannot detect.
$<LINK_LIBRARY:WHOLE_ARCHIVE,native_app_glue>
log
)
Expand Down
5 changes: 5 additions & 0 deletions native-activity/app/src/main/cpp/libnative-activity.map.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
LIBNATIVEACTIVITY {
global:
# When using NativeActivity and you don't need any of your own JNI
# functions this is the only symbol that should be exported. If your app
# needs additional JNI functions (and most apps will), then you'll need to
# include JNI_OnLoad (or your individual Java_... functions if you're not
# using RegisterNatives) here.
ANativeActivity_onCreate;
local:
*;
Expand Down
Loading