-
2021-11-16 14:46:46
NDK开发学习笔记。
1、在Java代码里写native方法。可以在Activity中写native方法,也可以重新创建一个Java类写。我这里是重新创建一个Java类写的。
public class GetString { //native方法 public native String getString(); }
2、在main目录下创建一个jni目录。用于存放头文件(.h)和源文件(.c)。
右击jni目录 -> New -> C/C++ Source File创建头文件和源文件
在弹出的弹窗中填写头文件和源文件的名称,勾选Create an associated header 可以同时生成头文件和源文件,如果不勾选,就只创建源文件。创建完,就可以在这两个文件中写C代码了。个人不是很喜欢这种方式创建头文件和源文件。
下面是第二种创建方式。
创建好jni目录后,打开Terminal,输入cd app/src/main/java 命令回车,
再输入 javah com.example.ndkdemo.GetString 回车。其中com.example.ndkdemo.GetString为你写native方法的类的全路径。
cd app/src/main/java javah com.example.ndkdemo.GetString
稍等片刻,会在app/src/main/java路径下生成一个头文件
把这个文件移动到jni目录下,打开该文件。
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_ndkdemo_GetString */ #ifndef _Included_com_example_ndkdemo_GetString #define _Included_com_example_ndkdemo_GetString #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_ndkdemo_GetString * Method: getString * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_ndkdemo_GetString_getString (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
JNIEXPORT jstring JNICALL Java_com_example_ndkdemo_GetString_getString
(JNIEnv *, jobject);就是native方法在jni层生成的对应的方法。注意Java_com_example_ndkdemo_GetString_getString这个方法名不要修改。这个方法的命名格式为:Java_类的全路径名_方法名,中间用_隔开。使用第一种方式创建头文件和源文件,需要自己写方法名,很容易出错。然后,右击jni目录 -> New -> C/C++ Source File创建源文件,只要不勾选Create an associated header就可以了。
3、将头文件中的方法 JNIEXPORT jstring JNICALL Java_com_example_ndkdemo_GetString_getString
(JNIEnv *, jobject);复制到源文件中,去掉JNIEXPORT和JNICALL就可以去实现这个方法了。注意,这里需要将头文件引进来 #include "GetString.h"。#include "GetString.h" jstring Java_com_example_ndkdemo_GetString_getString (JNIEnv *env, jobject jobj) { return (*env)->NewStringUTF(env, "asdfghjkl"); }
4、在app目录下创建CMakeLists.txt
CMakeList.txt的内容如下
# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html # 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. GetString # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/jni/GetString.c src/main/jni/GetString.h ) # 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. #这个函数的意思是给系统的log库起个别名,命名为log-lib 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. #这个函数的意思是刚刚之前的命名的库一起link进去 target_link_libraries( # Specifies the target library. # Hello # JavaCallC GetString # Links the target library to the log library # included in the NDK. ${log-lib})
5、在build.gradle(app)的android->defaultConfig节点下添加ndk配置信息
ndk { abiFilters "armeabi-v7a" //cpu类型,会生成对应cpu的so文件 }
在android节点下配置CMakeLists信息
externalNativeBuild { cmake { path "CMakeLists.txt" } }
6、加载动态链接库
public class GetString { //加载动态链接库 static { System.loadLibrary("GetString"); } //native 方法 public native String getString(); }
7、使用
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); GetString getString = new GetString(); findViewById(R.id.btnGetString).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String string = getString.getString(); Log.d(TAG, "string -> " + string); } }); } }
更多相关内容 -
如何在Android Studio下进行NDK开发
2021-01-03 11:45:29在AS中进行NDK开发之前,我们先来简单的介绍几个大家都容易搞懵的概念: 1. 到底什么是JNI,什么是NDK? 2. 何为“交叉编译”? 先看什么是JNI?JNI的全称就是Java Native Interface,即java本地开发接口。... -
Android NDK 开发教程
2021-01-05 09:58:25Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google称为“NDK”。 众所周知,Android程序运行在Dalvik虚拟机中,NDK允许用户使用类似C / C++之类的原生代码语言执行部分程序... -
《Android NDK 开发教程 - v1.0》PDF
2018-08-21 10:01:52《Android NDK 开发教程 - v1.0》 -
Android_NDK_android端_图片处理_android_NDK_ndk开发_
2021-10-03 13:04:10Android端开发,测试Java和C++处理图片的性能 -
NDK开发教程
2018-05-08 13:55:48NDK开发 详细开发教程 NDK开发 详细开发教程 NDK开发 详细开发教程 -
非常详细的android ndk开发样例
2015-06-15 17:50:34非常非常详细的NDK入门教程,如果有什么问题,可以去我博客了解,或者是联系我。 -
android NDK 开发指南
2018-04-04 20:31:34android NDK 开发指南 android NDK 开发指南 android NDK 开发指南 android NDK 开发指南 -
NDK 开发示例源码
2019-01-13 17:11:40ndk 开发源码,如积分不够,可至GitHub平台下载。https://github.com/flueky/Flueky-Sample/tree/master/ndk-sample -
详解如何使用Android Studio 进行NDK开发和调试
2021-01-06 01:03:01个人认为使用Android Studio作NDK开发是必然趋势,所以本文将简单介绍如何在Android Studio上实现NDK开发。 简介 JNI JNI 是Java Native Inteface的缩写,是Java中定义的一种用于连接Java和C/C++接口的一种实现方式... -
Android NDK开发入门
2021-01-21 20:15:49神秘的Android NDK开发往往众多程序员感到兴奋,但又不知它为何物,由于近期开发应用时,为了是开发的.apk文件不被他人解读(反编译),查阅了很多资料,其中有提到使用NDK开发,怀着好奇的心理,通过在线视频教育... -
android studio 3.0.1 NDK开发完整流程
2018-05-24 17:43:04解压密码123654,android studio 3.0.1 NDK开发完整流程,从0开始教你一步步完成开发,有完整DEMO,亲自测试,直接生成SO文件到指定路径,APP可直接使用。详细步骤参见:... -
Android NDK开发文档本地版
2019-03-13 17:04:09Android JNI开发指南 Android.mk Application.mk- -
Android之NDK开发入门
2020-05-11 14:21:51NDK全称Native Development Kit,是Android的一个工具开发包,能够快速开发C,C++的动态库,并自动将so和应用打包成APK。而NDK的使用场景就是通过NDK在Android中使用JNI,那么JNI又是啥呢?JNI全称是Java Native ...注意:本文操作环境为mac,Android Studio版本3.5
Android之NDK开发入门
前言
NDK全称Native Development Kit,是Android的一个工具开发包,能够快速开发C,C++的动态库,并自动将so和应用打包成APK。而NDK的使用场景就是通过NDK在Android中使用JNI,那么JNI又是啥呢?JNI全称是Java Native Interface,即Java的本地接口,JNI可以使得Java与C,C++语言进行交互。这么一来,通过NDK和JNI,就可以很方便的在Android的开发环境中使用c,c++的开源库。
一、安装和配置NDK
1.安装NDK
可通过Android Studio下载和官网下载,下面为Android Studio下载
- 打开Android Studio,点击Android Studio->Preferences,搜索SDK,然后在SDK Tools中勾选LLDB,NDK,Cmke进行下载。其中LLDB是调试本地代码的工具,可调试C++代码
- 打开File->Project Structure,然后在SDK Location中配置NDK路径,点击右下角就会出现我们刚刚下载的NDK路径,点击Default NDK路径即可,如果是在官网中下载的,可以根据自己下载的路径进行配置
2.配置NDK环境
-
启动终端,进入当前用户的home目录
cd ~(注意中间的空格)
-
创建.bash_profile(假如之前已经创建好了,执行这个命令行不会对原本的文件内容造成影响)
终端输入:touch .bash_profile
-
查看、编辑.bash_profile
如果忘记了NDK的目录,可以通过Android Studio的File->Project Structure中的NDK location查看
export NDK_ROOT=/Users/{你的用户名}/Library/Android/sdk/ndk-bundle export PATH=$PATH:$NDK_ROOT
-
保存然后关闭.bash_profile文件
-
更新刚配置的环境变量
终端输入: source .bash_profile
-
重新打开终端,检查是否配置成功(如果不成功,记得先关闭当前终端然后打开)
终端输入: ndk-build
如果出现下列结果的即为成功
Android NDK: Could not find application project directory ! Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
二、CMake的方式编译生成so库
1. Android Studio自动生成的示例
1.1 新建Native C++工程
在Android Studio新建一个Native C++的工程,然后填写项目名,并选择Toolchain Default使用默认的C++标准。
1.2 分析AS创建和添加的文件
当点击Finish后,Android Studio会自动添加NDK开发相关的文件。cpp是AS帮我们自动生成的,里面有两个文件:
- CMakeLists.text:构建脚本
- Native-lib.cpp:示例C++源文件
另外还对MainActivity,build.gradle进行了一些改动。下面将分析这四个重要的文件
-
CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1) # 这里会把 native-lib.cpp转换成共享库,并命名为 native-lib add_library( # 库的名字 native-lib # 设置成共享库 SHARED # 库的源文件(由于native-lib.app和CMakeLists.txt同处于一个包,因此可以直接写文件名 # 不然的话需要写成src/main/cpp/native-lib.cpp) native-lib.cpp) # 如果需要使用第三方库,可以使用 find-library来找到 find_library( # so库的变量路径名字,在关联的时候使用 log-lib # 你需要关联的so名字 log) # 通过link将源文件的库和第三方库添加进来 target_link_libraries( # 源文件库的名字 native-lib # 添加第三方库的变量名 ${log-lib})
需要注意的是第三库是路径变量名,因此需要用${}方式引用
-
Native-lib.cpp
一看就是C++的代码,这个方法的作用其实就是返回一个字符串“Hello from C++”。需要重点注意的是这个方法的命名格式,包名,类名,方法名其实就是在Java代码中定义这个native方法stringFromJNI所在的包名和类名。
-
MainActivity
public class MainActivity extends AppCompatActivity { //加载so库 static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = findViewById(R.id.sample_text); //直接调用native方法 tv.setText(stringFromJNI()); } //native方法 public native String stringFromJNI(); }
在这里我们验证了native-lib.cpp里面方法的命名格式,在MainActivity确实有一个stringFromJNI的方法。在这里我们首先需要加载so库,so库的名称就是我们在CmakeList.txt定义的库的名字。然后通过定义的native方法就可以调用C++层的Java_com_example_ndkdemo_MainActivity_stringFromJNI方法。
-
build.gradle
看到这,你可以先运行下,看看AS自动生成的JNI例子是否运行成功了。想必当你看到成功运行后是不是已经热血沸腾,迫不及待的想自己尝试下。并且一个so库中不可能只有一个方法,因此接下来就让我们照猫画虎的添加自己编写的c++文件。
2. 自己编写的so库
-
创建Java对应的加载类。在这里我们不准备在MainActivity中加载.so库,而是新建了一个JNI工具类来完成加载.so库和声明native方法的任务。然后将MainActivity中的native方法复制过来,并且新建了一个helloFromJNI的方法。另外为了在新项目中使用该so库,我们将so库的名字更改为hello,下面也会在CMakeList.txt中更改so库的名称。(可以发现下面的native方法是红色的,这是因为我们还没有在C++层中实现这两个方法)
-
添加需要的C/C++文件。我们直接在cpp中新建一个就行,cpp->右键->new->c/c++ source File。然后就可以命名一个c/c++文件了,并且勾选create an associated header,表示在创建才C/C++文件的同时会创建对应的头文件。
(1) 编写头文件hello.h,你可以将头文件看成Java的接口,在这里我们需要声明方法。
#ifndef NDKDEMO_HELLO_H #define NDKDEMO_HELLO_H //声明接口 extern const char* helloWorld(); #endif //NDKDEMO_HELLO_H
(2) 然后在hello.cpp中实现这个头文件,可以发现在这里我们只是简单的返回了一个hello world的字符串
#include "hello.h" extern const char* helloWorld(){ return "Hello World"; }
-
在native-lib中引入hello.h头文件。这个操作跟Java中的导包有点类似,并且我们新建了一个在前面JniUtil中声明的native方法,注意的是由于我们将加载so库和声明native方法都放到了JniUtil中,因此我们需要更改之前stringFromJNI的包名和方法名。
#include <jni.h> #include <string> #include "hello.h" extern "C" JNIEXPORT jstring JNICALL Java_com_example_ndkdemo_util_JniUtil_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); } extern "C" JNIEXPORT jstring JNICALL Java_com_example_ndkdemo_util_JniUtil_helloFromJNI( JNIEnv *env, jobject /* this */) { std::string helloStr = helloWorld(); return env->NewStringUTF(helloStr.c_str()); }
-
CMakeList.txt中加入hello.cpp的路径添加
这里需要注意的是,如果是多次使用add_library,则会生成多个so库。在这里我们只是将多个本地文件编译到一个so库中,因此只需要在原本的add_library中添加hello的相对路径。并且为了方便在新项目中使用该so库,在这里我将之前native-lib的名字改成了hello。因此生成so库的时候也会生成libhello.so文件(生成so库的时候会自动加上lib的前缀)
-
在MainActivity中使用调用JniUtil中的native方法
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = findViewById(R.id.sample_text); //直接调用native方法 tv.setText(JniUtil.helloFromJNI()); } }
-
运行项目
-
查看so库。在app->intermediates->cmake中就会生成对应类型的so库,因为生成so库的时候会自动加上lib的前缀。
三、使用CMake引入第三方so库
通常情况下,引入第三方.so库会有两种场景:
- JNI规范的so。比如返回的是JNI直接支持的类型,比如说上述NdkDemo中的native-lib.cpp中的两个方法。
- 只提供.so库和头文件。第三方共享.so库一般情况下只提供.so文件和头文件,就是没有将C++文件直接暴露给JAVA层,也没有编写JNI方法的C++文件,比如上述的hello.cpp,这个C++文件中的方法并不是JNI直接支持的类型。
在实际开发中,更常见的是第二种场景。两种场景的引入方法不同,第一种可以直接引入第三方so库,而第二种需要引入自己的so库,然后将自己的so库与第三方so库和头文件进行相关联。接下来我们就来分析这两种引入方式。
1. 引入JNI规范的so
-
新建一个普通的Android项目。引入JNI规范的.so库并不需要Native C++类型的项目。
-
在main中新建一个jniLibs。我们在app->src->main中新建立一个jniLibs,然后将上面生成的libhello.so文件拷贝过来,这里我们直接将上面cmake->debug->obj中的四个文件夹都拷贝过来
-
新建一个JniUtil类。注意包名和类名都要跟引入so库中的暴露的JNI方法中的一致,接着就是加载hello这个so库,然后声明native方法,这个native方法就是hello.so库中暴露的JNI方法。其实你会发现这个JniUtil中的代码跟上述的NdkDemo中的是一样的。
-
在MainActivity中使用JniUtil中的native方法
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = findViewById(R.id.tv); //调用native方法 tv.setText(JniUtil.helloFromJNI() + " & "+JniUtil.stringFromJNI()); } }
-
运行项目
可以发现引入JNI规范的.so库是很简单的,因为我们知道hello.so库中接口方法的命名方式,不用CMake,不用编写C++文件,直接在JniUtil中声明native方法,然后运行即可。
2. 引入第三方so库和头文件
这里我们使用场景就是,在Java层中要调用hello.so库中hello.cpp中的helloWorld方法
-
新建一个Native C++工程。
因为引入这种类型的so库,我们需要创建自己的so文件,然后在自己的so文件里再调用第三方so,最后在Java层中调用自己的so,因此需要进行NDK开发。而上面我们已经分析了新建Native C++工程AS帮我们建立和修改的文件,因此如果你想在原有的项目中进行NDK开发的话,其实就是自己手动增加和修改这些文件即可。
-
新增文件夹,用来存放要导入的第三方so库以及头文件。主要是在cpp文件中新建include文件和在main中新建jniLibs,然后将第三方的头文件放在include中,第三方so库放入jniLibs中。
-
配置CMakeLists.txt。我们需要关联第三方头文件到native-lib,并配置好第三方so库以及头文件导入的路径。这里需要注意的是set_target_properties这里配置的so库目录,你可以利用message打印,so库的路径是否正确,CMAKE_SOURCE_DIR代表着CMakeLists.txt的路径,由于我的CMakeLists.txt在cpp中,因此需要加上/…进行回退到上一级的main目录,然后配置libhello.so的相对路径。
cmake_minimum_required(VERSION 3.4.1) # 利用这个打印路径 message("******************************************************************") message("CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}") message("******************************************************************") # 这里会把 native-lib.cpp转换成共享库,并命名为 native-lib add_library( # 库的名字 native-lib # 设置成共享库 SHARED # 库的源文件(由于native-lib.app和CMakeLists.txt同处于一个包,因此可以直接写文件名 # 不然的话需要写成src/main/cpp/native-lib.cpp) native-lib.cpp) # 如果需要使用第三方库,可以使用 find-library来找到 find_library( # so库的变量路径名字,在关联的时候使用 log-lib # 你需要关联的so名字 log) #将native-lib关联到第三方库头文件 #由于我的inclue目录与CMakeList都在cpp目录,因此可以直接写include,否则需要写相对目录 include_directories(include) #导入第三库,不同到第三方库需要分开导入,因为有4个so库需要导入,因此需要4次 add_library(hello SHARED IMPORTED) #设置导入第三库名称,目标位置 set_target_properties(hello PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jnilibs/${ANDROID_ABI}/libhello.so) # 通过link将源文件的库和第三方库添加进来 target_link_libraries( # 源文件库的名字 native-lib #第三方库的名称 hello # 添加第三方库的变量名 ${log-lib})
-
新建JniUtil用于加载so库和声明native方法。在引入JNI规范的so库时,我们特别强调了该类要与hello.so库中的JniUtil包名,类名要一致。而在这里并不需要,因为在这里我们并不是引入hello.so库,而是引入自己的so库(native-lib),我们只是为了方便管理,然后取JniUtil。
-
在native-lib.cpp中引入第三方头文件(hello.h)。在这里我们引入了hello.h的头文件,然后实现了对外的JNI方法,在该方法中我们引用了第三方库中的hello.cpp中的helloWorld方法,而这也就是我们引入第三方so库和头文件的最终目的。
注意:JNI方法的包名,类名,方法名与上面的JniUtil一致
#include <jni.h> #include <string> #include "hello.h" extern "C" JNIEXPORT jstring JNICALL Java_com_example_jnidemo_util_JniUtil_helloFromJNI( JNIEnv *env, jobject /* this */) { std::string helloStr = helloWorld(); return env->NewStringUTF(helloStr.c_str()); }
-
在MainActivity中引用JniUtil中的native方法
-
运行项目。你就能发现神奇的HelloWorld
四、踩坑
-
在引入JNI规范的.so库时一定要记得包名,类名要和引入的so库中的一致,不然运行时会报No implementation found for java.lang.String com.example…之类的错误,然后闪退
-
在引入第三so库的时候,如果你将so库放在src/main/jniLibs时,可以不在项目的build.gradle中配置so库路径,因为AS默认加载so库的路径就是src/main/jniLibs。但是如果放在其他地方的时候,或者不取名jniLibs时,比如我们放在了src/main/jniLib,这时候就得在build.gradle中配置,如下
-
引入第二种so库和头文件中配置CMakeList.txt的时候,我们会通过set_target_properties来设置目标so库的路径,网上大部分的教程配置的路径都是:
${CMAKE_SOURCE_DIR}/jnilibs/${ANDROID_ABI}/so库完整名字.so
,但其实是要看具体情况的,如果你编译或运行的时候出现了类似下面这种的错误,那么大概率是由于so库的路径配置错误导致的。'F:/Android/JNIDemo/app/src/main/cpp/jnilibs/armeabi-v7a/libhello.so', needed by 'F:/Android/JNIDemo/app/build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so', missing and no known rule to make it
这时候我们可以利用message来打印
${CMAKE_SOURCE_DIR}/jnilibs/${ANDROID_ABI}/so库完整名字.so
这个路径,然后对比一下你引入第三方so库的位置,就可以进行判断是否路径配置错误。添加打印信息后我们进行编译,如果编译错误的话,应该能够在build中看到打印的message信息,如果看不到的话,可以查看app->.cxx->cmake->debug->随便一个机型->build_output.txt中的打印信息,然后对比你引入第三方库的位置。因为我的jnilibs是放在main层,所以这个路径明显是错误的,因此需要在
${CMAKE_SOURCE_DIR}
加上/…进行回退一个目录,即最终的目录应该为${CMAKE_SOURCE_DIR}/../jnilibs/${ANDROID_ABI}/libhello.so
如果你确定你的路径配置没有错误,那么你也可以看看报错的so库位置,也有可能是因为你运行的环境缺少了相关的机型,比如在虚拟机中运行需要x86的环境,而你引入so库的时候没有将x86的so库导进来,或者是说你的手机运行需要arm64-v8a或armeabi-v7a的环境,但是你没有引入对应的环境,也有可能
missing and no known rule to make it
的错误,所以最好是将所有机型的so库文件拷贝过来。
总结
自己是NDK开发和C++的小白,所以整个过程下来感觉收获很多。从安装到使用NDK开发,一路上下来也踩了不少的坑,所以想记录这整个过程,如果有错误的还请大家多多包涵,同时也欢迎大家指出错误。
参考博客:
- 打开Android Studio,点击Android Studio->Preferences,搜索SDK,然后在SDK Tools中勾选LLDB,NDK,Cmke进行下载。其中LLDB是调试本地代码的工具,可调试C++代码
-
Android NDK开发的环境搭建与简单示例
2021-01-04 04:38:47NDK开发的可以称之为底层开发或者jni(java native interface)层开发,SDK开发可以称为上层开发。 Android开发中使用NDK的原因: 1、众所周知,利用SDK编写的代码,生成的APK,很容易就可以反编译了,安全 -
Android Studio之NDK开发
2022-02-18 14:22:28Android Studio之NDK开发 一、 前言 NDK全称是Native Development Kit,是Android提供的一个开发工具包,能够快速将开发的C,C++的动态库,协议软件包,以及优秀的软件工具,用so和应用打包成APK,自由地在Android上...Android Studio之NDK开发
一、 前言
NDK全称是Native Development Kit,是Android提供的一个开发工具包,能够快速将开发的C,C++的动态库,协议软件包,以及优秀的软件工具,用so和应用打包成APK,自由地在Android上运行。而NDK开发工具,就是将C/C++程序,编译成为Android环境可运行的程序,再者,通过NDK实现Android中实现JNI编程。那么JNI又是啥呢?JNI的全称是Java Native Interface,即Java的本地接口,JNI可以实现Java与C/C++语言进行交互,实现JAVA与C/C++之间的贯通,实现JAVA层的穿透,实现JAVA不法完成的一些底层的功能。这么一来,通过NDK和JNI结合,就可以很方便的在Android的开发环境中使用C/C++技术互通,方便地使用C/C++所提供的开源库,实现更加广泛的应用功能。
由此,我们不难看出,NDK开发工具在Android应用中有两个使用点,一是对C/C++开源库的编译,获得在Android环境下可运行的SO文件或Lib库;二是JNI程序的编译,完美实现JAVA与C/C++程序的结合。一般情况下我们使用了Android与C/C++的结合,必定需要JNI技术,也就必定需要使用NDK开发工具。所以,我们应掌握NDK编译,掌握Android之NDK开发。二、 NDK安装
NDK开发工具的安装,可分C/C++开源库等编译环境的NDK开发工具的安装和Android Studio编程环境的安装。前者是基于OS环境交叉编译,制作在Android环境下可运行的SO文件或Lib文件,后者是编译JNI设计技术的C/C++文件,也可以编译Android Studio环境编写的Android下运行的C/C++工程,两个安装方法是不同的,我们在这里分别进行描述。2.1、Ubuntu环境下安装NDK开发工具
2.1.1、下载NDK安装包
进入官网下载:android-ndk-r16d-linux-x86_64.bin
我们应当注意的问题,在Android NDK版本17以后,不再提供编译功能,我们下载的版本需满足我们能的需求。
2.1.2、安装
#cd /usr/local/ #chmod +x android-ndk-r16d-linux-x86_64.bin #./android-ndk-r16d-linux-x86_64.bin
2.1.3、环境配置
#cd /root #vim .bashrc
在文件的最后行添加代码:
export NDK_HOME=/usr/local/android-ndk-16r/ export PATH=$PATH:$NDK_HOME
环境配置完成后,运行下面命令,表示配置环境生效。
#source .bashrc
2.2、Android Studio环境安装NDK开发工具
2.2.1、安装
运行Android studio程序,进入主界面,在主界面工具条上选择SDK manager,点击进入。
选择SDK tool
选择NDK和CMake两项工具安装,点击“ok”按钮,将自动安装NDK。
2.2.2、项目配置NDK编译环境
点击程序主界面菜单的“File”项,选择“project structure…”,进入编译环境配置。
在“NDK version”栏选择安装的版本,点击”OK“按钮,即编译化境配置完成。再次提醒注意的问题,版本17以后的版本,不在提供编译功能,我们需要下载版本17或以前的版本。
三、 Ubuntu环境下NDK编译
在这里需要说明的一点,在linux环境下编译,搭建环境比较简单,如果在windows环境下,往往需要安装cygwin,既费资源,有难找到三方提供(可能不支持cygwin),不如直接在linux环境下操作。
Linux环境下实现编译,主要是NDK运行环境配置和解决configure的配参问题。3.1、NDK运行环境配置
export NDK=/home/ndk_build/android-ndk-r14b export SYSROOT=$NDK/platforms/android-9/arch-arm/ export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64 export CPU=arm export PREFIX=$(pwd)/android/$CPU export ADDI_CFLAGS="-marm"
3.2、configure配参
--target-os=linux --arch=arm CC=$NDK_HOME/bin/ arm-linux-androideabi-gcc --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi-
3.3、具体的实例
编辑android_build.sh文件#!/bin/bash make clean export NDK=/home/ndk_build/android-ndk-r14b export SYSROOT=$NDK/platforms/android-9/arch-arm/ export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64 export CPU=arm export PREFIX=$(pwd)/android/$CPU export ADDI_CFLAGS="-marm" ./configure --target-os=linux \ --prefix=$PREFIX --arch=arm \ --disable-doc \ --enable-shared \ --disable-static \ --disable-yasm \ --disable-symver \ --enable-gpl \ --disable-ffmpeg \ --disable-ffplay \ --disable-ffprobe \ --disable-ffserver \ --disable-symver \ --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \ --enable-cross-compile \ --sysroot=$SYSROOT \ --extra-cflags="-Os -fpic $ADDI_CFLAGS" \ --extra-ldflags="$ADDI_LDFLAGS" \ $ADDITIONAL_CONFIGURE_FLAG make clean make make install
只要注意了NDK配置和configure配参问题,解决了NDK编译的主要问题。
四、 CMakelists.txt编译
CMakelists.txt编译的讨论,主要是为了在Android Studio环境下解决编译问题。
我们知道makefile是在Linux编译c或者c++代码的时候的一种脚本文件,但是每一个功能都要写一个makefile文件,这样如果这个工程很大,而且相关性比较强的话,makefile的书写就会变得相对繁琐,更要命的是如果以后需要添加新的功能或者是新人需要修改功能的话,看起来就会特别麻烦;因为介于此,cmake的出现就是为了解决这样的问题,cmake的入门相当容易,而且管理也特别方便简单,那我们开始吧。
cmake的所有语句都写在一个CMakeLists.txt的文件中,CMakeLists.txt文件确定后,直接使用cmake命令进行运行,但是这个命令要指向CMakeLists.txt所在的目录,cmake之后就会产生我们想要的makefile文件,然后再直接make就可以编译出我们需要的结果了。更简单的解释就是cmake是为了生成makefile而存在,这样我们就不需要再去写makefile了,只需要写简单的CMakeLists.txt即可。
cmake的执行流程很简单,我们的重点是如何编写CMakeLists.txt文件呢,我们通过例子来学习cmake的语法。
例子从这篇文章中学习http://blog.csdn.net/dbzhang800/article/details/6314073,大致如下:4.1、一个单文件的简单的例子
文件名字为main.c 内容如下:#include <stdio.h> int main() { printf("Hello World Test!\n"); return 0; }
编写CMakeLists.txt文件内容如下:
project(hello_jelly) set(APP_SRC main.c) add_executable(${PROJECT_NAME} main.c) #print message message(${PROJECT_SOURCE_DIR})
解释代码:
第一个行project不是强制性的,最好加上,这会引入两个变量:
HELLO_BINARY_DIR, HELLO_SOURCE_DIR
同时也会定义两个等价的变量:
PROJECT_BINARY_DIR, PROJECT_SOURCE_DIR
外部编译要时刻区分这两个变量对应的目录
可以通过message进行输出
message(${PROJECT_SOURCE_DIR})
set 命令用来设置变量
add_exectuable 告诉工程生成一个可执行文件。
add_library 则告诉生成一个库文件。
CMakeList.txt 文件中,命令名字是不区分大小写的,而参数和变量是大小写相关的。
然后将以上两个文件放在统一目录下面,注意编译产生时候分为两种,一种是直接在当前源码目录执行cmake命令#cmake ./,但是这样会在当前目录下产生很多临时文件和目录,另一种方式就是在当前目录新建一个build目录,然后我门进入到build目录,执行命令cmake …/,这样产生的所有临时文件都会生成在build目录下,而不影响源码目录的代码,此处我们采用第二种方法。我们进入到build目录,执行命令#cmake …/,然后在当前目录可以看到文件如下drwxrwxr-x 3 zqq zqq 4096 9月 28 17:12 CMakeFiles -rw-rw-r-- 1 zqq zqq 993 9月 28 17:12 cmake_install.cmake -rw-rw-r-- 1 zqq zqq 5479 9月 28 17:12 Makefile
最后再在此目录执行make即可生成相应的可执行程序。
4.2、多个源文件的操作
hello.h头文件内容如下#ifndef JELLYHELLO #define JELLYHELLO void hello(const char* name); #endif
hello.c文件内容
#include <stdio.h> #include "hello.h" void hello(const char* name) { printf("Hello my name is %s\n",name); }
main.c文件内容如下
#include <stdio.h> #include "hello.h" int main() { printf("Hello World Test!\n"); hello("jelly"); return 0; }
然后是编写CMakeLists.txt文件
project(hello_jelly) set(APP_SRC main.c hello.c) add_executable(${PROJECT_NAME} ${SRC_LIST}) #print message message(${PROJECT_SOURCE_DIR})
然后保存使用上面的方法进行cmake和make,就可以生成需要的可执行文件。
4.3、将hello.c生成一个库来调用
如果将hello生成成一个库来调用的话只需要在2的基础上修改一下CMakeLists.txt文件再进行编译即可
修改的CMakeLists.txt如下:project(hello_jelly) set(LIB_SRC hello.c) set(APP_SRC main.c) add_library(hello ${LIB_SRC}) add_executable(${PROJECT_NAME} ${APP_SRC}) target_link_libraries(${PROJECT_NAME} hello) #print message message(${PROJECT_NAME})
相比之下,我们只是添加了一个新的目标hello库,并将其链接到我们的demo程序
然后同样的方法进行cmake和make进行编译。4.4、工程分类文件夹编译
在前面,我们成功的使用了库,但是源代码都是在同一个路径下面,这样如果到时候代码量比较大的话,可能就会分类,形成多个文件夹,这样我们需要把代码分开放,此时我们需要些三个CMakeLists.txt文件,目录结构如下drwxrwxr-x 2 zqq zqq 4096 9月 28 17:32 app drwxrwxr-x 5 zqq zqq 4096 9月 28 17:12 build -rw-rw-r-- 1 zqq zqq 487 9月 27 14:42 CMakeLists.txt drwxrwxr-x 2 zqq zqq 4096 9月 28 17:19 libso
我们将main.c程序放在app目录下面,hello.c hello.h放在libso文件夹下面,然后该文件夹有一个CMakeLists.txt文件,app和libso文件夹下面也有CMakeLists.txt文件,这样就有三个CMakeLists.txt文件了,那么我们接下来来编辑这个三个文件吧。
首先是app文件夹的CMakeLists.txtproject(hello_jelly) include_directories(${PROJECT_SOURCE_DIR}/../libso) set(APP_SRC main.c) add_executable(${PROJECT_NAME} main.c) target_link_libraries(${PROJECT_NAME} helloso) message(${PROJECT_SOURCE_DIR})
然后是libso文件夹的CMakeLists.txt,其中SHARED 表示是生成的动态库,如果把SHARED去掉的话就是生成静态库
project(helloso) set(LIB_SRC hello.c) add_library(${PROJECT_NAME} SHARED ${LIB_SRC})
最后是外面那个和app在同一目录下的CMakeLists.txt
cmake_minimum_required (VERSION 3.2) project(jelly_cmake) add_subdirectory(./app) add_subdirectory(./libso)
其表示我们要到./app和./libso文件夹下面去寻找Cmake文件然后进行编译。
最后我们在build目录下面去执行上面的命令编译即可编译出我们需要的可执行文件。#cmake ../ #make
4.5、Cmake的install简单使用
我的理解cmake中的install其实就是一个将编译好的可执行文件或者是生成的库文件将它放到系统对应的位置,比如说可执行文件直接要放到bin目录下面,so库文件要放在对应的lib目录下面,我在上面的例子的基础上修改CMakeLists.txt文件,编辑完成后编译的步骤如下,就是多了个install步骤,这样我们就可以在Linux上面使用该执行文件,执行文件会去调用so库。#cmake ../ #make #make install
app目录修改的CMakeLists.txt如下:只是在之前的基础上加了最后install一行
project(hello_jelly) include_directories(${PROJECT_SOURCE_DIR}/../libso) set(APP_SRC main.c) add_executable(${PROJECT_NAME} main.c) target_link_libraries(${PROJECT_NAME} helloso) message(${PROJECT_SOURCE_DIR}) install(TARGETS ${PROJECT_NAME} DESTINATION bin)
libso目录修改的CMakeLists.txt如下:只是在之前的基础上加了最后install一行
project(helloso) set(LIB_SRC hello.c) add_library(${PROJECT_NAME} SHARED ${LIB_SRC}) install(TARGETS ${PROJECT_NAME} DESTINATION ../lib)
在此需要解释下这个路径问题,install(TARGETS P R O J E C T N A M E D E S T I N A T I O N b i n ) 这 句 话 的 意 思 是 安 装 T A R G E R S h e l l o j e l l y 这 个 可 执 行 文 件 到 {PROJECT_NAME} DESTINATION bin)这句话的意思是安装TARGERS hello_jelly这个可执行文件到 PROJECTNAMEDESTINATIONbin)这句话的意思是安装TARGERShellojelly这个可执行文件到{CMAKE_INSTALL_PREFIX}/bin目录下面,我测试打印我的 C M A K E I N S T A L L P R E F I X 路 径 是 / u s r / l o c a l 路 径 , b i n 前 面 不 能 有 / , 否 则 会 是 绝 对 路 径 , 它 不 再 会 去 获 取 {CMAKE_INSTALL_PREFIX}路径是/usr/local路径,bin前面不能有/,否则会是绝对路径,它不再会去获取 CMAKEINSTALLPREFIX路径是/usr/local路径,bin前面不能有/,否则会是绝对路径,它不再会去获取{CMAKE_INSTALL_PREFIX}路径,
综上所述,可执行文件安装的路径是:
/usr/local/bin/
so库文件的安装路径是:
/usr/local/…/lib/
最后执行那三个命令就完了,此时你可以在你的Linux系统里面的任何目录执行./hello_jelly
注:如果执行make install的时候出现错误,可以加上sudo再次执行试试。4.6、给出一个实际的例子
cmake_minimum_required(VERSION 3.4.1) set(APP_SRC src/main/cpp/native-lib.cpp) add_library( native-lib SHARED ${SRC_LIST}) find_library( log-lib log ) include_directories(libs/include) set(DIR ../../../../libs) add_library(avcodec-56 SHARED IMPORTED) set_target_properties(avcodec-56 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libavcodec-56.so) add_library(avdevice-56 SHARED IMPORTED) set_target_properties(avdevice-56 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libavdevice-56.so) add_library(avformat-56 SHARED IMPORTED) set_target_properties(avformat-56 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libavformat-56.so) add_library(avutil-54 SHARED IMPORTED) set_target_properties(avutil-54 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libavutil-54.so) add_library(postproc-53 SHARED IMPORTED) set_target_properties(postproc-53 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libpostproc-53.so) add_library(swresample-1 SHARED IMPORTED) set_target_properties(swresample-1 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libswresample-1.so) add_library(swscale-3 SHARED IMPORTED) set_target_properties(swscale-3 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libswscale-3.so) add_library(avfilter-5 SHARED IMPORTED) set_target_properties(avfilter-5 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libavfilter-5.so) target_link_libraries( native-lib avfilter-5 avcodec-56 avdevice-56 avformat-56 avutil-54 postproc-53 swresample-1 swscale-3 ${log-lib} android)
五、 JNI编程技术
我们为更好地掌握Android Studio JNI编程技术,我们首先介绍JAVA平台下JNI实现方法,了解JNI实现的基本要素。5.1、编写Native的JAVA接口
一个NetworkUtils.java程序, 文件路径:d:\jni\com\example\ administrator\testfirst\package com.example.administrator.testfirst; public class NetworkUtils { public native String GetMACAddressByIP(String ipAddr); static { System.loadLibrary("libnetutils"); } public static void main(String[] args) { System.out.printf("Jni libnetutils"); } }
接口函数:
public native String GetMACAddressByIP(String ipAddr);实现接口函数调用的C/C++封装的SO库:
System.loadLibrary(“libnetutils”);Main函数是解决JAVA编译可能出现没有主函数引起的错误。
5.2、编写JAVA编译文件
编写Java编译的build.sh文件,文件路径:d:\jni\com\example\ administrator\testfirst\#! /bin/bash javac NetworkUtils.java
5.3、编写生成H头文件sh文件
编写Build_c.sh文件:文件路径:d:\jni\#! /bin/bash Javah -o jni_example.h -classpath D:\jni;D\Android- studio\platforms\android-28\android.jar -jni com.example.administrator.testfirst.NetworkUtils
根据生成的H头文件,建立C/C++文件。
5.4、编写C/C++生成SO文件
编写MakeFile文件,文件路径:d:\jni\CC = arm-linux-gnueabi-gcc CFLAGS = -Wall -g -O -fPIC CXXFLAGS = INCLUDE = -I ./inc -I ../comm/inc -I/usr/include -I/usr/lib/jvm/java-7-openjdk-armel/include TARGET = libnetutils.so LIBPATH = ./libs vpath %.h ./inc OBJS = com_example_administrator_testfirst_GetMACAddressByIP.o SRCS = com_example_administrator_testfirst_GetMACAddressByIP.c all:$(OBJS) $(OBJS):$(SRCS) $(CC) $(CFLAGS) $(INCLUDE) -c $^ $(CC) -shared -fPIC -o $(TARGET) $(OBJS) # mv $(TARGET) $(LIBPATH) clean: rm -rf $(TARGET) $(OBJS)
5.5、编写jar包生成文件
编写build_jar.sh文件,文件路径:D:\jni\jar cvf libjni_example.jar -C .
六、 Android studio之NDK开发
我们在前面,已经讨论了NDK安装,CMaklists.txt的编写,JNI编程技术,这里编写JNI编程技术,主要是针对Android Studio环境下JNI编程,完整地介绍使用Android Studio实现JNI编程。
Android Studio JNI编程主要由:1)NDK安装;2)NDK配置;3)配置C/C++环境;4)配置CMakelists.txt编译;5)编写JNI接口程序;6)生成H头文件;7)编写C/C++程序。
前面我已介绍了NDK安装,以及NDK配置,这里,我们开始介绍如何编译JNI程序。6.1、导入C/C++的SO及H文件
建立armeable和include两个目录,将SO文件拷贝到armeable目录里,将H头文件拷贝到include目录里。
6.2、建立C/C++编译环境
修改app级的build.gradle文件,插入:externalNativeBuild { cmake { cppFlags "-frtti -fexceptions" abiFilters 'armeabi' } } } externalNativeBuild { cmake { path "CMakeLists.txt" } }
我们只要看到图中三个点存在,表示JNI编译环境已经建立起来了。其布局位置:
abiFilters 完整表达格式是:
abiFilters "armeabi", "armeabi-v7a" , "arm64-v8a", "x86", "x86_64", "mips", "mips64"
具体表达描述请查阅abiFilters相关资料。
对了,还有一个值得注意的是:
6.3、编写CMakelista.txt程序
cmake_minimum_required(VERSION 3.4.1) set(APP_SRC src/main/cpp/native-lib.cpp) add_library( native-lib SHARED ${SRC_LIST}) find_library( log-lib log ) include_directories(libs/include) set(DIR ../../../../libs) add_library(avcodec-56 SHARED IMPORTED) set_target_properties(avcodec-56 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libavcodec-56.so) add_library(avdevice-56 SHARED IMPORTED) set_target_properties(avdevice-56 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libavdevice-56.so) add_library(avformat-56 SHARED IMPORTED) set_target_properties(avformat-56 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libavformat-56.so) add_library(avutil-54 SHARED IMPORTED) set_target_properties(avutil-54 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libavutil-54.so) add_library(postproc-53 SHARED IMPORTED) set_target_properties(postproc-53 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libpostproc-53.so) add_library(swresample-1 SHARED IMPORTED) set_target_properties(swresample-1 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libswresample-1.so) add_library(swscale-3 SHARED IMPORTED) set_target_properties(swscale-3 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libswscale-3.so) add_library(avfilter-5 SHARED IMPORTED) set_target_properties(avfilter-5 PROPERTIES IMPORTED_LOCATION ${DIR}/armeabi/libavfilter-5.so) target_link_libraries( native-lib avfilter-5 avcodec-56 avdevice-56 avformat-56 avutil-54 postproc-53 swresample-1 swscale-3 ${log-lib} android)
将CMakelists.txt文件拷贝到app目录下。我们需要注意点,add_library( native-lib SHARED ${SRC_LIST})语句,是由SRC_LIST所有文件编译为native_lib.so文件。
6.4、编写JNI的JAVA程序
package com.example.ffmpegplay; import android.view.Surface; import java.util.Map; public class FFmpegPlayer { public native int initNative(); public native void stopNative(); native void renderFrameStart(); native void renderFrameStop(); private native void seekNative(long positionUs) throws NotPlayingException; private native long getVideoDurationNative(); public native void render(Surface surface); private native void deallocNative(); private native int setDataSourceNative(String url, Map<String, String> dictionary, int videoStreamNo, int audioStreamNo, int subtitleStreamNo); private native void pauseNative() throws NotPlayingException; private native void resumeNative() throws NotPlayingException; static { System.loadLibrary("avcodec-56"); System.loadLibrary("avdevice-56"); System.loadLibrary("avfilter-5"); System.loadLibrary("avformat-56"); System.loadLibrary("avutil-54"); System.loadLibrary("postproc-53"); System.loadLibrary("swresample-1"); System.loadLibrary("swscale-3"); System.loadLibrary("native-lib"); }; //public static void main(String[] args) { // System.out.printf("FFmpegPlayer\n"); //} }
文件路径按照工程路径,不需另造目录。
6.5、编写生成H文件的build.bat文件
javah -o FFmpegPlayer.h -classpath G:\VideoViewer\app\build\intermediates\javac\debug\classes;G:\Android-studio\platforms\android-28\android.jar -jni com.example.ffmpegplay.FFmpegPlayer com.example.ffmpegplay.NotPlayingException
build.bat文件放在目录G:\VideoViewer\app\build\intermediates\javac\debug\classes下。一旦我们的工程编译成功,在该目录下会生成对应的class文件,我们就可以执行build.bat文件,生成H头文件了。运行build.bat文件:
注意:该文件最好备份一个到其它地方,防止丢失。
6.6、建C/C++工程,编写C\C++文件。
创建一个CPP目录于app/scrc/main/目录下,并将H头文件拷贝到CPP目录下,接下来就可以开启C/C++编程之旅了。
七、 Android studuio实例描述
八、 -
JNI NDK 开发指南
2018-02-27 10:15:05JNI NDK 开发指南 对JNI有个初步的认识,里面涉及必要的函数说明 -
Android NDK开发详细介绍
2021-01-20 09:27:42Android之NDK开发 一、NDK产生的背景 Android平台从诞生起,就已经支持C、C++开发。众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第三方应用都必须使用Java语言。但这并不等同于“第... -
Android NDK开发动态加载so
2014-10-21 18:15:49Android NDK开发动态加载so,采用System.load方法实现 -
NDK开发C/C++配置Log打印日志
2018-07-10 20:57:30NDK开发C/C++配置Log打印日志,可以参考:https://blog.csdn.net/niuba123456/article/details/80991477 -
android串口开发入门之搭建ndk开发环境及第一个jni调用程序
2021-01-05 13:45:24所以觉得自己来一篇,本文将详细介绍关于android搭建ndk开发环境及第一个jni调用程序的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。 一:ndk环境搭建 1:开发环境 我使用的是... -
Android NDK开发简单程序分享(Hello Word!)
2021-01-20 10:31:27在之前的博客中已经为大家介绍了,如何在win环境下配置DNK程序,本篇我将带大家实现一个简单的Hello jni程序,让大家真正感受一下NDK开发的魅力。这里我们选择使用C+JAVA开发Android程序,首先你必须了解C语言、JAVA... -
ndk开发指南
2015-11-23 19:28:41ndk ,jni ,安卓,android -
Android Studio&ndk开发感受
2017-04-20 18:51:33结合走过的一些坑,总结自身在ndk开发过程中的经验,希望和大家一起分享!! -
Android NDK开发PPT
2022-06-21 15:48:41Android NDK开发PPT -
【NDK学习第一章】windows上ndk开发环境搭建,编译运行NDK自带的代码示例
2019-04-16 01:41:08NULL 博文链接:https://android-zhang.iteye.com/blog/1779317 -
android NDK模块开发教程pdf
2020-11-28 16:35:54android NDK模块开发详细教程