如何解决android中国开发者 NDK开发中的NDK

匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。14183人阅读
android(72)
eclipse(7)
& & & & & &&今天是号,整理一下昨天使用eclipse adt搭建的android ndk开发平台,遇到的eclipse里面没有NDK选项的问题。
& & & &自从Google推出android studio 1.0版以后,官方就不再提供bundle的版本下载,只能先下载eclipse,然后在eclipse上安装adt,只要是全选,这个时候是不存在没有NDK选项这个问题的。但是很多以前下载的bundle版本的,是没有NDK这个选项的,这个时候就需要我们手动去安装,步骤如下:
& & & &1. Help--&Install New Software... --& Work with 输入 https://dl-/android/eclipse/。
& & & &2.在打开的窗口出现的列表中会出现Developer tools,将其全选。
& & & &3.点击Next。若有提示就点击OK,一路下去。最后提示你重启Eclipse(ADT)。
重启后发现 Window-&References-&Android 里面有NDK设置选项了。
& & & &其实写这篇博客的意义不是很大,因为adt bundle版本官方已经不提供下载了,而且eclipse也逐渐被Google放弃而推荐使用android studio但还是写这篇博客,给以前遗留的adt bundle版本提供一些帮助。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:747148次
积分:6289
积分:6289
排名:第3683名
原创:93篇
转载:17篇
评论:330条
欢迎大家关注我的微信公众号
(1)(1)(13)(4)(16)(11)(8)(1)(2)(2)(1)(3)(6)(5)(1)(5)(1)(1)(1)(3)(1)(2)(1)(3)(14)(3)(2)Eclipse&+&ADT(包括NDK&Plugin)&+&CDT&搭建Android&NDK开发环境
&Android应用程序的开发环境比较容易搭建,下载完Android
SDK,在Eclipse中安装ADT插件就好了。前段时间由于要在Android上做三维程序的开发,三维开发的资源(例如几何算法等)大多数都是C++写,如果想开发出高效的程序,那就必须用到NDK,一部分代码用C/C++编写,生成本地动态链接库libXXX.so,通过Android的JNI接口调用动态库中的本地方法。NDK实际上就是一些开发包和工具包的集合,辅助开发者能够方便的编写本地动态链接库并打包到Android应用的程序的APK包中,尤其是使用ADT中的NDK
plugin,可以方便的调试本地C/C++代码,这一点非常重要,如果不能调试,那在正式的项目中使用是很危险的,如果是我,肯定不会采用这种技术。
虽说,NDK已经推出很多版了,但是环境搭建过程中会遇到各种各样的问题,市面上的资料基本上没有说得很透彻的,我这次花了大概2天的时间才将环境搭建好,解决了许多问题,但是也走了许多弯路,这里特将过程记录下来。
我用的环境是Windows 7旗舰版、Java EE Kepler版本(build id:
9)、ADT-22.0.1、CDT-8.2.0、NDK-r8e。很多地方说需要安装交叉编译工具Cgywin,我也确实安装了,事实证明应该是不需要,我在另外一台电脑试过了,下面具体说如何搭建NDK开发环境,Eclipse和Android
SDK的安装就不说了,注意顺序,尤其是CDT和ADT的安装顺序。
(1)下载NDK包,并解压
(2)下载CDT(C/C++开发环境插件),在Eclipse中安装此插件。
(3)下载ADT,在Ecplise中安装此插件,一定要选中NDK Plugin
(4)在Ecplise中配置Android SDK和NDK
SDK的路径,在Eclipse的Window-&Preferences-&Android中设置,我本机的设置为Android
SDK Location为“C:\Android\android-sdk”,NDK
Location为“F:\hexm_private\Android\android-ndk-r8e”。
环境搭建完了,看着很简单吧,实际上还会遇到很多问题,下面一一讲解,为了便于说明问题,做一个HelloWorld程序吧。
(1)首先在Eclipse中创建一个Android工程
(2)在Package Explorer中选中刚才创建的Android工程,右键选择Android Tools -&
Add Native
Support..,填写需要生成的动态库的名称,比如“HelloWorld”,也可以是别的,你会发现工程中多了一个叫做“jni”的目录,并自动生成了一个C++文件和一个Androd.MK文件,所有的C++源代码就放在这个目录中。这样,就跟Eclipse工程添加了C/C++属性,就能够用CDT插件在Eclipse中进行C/C++开发了。
(3)在Java代码中声明本地方法并调用,部分代码如下(点击屏幕的时候,调用本地方法stringFromJNI,该方法在动态链接库HelloWorld中实现):
&&@Override
&&&&public&boolean&onTouch(View v,
MotionEvent event)
&&&switch(event.getAction())
&&&case&MotionEvent.&ACTION_DOWN:
&&&&&&&&&&&textView.setText(stringFromJNI());
&&&&&&&&&&&break;
&&&return&&true;
&&&&public&native&String&
stringFromJNI();
&&&&static
&&&System.loadLibrary("HelloWorld"&);
(4)在C/C++代码中实现本地方法
在jni目录的HelloWorld.cpp中添加如下代码,并添加NDK
SDK的头文件目录到工程设置中,在Package
Explorer中选中刚才创建的Android工程,右键选择Properties,在弹出的界面中选择C/C++
General-〉Paths and
Symbols,在包含文件路径中添加即可,我本机上是,”F:\hexm_private\Android\android-ndk-r8e\platforms\android-14\arch-arm\usr\include“
JNIEXPORT&jstring&JNICALL&Java_com_hexmdemo_helloworld_MainActivity_stringFromJNI(&JNIEnv*
env,&jobject&thiz)
&&&&return&env-&NewStringUTF("Hello from
看上面的代码,应该跟许多例子中给得不一样,例如官方给出的hello-jni中的例子
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv*
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
jobject thiz )
&&& return
(*env)-&NewStringUTF(env, "Hello from JNI");
你会发现env参数使用的参数完全不一样,这是因为前者是C++源文件,后者是C源文件,结构JNIEnv在两种环境中定义方式不一样,如果按照C的方式去写肯定没法编译通过。
不仅如此,如果是C源代码,做到这一步,程序就已经写完了,但是我们现在还需要做一些额外的工作,即注册本地方法,如下:
static&JNINativeMethod&methods[] =
& {&"stringFromJNI",&"()Ljava&/lang/S"&,
(void&*)Java_com_hexmdemo_helloworld_MainActivity_stringFromJNI
//Java代码中本地方法所属的类
static&const&char&*classPathName
=&"com/example/helloworld1/MainActivity"&;
static&int&registerNativeMethods&(JNIEnv&*
env,&const&char*
className,
&&&&JNINativeMethod*
gMethods,&int&numMethods)
&&&&jclass&
clazz = env-&FindClass(className);
&&&&if&(clazz
== NULL) {
&&&&&&&&return&JNI_FALSE;
&&&&if&(env-&RegisterNatives(clazz,
gMethods, numMethods) & 0) {
&&&&&&&&return&JNI_FALSE;
&&&&return&JNI_TRUE;
static&int&registerNatives(&JNIEnv* env)
&&if&(!registerNativeMethods(env,
classPathName,
&&&&&&&&&&&&&&&&
methods,&sizeof&(methods)
/&sizeof(methods[0])))
&&&&return&JNI_FALSE;
&&return&JNI_TRUE;
typedef&union&{
&&&&JNIEnv*&env;
&&&&void*&venv;
}&UnionJNIEnvToVoid;
jint&JNI_OnLoad(&JavaVM*
vm,&void&*
&&&&UnionJNIEnvToVoid&
uenv.&venv&=
&&&&jint&result =
&&&&JNIEnv* env = NULL;
&&&&if&(vm-&GetEnv(&uenv.&venv, JNI_VERSION_1_4) != JNI_OK)
&&&&&&&&goto&
env = uenv.&env;
&&&&if&(registerNatives(env)
!= JNI_TRUE) {
&&&&&&&&goto&
result = JNI_VERSION_1_4;
&&&&return&
为什么有这么多麻烦事情呢,这是因为C语言里面由于没有函数重载,函数签名不会被编译器改变,这样本地方法stringFromJNI直接按照规则调用Java_com_example_hellojni_HelloJni_stringFromJNI方法就好了。C++则不同,由于需要支持函数重载,C++函数的签名都会根据参数类型进行改写,因此java虚拟机就无法找到相应的本地函数了。这样就糟糕了,总不能只能用C语言,不能用C++吧,那这样的话大量的C++资源都没法用了。当然,事实不是这样的,java虚拟机允许开发人员自己注册本地函数,一个字符串和一个函数指针关联起来,这样就没有函数签名对应的问题了。上面的代码就是将”stringFromJNI”和函数Java_com_hexmdemo_helloworld_MainActivity_stringFromJNI对应起来。当动态链接库加载的时候,JNI_OnLoad函数就会调用,完成注册过程即可。
(4)如何调试C/C++代码,还是比较方便的,直接在C++代码中下断点,右键点击Debug
As-〉Android Native Application即可。不过,还需要改一个设置。在Package
Explorer中选中工程,右键选择Properties,在弹出的界面中选择C/C++ Build,设置build
command为“ndk-build NDK_DEBUG=1”,NDK_DEBUG和1之间不要有空格。
(5)至此,这个HelloWorld程序就已经做完了,从理论上应该是可以运行了,但是这里会出现一个编译错误,类似于:Android
NDK: WARNING: APP_PLATFORM android-14 is larger than
Android:&minSdkVersion
8,网上有一些解决方法,这里采用了一种比较
粗暴的方法,就是讲NDK目录uild\core下的add-application.mk文件中的第125~130行注释掉,如下:
#ifdef APP_MANIFEST
#& APP_MIN_PLATFORM_LEVEL := $(shell $(HOST_AWK)
-f $(BUILD_AWK)/extract-minsdkversion.awk $(call
host-path,$(APP_MANIFEST)))
#& ifneq (,$(call
gt,$(APP_PLATFORM_LEVEL),$(APP_MIN_PLATFORM_LEVEL)))
#&&& $(call
__ndk_info,WARNING: APP_PLATFORM $(APP_PLATFORM) is larger than
android:minSdkVersion $(APP_MIN_PLATFORM_LEVEL) in
$(APP_MANIFEST))
最近看到有网友说在工程的Application.MK文件中添加“APP_PLATFORM&:=&android-8”即可,我还没有试验过。
(6) 如果要在NDK中做一些实用的事情,使用STL是必不可少的。NDK开发包中带了STL
Port,设置一下就可以使用,我比较习惯使用STL的静态库版本。需要做两件事情,一是将STL的头文件目录添加到工程中,这个步骤和第(4)步一样,我本机的目录是“F:\hexm_private\Android\android-ndk-r8e\sources\cxx-stl\stlport\stlport”,二是在jni目录中新增Application.MK文件,添加内容“APP_STL
stlport_static”。
总体来说,过程并不算太复杂,但是NDK的使用毕竟比较小众,摸索过程还是有些痛苦,希望此文对需要进行NDK开发的广大码农有所帮助。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。查看:37606|回复:17
【本范例所采用的配置】
·系统:Windows7 旗舰版,Service Pack 1,32位(最新的NDK已不支持WindowsXP)
·JDK(java包):1.7版
·Android Studio(制作安卓程序的主要工具):1.4版
·SDK(安卓开发工具包):Android Studio 1.4自带的
·NDK(原生开发工具包,用来做安卓程序的C/C++部分):用Android Studio 1.4内置的链接下载
·Experimental Plugin(一个实验版插件,目前NDK必不可少的助手):NDK自带的
·gradle(负责安卓程序的编译):2.5版(目前NDK只支持gradle2.5,版本高了低了都不行)
上述工具,除了Windows7 ,共有五个,但有些工具是捆绑在别的工具上的,所以,如果你的机器上一个也没有,要下载的只是这三个:
JDK在网上很容易搜到。另外两个,你可以到/下载,如果google的网站上不去,国内有一个网站/收录了绝大部分安卓开发工具。
假如你的系统从来没有碰过Android,要做的事情是:
一、安装JDK 1.7
重要的是记住安装路径。我的电脑是双系统,Windows7在D盘上,所以我装java的路径是“D:\Program Files\Java\jdk1.7.0_71”。
过去,在WindowsXP中使用Android Studio,装java要避免带空格的路径,现在Windows7没有这个限制了,你按默认的路径安装即可。
二、给java设环境变量
在电脑桌面左下角点“开始”按钮,然后依次选“控制面板”、“系统和安全”、“系统”、“环境变量”,打开“环境变量”对话框,这里有两个“新建”按钮,点下面那个(再次强调,是下面那个),建一个新的系统变量,名为“JAVA_HOME”,值为java的安装路径(我的是“D:\Program Files\Java\jdk1.7.0_71”)。
再建一个新的系统变量,名为“CLASSPATH”,值为“.;%JAVA_HOME%\%JAVA_HOME%\lib\tools.jar”,注意前面有点和分号。
找到已有的系统变量“PATH”,双击它,打开编辑它的窗口,在变量值的末尾加“;%JAVA_HOME%\%JAVA_HOME%\jre\bin”,注意前面有分号。
总结刚才的3个环境变量:
·JAVA_HOME(新建的) java的安装路径
·CLASSPATH(新建的) 
.;%JAVA_HOME%\%JAVA_HOME%\lib\tools.jar
·PATH(改原来的)   ;%JAVA_HOME%\%JAVA_HOME%\jre\bin(加在原值的后面)
改完之后一连串的“确定”,使这些变量得以保存。
验证java是否装好的方法:在DOS窗口中输入java -version回车,若看到版本信息,就是装好了。
三、安装AndroidStudio1.4及NDK
安装过程略。从提示文字上可以看到,这个版本Android Studio把SDK也一并安装到你电脑里了。
若不带SDK,就要单独下载和安装SDK,过后还要在Android Studio中填写SDK安装路径。
装好之后先别着急启动,在Android Studio的安装目录中找到bin文件夹,在其中找到idea.properties,用记事本打开,在其末尾添加一行并保存:
disable.android.first.run=true
如果Windows7不让你修改这个位于系统盘的文件,那就把它拷贝到别的地方修改,再拷回去覆盖原文件,这是可以有的,因为Windows7允许系统盘更换文件,只不过先问问你是不是管理员。
做这件事,是为了防止Android Studio启动时不停地连google服务器(在不翻墙的情况下根本连不上,只能让程序停在那儿不动)。
然后启动Android Studio,如果走不动,多半是因为java没装好。
出现“Welcome...”窗口后,选“Configure”、“Project Defaults”、“Project Structure”,打开“Project Structure”窗口:
你第一次启动时看到的窗口不是这样的,“Android NDK location”中没有东西,可能“JDK location”中也没有。
“Android SDK lication”肯定有了,因为SDK是这个版本自带的,它装好了,路径也就自动填上了。但JDK可能需要你手工填写,把java的安装路径填进去(也就是刚才设环境变量“JAVA_HOME”时填的路径)。
至于NDK,先要安装。
尚未安装NDK时,在此窗口的“Android NDK location”下会有一个按钮让你安装,点它按提示进行,在翻墙的情况下,经过漫长的等待,Android Studio告诉你在下SDK,其实也在下NDK,下载完在提示文字中就看到了,这是NDK。接着进入NDK安装,这用不了多久。装好后就自动填上了NDK的路径,就成了上图的样子。
有人说翻墙麻烦,不如找一个国内的链接下载NDK,安装,把地址告诉Android Studio。但这样一来,只能在项目中填写NDK地址,不能在整个程序中固定它。
而且google的官网建议用Android Studio内置的链接下载NDK,版本是r10e,必须装在SDK目录下的ndk-bundle文件夹中,配套的gradle只能是2.5版,SDK至少是19.0.0版且带生成工具(参阅/tech-docs/new-build-system/gradle-experimental),既然这么麻烦,还不如直接用Android Studio内置的链接下载。
如果你永远不需要在项目中写C或C++代码,就不用管NDK了,gradle也就用Android Studio自带的就行了,下一步也就免了。
在这里还要为NDK设环境变量:
NDK_ROOT(新建) NDK的安装路径
PATH(结尾增加) ;%NDK_ROOT%
四、安装gradle
下载的gradle是个压缩文件,把它解压成一个文件夹,放到Android Studio自带的gradle文件夹旁边,像这样:
(13.99 KB)
此图中,gradle-2.4是Android Studio 1.4原配的,gradle-2.5即将取代它的,是NDK要求的。
五、安装手机驱动
Android Studio有模拟器供你调试,但最好用真机,一是真机调试快,二是能表现所有功能、暴露所有问题。
1. 把安卓手机用数据线连在电脑的USB口上,就是你充电用的那根线,把插头拔下来,只用线,线的细的那头插在手机上,粗的那头插在电脑的USB口上。
2. 在手机的“开发者选项”中勾选“USB调试”。不同的手机品牌或安卓版本,这个选项的位置有所不同,我手头这个手机,点“设定”按钮后可以看到一串选项的底部有“开发者选项”,点开它可以看到“USB调试”这个选项。你的手机怎么样,自己找找吧。
3. 安装该手机的驱动程序。可以装“360手机助手”、“91手机助手”之类的,它发现电脑连上手机,就会自动下载该手机的驱动程序,当它显示手机型号时,驱动就装好了,360手机助手是把手机型号显示在左上角的。这时手机上也会出现USB图标,拉开它会看见“已连接为媒体设备”。
还有一个迹象表明手机驱动装好了——电脑的设备管理器显示“Android Phone”
Android Studio第一次识别手机可能比较慢,可能要依赖“360手机助手”这样的软件来装手机驱动,可能在驱动装好之后还是找不到手机,但重启电脑就找到了。或者还有种种稀奇古怪的问题,来回折腾碰巧哪一次找到了,以后就能找到了。手机软件开发就是这样,搞不明白就折腾,碰运气,奇怪的是问题总能解决,要真是永远解决不了倒好了,再也不用受它的气了。
说了这么多,就是一连手机,二装驱动,三看软件找到手机没有。
找到手机,Android Studio会显示手机型号,在界面下方点“Android Monitor”选项卡,就可以看到手机型号。
(10.37 KB)
【入门练习】
一、建新项目
重新启动Android Studio,在“Welcome...”窗口中选“Start a new Android Studio project”,按提示一步一步“Next”。
首先是起名:
Application name(应用名):就是你要做的程序的名称,也将是项目名称。
Company Domain(公司名):一般的格式是“类别.部门.公司”,这个练习只使用了“类别.部门”,填的是“exercise.myself”,意思就是“我自己.练习”。
你修改上面两项,软件就会自动更新下面的一项:
Package name(包名):这很重要,是你的程序的标识,C/C++代码会引用它。你不必现在记住它,因为写代码时随时可以在java代码中查到它。
然后是项目路径:
Project location(项目路径):就是说这个项目的文件装在哪一个文件夹里,这个可以随便设,只要自己记得住。
后面有一步选模板,最好选“Empty Activity”,其他模板会给你预备一堆没用的组件。
二、改gradle代码
对于不需要C/C++的项目,这一步完全可以跳过。但是不需要C/C++,也就不用看这篇文章了。
打开项目后,在界面左上方选“Project”,再把目录展开成这样:
上图中选中的“gradle-wrapper.properties”,是马上要修改的。它在目录中的位置,可表示为“gradle/wrapper/gradle-wrapper.properties”。
双击它,就在界面右边看到了它的代码,将“2.4”改成“2.5”,如下图所示。
这么改,是因为目前的NDK只支持gradle2.5,版本高了或低了都不行。
(12.63 KB)
如法炮制,修改build.gradle,将“:1.3.0”改为“-experimental:0.2.0”。
这是因为目前的NDK需要一个叫“experimental”的插件,它目前的版本是0.2.0。
注意:在现在以及后面的代码修改中,一个字符也不要错,仔细看本文的说明,一个冒号、一个点也不要漏掉,不然调试时报错,你都看不懂(调试不会说“你这里缺了一个分号”,它只会说一句让你丈二和尚摸不着头脑的话,甚至跟真正的错误八竿子也打不着)。
改完之后,立刻会冒出一个***警告,说gradle已经被你改了,你得在项目中更新它。这件事待会儿再做。
(22.94 KB)
接下来修改app/build.gradle(注意,有两个build.gradle,刚才改的是根目录下的,现在改的是app目录下的)。
这里要改的地方特别多,先用纯文本标一下(你别复制这段代码,因为可能有些数字和你的项目不匹配,你就照着这个模板改你的代码)。
再次提醒:必要的地方,一个字也别漏掉,一个也别多,哪怕是一个大小写的区别。
apply plugin: 'com.android.model.application' //红色代码是新加的
& & android {
& && &&&compileSdkVersion = 23 //这些数字是软件根据你选择的版本自动写的,不必改
& && &&&buildToolsVersion = &23.0.1&
& && && &defaultConfig.with {
& && && && &applicationId = &myself.exercise.ndktest& //这是程序包名,用你自己的
          minSdkVersion.apiLevel = 11
& && && && &targetSdkVersion.apiLevel = 23
& && && && &versionCode = 1
& && && && &versionName = &1.0&
& && && &}
& && && &tasks.withType(JavaCompile) {
& && && && &sourceCompatibility = JavaVersion.VERSION_1_7
& && && && & targetCompatibility = JavaVersion.VERSION_1_7
& && && &}
& & android.ndk {
& && &&&moduleName = &lb& //这是将来so文件的名称,自己取
& & android.buildTypes { //蓝色代码是移动了的
& && &&&release {
& && && && &minifyEnabled = true
& && && && & proguardFiles.add(file(&proguard-rules.pro&)) //紫色代码是改过了的
dependencies {
& & compile fileTree(dir: 'libs', include: ['*.jar'])
& & //testCompile 'junit:junit:4.12' //灰色代码的句子,你看到了就删
& & compile 'com.android.support:appcompat-v7:23.0.1'
& & //compile 'com.android.support:design:23.0.1'
改完了再一行一行检查,这要是错了,调试会痛苦不堪。
说到这里得提一下google的态度,把这么麻烦的事交给用户,他们自己也挺不好意思的,在官网上说,因为NDK还是实验版,不得已才让用户自己改代码,他们会逐渐让这些东西自动化。
三、更新gradle
现在来解决那个***警告的问题。
警告右边有一个“Sync Now”链接,点它,故意引发报错:
它是说,现在的gradle是2.4版的,你设置了2.5,就得找到2.5的安装路径。
正如前面所说,目前的NDK只支持gradle2.5,这也就是为什么我们在安装阶段就把2.5放在2.4的旁边。
在上述报错窗口中,点“Gradle settings”,打开如下窗口:
(16.99 KB)
点“gradle-2.4”旁边的“…”按钮,去找你已经安装好的gradle-2.5。之后报错信息没有了,程序会重建gradle。
本帖最后由 grayhat 于
23:02 编辑
四、关于实验版插件
还有一个隐藏的家伙一直没有露面,但是跟着NDK一起进入了你的Android Studio,悄悄地进村,打qiang地不要,它就是Experimental Plugin,是C++制作必不可少的。刚才改“build.gradle”已经提到了它,就是“experimental:0.2.0”。
目前Android Studio的C/C++开发离不开这三个家伙(而且对它们的版本有严格限制):
·NDK(原生开发工具包,Native Develop Kit)r10e版;
·gradle2.5版;
·Experimental Plugin(实验版插件)。
总结一下它们的来历:
·你单独下载安装了gradle2.5
·用Android Studio内置的链接下载、由Android Studio自动安装了NDK
·NDK悄悄带来了实验版插件
关于此插件的详情,参阅/tech-docs/new-build-system/gradle-experimental
关于NDK,参阅/intl/zh-cn/ndk/index.html、/tech-docs/android-ndk-preview
五、创建jni文件夹
在app/src/main文件夹上点右键,在弹出菜单中选择“New”、“Folder”、“JNI Folder”,按提示进行。
有一个“Change Folder Location”选项,不需要勾选,因为jni文件夹采用默认的位置(在main文件夹中)就行。
然后main目录下会出现jni文件夹。
六、创建C++源文件
在jni文件夹上点右键,在弹出菜单中选择“New”、“C/C++ Source File”。
在出现的对话框中,给即将创建的C++文件取名(不要加后缀,软件会自动加上的)。
有一个选项“Create associated header”,不要勾选它,现在不需要创建头文件。
之后,jni文件夹中出现了“myCode.cpp”,我们先不管它,先到Android部分做一些准备工作。
(11.12 KB)
七、建一个演示按钮
这是纯Android操作,可能你已经熟悉了,但初学者需要知道。
编辑主窗口的xml文件(在此范例里是“activity_main.xml”)。如果你在界面上看不到它,就到Project目录的“app/src/main/res/layout/”中找到它,双击它打开编辑窗口。
窗口中有个手机图样,是模拟软件显示的效果,上面有一行字“Hello World”,是AndroidStudio自动加上的显示文字的控件。双击它,会打开一个小窗口,上面有“id”栏,在这里给该控件取名为“textView”(实际上可以随便取,但为了和后面的步骤衔接,就取这个名吧),这是java代码将要引用的。
再添加一个按钮,方法是:在左边的组件列表中找到“Button”,拖到手机图上。
当然你也可以用编辑代码的方式加这个按钮,这里对纯Android操作只说最简单的方法。
双击这个按钮打开小窗口,给它的“id”取名叫“button1”。
然后点上方的“MainActivity.java”选项卡,进入主窗口代码编辑界面,把代码改成这样:
package myself.exercise.
import android.app.A
import android.os.B
import android.view.V
import android.widget.B
import android.widget.TextV
public class MainActivity extends Activity {
& & TextView textV //声明一个标签
& & Button button1; //声明一个按钮
& & protected void onCreate(Bundle savedInstanceState) {
& && &&&super.onCreate(savedInstanceState);
& && &&&setContentView(R.layout.activity_main);
& && &&&textView = (TextView)findViewById(R.id.textView); //在xml中找到标签
button1 = (Button)findViewById(R.id.button1); //在xml中找到按钮
button1.setOnClickListener(new View.OnClickListener() {
& && && && &
& && && && &public void onClick(View v) {
& && && && && & //点按钮以后发生的事
& && && && &}
& && &&&});
新手注意:顶端的这一句:“package myself.exercise.”,表明这个项目的包名是“myself.exercise.ndktest”,以后引用包名时,可以到这儿来拷贝。
另外,在Android Studio中输入类名(比如“TextView”)时,如果你没写错它却显示为红色,表明Android Studio还没有识别这个名称,上面的“import”还缺点什么,你有两种办法:
1. 重新输入它,等待一个提示框出现,再按Tab键,Android Studio会自动帮你完成输入,把相应的“import”添加到代码中,比如添加“import android.widget.TextV”,这时就不报错了。
2. 单击它,当提示框出现时,按“Alt + 回车键”。
八、建一个新的java类,用来加载C++库
在“Project”目录中找到“app/src/main/java/myself.exercise.ndktest”:
(11.51 KB)
在这个文件夹上点右键,在弹出菜单中选择“New”、“Java Class”,在弹出窗口中取名“Load”(不要加后缀,软件会自动加上的):
点“OK”之后,“myself.exercise.ndktest”文件夹中就出现了新的类“Load”(其实它的全名是“Load.java”)。双击它打开它的代码窗口,把代码改成这样:
package myself.exercise.
public class Load {
& & static { //载入名为“lb”的C++库
& && &&&System.loadLibrary(&lb&);
& & public native int addInt(int a, int b); //调用库里的方法“addInt”,这是计算a和b两个整数相加
(10.39 KB)
现在“addInt”是红色的,因为前面的“native”还没有找到下落,以后等native函数(C/C++函数)有了,这里自然就不红了。
现在记住几个关键的名称:
·即将创建的C++库的名称:lb
·加载此库的java包名:myself.exercise.ndktest
·加载此库的java类名:Load
·从此库中调用的方法:int addInt(int a, int b)(参数是两个整数,返回是整数)
九、生成头文件
走一遍主菜单“Build & Make Project”,这一步有没有效果,要看这里:
(15.81 KB)
原来是没有“Load.class”的,但是“Make Project”之后,它就加进来了。前提是“Make Project”要成功,不成功,什么也不会改变。至于不成功的原因,可能是你漏了哪个字符,可能是没有严格按本指南操作,也可能你的软件版本与本文所说的不一样。软件开发是由一堆一堆人为的规矩组成的,不是理论物理,不是纯粹数学,没有真理,我们永远挣扎在别人制定的规矩中,没办法,因为我们没有比尔·盖茨那样的本事。
点界面左下角的“Terminal”标签,打开命令行窗口:
在这句话的末尾输入:
cd app/build/intermediates/classes/debug
按回车键,进入“debug”目录:
接着输入:
javah -jni myself.exercise.ndktest.Load
按回车键。
注意:“myself.exercise.ndktest”是包名,“Load”是调用C++库的类的名称,你开发自己的软件时,这些要改的。
要是你没写错,“debug”目录下会有一个头文件生成:
(12.67 KB)
它的后缀是“h”,表明它是C/C++头文件;它的名字仍然由上述包名和类名组成,只不过点变成了横杠。
右键单击此文件,在弹出菜单中选择“Cut”。
然后找到“app/src/main/jni”目录,在jni文件夹上右键单击,在弹出菜单中选择“Paste”,在接下来的对话框中什么也不改直接点“OK”,你会看到头文件已经被转移到了jni文件夹中,与我们先前建立的源文件“myCode.cpp”文件在一起。
(13.24 KB)
双击此文件,可以看到它的代码:
(15.39 KB)
C++的普通语法不解释了,这里特别的一段是:
JNIEXPORT jint JNICALL Java_myself_exercise_ndktest_Load_addInt
&&(JNIEnv *, jobject, jint, jint);
它声明了方法“addInt”,并指定了可以调用它的java包是“myself.exercise.ndktest”、可以调用它的java类是“Load”。
“jint”与“JNICALL”之间有一个红色警告,不管它,这是假报错。
上面有一句“extern &C&”,表明此方法是向外输出的,是给java程序调用的。
在这里,我们什么也不用改。
十、编辑源文件
双击“myCode.cpp”打开其代码编辑窗口,写入以下代码:
#include &myself_exercise_ndktest_Load.h&
JNIEXPORT jint JNICALL Java_myself_exercise_ndktest_Load_addInt (JNIEnv *, jobject, jint a, jint b) {
& & return a +
这是方法“addInt”的实现——两个整数相加,得到一个整数结果。
十一、调用库方法
刚才在“Load.java”中加载了库,调用了库方法,现在在主窗口中调用“Load.java”所调用的这个方法。
主窗口的代码刚才已经贴过了,现在,在“点按钮以后发生的事”这句话下面添加:
Load load = new Load();
int r = load.addInt(100, 50);
textView.setText(&C++库计算“100+50”的结果是:& + String.valueOf(r));
整个过程是:
1. 调试或发布时,“myCode.cpp”将被编译为库“lb”。
2. “Load”加载了库“lb”,调用了库中的方法“addInt”。
3. 主窗口的按钮点击事件,通过“Load”调用这个方法,算出100+50等于多少。
十二、调试
手机连好,点界面上方的调试按钮
(585 Bytes)
过一会儿弹出一个窗口,在其中选择已连好的手机,点“OK”,等一会儿到手机上看结果。
不出意外的话,手机上会出现刚做的软件,点其中的按钮会显示C++计算的结果:
【练习小结】(只说C++的部分)
一、改gradle代码
1. “gradle/wrapper/gradle-wrapper.properties”中,“2.4”改成“<font color="#ff”。
2. “build.gradle”中,“:1.3.0”改为“-experimental:0.2.0”。
3. “app/build.gradle”中:
apply plugin: 'com.android.model.application' //红色代码是新加的
& & android {
& && &&&compileSdkVersion = 23 //这些数字是软件根据你选择的版本自动写的,不必改
& && &&&buildToolsVersion = &23.0.1&
& && && &defaultConfig.with {
& && && && &applicationId = &myself.exercise.ndktest& //这是程序包名,用你自己的
          minSdkVersion.apiLevel = 11
& && && && &targetSdkVersion.apiLevel = 23
& && && && &versionCode = 1
& && && && &versionName = &1.0&
& && && &}
& && && &tasks.withType(JavaCompile) {
& && && && &sourceCompatibility = JavaVersion.VERSION_1_7
& && && && & targetCompatibility = JavaVersion.VERSION_1_7
& && && &}
& & android.ndk {
& && &&&moduleName = &lb& //这是将来so文件的名称,自己取
& & android.buildTypes { //蓝色代码是移动了的
& && &&&release {
& && && && &minifyEnabled = true
& && && && & proguardFiles.add(file(&proguard-rules.pro&)) //紫色代码是改过了的
dependencies {
& & compile fileTree(dir: 'libs', include: ['*.jar'])
& & //testCompile 'junit:junit:4.12' //灰色代码的句子,你看到了就删
& & compile 'com.android.support:appcompat-v7:23.0.1'
& & //compile 'com.android.support:design:23.0.1'
二、更新gradle
“Sync Now”、“Gradle settings”,找gradle-2.5。
三、创建jni文件夹,在此文件夹中创建C++源文件*.cpp
四、建一个新的java类,用来加载C++库
代码模板:
package myself.exercise.
public class Load {
& & static {
& && &&&System.loadLibrary(&lb&);
& & public native int addInt(int a, int b);
五、生成头文件*.h
主菜单“Build & Make Project”。
Terminal窗口中:
cd app/build/intermediates/classes/debug 回车
javah -jni myself.exercise.ndktest.Load 回车
把头文件挪到jni文件夹中与源文件*.cpp在一起。
六、编辑源文件*.cpp
代码模板:
#include &myself_exercise_ndktest_Load.h&
JNIEXPORT jint JNICALL Java_myself_exercise_ndktest_Load_addInt (JNIEnv *, jobject, jint a, jint b) {
& & return a +
七、调用库方法
代码模板:
Load load = new Load();
int r = load.addInt(100, 50);
【更多的jni函数】
刚才处理的只是整数,现在扩展一下。
首先“Load.java”增加了一些函数声明,先在这里声明,以后在C++中实现:
package myself.exercise.
public class Load {
& & static {
& && &&&System.loadLibrary(&lb&);
& & public native int addInt(int a, int b); //输入整数,输出整数
& & public native double mulDouble(double a, double b); //输入实数,输出实数
& & public native boolean bigger(float a, float b); //输入float型实数,输出布尔值
& & public native String addString(String a, String b); //输入字符串,输出字符串
& & public native int[] intArray(int[] a); //输入整数数组,输出整数数组
& & public native double[] doubleArray(double[] a); //输入实数数组,输出实数数组
& & public native String[] stringArray(String[] a); //输入字符串数组,输出字符串数组
然后主窗口增加了一些组件,用来测试这些函数:
(84.34 KB)
为了省事,你可以直接把下述代码拷到“activity_main_xml”中去:
&?xml version=&1.0& encoding=&utf-8&?&
&RelativeLayout xmlns:android=&/apk/res/android&
& & xmlns:tools=&/tools& android:layout_width=&match_parent&
& & android:layout_height=&match_parent& android:paddingLeft=&@dimen/activity_horizontal_margin&
& & android:paddingRight=&@dimen/activity_horizontal_margin&
& & android:paddingTop=&@dimen/activity_vertical_margin&
& & android:paddingBottom=&@dimen/activity_vertical_margin& tools:context=&.MainActivity&&
& & &TextView android:text=&Hello World!& android:layout_width=&wrap_content&
& && &&&android:layout_height=&wrap_content&
& && &&&android:id=&@+id/textView&
& && &&&android:layout_alignParentTop=&true&
& && &&&android:layout_alignParentLeft=&true&
& && &&&android:layout_alignParentStart=&true&
& && &&&android:layout_marginTop=&111dp& /&
& & &Button
& && &&&android:layout_width=&wrap_content&
& && &&&android:layout_height=&wrap_content&
& && &&&android:text=&addInt&
& && &&&android:id=&@+id/button1&
& && &&&android:layout_marginTop=&41dp&
& && &&&android:layout_below=&@+id/textView&
& && &&&android:layout_alignParentLeft=&true&
& && &&&android:layout_alignParentStart=&true& /&
& & &EditText
& && &&&android:layout_width=&wrap_content&
& && &&&android:layout_height=&wrap_content&
& && &&&android:id=&@+id/editText1&
& && &&&android:layout_alignParentTop=&true&
& && &&&android:layout_alignParentLeft=&true&
& && &&&android:layout_alignParentStart=&true&
& && &&&android:width=&150dp& /&
& & &EditText
& && &&&android:layout_width=&wrap_content&
& && &&&android:layout_height=&wrap_content&
& && &&&android:id=&@+id/editText2&
& && &&&android:width=&200dp&
& && &&&android:layout_alignTop=&@+id/editText1&
& && &&&android:layout_alignParentRight=&true&
& && &&&android:layout_alignParentEnd=&true& /&
& & &Button
& && &&&android:layout_width=&wrap_content&
& && &&&android:layout_height=&wrap_content&
& && &&&android:text=&mulDouble&
& && &&&android:id=&@+id/button2&
& && &&&android:layout_above=&@+id/button3&
& && &&&android:layout_alignLeft=&@+id/button4&
& && &&&android:layout_alignStart=&@+id/button4& /&
& & &Button
& && &&&android:layout_width=&wrap_content&
& && &&&android:layout_height=&wrap_content&
& && &&&android:text=&intArray&
& && &&&android:id=&@+id/button3&
& && &&&android:layout_below=&@+id/button1&
& && &&&android:layout_alignParentLeft=&true&
& && &&&android:layout_alignParentStart=&true& /&
& & &Button
& && &&&android:layout_width=&wrap_content&
& && &&&android:layout_height=&wrap_content&
& && &&&android:text=&doubleArray&
& && &&&android:id=&@+id/button4&
& && &&&android:layout_below=&@+id/button2&
& && &&&android:layout_centerHorizontal=&true& /&
& & &Button
& && &&&android:layout_width=&wrap_content&
& && &&&android:layout_height=&wrap_content&
& && &&&android:text=&stringArray&
& && &&&android:id=&@+id/button5&
& && &&&android:layout_below=&@+id/button3&
& && &&&android:layout_alignParentLeft=&true&
& && &&&android:layout_alignParentStart=&true& /&
& & &Button
& && &&&android:layout_width=&wrap_content&
& && &&&android:layout_height=&wrap_content&
& && &&&android:text=&addString&
& && &&&android:id=&@+id/button6&
& && &&&android:layout_below=&@+id/button5&
& && &&&android:layout_alignParentLeft=&true&
& && &&&android:layout_alignParentStart=&true& /&
&/RelativeLayout&
然后重新生成头文件(因为新增函数的声明需要写在头文件中,我们自己写,不如让Android Studio替我们写),具体做法是:
再走一遍“Build & Make Project”。
在Terminal窗口中再来一遍:
(如果没有进入“debug”目录就)cd app/build/intermediates/classes/debug 回车
javah -jni myself.exercise.ndktest.Load 回车
“debug”目录又冒出了一个头文件“myself_exercise_ndktest_Load.h”,和先前做的头文件名字一样。
把它挪到jni文件夹中,覆盖原来那个头文件。
新的头文件中已经包含了新方法的C++声明:
(31.56 KB)
上面又出现了***警告,这是又需要合成gradle了,点右边的“Sync Now”即可。
对头文件本身,什么都不用改。
要改的是源文件“myCode.cpp”,改成这样:
#include &myself_exercise_ndktest_Load.h&
#include &string.h&
JNIEXPORT jint JNICALL Java_myself_exercise_ndktest_Load_addInt (JNIEnv *, jobject, jint a, jint b) {
& & return a + //输入整数,输出整数
JNIEXPORT jdouble JNICALL Java_myself_exercise_ndktest_Load_mulDouble (JNIEnv *, jobject, jdouble a, jdouble b) {
& & return a * //输入实数,输出实数
JNIEXPORT jboolean JNICALL Java_myself_exercise_ndktest_Load_bigger (JNIEnv *, jobject, jfloat a, jfloat b) {
& & return a & //输入float型实数,输出布尔值,判断a是否大于b
char * JstringToCstr(JNIEnv * env, jstring jstr) { //jstring转换为C++的字符数组指针
& & char * pChar = NULL;
& & jclass classString = env-&FindClass(&java/lang/String&);
& & jstring code = env-&NewStringUTF(&GB2312&);
& & jmethodID id = env-&GetMethodID(classString, &getBytes&, &(Ljava/lang/S)[B&);
& & jbyteArray arr = (jbyteArray)env-&CallObjectMethod(jstr, id, code);
& & jsize size = env-&GetArrayLength(arr);
& & jbyte * bt = env-&GetByteArrayElements(arr, JNI_FALSE);
& & if(size & 0) {
& && &&&pChar = (char*)malloc(size + 1);
& && &&&memcpy(pChar, bt, size);
& && &&&pChar[size] = 0;
& & env-&ReleaseByteArrayElements(arr, bt, 0);
& & return pC
JNIEXPORT jstring JNICALL Java_myself_exercise_ndktest_Load_addString (JNIEnv * env, jobject, jstring a, jstring b) {
& & char * pA = JstringToCstr(env, a); //取得a的C++指针
& & char * pB = JstringToCstr(env, b); //取得b的C++指针
& & return env-&NewStringUTF(strcat(pA, pB)); //输出a+b
JNIEXPORT jintArray JNICALL Java_myself_exercise_ndktest_Load_intArray (JNIEnv * env, jobject, jintArray a) {
& & //输入整数数组,将其每个元素加10后输出
& & //输入值为a,输出值为b
& & int N = env-&GetArrayLength(a); //获取a的元素个数
& & jint * pA = env-&GetIntArrayElements(a, NULL); //获取a的指针
& & jintArray b = env-&NewIntArray(N); //创建数组b,长度为N
& & jint * pB = env-&GetIntArrayElements(b, NULL); //获取b的指针
& & for (int i = 0; i & N; i++) pB = pA + 10; //把a的每个元素加10,赋值给b中对应的元素
& & /*//另一种方法
& & env-&SetIntArrayRegion(b, 0, N, pA); //b是a的复制品
& & for (int j = 0; j & N; j++) pB[j] += 10; //b的每个元素加10
& & env-&ReleaseIntArrayElements(a, pA, 0); //释放a的内存
& & env-&ReleaseIntArrayElements(b, pB, 0); //释放b的内存
& & //输出b
JNIEXPORT jdoubleArray JNICALL Java_myself_exercise_ndktest_Load_doubleArray (JNIEnv * env, jobject, jdoubleArray a) {
& & //输入实数数组,将其每个元素乘2后输出
& & //输入值为a,输出值为b
& & int N = env-&GetArrayLength(a); //获取a的元素个数
& & jdouble * pA = env-&GetDoubleArrayElements(a, NULL); //获取a的指针
& & jdoubleArray b = env-&NewDoubleArray(N); //创建数组b,长度为N
& & jdouble * pB = env-&GetDoubleArrayElements(b, NULL); //获取b的指针
& & for (int i = 0; i & N; i++) pB = pA * 2; //把a的每个元素乘2,赋值给b中对应的元素
& & /*//另一种方法
& & env-&SetDoubleArrayRegion(b, 0, N, pA); //b是a的复制品
& & for (int j = 0; j & N; j++) pB[j] *= 2; //b的每个元素乘2
& & env-&ReleaseDoubleArrayElements(a, pA, 0); //释放a的内存
& & env-&ReleaseDoubleArrayElements(b, pB, 0); //释放b的内存
& & //输出b
JNIEXPORT jobjectArray JNICALL Java_myself_exercise_ndktest_Load_stringArray (JNIEnv * env, jobject, jobjectArray a) {
& & //输入字符串数组,颠倒顺序后输出
& & //输入值为a,输出值为b
& & int N = env-&GetArrayLength(a); //获取a的元素个数
& & jobjectArray b = env-&NewObjectArray(N, env-&FindClass(&java/lang/String&), env-&NewStringUTF(&&)); //创建数组b,长度为N
& & for (int i = 0; i & N; i++) { //对于a中的每个元素
& && &&&jstring ai = (jstring)env-&GetObjectArrayElement(a, i); //读出这个元素的值
& && &&&env-&SetObjectArrayElement(b, N - 1 - i, ai); //赋值给b中倒序对应的元素
& && &&&env-&DeleteLocalRef(ai);//释放内存
& & //输出b
再把主窗口代码改成这样:
package myself.exercise.
import android.app.A
import android.os.B
import android.view.V
import android.widget.B
import android.widget.EditT
import android.widget.TextV
public class MainActivity extends Activity {
& & Load load = new Load();
& & EditText editText1; EditText editText2;
& & TextView textV
& & Button button1; Button button2; Button button3;
& & Button button4; Button button5; Button button6;
& & protected void onCreate(Bundle savedInstanceState) {
& && &&&super.onCreate(savedInstanceState);
& && &&&setContentView(R.layout.activity_main);
& && &&&editText1 = (EditText)findViewById(R.id.editText1); //左边的文字输入框,以下简称“左框”
& && &&&editText2 = (EditText)findViewById(R.id.editText2); //右边的文字输入框,以下简称“右框”
& && &&&textView = (TextView)findViewById(R.id.textView);
& && &&&button1 = (Button)findViewById(R.id.button1);
& && &&&button1.setOnClickListener(new View.OnClickListener() {
& && && && &
& && && && &public void onClick(View v) {
& && && && && & //按钮“addInt”用来计算左框里的整数和右框里的整数相加
& && && && && & //你要是不输入整数,它就当你输入了“0”
& && && && && & int a = 0;
& && && && && & try { a = Integer.parseInt(String.valueOf(editText1.getText())); }
& && && && && & catch (NumberFormatException e) { }
& && && && && & int b = 0;
& && && && && & try { b = Integer.parseInt(String.valueOf(editText2.getText())); }
& && && && && & catch (NumberFormatException e) { }
& && && && && & int r = load.addInt(a, b);
& && && && && & textView.setText(&C++库计算& + a + &+& + b +&的结果是:& + r);
& && && && &}
& && &&&});
& && &&&button2 = (Button)findViewById(R.id.button2);
& && &&&button2.setOnClickListener(new View.OnClickListener() {
& && && && &
& && && && &public void onClick(View v) {
& && && && && & //按钮“mulDouble”用来计算左框里的实数和右框里的实数相乘
& && && && && & //你要是不输入数字,它就当你输入了“0”
& && && && && & double a = 0;
& && && && && & try { a = Double.parseDouble(String.valueOf(editText1.getText())); }
& && && && && & catch (NumberFormatException e) { }
& && && && && & double b = 0;
& && && && && & try { b = Double.parseDouble(String.valueOf(editText2.getText())); }
& && && && && & catch (NumberFormatException e) { }
& && && && && & double r = load.mulDouble(a, b);
& && && && && & textView.setText(&C++库计算& + a + &*& + b +&的结果是:& + r);
& && && && &}
& && &&&});
& && &&&button3 = (Button)findViewById(R.id.button3);
& && &&&button3.setOnClickListener(new View.OnClickListener() {
& && && && &
& && && && &public void onClick(View v) {
& && && && && & //按钮“intArray”用来计算左框里的整数数组每个元素加10会变成什么,结果放在右框里
& && && && && & //输入数组时,元素之间用英文逗号隔开,比如“1,2,3”
& && && && && & //对于每个元素,你要是不输入整数,它就当你输入了“0”
& && && && && & //注意不要用中文逗号,不然它无法识别
& && && && && & String str = String.valueOf(editText1.getText());
& && && && && & String[] strA
& && && && && & if (str.contains(&,&)) strArray = str.split(&,&);
& && && && && & else strArray = new String[] { str };
& && && && && & int N = strArray.
& && && && && & int[] array = new int[N];
& && && && && & for (int i = 0; i & N; i++) {
& && && && && && &&&int t = 0;
& && && && && && &&&try { t = Integer.parseInt(strArray); }
& && && && && && &&&catch (NumberFormatException e) { }
& && && && && && &&&array =
& && && && && & }
& && && && && & int[] newArray = load.intArray(array);
& && && && && & str = &&;
& && && && && & for (int i = 0; i & N; i++) {
& && && && && && &&&if (i & N - 1) str += String.valueOf(newArray) + &,&;
& && && && && && &&&else str += String.valueOf(newArray);
& && && && && & }
& && && && && & editText2.setText(str);
& && && && && & textView.setText(&C++库用左边的数组计算,结果在右边&);
& && && && &}
& && &&&});
& && &&&button4 = (Button)findViewById(R.id.button4);
& && &&&button4.setOnClickListener(new View.OnClickListener() {
& && && && &
& && && && &public void onClick(View v) {
& && && && && & //按钮“doubleArray”用来计算左框里的实数数组每个元素乘2会变成什么,结果放在右框里
& && && && && & //输入数组时,元素之间用英文逗号隔开,比如“2.5,3.14,8”
& && && && && & //对于每个元素,你要是不输入数字,它就当你输入了“0”
& && && && && & //注意不要用中文逗号,不然它无法识别
& && && && && & String str = String.valueOf(editText1.getText());
& && && && && & String[] strA
& && && && && & if (str.contains(&,&)) strArray = str.split(&,&);
& && && && && & else strArray = new String[] { str };
& && && && && & int N = strArray.
& && && && && & double[] array = new double[N];
& && && && && & for (int i = 0; i & N; i++) {
& && && && && && &&&double t = 0;
& && && && && && &&&try { t = Double.parseDouble(strArray); }
& && && && && && &&&catch (NumberFormatException e) { }
& && && && && && &&&array =
& && && && && & }
& && && && && & double[] newArray = load.doubleArray(array);
& && && && && & str = &&;
& && && && && & for (int i = 0; i & N; i++) {
& && && && && && &&&if (i & N - 1) str += String.valueOf(newArray) + &,&;
& && && && && && &&&else str += String.valueOf(newArray);
& && && && && & }
& && && && && & editText2.setText(str);
& && && && && & textView.setText(&C++库用左边的数组计算,结果在右边&);
& && && && &}
& && &&&});
& && &&&button5 = (Button)findViewById(R.id.button5);
& && &&&button5.setOnClickListener(new View.OnClickListener() {
& && && && &
& && && && &public void onClick(View v) {
& && && && && & //按钮“stringArray”用来把左框里的字符串数组颠倒顺序,结果放在右框里
& && && && && & //输入数组时,元素之间用英文逗号隔开,比如“China,USA,England”
& && && && && & //注意不要用中文逗号,不然它把这个逗号当成一个字符,与左右字符串相连成为数组中的一个元素
& && && && && & String str = String.valueOf(editText1.getText());
& && && && && & String[] strA
& && && && && & if (str.contains(&,&)) strArray = str.split(&,&);
& && && && && & else strArray = new String[] { str };
& && && && && & int N = strArray.
& && && && && & String[] newArray = load.stringArray(strArray);
& && && && && & str = &&;
& && && && && & for (int i = 0; i & N; i++) {
& && && && && && &&&if (i & N - 1) str += newArray + &,&;
& && && && && && &&&else str += newArray;
& && && && && & }
& && && && && & editText2.setText(str);
& && && && && & textView.setText(&C++库颠倒了左边数组的顺序,结果在右边&);
& && && && &}
& && &&&});
& && &&&button6 = (Button)findViewById(R.id.button6);
& && &&&button6.setOnClickListener(new View.OnClickListener() {
& && && && &
& && && && &public void onClick(View v) {
& && && && && & //按钮“addString”用来把左框里的字符串与右框里的字符串相加
& && && && && & String a = String.valueOf(editText1.getText());
& && && && && & String b = String.valueOf(editText2.getText());
& && && && && & String r = load.addString(a, b);
& && && && && & textView.setText(&C++库计算& + a + &+& + b +&的结果是:& + r);
& && && && &}
& && &&&});
好了,调试。
比如我在左边输入“1,2,3,4”,点“intArray”按钮,右边就会是“11,12,13,14”,这是C++处理数组的结果,还可以试其他按钮,详细情况在主窗口代码的注释中。
(111.85 KB)
本帖最后由 grayhat 于
00:40 编辑
【更多更多的jni函数】
基本的函数,语句是相似的,比如取得数组指针,对于int数组,这个函数是GetIntArrayElements,对于byte数组,就是GetByteArrayElements,把Int改成Byte就行了。下面用byte总结一下最常用的函数:
private jobjectArray arrToArrArr(JNIEnv * env, jbyteArray arr) {
& & int N = env-&GetArrayLength(arr); //取得数组长度
& & jbyte * pArr = env-&GetByteArrayElements(arr , NULL); //取得数组指针
& & int M = 16;
& & jobjectArray arrArr = env-&NewObjectArray(M, env-&FindClass(&[B&), NULL); //创建二维数组
for (int i2 = 0; i2 & M; i2++) {
& && &&&jbyteArray arrCopy = env-&NewByteArray(N); //创建一维数组
& && &&&jbyte * pArrCopy = env-&GetByteArrayElements(arrCopy, NULL); //取得相应指针
& && &&&for (int j = 0; j & N; j++) pArrCopy[j] = pArr[j] + 1; //一维数组元素操作
& && &&&env-&SetObjectArrayElement(arrArr, i2, arrCopy); //二维数组元素操作
& && &&&env-&ReleaseByteArrayElements(arrCopy, pArrCopy, 0); //释放一维数组指针
& && &&&env-&DeleteLocalRef(arrCopy); //释放对它的引用
jobject obj = env-&GetObjectArrayElement(arrArr, 0); //取二维数组的元素
jbyteArray newArr = (jbyteArray) //强制转换
& & int thisN = env-&GetArrayLength(newArr);
jbyte * pNewArr = env-&GetByteArrayElements(newArr, NULL); //取指针
& & for (int i3 = 0; i3 & thisN; i3++) pNewArr[i3] += 2; //元素操作
& & env-&SetObjectArrayElement(arrArr, M - 1, newArr); //用修改后的一维数组替代二维数组的最后一行
& & env-&ReleaseByteArrayElements(newArr, pNewArr, 0); //释放这个一维数组的指针
& & env-&DeleteLocalRef(newArr); //释放对它的引用
env-&ReleaseByteArrayElements(arr, pArr, 0); //释放最初那个一维数组的指针
& & return arrA
JNIEXPORT jobjectArray JNICALL Java_myself_exercise_ndktest_Load_arrToArrArr(JNIEnv * env, jobject, jbyteArray arr) {
& & return arrToArrArr(env, arr);
jni中的基本数据类型有:
jboolean:对应于C++的bool、java的boolean
jbyte:对应于C++和java的byte
jchar:对应于C++和java的char
jshort:对应于C++和java的short
jint:对应于C++和java的int
jlong:对应于C++和java的long
jfloat:对应于C++和java的float
jdouble:对应于C++和java的double
带“JNIEXPORT”的外联语句应该使用“j”字头数据类型,而在jni内部,对于基本数据类型,一般来说既可以用jni式的,也可以用C++式的,比如定义一个名叫“flag”的变量,可以用“jboolean flag = false”,也可以用“bool flag = false”,涉及到整数,可以用“jint”,也可以用“int”。但byte是个例外,jni不支持“byte”这个关键词,只能用“jbyte”。
数组类型有jbooleanArray、jbyteArray、jcharArray、jshortArray、jintArray、jlongArray、jfloatArray、jdoubleArray,实际上就是每个基本类型都有一个Array。它们的函数写法也是类似的,比如上面那段代码,把“byte”统一替换成“int”,把“Byte”统一替换成“Int”,就是jint型的函数了(不过涉及到二维数组时,“FindClass(&[B&)”要改成“FindClass(&[I&)”)。
jni数组可以写成C++指针,比如:
jcharArray chArr = env-&NewCharArray(10);
jchar * pChArr = env-&GetCharArrayElements(chArr, NULL);
jintArray intArr = env-&NewIntArray(10);
jint * pIntArr = env-&GetIntArrayElements(intArr, NULL);
for (int i = 0; i & 10; i++) {
& & pChArr = 65 +
& & pIntArr = pChArr + 100;
env-&ReleaseCharArrayElements(chArr, pChArr, 0);
env-&ReleaseIntArrayElements(intArr, pIntArr, 0);
可以写成C++式的:
char chArr[10];
int intArr[10];
for (int i = 0; i & 10; i++) {
& & *(chArr + i) = 65 +
& & *(intArr + i) = *(chArr + i) + 100;
free(chArr);
free(intArr);
但不能、也不可能写成java式的,像“char[] chArr = new char[10]”这样的句子进去以后直接就报错了,就变红了。
字符串是比较特别的类型,按理说它是字符数组,但它的函数写法与众不同:
jstring str = env-&NewStringUTF(&abcde&); //创建字符串
const jchar * pJchars = env-&GetStringChars(str, NULL); //获得Unicode编码的字符串指针
const char * pChars = env-&GetStringUTFChars(str, NULL); //获得UTF-8编码的字符串指针
const jbyte * pBytes = env-&GetStringUTFChars(str, NULL); //获得与UTF-8编码的字符串等价的byte数组指针
int N = env-&GetStringLength(str); //获得Unicode编码的字符串长度
int start = 0; int n = N - 1;
jchar * jchB
env-&GetStringRegion(str, start, n, jchBuf); //从序号为start的字符开始,把n个字符复制给Unicode编码的jchar数组
int utfN = env-&GetStringUTFLength(str); //获得UTF-8编码的字符串长度
char * chB
env-&GetStringUTFRegion(str, start, n, chBuf); //从序号为start的字符开始,把n个字符复制给UTF-8编码的char数组
const jchar * cr = env-&GetStringCritical(str, NULL); //获得编码格式的字符串指针
env-&ReleaseStringChars(str, pJchars); //由上述函数产生的各种指针的释放
env-&ReleaseStringUTFChars(str, pChars);
env-&ReleaseStringUTFChars(str, pBytes);
env-&ReleaseStringChars(str, jchBuf);
env-&ReleaseStringUTFChars(str, chBuf);
env-&ReleaseStringCritical(str, cr);
并不能用string来代替jstring,因为jni中根本就没有string这个写法。但对于char*指针,可以用C++的函数,比如strcpy。
一个简单的例子,注册校验中常用的,比较两个字符串是否相同:
bool sameJstring(JNIEnv *env, jstring a, jstring b) {
& & if (a == NULL) {
& && &&&if (b == NULL)
& & } else {
& && &&&if (b == NULL)
& && &&&else {
& && && && &int aN = env-&GetStringUTFLength(a);
& && && && &int bN = env-&GetStringUTFLength(b);
& && && && &if (aN &= 0) {
& && && && && & if (bN &= 0)
& && && && && &
& && && && &} else {
& && && && && & if (bN &= 0)
& && && && && & else {
& && && && && && &&&if (aN != bN)
& && && && && && &&&else {
& && && && && && && && &const char * pA = env-&GetStringUTFChars(a, NULL); //得到这两个字符串的指针
& && && && && && && && &const char * pB = env-&GetStringUTFChars(b, NULL);
& && && && && && && && &bool result =
& && && && && && && && &for (int i = 0; i & aN; i++) {
& && && && && && && && && & if (pA != pB) { //指到每个位置的字符,进行比较
& && && && && && && && && && &&&result =
& && && && && && && && && && &&&
& && && && && && && && && & }
& && && && && && && && &}
& && && && && && && && &env-&ReleaseStringUTFChars(a, pA);
& && && && && && && && &env-&ReleaseStringUTFChars(b, pB);
& && && && && && && && &
& && && && && && &&&}
& && && && && & }
& && && && &}
更多的例子到网上搜吧,写不下了。
网上很多例子是老版本NDK的,与Eclipse合作的,有很多这样的句子:“(*env)-&函数名(env, 其他参数)”。这个,到了本文所说的NDK版本中,统一替换成“env-&函数名(其他参数)”,就是把前面那个“env”两边的括号和星号去掉,把参数中那个“env,”去掉。
有时候这样还报错,你可以考虑是不是表达式左右的类型不匹配,试试强制转换,比如老版本的“jobjectArray arr = (*env)-&GetObjectField(env, packageInfo, fieldID);”改成“jobjectArray arr = env-&GetObjectField(packageInfo, fieldID_signatures);”之后还报错,就试试“jobjectArray arr = (jobjectArray)env-&GetObjectField(packageInfo, fieldID_signatures);”。
但有些老句子是彻底不行了,比如“int iReturn = __system_property_get(key, value);”调试时报错“Error:(87) undefined reference to `__system_property_get'”,这是因为NDK10已经把“__system_property_get”废了,这没办法。
有一类例子,几乎从来没人把它说清楚,就是引用java的Context。
比如说查签名,这得通过java查,要把java的环境(Context)导进来,才知道里面的东西比如签名是什么样的。在网上的例子中,这类函数都有一个参数“jobject context”,有时候写成“jobject obj”之类的,但是,从来就没有人说清楚这个context或obj从哪来,很容易让人误解为是这样:
jstring getSignature(JNIEnv *env, jobject context) {
& & jstring currentS
& & //获取签名
& & return currentS
Jboolean compareSignatures(JNIEnv *env, jstring legalSignature, jstring currentSignature) {
//比较正确的签名和当前的签名
JNIEXPORT Jboolean JNICALL Java_myself_exercise_ndktest_Load_compareSignatures(JNIEnv * env, jobject context) {
& & return compareSignatures(env, context); //以为上面的context是从这儿来的,错!
其实,这个context需要一个专门的函数,从java中调用:
/*获取当前应用的Context*/
jobject getContext(JNIEnv *env) {
& & jclass activityThread = env-&FindClass(&android/app/ActivityThread&);
& & jmethodID currentActivityThread = env-&GetStaticMethodID(activityThread, &currentActivityThread&, &()Landroid/app/ActivityT&);
& & jobject at = env-&CallStaticObjectMethod(activityThread, currentActivityThread);
& & jmethodID getApplication = env-&GetMethodID(activityThread, &getApplication&, &()Landroid/app/A&);
& & jobject context = env-&CallObjectMethod(at, getApplication);
/*获取当前应用签名的Hashcode*/
int getSignatureHashcode(JNIEnv *env) {
& & jobject context = getContext(env); //context是这么来的!
& & jclass contextClass = env-&GetObjectClass(context);
& & jmethodID methodID_getPackageManager = env-&GetMethodID(contextClass, &getPackageManager&, &()Landroid/content/pm/PackageM&);
& & jobject packageManager = env-&CallObjectMethod(context, methodID_getPackageManager);
& & jclass packageManagerClass = env-&GetObjectClass(packageManager);
& & jmethodID methodID_getPackageInfo = env-&GetMethodID(packageManagerClass, &getPackageInfo&, &(Ljava/lang/SI)Landroid/content/pm/PackageI&);
& & jmethodID methodID_getPackageName = env-&GetMethodID(contextClass, &getPackageName&, &()Ljava/lang/S&);
& & jstring packageName = (jstring)env-&CallObjectMethod(context, methodID_getPackageName);
& & jobject packageInfo = env-&CallObjectMethod(packageManager, methodID_getPackageInfo, packageName, 64);
& & jclass packageinfoClass = env-&GetObjectClass(packageInfo);
& & jfieldID fieldID_signatures = env-&GetFieldID(packageinfoClass, &signatures&, &[Landroid/content/pm/S&);
& & jobjectArray signature_arr = (jobjectArray)env-&GetObjectField(packageInfo, fieldID_signatures);
& & jobject signature = env-&GetObjectArrayElement(signature_arr, 0);
& & jclass signatureClass = env-&GetObjectClass(signature);
& & jmethodID methodID_hashcode = env-&GetMethodID(signatureClass, &hashCode&, &()I&);
& & jint hashCode = env-&CallIntMethod(signature, methodID_hashcode);
& & return hashC
/*获取当前应用签名的jstring*/
jstring getSignature(JNIEnv *env) {
& & jobject context = getContext(env);//context是这么来的!
& & jclass contextClass = env-&GetObjectClass(context);
& & jmethodID methodID_getPackageManager = env-&GetMethodID(contextClass, &getPackageManager&, &()Landroid/content/pm/PackageM&);
& & jobject packageManager = env-&CallObjectMethod(context, methodID_getPackageManager);
& & jclass packageManagerClass = env-&GetObjectClass(packageManager);
& & jmethodID methodID_getPackageInfo = env-&GetMethodID(packageManagerClass, &getPackageInfo&, &(Ljava/lang/SI)Landroid/content/pm/PackageI&);
& & jmethodID methodID_getPackageName = env-&GetMethodID(contextClass, &getPackageName&, &()Ljava/lang/S&);
& & jstring packageName = (jstring)env-&CallObjectMethod(context, methodID_getPackageName);
& & jobject packageInfo = env-&CallObjectMethod(packageManager, methodID_getPackageInfo, packageName, 64);
& & jclass packageinfoClass = env-&GetObjectClass(packageInfo);
& & jfieldID fieldID_signatures = env-&GetFieldID(packageinfoClass, &signatures&, &[Landroid/content/pm/S&);
& & jobjectArray signature_arr = (jobjectArray)env-&GetObjectField(packageInfo, fieldID_signatures);
& & jobject signature = env-&GetObjectArrayElement(signature_arr, 0);
& & jclass signatureClass = env-&GetObjectClass(signature);
& & jmethodID methodID_toCharsString = env-&GetMethodID(signatureClass, &toCharsString&, &()Ljava/lang/S&);
& & return (jstring)env-&CallObjectMethod(signature, methodID_toCharsString);
/*比较两个jstring是否相同*/
bool sameJstring(JNIEnv *env, jstring a, jstring b) {
& & if (a == NULL) {
& && &&&if (b == NULL)
& & } else {
& && &&&if (b == NULL)
& && &&&else {
& && && && &int aN = env-&GetStringUTFLength(a);
& && && && &int bN = env-&GetStringUTFLength(b);
& && && && &if (aN &= 0) {
& && && && && & if (bN &= 0)
& && && && && &
& && && && &} else {
& && && && && & if (bN &= 0)
& && && && && & else {
& && && && && && &&&if (aN != bN)
& && && && && && &&&else {
& && && && && && && && &const char * pA = env-&GetStringUTFChars(a, NULL);
& && && && && && && && &const char * pB = env-&GetStringUTFChars(b, NULL);
& && && && && && && && &bool result =
& && && && && && && && &for (int i = 0; i & aN; i++) {
& && && && && && && && && & if (pA != pB) {
& && && && && && && && && && &&&result =
& && && && && && && && && && &&&
& && && && && && && && && & }
& && && && && && && && &}
& && && && && && && && &env-&ReleaseStringUTFChars(a, pA);
& && && && && && && && &env-&ReleaseStringUTFChars(b, pB);
& && && && && && && && &
& && && && && && &&&}
& && && && && & }
& && && && &}
/*判断是否正版(签名未被篡改)*/
jboolean compareSignatures(JNIEnv *env) {
& & ///比对整个签名
& & jstring signature = getSignature(env);
& & jstring legalSignature = env-&NewStringUTF(&把这个字符串替换成你自己程序的正确签名&);
& & return sameJstring(env, signature, legalSignature);
& & ///只比对签名Hashcode
/*jint hashCode = getSignatureHashcode(env);
& & jint legalHashCode = 0; //把这个“0”替换成你自己程序的正确签名的HashCode
& & return hashCode == legalHashC*/
/*JNIEXPORT 那一段就不要了,比较的结果不要输出给java,就在jni内部使用。
而且,在实际应用中,简单地返回一个jboolean是不行的,让人找到改成true就破解了。即使在jni内部,变量也可以被修改。能增加破解难度的办法是把校验过程包在一个加密的区块中,结果不外露,向外输出的只是一些被它控制的变量,这些变量的值是不固定的、有无限的取值范围可以让程序正常运转、又有无限的取值范围让程序混乱,因此别人无法通过修改这些变量来达到正版,他破解的唯一办法是把这个区块的秘钥找到。
还有,这些函数的名称,不要用傻乎乎的“getSignature”之类,要用谁也看不懂的词,代码中也不要出现“感谢你使用正版”之类不打自招的字符串,即使区块已经加密,也要保持良好的习惯。
有人也许要问:区块加密之后肯定要重新签名,签名变了就得回jni里面修改,修改过后又得重新加密,加密过后签名又变了……别说了,下载一个可以调用作者jks的签名工具就行了(比如360加固保的签名工具),签名会恢复原状的*/
【jni的注意事项】
最重要的是释放内存。jni不像java那样保姆,不会帮你打扫房间的,垃圾要自己扫。
基本数据类型不用扫,但引用类型(jstring、带“array”的)用过之后必须扫。不扫的结果是什么呢,不像在家里一样躺在垃圾堆里还能睡着,jni循环几轮就可能死给你看。
清扫垃圾大致的规律是:有指针就要扫。因此可以这样:写完一段代码,从上往下搜 * 符号,有一个算一个都在用完后释放。对于jni指针(jintArray之类的),释放语句都是带“Release”的(如env-&ReleaseIntArrayElements);对于纯C++指针,用free来释放,前面都举过例子。
特别提醒:用完才释放,别没用完就把人家给灭了那就查不出什么来了。
其实jni非常脆弱,即使释放,循环次数多了也吃不消。你会遇到这种情况:某个复杂的函数,里面有N多循环,动用了N多指针,你打算从0算到1000,在手机上一调试,到100就死球了,甚至到20就跳出“已停止”的讣告,这还算好的,要是按钮按下后永远起不来直到人工重启,那才气人。这种情况很可能是超负荷了,那就小批小批地传给jni计算,它有个特点,执行完java传来的一个任务会清理一次内存,比代码中的“Release”清理得彻底,所以你让它多和java对话,内存就“时时勤拂拭,勿使惹尘埃”了。
但也有可能是代码本身的问题,这在jni里很难查。Jni打不了断点,调试的劳动量可想而知。那就不要在jni里写太多的东西,当然我们也不指望把整个软件都搬到jni里去。还有一个好习惯,代码先在Android里调试,逻辑上没问题了再改成jni格式搬进cpp文件里,这时候再出问题就是jni或C++的问题,而且经我使用,函数本身的bug是很少的,出问题几乎总是内存崩溃,解决的办法也就很简单了,分段给。
jni内部的全局变量,用数组会出问题,重复调用它会死机,但基本数据类型不怕这个。
用到jni的Android程序还有个怪癖,不让改目录。你改了以后,它会失去项目结构,打开后就无法显示目录树也无法调试,即使你恢复了原来的文件夹名,它也无法恢复了。在做jni之前,就要想好文件目录,不变了。时时备份也是必须的,google自己都说,NDK是个实验性的东西,我们就更不知道它会出什么妖蛾子。
像普通的Android项目一样,“Build & Generate Signed APK”,按提示进行。
结果有什么不同呢?刚才那些C++代码体现在哪里呢?你找到发布的apk文件,用WinRAR打开,里面的lib文件夹有一堆子文件夹,每个里面都有一个后缀为so的文件,它就是C++的输出结果。之所以有这么多so,是因为手机CPU有多种,一部手机打开程序时会自动寻找与它匹配的so库来加载。
即使没有发布,在调试后,项目的“app\build\outputs\apk”目录下也会有调试版的apk生成,里面同样有那些装着so文件的文件夹。
因此,so文件是裹在apk里面发给用户的,并不像Windows的dll那样放在可执行文件外面。
对于so文件的调用,Android做得比较周到,该链接的都在java和C++代码里接好了,不会出现Windows经常出现的那种动态链接库又依赖其他库而客户的机器上没有这些库而无法打开的情况。
但是有一点不方便,Android的so库不能单独制作,至少目前官网上说还不支持单独生成so库,它必须在Android项目里与java代码一起编译,这样就很慢,而且要改一点点东西也要把整个包含java和C++的项目都打开。
有一种叫“VisualGDB”的软件,可以作为VisualStudio的插件来编写带C++的Android项目,写的过程要快些了,但输出一样慢,因为它还是在用Android的SDK。而且它编代码的自由度比AndroidStudio差很远。
好详细 我这个不懂c++的 android都弄起来了 ,多谢楼主&&膜拜了~
楼主果断十分给力啊,干货
android studio的ndk可以导入源码么,想ec一样可以自动提示,点进去查看之类的
楼主请指点啊
classpath 'com.android.tools.build:gradle:-experimental:0.2.0'
我改成上面这个之后报错Error:Cause: hostname in certificate didn't match: &repo.jfrog.org& != &*.& OR &*.& OR && OR && OR && OR &dev.& OR &dev.& OR &dev.& OR &dev.&
请问如何解决
感谢作者写的这么详细,虽说有的地方是版本不一致导致一系列的配置会不太一样,但大致是您写的这些流程,之前看过别的NDK配置写的都很含糊,看他们一下子就配好了,而我还在一脸闷逼的报错。或许他们的确是那样配置的,但我觉得还不够详细,不过幸好遇到了这篇文章给我大体上有个方向,再次感谢作者辛苦的成果。

我要回帖

更多关于 android中间件开发 的文章

 

随机推荐