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

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

一、创建settings.gradle

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


pluginManagement {

    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}


dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

二、创建build.gradle

     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用于配置项目的权限及组件,本示例的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"作为库名称。

四、创建CMakeLists.txt

    定义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

Android Studio自动生成的工程

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/655998.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【go项目01_学习记录15】

重构MVC 1 Article 模型1.1 首先创建 Article 模型文件1.2 接下来创建获取文章的方法1.3 新增 types.StringToUint64()函数1.4 修改控制器的调用1.5 重构 route 包1.6 通过 SetRoute 来传参对象变量1.7 新增方法&#xff1a;1.8 控制器将 Int64ToString 改为 Uint64ToString1.9…

ubuntu24.04LVM扩容问题

目录 一、 开机前设置&#xff1a;扩展 二、 开机后设置&#xff1a;分区管理 通过gparted管理分区有效做法。 一、 开机前设置&#xff1a;扩展 虚拟机关机。打开虚拟机设置。 挂起状态是不能扩容的 这里选择扩容到40G 二、 开机后设置&#xff1a;分区管理 使用gpar…

基于Matlab的车道线检测系统 (文末有代码获取链接)【含Matlab源码 MX_001期】

运行环境&#xff1a;Matlab2014b 部分代码&#xff1a; %% 视频流循环处理 % 创建一个循环过程来对给定视频进行车道线检测 % 该循环使用之前初始化的系统对象 warningTextColors {[1 0 0], [1 0 0], [0 0 0], [0 0 0]}; while ~isDone(hVideoSrc) RGB step(hVideoSrc);% …

Java入门基础学习笔记43——包

什么是包&#xff1f; 包是用来分门别类的管理各种不同程序的&#xff0c;类似文件夹&#xff0c;建包有利于程序的管理和维护。 建包的语法规则&#xff1a; package cn.ensource.javabean;public class Car() {} 在自己的程序中调用其他包下的程序的注意事项&#xff1a; 1…

Web3探索加密世界:空投常见类型有哪些?附操作教程

每种空投类型都有独特的特征和目的&#xff0c;我们需要了解不同类型的加密空投。本文给大家介绍的是流行的加密货币空投类型&#xff0c;以及一般空投是如何做的。感兴趣的话请看下去。 一、空投常见类型 1、持有者空投 持有者空投向钱包中持有一定数量数字货币的人免费发放…

探索Python的包与模块:构建项目的基石

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、模块与包的基础认知 1. 模块的定义与创建 2. 包的组织与管理 二、模块与包的进阶使用…

【测评】香橙派 AIpro上手初体验

AI毋庸置疑是近年来&#xff0c;热度最高的技术之一&#xff0c;作为一名工程师拥抱新技术的同时不可或缺的需要一块强悍的开发板&#xff0c;香橙派 AIpro除了拥有好看的皮囊之外&#xff0c;还拥有一个有趣且充满魅力的灵魂。作为一位长期活跃在嵌入式开发领域的工程师&#…

SQL刷题笔记day5

SQL218题目 我的错误代码&#xff1a; select de.dept_no,de.emp_no,s.salary from employees e join dept_emp de on de.emp_no e.emp_no join salaries s on s.emp_no e.emp_no where de.dept_no not in dept_manager.dept_no #not in 好像不能直接这样用 这里报错 正确代…

在树莓派3B+中下载opencv(遇到的各种问题及解决)

目录 前言 1、删除原版本下新版本 2、python虚拟环境 3、python版本共存换链接——给版本降低 4、烧录之前版本的文件&#xff08;在清华源中可以找&#xff0c;不用官网的烧录文件就行&#xff1b; 比如&#xff1a;&#xff08;balenaEtcher&#xff09;重新烧录有问题…

面试二十六、c++语言级别的多线程编程

一、 多线程编程 ​​​​​ 这里的c语言级别的多线程和linux的有一定的区别&#xff0c;c语言级别提供的多线程比较严格&#xff0c;如果主线程结束了&#xff0c;但是子线程没有结束&#xff0c;进程就会异常终止&#xff0c;而linux不会&#xff0c;会继续执行。 二、模拟卖…

三十、openlayers官网示例解析Double click, Drag and Zoom——第二次点击鼠标拖拽缩放地图效果、取消地图双击放大事件

这篇展示了如何在地图上添加第二次按下鼠标移动鼠标实现拖拽缩放地图效果。 官网demo地址&#xff1a; Double click, Drag and Zoom 官网介绍文字的翻译如下&#xff1a; 示例比较简单&#xff0c;直接贴代码&#xff1a; const map new Map({//添加第二次点击拖拽缩放地图i…

es安装错误Exception in thread “main“ java.nio.file.NoSuchFileException解决方案

docker 启动es出现一下错误的解决方案 Exception in thread “main” java.nio.file.NoSuchFileException: /usr/share/elasticsearch/config/jvm.options Exception in thread "main" java.nio.file.NoSuchFileException: /usr/share/elasticsearch/config/jvm.op…

React@16.x(11)ref

目录 1&#xff0c;介绍1.1&#xff0c;得到的结果 2&#xff0c;参数类型2.1&#xff0c;字符串&#xff08;不再推荐&#xff09;2.2&#xff0c;对象2.3&#xff0c;函数函数调用时机 3&#xff0c;注意点 1&#xff0c;介绍 reference 引用。和 vue 中的 refs 类似&#x…

装机必备——360压缩安装教程

装机必备——360压缩安装教程 软件下载 软件名称&#xff1a;360压缩 软件语言&#xff1a;简体中文 软件大小&#xff1a;3.38M 系统要求&#xff1a;Windows7或更高&#xff0c; 32/64位操作系统 硬件要求&#xff1a;CPU2GHz &#xff0c;RAM4G或更高 下载通道①迅雷云盘丨…

python自动化-自动化网络配置工具v2(可巡检,可批量配置)

在日常工作中遇到需要配置相同配置的场景&#xff0c;网络工程师一个个去登陆配置会让工作效率显得没那么高效。 但是随着科技发展&#xff0c;人们不断的学习&#xff0c;我们似乎可以使用一些软件或者脚本来帮助我们实现巡检任务或者配置任务。 今天我想给大家分享一款我自己…

PCIe协议之-DLLP详解

✨前言&#xff1a; &#x1f31f;数据链路层的功能 数据链路层将从物理层中获得报文&#xff0c; 并将其传递给事务层&#xff1b; 同时接收事务层的报文&#xff0c; 并将其转发到物理层; 核心的功能有以下三点 1.保证TLP在 PCIe 链路中的正确传递; 2.数据链路层使用了容错…

YOLOv10:实时端到端目标检测

Ao Wang Hui Chen∗  Lihao Liu Kai Chen Zijia Lin  Jungong Han Guiguang Ding Tsinghua University Corresponding Author. 文献来源&#xff1a;中英文对照阅读 摘要 在过去的几年里&#xff0c;YOLO 因其在计算成本和检测性能之间的有效平衡而成为实时目标检测领…

GitLab的安装及基础操作

1. 项目目标 &#xff08;1&#xff09;熟练使用rpm包安装gitlab &#xff08;2&#xff09;熟练配置gitlab &#xff08;3&#xff09;熟练创建gitlab群组、成员、项目 &#xff08;4&#xff09;熟练使用gitlab推送和拉取代码 2. 项目准备 2.1. 规划节点 主机名 主机I…

景源畅信电商:做抖音运营怎么开始第一步?

在数字化时代的浪潮中&#xff0c;抖音作为一款短视频平台迅速崛起&#xff0c;成为许多人表达自我、分享生活的重要舞台。随着用户量的激增&#xff0c;如何做好抖音运营&#xff0c;尤其是迈出成功的第一步&#xff0c;成为了众多内容创作者和品牌主们关注的焦点。接下来&…

鹏哥C语言复习——调试

目录 什么是调试&#xff1f; Debug和Release&#xff1a; 调试方法&#xff1a; 环境准备&#xff1a; 调试快捷键介绍&#xff1a; 调试快捷键注意事项&#xff1a; 监视与内存查看&#xff1a; 数组元素的监视&#xff1a; 编译常见错误归类&#xff1a; 编译型错…