使用CMake来进行Android NDK开发

前言


Android NDK 开发可能在平时的项目开发中不常用到,但是这并不代表其不重要,  

相反NDK开发是Android开发人员的进阶过程中必须要掌握的技能。  

Android NDK 是一组允许将C或C++(原生代码)嵌入到Android应用中的工具。  

如果开发者在需要以下操作的时候,使用NDK开发特别有用:  

  • 在平台之间移植其应用

  • 从设备获取卓越性能以用于计算密集型应用,例如游戏或物理模拟。  

  • 重复使用现有库或者提供自己库供重复使用。  

除此之外,对于 ndk 的学习,也有助于加深开发者在阅读框架的源码理解。  

ndk 开发有两种编译方式,一种是通过 ndk-build 来构建;  

一种是通过 CMake 构建原生库。通过 CMake 构建原生库是 Google 新提出来的方式,比较方便、强大。


准备


通过 cmake 进行ndk开发首先有个要求,需要 Android Studio 的版本是2.2以上版本(包含2.2) ,Gradle 的版本需要升到2.2.0及以上。  

满足上面的条件下,我们需要下载 ndk 和构建工具。如下图:

红线标记的三个工具下载好就行。CMake 和 NDK 就不说了,都好理解,  LLDB 呢是一种调试程序,用来调试原生代码的。  


向项目添加原生代码


上面的准备工作做完之后就可以向项目中添加原生代码,构建原生库进行ndk开发了。  

这边有个讨论,向项目中添加原生代码有两种情况:  

  • 一种是新建一个新的项目支持 C/C++;  

  • 一种是在已有项目中添加原生代码。  

所以这边相应的也有两种方式。先说第一种方式。

创建支持C/C++的新项目

创建支持原生代码的新项目跟平常开发中创建一个新项目没有太大的区别,说一下不同的地方。

  • 前者在向导的Config your new project界面需要选中Include C++ Support 复选框。如图1:

  • 前者在向导的Customize C++ Support 界面会有C++ Standard 的选择  意思是你希望使用哪种 C++ 标准。  选择 ToolchainDefault 会使用默认的 CMake 设置。  这里我们选择默认的。  

Exceptions Support:如果你希望启用对 C++ 异常处理的支持,请选中此复选框。  

如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

Runtime Type Information Support:如果你希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的   cppFlags 中,Gradle 会将其传递到 CMake。  这里自己看需求选择勾不勾选,这边演示的demo选择勾选。如图2:

创建项目完成之后,在 as 的左侧项目结构目录中的 app 应用模块中可以看到 cpp 文件夹,cpp 文件件里面存放有属于项目的所有原生源文件、标头和预构建库。对于新项目,Android Studio 会创建一个示例 C++ 源文件 native-lib.cpp 除了 cpp 文件夹之外,我们还看一个 CMakeLists.txt 的这样一个文件。这个文件是CMake的构建脚本,下面会详细说,这里就暂且不说。之前有说过新建支持 C/C++ 的项目会提供了一个示例的 c++ 源文件native-lib.cpp,存放在 cpp 文件夹中  ,我们点开看看内容

#include <jni.h> 
#include <string> 

extern "C" JNIEXPORT jstring JNICALL 
Java_com_example_ndkdemo_MainActivity_stringFromJNI( 
        JNIEnv *env, 
        jobject /* this */) { 
    std::string hello = "Hello from C++"; 
    return env->NewStringUTF(hello.c_str()); 
}

恩,是使用 C++ 写的一个方法,返回一个"Hello from C++",我们跟踪进去这个方法,看看哪里调用。

public class MainActivity extends AppCompatActivity { 

    // Used to load the 'native-lib' library on application startup. 
    static { 
        System.loadLibrary("native-lib"); 
    } 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 

        // Example of a call to a native method 
        TextView tv = (TextView) findViewById(R.id.sample_text); 
        tv.setText(stringFromJNI()); 
    } 

    /** 
     * A native method that is implemented by the 'native-lib' native library, 
     * which is packaged with this application. 
     */ 
    public native String stringFromJNI(); 
}

我们发现,这个原生方法是在MainActivity中调用的,返回的字符串设置给了TextView。  

通过这两部分代码我们发现,原生方法通过native关键字来表示、在使用原生库之前,需要通过 System.loadLibrary(“库名称”)加载、原生方法的方法名命名等常见要点。这边就一笔略过。因为本文重点是CMake构建原生库。 所以ndk的一些基础知识,以及JNI等,就不细说。本文假设读者知道这些。这样的一个新的支持原生代码的项目创建之后,并且还自带demo,开发者就可以很方便在上面进行开发。 假设现在要创建一个原生库进行ndk开发,我们在cpp文件夹下new一个C/C++ Source file ,在你new出来的文件编写你的C/C++代码逻辑,然后在CMakeLists.txt配置文件上稍作配置,即可。下面会详述CMakeLists.txt 的配置。 

向现有项目添加原生代码

向现有项目添加原生代码,进行ndk开发,这里选择一个我的项目,空闲时间写的一个APP,  一款仿今日头条的资讯类软件(求star)-

PalmRead

https://github.com/MRYangY/PalmRead

主要有三个大步骤。第一步:创建新的原生源文件;第二步:创建CMake构建脚本;第三步:将Gradle关联到原生库。现在我们来看第一步,创建新的原生源文件,首先我们要先在应用模块src/main 目录下创建一个cpp文件夹用来存放原生源文件等。然后在cpp文件夹下面创建C/C++源文件New > C/C++ Source File。如图:

创建了一个名为 palmread-lib 文件。第二步,我们创建CMake构建脚本,也就是CMakeLists.txt 文件,在应用模块下new一个file文件,命名为 CMakeLists.txt 即可,注意哦,这个文件的名称不能搞错哦。CMakeLists.txt 创建好打开后,发现没有任何内容,这就对了。。需要我们自己配置的。不像是通过第一种新创建支持原生代码的项目那种,还给你写好,不过我们可以参考第一种方式下,系统给我们默认生成的内容。咱们看一下第一种方式下,系统默认生成的 CMakeLists.txt 文件的内容是什么样子吧。如下:

# Sets the minimum version of CMake required to build the native library. 

cmake_minimum_required(VERSION 3.4.1) 

# Creates and names a library, sets it as either STATIC 
# or SHARED, and provides the relative paths to its source code. 
# You can define multiple libraries, and CMake builds them for you. 
# Gradle automatically packages shared libraries with your APK. 

add_library( # Sets the name of the library. 
             native-lib 

             # Sets the library as a shared library. 
             SHARED 

             # Provides a relative path to your source file(s). 
             src/main/cpp/native-lib.cpp ) 

# Searches for a specified prebuilt library and stores the path as a 
# variable. Because CMake includes system libraries in the search path by 
# default, you only need to specify the name of the public NDK library 
# you want to add. CMake verifies that the library exists before 
# completing its build. 

find_library( # Sets the name of the path variable. 
              log-lib 

              # Specifies the name of the NDK library that 
              # you want CMake to locate. 
              log ) # Specifies libraries CMake should link to your target library. You 
# can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. 

target_link_libraries( # Specifies the target library. 
                       native-lib 

                       # Links the target library to the log library 
                       # included in the NDK. 
                       ${log-lib} )

通过图片我们看到,其实内容也没什么。就cmake_minimum_required()add_library()、find_library、target_link_libraries()。这几个CMake命令,每个CMake命令都有英文介绍很好理解。比如add_library创建和命名一个库,这边名称我们就填palmread-lib,种类分为static和shared,具体区别嘛移步:

static和shared的区别

https://stackoverflow.com/questions/2649334/difference-between-static-and-shared-libraries

这里我们选择 SHARED,然后就是提供一个 library 的相对路径。只有在 CMakeLists.txt 文件中配置了该命令 ,才能找到编译这个库。其他的一些 CMake 命令大家自行搜索了解,都不是很难,很好理解,这里篇幅有限就不细说了。那现在我们可以按照系统提供的模板,基于自己的项目写了一份 CMakeLists.txt 文件,代码如下:

cmake_minimum_required(VERSION 3.4.1)  

add_library(  
             palmread-lib  

             SHARED  

             src/main/cpp/palmread-lib.c )  

find_library( # Sets the name of the path variable.  
              log-lib  

              # Specifies the name of the NDK library that  
              # you want CMake to locate.  
              log )  

target_link_libraries( # Specifies the target library.  
                       palmread-lib  

                       # Links the target library to the log library  
                       # included in the NDK.  
                       ${log-lib} )

随后在 Java 类中加载 palmread-lib 库,这里我这边写了一个 NdkHelper 类,专门用来定义 native 方法的:  

public class NdkHelper {  


    static {  
        System.loadLibrary("palmread-lib");  
    }  

    public static native String GetStringFromC(String str);  
}

这一步做完后,第二步也算告一段落了,现在我们来看第三步:将 Gradle 关联到你的原生库。 将 gradle 关联到原生库有两种方式,这里就先说第一种比较简单的方式,第二种下篇博文再说。 第一种方式是通过as的快捷键来实现的,右键点击你想要关联到原生库的模块(例如 app 模块), 并从菜单中选择 Link C++ Project with Gradle。 BuildSystem选择CMake,projectpath 就是CMakeLists.txt文件的路径。点击ok完成。 

你会在应用模块的build.gradle文件中发现,android闭包里出现了

externalNativeBuild { 
        cmake { 
            path 'CMakeLists.txt' 
        } 
    }

这个语句就跟我们之前说的第二种方式有关系,这里先不说了,后面博文再说。

同步项目后,突然想到之前新建的palmread-lib.c文件好像还没有写C代码呢,呀,这脑子,没事,我们现在写。  写些什么呢?这样吧我们用C来实现这样一个功能,接受一个字符串参数,然后对字符串进行拼接修改操作  ,使得返回一个新的字符串。  

代码如下:

#include "jni.h" #include <stdlib.h> 
#include <string.h> 

JNIEXPORT jstring JNICALL 
Java_com_example_yangyu_palmread_Logic_NdkHelper_GetStringFromC(JNIEnv *env, jclass type, 
                                                                jstring str_) { 
    const char *a = (*env)->GetStringUTFChars(env, str_, 0); 
    // TODO 
    char * b = " from c" ; 
    char *result = malloc(strlen(a)+strlen(b)+1); 
    strcpy(result, a); 
    strcat(result, b); 

    (*env)->ReleaseStringUTFChars(env, str_, a); 

    return (*env)->NewStringUTF(env, result); 
}

代码写好之后,就是去调用了,为了方便测试,我们选在在应用的首页activity去调用这个原生方法。  通过toast显示方法返回的值。  

如图:

通过代码,我们可以看出,我们调用了,NdkHelper.GetStringFromC()这个原生方法,传入一个“欢迎来到PalmRead”字符串作为参数。按照之前的 C 逻辑,应该会返回一个新的字符串为  “欢迎来到PalmRead from c”。  那我们运行程序试试效果吧。  

O(∩_∩)O哈!,我们发现toast显示的值确实是我们设想的返回值。这样就架起了Java与原生代码之间的桥梁。 现在我们可以应用模块的build/outputs/apk目录下,打开我们的apk,我们会发现,我们创建的原生文件,编译成了原生库"libpalmread-lib.so"也打包进了apk文件。如图:  


结语


好了,关于CMake来进行 Android NDK 开发,大致的流程就是这样。CMakeLists 的一些命令 还有Gradle关联原生库的第二种通过编辑build.gradle文件的方式 , 通过 CMake 来进行ndk开发之补充篇

http://blog.csdn.net/qq_34902522/article/details/78144127

 有详细说明。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android NDK开发是指利用NDK(Native Development Kit)将C/C++开发的代码编译成so库,然后通过JNI(Java Native Interface)让Java程序调用。在Android开发中,默认使用的是Android SDK进行Java语言的开发,而对于一些需要使用C/C++的高性能计算、底层操作或跨平台需求的场景,可以使用NDK进行开发。 在Android Studio中进行NDK开发相对于Eclipse来说更加方便,特别是在Android Studio 3.0及以上版本中,配置更加简化,并引入了CMake等工具,使得开发更加便捷。首先要进行NDK开发,需要配置环境,包括导入NDK、LLDB和CMake等工具。可以通过打开Android Studio的SDK Manager,选择SDK Tools,在其中选中相应的工具进行导入。 在项目的build.gradle文件中,可以配置一些NDK相关的参数,例如编译版本、ABI过滤器等。其中,可以通过externalNativeBuild配置CMake的相关设置,包括CMakeLists.txt文件的路径和版本号。此外,在sourceSets.main中还可以设置jniLibs.srcDirs,指定so库的位置。 在进行NDK开发时,可以在jni文件夹中编写C/C++代码,并通过JNI调用相关的函数。通过JNI接口,可以实现Java与C/C++之间的相互调用,从而实现跨语言的开发。 综上所述,Android NDK开发是指利用NDK将C/C++开发的代码编译成so库,并通过JNI实现与Java的相互调用。在Android Studio中进行NDK开发相对方便,可以通过配置环境和相应的参数来进行开发。<span class="em">1</span><span class="em">2</span><span class="em">3</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值