赞
踩
使用NDK编写的本地代码具有高性能等特性,在游戏、图形处理等领域有广泛应用,下面介绍如何手动搭建一个纯C++版的Android项目,通过该项目可以理解Android的项目结构。
Android项目是基于Gradle构建的,首先得有settings.gradle文件,正常情况下该文件主要用于配置子模块,但是这里没有子模块,只配置了插件管理和依赖的远程仓库,内容如下:
pluginManagement { repositories { gradlePluginPortal() google() mavenCentral() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
build.gradle 是Gradle的构建脚本,它用于定义项目的构建配置和依赖关系,可以指定项目的编译版本、依赖库、插件等信息,以及定义构建任务和构建流程。本示例的build.gradle内容如下。
- plugins {
- id 'com.android.application' version '7.2.2'
- }
-
-
- android {
- compileSdk 29
- ndkVersion "25.1.8937393"
- buildToolsVersion = "30.0.3"
- namespace 'com.sino.nativesample'
-
- defaultConfig {
- applicationId "com.sino.nativesample"
-
- minSdkVersion 28 // for Vulkan, need at least 24
-
- versionCode 1
- versionName "1.0"
-
- externalNativeBuild {
- cmake {
- cppFlags '-std=c++17'
- arguments "-DANDROID_STL=c++_shared"
- abiFilters 'armeabi-v7a', 'arm64-v8a'
- }
- }
- }
- sourceSets {
- main {
- manifest.srcFile 'AndroidManifest.xml'
- res.srcDir 'res'
- }
-
- }
-
- buildTypes {
- release {
- minifyEnabled false
- }
- }
-
- externalNativeBuild {
- cmake {
- path "CMakeLists.txt"
- }
- }
-
- }
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包含的内容如下。
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:installLocation="auto"
- android:versionCode="1"
- android:versionName="1.0">
-
-
- <application
- android:allowBackup="true"
- android:hasCode="false"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher">
-
- <activity
- android:name="android.app.NativeActivity"
- android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode|density"
- android:excludeFromRecents="false"
- android:launchMode="singleTask"
- android:resizeableActivity="false"
- android:screenOrientation="landscape"
- android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
- android:exported="true"
- tools:ignore="NonResizeableActivity">
- <!-- Tell NativeActivity the name of the .so -->
- <meta-data
- android:name="android.app.lib_name"
- android:value="native" />
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- </manifest>
AndroidManifest.xml主要内容解析如下。
1、在application标签中,指定了应用的名称和图标,这些信息依赖于res资源目录。
2、声明Activity组件,这里使用NativeActivity,这是框架提供的Activity,能直接于Native层交互,有了该Activity后,就可以不用在项目中自定义其它Activity了。NativeActivity需要指定加载Native层的库,需要在标签lib_name指定库名称,这里使用"native"作为库名称。
定义Activity后,接下来是为它生成对应的库文件,库文件由CMakeLists.txt构建,内容如下。
cmake_minimum_required(VERSION 3.18.1) # Declares and names the project. project("Native") option(ENABLE_ASAN "enable address sanitizer" ON) add_definitions(-DXR_USE_PLATFORM_ANDROID -DXR_OS_ANDROID -D__ANDROID__ ) #define macro set(CXX_STANDARD "-std=c++17") if (MSVC) set(CXX_STANDARD "/std:c++latest") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} /W0 /Zc:__cplusplus") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_STANDARD} -fstrict-aliasing -Wno-unknown-pragmas -Wno-unused-function -Wno-deprecated-declarations") endif() include_directories(${ANDROID_NDK}/sources/android/native_app_glue) add_library(native SHARED main.cpp ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) #android set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate" ) #link all the module target_link_libraries( native PRIVATE android log)
在add_library指定库名称为native,正好于NativeActivity的lib_name对应起来,源文件为main.cpp,android_native_app_glue.c是NDK提供的文件,封装了NativeActivity的回调函数,main.cpp正是基于这些回调进行开发。
在main.cpp中可以开始基于C++进行程序开发了,这里的main.cpp的主要内容如下。
-
- #include <android_native_app_glue.h>
- #include <jni.h>
- #include <exception>
- #include <android/log.h>
-
- #define LOG_TAG "Native"
- #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
- #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
-
- struct AndroidAppState {
- ANativeWindow* NativeWindow = nullptr;
- bool Resumed = false;
- };
-
-
- static void handleAppCmd(struct android_app* app, int32_t cmd) {
- AndroidAppState* appState = (AndroidAppState*)app->userData;
-
- switch (cmd) {
- case APP_CMD_START: {
- break;
- }
- case APP_CMD_RESUME: {
- appState->Resumed = true;
- break;
- }
- case APP_CMD_PAUSE: {
- appState->Resumed = false;
- break;
- }
- case APP_CMD_STOP: {
- break;
- }
- case APP_CMD_DESTROY: {
- appState->NativeWindow = NULL;
- break;
- }
- case APP_CMD_INIT_WINDOW: {
- appState->NativeWindow = app->window;
-
- break;
- }
- case APP_CMD_TERM_WINDOW: {
- appState->NativeWindow = NULL;
- break;
- }
- }
- }
-
-
-
- static int32_t handleInputEvent(struct android_app* app, AInputEvent* event)
- {
- LOGI("android_main handleInputEvent");
- if(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION
- && AInputEvent_getSource(event) == AINPUT_SOURCE_TOUCHSCREEN){
- int32_t action = AMotionEvent_getAction(event);
- float x = AMotionEvent_getX(event,0);
- float y = AMotionEvent_getY(event,0);
- switch(action){
- case AMOTION_EVENT_ACTION_DOWN:
- LOGI("android_main AMOTION_EVENT_ACTION_DOWN (x %f, y %f)", x,y);
- break;
- case AMOTION_EVENT_ACTION_MOVE:
- LOGI("android_main AMOTION_EVENT_ACTION_MOVE");
- break;
- case AMOTION_EVENT_ACTION_UP:
- LOGI("android_main AMOTION_EVENT_ACTION_UP");
- break;
- default:
- break;
- }
-
- }
-
- return 0;
- }
-
-
- void android_main(struct android_app* app)
- {
- LOGI("android_main");
- try {
- JNIEnv* Env;
- app->activity->vm->AttachCurrentThread(&Env, nullptr);
-
- JavaVM *vm;
- Env->GetJavaVM(&vm);
-
- AndroidAppState appState = {};
-
- app->userData = &appState;
- app->onAppCmd = handleAppCmd;
- app->onInputEvent = handleInputEvent;
-
-
- while (app->destroyRequested == 0) {
- for (;;) {
- int events;
- struct android_poll_source *source;
- const int timeoutMilliseconds =
- (!appState.Resumed &&
- app->destroyRequested == 0) ? -1 : 0;
- if (ALooper_pollAll(timeoutMilliseconds, nullptr, &events, (void **) &source) < 0) {
- break;
- }
- if (source != nullptr) {
- source->process(app, source);
-
- }
- }
-
- if(appState.NativeWindow != nullptr){
- ANativeWindow_setBuffersGeometry(appState.NativeWindow, 100, 100, WINDOW_FORMAT_RGBA_8888);
- ANativeWindow_Buffer buffer;
- if (ANativeWindow_lock(appState.NativeWindow, &buffer, nullptr) == 0) {
- uint32_t* bits = static_cast<uint32_t*>(buffer.bits);
- for (int i = 0; i < buffer.stride * buffer.height; i++) {
- bits[i] = 0xFFFFFFFF;//ABGR
- }
-
- ANativeWindow_unlockAndPost(appState.NativeWindow);
- }
- }
- LOGI("android_main loop");//why is 16
-
- //handle
-
- }
- app->activity->vm->DetachCurrentThread();
- } catch (const std::exception& ex) {
- LOGE("%s",ex.what());
- } catch (...) {
- LOGE("Unknow Error");
- }
- }
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
本示例是一个基础工程,后续介绍的示例大部分基于上面的工程扩展而来。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。