在Android中调用C++其实就是在Java中调用C++代码,只是在windows下编译生成DLL,在Android中会生成Linux系统下的.so文件(好吧,其实我基本没用过Linux)。
没写过JNI的可以看看我之前的博客(Windows下利用Visual Studio开发的过程):[url]http://cherishlc.iteye.com/admin/blogs/1328136[/url]
以及自动生成工具swig的使用方法(数组的支持不好!其他挺方便):[url]http://cherishlc.iteye.com/admin/blogs/1689224[/url]
另外推荐一篇非常不错的NDK博文,(配置忽略,主要是各种数据的传递,下代码看看吧)[url]http://vaero.blog.51cto.com/4350852/782787[/url]
扯远了,下面来看看真正在Android中的开发过程。
[size=x-large][color=blue]1、下载ADT及NDK[/color][/size]
[list]
[*]下载ADT (Android Developer Tools) Bundle:[url]http://developer.android.com/sdk/index.html[/url]
[*]下载NDK[url]http://developer.android.com/tools/sdk/ndk/index.html[/url]
[/list]
其中ADT中包含了Eclipse及google的开发套件,不用写C++的下载ADT就足够了。
NDK则是包含了GCC的编译器,以及各个平台(arm,X86,MIPS)的相关头文件,交叉编译的一些平台相关文件等。
[size=x-large][color=blue]2、在ADT中配置NDK路径[/color][/size]
解压NDK压缩包到任意路径,按下图在ADT中(也即ADT解压后的Eclipse文件下的Eclipse中)设置NDK的路径。
设置方法如下图所示:
[img]http://dl.iteye.com/upload/attachment/0078/5771/da661d43-a1cf-3ca4-9ac0-51c15a3ad626.png[/img]
[size=x-large][color=blue]3、创建含有本地代码的Android Project[/color]
[/size]
该过程分为以下两步:
[list]
[*]创建普通的Android Application工程(注意最小支持的API版本要不小于14)
[*]加入本地代码支持
[/list]
具体过程如下图所示:
创建工程:
[img]http://dl.iteye.com/upload/attachment/0078/5775/7ca2b38a-caaf-3903-8379-44c6d95c660b.png[/img]
加入本地代码支持:
[img]http://dl.iteye.com/upload/attachment/0078/5777/a108d513-5d63-3281-9106-d081b5aaa481.png[/img]
完成情况:
[img]http://dl.iteye.com/upload/attachment/0078/5779/69d6236e-1c37-3a50-b484-102fd0f5f97f.png[/img]
点击菜单栏Project->Build All命令进行编译。
[size=large][color=red]注意:[/color][/size]如果之前最小支持的API版本要不小于14,将出现编译错误。“Android NDK: WARNING: APP_PLATFORM android-14 is larger than android:minSdkVersion 7 in ./AndroidManifest.xml”
[size=large]解决方法如下:[/size]
打开AndroidManifest.xml,切换到源文件视图,将minSdkVersion 改为14以上:
[img]http://dl.iteye.com/upload/attachment/0078/5781/434a97c1-4034-3eef-8e01-2487ebf22cd6.png[/img]
[size=x-large][color=blue]4、编写Java端代码和C++端代码[/color][/size]
Java端,注意不要继承自Android中的类,否则javah编译头文件时要指定android类路径。
javah推荐两种方法:
[list]
[*]使用我写的工具,含本地方法的类多时,一次编译完成:[url]http://cherishlc.iteye.com/blog/1326893[/url]
[*]在Eclipse中配置外部工具,配置方法参考自(同时也是讲Android中JNI配置方法的博客):[url]http://www.cnblogs.com/yemeishu/archive/2012/12/24/NDK%E5%BC%80%E5%8F%91.html[/url]
[/list]
在Eclipse中配置javah外部工具方法为:
[img]http://dl.iteye.com/upload/attachment/0078/5783/49c9e341-112b-3bcd-8188-5b11894fe86d.png[/img]
[img]http://dl.iteye.com/upload/attachment/0078/5785/dfc13509-deb8-3328-8a84-8ed77468f672.png[/img]
上图中最长的一行命令如下:
配置好之后:
[img]http://dl.iteye.com/upload/attachment/0078/5787/887333d0-3654-345c-85d9-df8f4cf6ed7c.png[/img]
点刚才配置好的javah工具,生成.h文件,然后:
[img]http://dl.iteye.com/upload/attachment/0078/5789/fd42a6ff-95d6-330c-9e63-4e98e84adf72.png[/img]
[size=large][color=blue]Java端调用JNI方法的代码:[/color][/size]
将MainActivity改为:
[size=large][color=blue]编写C++代码:[/color][/size]
打开刚才系统生成的TestNDK2.cpp,修改成如下样子:
[size=x-large][color=blue]5、配置生成的.so文件的目标平台[/color][/size]
Java是跨平台的可是C++生成动态链接文件不是!!!同是Android,底层的CPU架构不同,动态链接文件也不同。。。好吧,这个我不知道原因。。。
于是乎,还得为不同的CPU创建不同的动态链接库文件,好在一行命令搞定~所有的动态链接一起打包,管他是哪个CPU,统统适用,Happy啊。
参考自:[url]http://bbs.csdn.net/topics/390158301[/url]
过程如下:
[img]http://dl.iteye.com/upload/attachment/0078/5802/33780db9-cddf-3c15-a733-54598a284a57.png[/img]
再编译时会发现生成了对应以上四个平台的.so文件~~~
[img]http://dl.iteye.com/upload/attachment/0078/5804/20e2e2b0-6a25-339d-9716-e7f7bb611d1f.png[/img]
一切搞定,可以运行了!!!运行结果如下:
[img]http://dl.iteye.com/upload/attachment/0078/5833/2bd7f4ff-b80e-3db8-87af-6df1a8ed746a.png[/img]
[size=x-large][color=blue]6、Java与C++联合调试[/color][/size]
参见:[url]http://blog.csdn.net/wjr2012/article/details/7993722[/url]
[size=large][color=red]注意:[/color][/size]
[list]
[*]C++的调试器有几秒的延迟才能启动好,也就是程序运行了一会儿才可以开始调试,所以要调试的代码一定要是几秒钟后才能调试!!!
[*]断点设置在C++中才有效。。。
[/list]
过程为:
右键点击工程文件, 在properties -> C/C++ Build中:
[img]http://dl.iteye.com/upload/attachment/0078/5807/4c3b7dcc-512e-318d-aef0-4af37ff85835.png[/img]
完了设置断点(只能在C++中)就可以启动调试了~~
[img]http://dl.iteye.com/upload/attachment/0078/5809/150ab86b-0239-3849-aaf7-83163b25e68c.png[/img]
好吧,,,调试怎么不灵光呢。。。再想想刚才的注意事项。。。好吧,早执行完了JNI代码了。。。
于是乎,修改MainActivity代码如下:
运行到断点的结果:
[img]http://dl.iteye.com/upload/attachment/0078/5845/37a83325-362e-3f96-91c2-eaf6d8a5ff9f.png[/img]
没写过JNI的可以看看我之前的博客(Windows下利用Visual Studio开发的过程):[url]http://cherishlc.iteye.com/admin/blogs/1328136[/url]
以及自动生成工具swig的使用方法(数组的支持不好!其他挺方便):[url]http://cherishlc.iteye.com/admin/blogs/1689224[/url]
另外推荐一篇非常不错的NDK博文,(配置忽略,主要是各种数据的传递,下代码看看吧)[url]http://vaero.blog.51cto.com/4350852/782787[/url]
扯远了,下面来看看真正在Android中的开发过程。
[size=x-large][color=blue]1、下载ADT及NDK[/color][/size]
[list]
[*]下载ADT (Android Developer Tools) Bundle:[url]http://developer.android.com/sdk/index.html[/url]
[*]下载NDK[url]http://developer.android.com/tools/sdk/ndk/index.html[/url]
[/list]
其中ADT中包含了Eclipse及google的开发套件,不用写C++的下载ADT就足够了。
NDK则是包含了GCC的编译器,以及各个平台(arm,X86,MIPS)的相关头文件,交叉编译的一些平台相关文件等。
[size=x-large][color=blue]2、在ADT中配置NDK路径[/color][/size]
解压NDK压缩包到任意路径,按下图在ADT中(也即ADT解压后的Eclipse文件下的Eclipse中)设置NDK的路径。
设置方法如下图所示:
[img]http://dl.iteye.com/upload/attachment/0078/5771/da661d43-a1cf-3ca4-9ac0-51c15a3ad626.png[/img]
[size=x-large][color=blue]3、创建含有本地代码的Android Project[/color]
[/size]
该过程分为以下两步:
[list]
[*]创建普通的Android Application工程(注意最小支持的API版本要不小于14)
[*]加入本地代码支持
[/list]
具体过程如下图所示:
创建工程:
[img]http://dl.iteye.com/upload/attachment/0078/5775/7ca2b38a-caaf-3903-8379-44c6d95c660b.png[/img]
加入本地代码支持:
[img]http://dl.iteye.com/upload/attachment/0078/5777/a108d513-5d63-3281-9106-d081b5aaa481.png[/img]
完成情况:
[img]http://dl.iteye.com/upload/attachment/0078/5779/69d6236e-1c37-3a50-b484-102fd0f5f97f.png[/img]
点击菜单栏Project->Build All命令进行编译。
[size=large][color=red]注意:[/color][/size]如果之前最小支持的API版本要不小于14,将出现编译错误。“Android NDK: WARNING: APP_PLATFORM android-14 is larger than android:minSdkVersion 7 in ./AndroidManifest.xml”
[size=large]解决方法如下:[/size]
打开AndroidManifest.xml,切换到源文件视图,将minSdkVersion 改为14以上:
[img]http://dl.iteye.com/upload/attachment/0078/5781/434a97c1-4034-3eef-8e01-2487ebf22cd6.png[/img]
[size=x-large][color=blue]4、编写Java端代码和C++端代码[/color][/size]
Java端,注意不要继承自Android中的类,否则javah编译头文件时要指定android类路径。
package com.lc.testndk2;
import android.util.Log;
public class NativeClass {
//数组a中的每个元素都加上b,返回值为在C++中数据是否为a中数据拷贝得到的(按值拷贝还是传递指针)
public static native boolean jniArrayAdd(int[] a, int b);
// 在C++中创建Java中的int数组,其中元素为 数组a中的对应元素乘以b
public static native int[] jnitArrayMul(int[] a,int b);
static {
Log.i("NativeClass","before load library");
System.loadLibrary("TestNDK2");//注意这里为自己指定的.so文件,无lib前缀,亦无后缀
Log.i("NativeClass","after load library");
}
}
javah推荐两种方法:
[list]
[*]使用我写的工具,含本地方法的类多时,一次编译完成:[url]http://cherishlc.iteye.com/blog/1326893[/url]
[*]在Eclipse中配置外部工具,配置方法参考自(同时也是讲Android中JNI配置方法的博客):[url]http://www.cnblogs.com/yemeishu/archive/2012/12/24/NDK%E5%BC%80%E5%8F%91.html[/url]
[/list]
在Eclipse中配置javah外部工具方法为:
[img]http://dl.iteye.com/upload/attachment/0078/5783/49c9e341-112b-3bcd-8188-5b11894fe86d.png[/img]
[img]http://dl.iteye.com/upload/attachment/0078/5785/dfc13509-deb8-3328-8a84-8ed77468f672.png[/img]
上图中最长的一行命令如下:
-v -classpath "${project_loc}/bin/classes" -d "${project_loc}/jni" ${java_type_name}
配置好之后:
[img]http://dl.iteye.com/upload/attachment/0078/5787/887333d0-3654-345c-85d9-df8f4cf6ed7c.png[/img]
点刚才配置好的javah工具,生成.h文件,然后:
[img]http://dl.iteye.com/upload/attachment/0078/5789/fd42a6ff-95d6-330c-9e63-4e98e84adf72.png[/img]
[size=large][color=blue]Java端调用JNI方法的代码:[/color][/size]
将MainActivity改为:
package com.lc.testndk2;
import java.util.Arrays;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
int[] array = new int[] { 1, 2, 3};
String str = "数组,调用C++前" + Arrays.toString(array);
boolean isCopyOfArrayInCpp = NativeClass.jniArrayAdd(array, 1);
str += "\n在C++中为副本? " + isCopyOfArrayInCpp;
str += "\n数组,调用C++后:" + Arrays.toString(array);
tv.setText(str);
setContentView(tv);
}
}
[size=large][color=blue]编写C++代码:[/color][/size]
打开刚才系统生成的TestNDK2.cpp,修改成如下样子:
#include <jni.h>
#include "com_lc_testndk2_NativeClass.h"
#ifdef __cplusplus //最好有这个,否则被编译器改了函数名字找不到不要怪我
extern "C" {
#endif
/*
* Class: com_lc_testndk2_NativeClass
* Method: jinArrayAdd
* Signature: ([II)[I
*/JNIEXPORT jboolean JNICALL Java_com_lc_testndk2_NativeClass_jniArrayAdd(
JNIEnv * env, jclass, jintArray array, jint b) {
jsize size = env->GetArrayLength(array);
// jintArray sum=env->NewIntArray(2);
jboolean isCopy;
jint* pArray = (jint*) env->GetPrimitiveArrayCritical(array, &isCopy);
for (int i = 0; i < size; i++)
pArray[i] += b;
env->ReleasePrimitiveArrayCritical(array, pArray, JNI_COMMIT);
//env->ReleasePrimitiveArrayCritical(sum,pSum,JNI_COMMIT);
return isCopy;
}
/*
* Class: com_lc_testndk2_NativeClass
* Method: jnitArrayMul
* Signature: ([II)[I
*/JNIEXPORT jintArray JNICALL Java_com_lc_testndk2_NativeClass_jnitArrayMul(
JNIEnv * env, jclass, jintArray array, jint b) {
jsize size = env->GetArrayLength(array);
jintArray product = env->NewIntArray(size);
jint* pArray = (jint*) env->GetPrimitiveArrayCritical(array, 0);
jint* pProduct=(jint*)env->GetPrimitiveArrayCritical(product,0);
// jintArray product = env->NewIntArray(size); //不能在这里创建!!因为上面的方法会使java进入critical region, 在这里创建的话虚拟机直接崩溃
for (int i = 0; i < size; i++)
pProduct[i] =pArray[i]* b;
env->ReleasePrimitiveArrayCritical(array, pArray, JNI_COMMIT);
env->ReleasePrimitiveArrayCritical(product,pProduct,JNI_COMMIT);
return product;
}
#ifdef __cplusplus
}
#endif
[size=x-large][color=blue]5、配置生成的.so文件的目标平台[/color][/size]
Java是跨平台的可是C++生成动态链接文件不是!!!同是Android,底层的CPU架构不同,动态链接文件也不同。。。好吧,这个我不知道原因。。。
于是乎,还得为不同的CPU创建不同的动态链接库文件,好在一行命令搞定~所有的动态链接一起打包,管他是哪个CPU,统统适用,Happy啊。
参考自:[url]http://bbs.csdn.net/topics/390158301[/url]
过程如下:
[img]http://dl.iteye.com/upload/attachment/0078/5802/33780db9-cddf-3c15-a733-54598a284a57.png[/img]
再编译时会发现生成了对应以上四个平台的.so文件~~~
[img]http://dl.iteye.com/upload/attachment/0078/5804/20e2e2b0-6a25-339d-9716-e7f7bb611d1f.png[/img]
一切搞定,可以运行了!!!运行结果如下:
[img]http://dl.iteye.com/upload/attachment/0078/5833/2bd7f4ff-b80e-3db8-87af-6df1a8ed746a.png[/img]
[size=x-large][color=blue]6、Java与C++联合调试[/color][/size]
参见:[url]http://blog.csdn.net/wjr2012/article/details/7993722[/url]
[size=large][color=red]注意:[/color][/size]
[list]
[*]C++的调试器有几秒的延迟才能启动好,也就是程序运行了一会儿才可以开始调试,所以要调试的代码一定要是几秒钟后才能调试!!!
[*]断点设置在C++中才有效。。。
[/list]
过程为:
右键点击工程文件, 在properties -> C/C++ Build中:
[img]http://dl.iteye.com/upload/attachment/0078/5807/4c3b7dcc-512e-318d-aef0-4af37ff85835.png[/img]
完了设置断点(只能在C++中)就可以启动调试了~~
[img]http://dl.iteye.com/upload/attachment/0078/5809/150ab86b-0239-3849-aaf7-83163b25e68c.png[/img]
好吧,,,调试怎么不灵光呢。。。再想想刚才的注意事项。。。好吧,早执行完了JNI代码了。。。
于是乎,修改MainActivity代码如下:
package com.lc.testndk2;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;
/**
* @author LC
*
*完整的演示Android通过JNI调用C++代码的工程
*/
public class MainActivity extends Activity {
TextView tv = null;
int count = 0;
Timer timer;
@SuppressLint("HandlerLeak")
class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
if (tv != null) {
tv.setText(msg.getData().getString("text"));
}
super.handleMessage(msg);
}
};
Handler handle= new MyHandler();
class refreshTask extends TimerTask {
@Override
public void run() {
try {
count++;
Log.i("MainActivity", "before call native code,count="
+ count);
int[] array = new int[] { count, -count, 2*count };
String str = "第" + count + "次了\n";
str += "数组,调用C++前" + Arrays.toString(array);
boolean isCopyOfArrayInCpp = NativeClass.jniArrayAdd(array,1);
str += "\n在C++中为副本? " + isCopyOfArrayInCpp;
str += "\n数组,调用C++后:" + Arrays.toString(array)+"\n\n";
str+="测试在C++中创建数组:\n";
str += Arrays.toString(array)+"* 2 =";
str+=Arrays.toString(NativeClass.jnitArrayMul(array, 2))+"\n\n";
Message msg=new Message();
Bundle b=new Bundle();
b.putString("text", str);
msg.setData(b);
handle.sendMessage(msg);
Log.i("MainActivity", "after call native code");
} catch (Exception e) {
Log.i(MainActivity.class.getSimpleName(), e.toString());
e.printStackTrace();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tv = new TextView(this);
tv.setText("我是初始值");
setContentView(tv);
}
@Override
protected void onPause() {
Log.i(MainActivity.class.getSimpleName(),"onPuase()");
timer.cancel();
timer=null;
super.onPause();
}
@Override
protected void onResume() {
Log.i(MainActivity.class.getSimpleName(),"onResume()");
timer=new Timer();
timer.scheduleAtFixedRate(new refreshTask(), 0, 1000);
super.onResume();
}
}
运行到断点的结果:
[img]http://dl.iteye.com/upload/attachment/0078/5845/37a83325-362e-3f96-91c2-eaf6d8a5ff9f.png[/img]