当前位置:   article > 正文

Android NDK系列(一)手动搭建Native Project

Android NDK系列(一)手动搭建Native Project

    使用NDK编写的本地代码具有高性能等特性,在游戏、图形处理等领域有广泛应用,下面介绍如何手动搭建一个纯C++版的Android项目,通过该项目可以理解Android的项目结构。

一、创建settings.gradle

    Android项目是基于Gradle构建的,首先得有settings.gradle文件,正常情况下该文件主要用于配置子模块,但是这里没有子模块,只配置了插件管理和依赖的远程仓库,内容如下:

  1. pluginManagement {
  2. repositories {
  3. gradlePluginPortal()
  4. google()
  5. mavenCentral()
  6. }
  7. }
  8. dependencyResolutionManagement {
  9. repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  10. repositories {
  11. google()
  12. mavenCentral()
  13. }
  14. }

二、创建build.gradle

     build.gradle 是Gradle的构建脚本,它用于定义项目的构建配置和依赖关系,可以指定项目的编译版本、依赖库、插件等信息,以及定义构建任务和构建流程。本示例的build.gradle内容如下。

  1. plugins {
  2. id 'com.android.application' version '7.2.2'
  3. }
  4. android {
  5. compileSdk 29
  6. ndkVersion "25.1.8937393"
  7. buildToolsVersion = "30.0.3"
  8. namespace 'com.sino.nativesample'
  9. defaultConfig {
  10. applicationId "com.sino.nativesample"
  11. minSdkVersion 28 // for Vulkan, need at least 24
  12. versionCode 1
  13. versionName "1.0"
  14. externalNativeBuild {
  15. cmake {
  16. cppFlags '-std=c++17'
  17. arguments "-DANDROID_STL=c++_shared"
  18. abiFilters 'armeabi-v7a', 'arm64-v8a'
  19. }
  20. }
  21. }
  22. sourceSets {
  23. main {
  24. manifest.srcFile 'AndroidManifest.xml'
  25. res.srcDir 'res'
  26. }
  27. }
  28. buildTypes {
  29. release {
  30. minifyEnabled false
  31. }
  32. }
  33. externalNativeBuild {
  34. cmake {
  35. path "CMakeLists.txt"
  36. }
  37. }
  38. }

build.gradle主要内容如下:

1、使用插件com.android.application构建Android应用程序。

2、配置Android应用的构建信息,这里的信息大部分与Android Studio自动生成的信息,不一样的是sourceSets用于指定AndroidManifest.xml文件和resource目录,这里分别使用根目录的AndroidManifest.xml和res目录。

3、本示例使用使用C++语言开发,在externalNativeBuild指定CMakeLists.txt文件路径,这里使用根目录的CMakeLists.txt。

三、创建AndroidManifest.xml

    AndroidManifest.xml用于配置项目的权限及组件,本示例的AndroidManifest.xml包含的内容如下。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:installLocation="auto"
  5. android:versionCode="1"
  6. android:versionName="1.0">
  7. <application
  8. android:allowBackup="true"
  9. android:hasCode="false"
  10. android:icon="@mipmap/ic_launcher"
  11. android:label="@string/app_name"
  12. android:roundIcon="@mipmap/ic_launcher">
  13. <activity
  14. android:name="android.app.NativeActivity"
  15. android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode|density"
  16. android:excludeFromRecents="false"
  17. android:launchMode="singleTask"
  18. android:resizeableActivity="false"
  19. android:screenOrientation="landscape"
  20. android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
  21. android:exported="true"
  22. tools:ignore="NonResizeableActivity">
  23. <!-- Tell NativeActivity the name of the .so -->
  24. <meta-data
  25. android:name="android.app.lib_name"
  26. android:value="native" />
  27. <intent-filter>
  28. <action android:name="android.intent.action.MAIN" />
  29. <category android:name="android.intent.category.LAUNCHER" />
  30. </intent-filter>
  31. </activity>
  32. </application>
  33. </manifest>

    AndroidManifest.xml主要内容解析如下。

1、在application标签中,指定了应用的名称和图标,这些信息依赖于res资源目录。

2、声明Activity组件,这里使用NativeActivity,这是框架提供的Activity,能直接于Native层交互,有了该Activity后,就可以不用在项目中自定义其它Activity了。NativeActivity需要指定加载Native层的库,需要在标签lib_name指定库名称,这里使用"native"作为库名称。

四、创建CMakeLists.txt

    定义Activity后,接下来是为它生成对应的库文件,库文件由CMakeLists.txt构建,内容如下。

  1. cmake_minimum_required(VERSION 3.18.1)
  2. # Declares and names the project.
  3. project("Native")
  4. option(ENABLE_ASAN "enable address sanitizer" ON)
  5. add_definitions(-DXR_USE_PLATFORM_ANDROID
  6. -DXR_OS_ANDROID
  7. -D__ANDROID__
  8. ) #define macro
  9. set(CXX_STANDARD "-std=c++17")
  10. if (MSVC)
  11. set(CXX_STANDARD "/std:c++latest")
  12. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} /W0 /Zc:__cplusplus")
  13. else()
  14. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} -fstrict-aliasing -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations")
  15. endif()
  16. include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
  17. add_library(native SHARED main.cpp
  18. ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
  19. #android
  20. set(CMAKE_SHARED_LINKER_FLAGS
  21. "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate"
  22. )
  23. #link all the module
  24. target_link_libraries(
  25. native PRIVATE
  26. android log)

    在add_library指定库名称为native,正好于NativeActivity的lib_name对应起来,源文件为main.cpp,android_native_app_glue.c是NDK提供的文件,封装了NativeActivity的回调函数,main.cpp正是基于这些回调进行开发。

五、程序开发

    在main.cpp中可以开始基于C++进行程序开发了,这里的main.cpp的主要内容如下。

  1. #include <android_native_app_glue.h>
  2. #include <jni.h>
  3. #include <exception>
  4. #include <android/log.h>
  5. #define LOG_TAG "Native"
  6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  7. #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
  8. struct AndroidAppState {
  9. ANativeWindow* NativeWindow = nullptr;
  10. bool Resumed = false;
  11. };
  12. static void handleAppCmd(struct android_app* app, int32_t cmd) {
  13. AndroidAppState* appState = (AndroidAppState*)app->userData;
  14. switch (cmd) {
  15. case APP_CMD_START: {
  16. break;
  17. }
  18. case APP_CMD_RESUME: {
  19. appState->Resumed = true;
  20. break;
  21. }
  22. case APP_CMD_PAUSE: {
  23. appState->Resumed = false;
  24. break;
  25. }
  26. case APP_CMD_STOP: {
  27. break;
  28. }
  29. case APP_CMD_DESTROY: {
  30. appState->NativeWindow = NULL;
  31. break;
  32. }
  33. case APP_CMD_INIT_WINDOW: {
  34. appState->NativeWindow = app->window;
  35. break;
  36. }
  37. case APP_CMD_TERM_WINDOW: {
  38. appState->NativeWindow = NULL;
  39. break;
  40. }
  41. }
  42. }
  43. static int32_t handleInputEvent(struct android_app* app, AInputEvent* event)
  44. {
  45. LOGI("android_main handleInputEvent");
  46. if(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION
  47. && AInputEvent_getSource(event) == AINPUT_SOURCE_TOUCHSCREEN){
  48. int32_t action = AMotionEvent_getAction(event);
  49. float x = AMotionEvent_getX(event,0);
  50. float y = AMotionEvent_getY(event,0);
  51. switch(action){
  52. case AMOTION_EVENT_ACTION_DOWN:
  53. LOGI("android_main AMOTION_EVENT_ACTION_DOWN (x %f, y %f)", x,y);
  54. break;
  55. case AMOTION_EVENT_ACTION_MOVE:
  56. LOGI("android_main AMOTION_EVENT_ACTION_MOVE");
  57. break;
  58. case AMOTION_EVENT_ACTION_UP:
  59. LOGI("android_main AMOTION_EVENT_ACTION_UP");
  60. break;
  61. default:
  62. break;
  63. }
  64. }
  65. return 0;
  66. }
  67. void android_main(struct android_app* app)
  68. {
  69. LOGI("android_main");
  70. try {
  71. JNIEnv* Env;
  72. app->activity->vm->AttachCurrentThread(&Env, nullptr);
  73. JavaVM *vm;
  74. Env->GetJavaVM(&vm);
  75. AndroidAppState appState = {};
  76. app->userData = &appState;
  77. app->onAppCmd = handleAppCmd;
  78. app->onInputEvent = handleInputEvent;
  79. while (app->destroyRequested == 0) {
  80. for (;;) {
  81. int events;
  82. struct android_poll_source *source;
  83. const int timeoutMilliseconds =
  84. (!appState.Resumed &&
  85. app->destroyRequested == 0) ? -1 : 0;
  86. if (ALooper_pollAll(timeoutMilliseconds, nullptr, &events, (void **) &source) < 0) {
  87. break;
  88. }
  89. if (source != nullptr) {
  90. source->process(app, source);
  91. }
  92. }
  93. if(appState.NativeWindow != nullptr){
  94. ANativeWindow_setBuffersGeometry(appState.NativeWindow, 100, 100, WINDOW_FORMAT_RGBA_8888);
  95. ANativeWindow_Buffer buffer;
  96. if (ANativeWindow_lock(appState.NativeWindow, &buffer, nullptr) == 0) {
  97. uint32_t* bits = static_cast<uint32_t*>(buffer.bits);
  98. for (int i = 0; i < buffer.stride * buffer.height; i++) {
  99. bits[i] = 0xFFFFFFFF;//ABGR
  100. }
  101. ANativeWindow_unlockAndPost(appState.NativeWindow);
  102. }
  103. }
  104. LOGI("android_main loop");//why is 16
  105. //handle
  106. }
  107. app->activity->vm->DetachCurrentThread();
  108. } catch (const std::exception& ex) {
  109. LOGE("%s",ex.what());
  110. } catch (...) {
  111. LOGE("Unknow Error");
  112. }
  113. }

    main.cpp的主要方法如下:

1、android_main是入口函数,在该函数中主要定义循环,在循环中处理程序的核心流程。

2、handleAppCmd处理Activity的回调事件

3、handleInputEvent处理输入事件。

六、准备构建工具

    上面的文件准备好以后,下一步是准备构建工具,到Android Studio的工程中把gradle目录、gradle.properties、gradlew、gradlew.bat拷贝到当前工程的根目录,就可以使用gradlew命令构建工程了

七、构建工程

    本机已经准备好Android开发环境的情况下,在命令窗口切换到工程根目录,使用命令gradlew.bat assemble即可生成可以运行的apk

八、执行效果

   通过脚本构建得到apk后,安装到设备上即可在桌面上显示应用图标,点击图标可启动应用,下面是在模拟器上运行的效果,如下图所示。

    在图中显示的是白色背景,这是由于在循环中对窗口的图形缓存的每个像素点都赋值为白色,bits[i] = 0xFFFFFFFF;//ABGR 

九、完整工程路径

本示例的工程已上传到github,链接如下。

示例工程地址

上面是手动搭建的工程地址,下面是通过Android Studio生成的工程,也是基于NativeActivity

Android Studio自动生成的工程

本示例是一个基础工程,后续介绍的示例大部分基于上面的工程扩展而来。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/640503
推荐阅读
相关标签
  

闽ICP备14008679号