Android 学习笔记——利用JNI技术在Android中调用、调试C++代码

本文详细介绍了如何在Android中使用JNI技术调用C++代码,包括配置环境、编写Java与C++代码、生成.so文件、多平台适配及联合调试等步骤。
在Android中调用C++其实就是在Java中调用C++代码,只是在windows下编译生成DLL,在Android中会生成Linux系统下的.so文件(好吧,其实我基本没用过Linux)。
没写过JNI的可以看看我之前的博客(Windows下利用Visual Studio开发的过程): http://cherishlc.iteye.com/admin/blogs/1328136

以及自动生成工具swig的使用方法(数组的支持不好!其他挺方便): http://cherishlc.iteye.com/admin/blogs/1689224

另外推荐一篇非常不错的NDK博文,(配置忽略,主要是各种数据的传递,下代码看看吧) http://vaero.blog.51cto.com/4350852/782787
扯远了,下面来看看真正在Android中的开发过程。

1、下载ADT及NDK

其中ADT中包含了Eclipse及google的开发套件,不用写C++的下载ADT就足够了。
NDK则是包含了GCC的编译器,以及各个平台(arm,X86,MIPS)的相关头文件,交叉编译的一些平台相关文件等。

2、在ADT中配置NDK路径
解压NDK压缩包到任意路径,按下图在ADT中(也即ADT解压后的Eclipse文件下的Eclipse中)设置NDK的路径。
设置方法如下图所示:


3、创建含有本地代码的Android Project

该过程分为以下两步:
  • 创建普通的Android Application工程(注意最小支持的API版本要不小于14)
  • 加入本地代码支持

具体过程如下图所示:
创建工程:


加入本地代码支持:


完成情况:


点击菜单栏Project->Build All命令进行编译。


注意:如果之前最小支持的API版本要不小于14,将出现编译错误。“Android NDK: WARNING: APP_PLATFORM android-14 is larger than android:minSdkVersion 7 in ./AndroidManifest.xml”

解决方法如下:
打开AndroidManifest.xml,切换到源文件视图,将minSdkVersion 改为14以上:


4、编写Java端代码和C++端代码
Java端,注意不要继承自Android中的类,否则javah编译头文件时要指定android类路径。
Java代码 复制代码  收藏代码
  1. package com.lc.testndk2;   
  2. import android.util.Log;   
  3. public class NativeClass {   
  4.     //数组a中的每个元素都加上b,返回值为在C++中数据是否为a中数据拷贝得到的(按值拷贝还是传递指针)   
  5.     public static native boolean jniArrayAdd(int[] a, int b);   
  6.   // 在C++中创建Java中的int数组,其中元素为 数组a中的对应元素乘以b   
  7.     public static native int[] jnitArrayMul(int[] a,int b);   
  8.     static {   
  9.         Log.i("NativeClass","before load library");   
  10.         System.loadLibrary("TestNDK2");//注意这里为自己指定的.so文件,无lib前缀,亦无后缀   
  11.         Log.i("NativeClass","after load library");     
  12.     }   
  13. }  
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推荐两种方法:

在Eclipse中配置javah外部工具方法为:


上图中最长的一行命令如下:
Cmdineclipse代码 复制代码  收藏代码
  1. -v -classpath "${project_loc}/bin/classes" -d "${project_loc}/jni" ${java_type_name}  

配置好之后:

点刚才配置好的javah工具,生成.h文件,然后:



Java端调用JNI方法的代码:
将MainActivity改为:
Java代码 复制代码  收藏代码
  1. package com.lc.testndk2;   
  2.   
  3. import java.util.Arrays;   
  4.   
  5. import android.app.Activity;   
  6. import android.os.Bundle;   
  7. import android.widget.TextView;   
  8.   
  9. public class MainActivity extends Activity {   
  10.   
  11.     @Override  
  12.     protected void onCreate(Bundle savedInstanceState) {   
  13.         super.onCreate(savedInstanceState);   
  14.         TextView  tv = new TextView(this);        
  15.         int[] array = new int[] { 12,  3};       
  16.         String str = "数组,调用C++前" + Arrays.toString(array);   
  17.         boolean isCopyOfArrayInCpp = NativeClass.jniArrayAdd(array,             1);   
  18.         str += "\n在C++中为副本?  " + isCopyOfArrayInCpp;   
  19.         str += "\n数组,调用C++后:" + Arrays.toString(array);   
  20.         tv.setText(str);   
  21.         setContentView(tv);   
  22.     }   
  23. }  
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);
    }
}



编写C++代码:
打开刚才系统生成的TestNDK2.cpp,修改成如下样子:
C++代码 复制代码  收藏代码
  1. #include <jni.h>   
  2. #include "com_lc_testndk2_NativeClass.h"   
  3. #ifdef __cplusplus  //最好有这个,否则被编译器改了函数名字找不到不要怪我   
  4. extern "C" {   
  5. #endif   
  6. /*  
  7.  * Class:     com_lc_testndk2_NativeClass  
  8.  * Method:    jinArrayAdd  
  9.  * Signature: ([II)[I  
  10.  */JNIEXPORT jboolean JNICALL Java_com_lc_testndk2_NativeClass_jniArrayAdd(   
  11.         JNIEnv * env, jclass, jintArray array, jint b) {   
  12.   
  13.     jsize size = env->GetArrayLength(array);   
  14. //  jintArray sum=env->NewIntArray(2);   
  15.   
  16.     jboolean isCopy;   
  17.     jint* pArray = (jint*) env->GetPrimitiveArrayCritical(array, &isCopy);   
  18.     for (int i = 0; i < size; i++)   
  19.         pArray[i] += b;   
  20.     env->ReleasePrimitiveArrayCritical(array, pArray, JNI_COMMIT);   
  21.     //env->ReleasePrimitiveArrayCritical(sum,pSum,JNI_COMMIT);   
  22.   
  23.     return isCopy;   
  24. }   
  25.   
  26. /*  
  27.  * Class:     com_lc_testndk2_NativeClass  
  28.  * Method:    jnitArrayMul  
  29.  * Signature: ([II)[I  
  30.  */JNIEXPORT jintArray JNICALL Java_com_lc_testndk2_NativeClass_jnitArrayMul(   
  31.         JNIEnv * env, jclass, jintArray array, jint b) {   
  32.   
  33.     jsize size = env->GetArrayLength(array);   
  34.     jintArray product = env->NewIntArray(size);   
  35.     jint* pArray = (jint*) env->GetPrimitiveArrayCritical(array, 0);   
  36.     jint* pProduct=(jint*)env->GetPrimitiveArrayCritical(product,0);   
  37. //  jintArray product = env->NewIntArray(size); //不能在这里创建!!因为上面的方法会使java进入critical region, 在这里创建的话虚拟机直接崩溃   
  38.     for (int i = 0; i < size; i++)   
  39.         pProduct[i] =pArray[i]* b;   
  40.     env->ReleasePrimitiveArrayCritical(array, pArray, JNI_COMMIT);   
  41.     env->ReleasePrimitiveArrayCritical(product,pProduct,JNI_COMMIT);   
  42.     return product;   
  43. }   
  44.   
  45. #ifdef __cplusplus   
  46. }   
  47. #endif  


5、配置生成的.so文件的目标平台
Java是跨平台的可是C++生成动态链接文件不是!!!同是Android,底层的CPU架构不同,动态链接文件也不同。。。好吧,这个我不知道原因。。。
于是乎,还得为不同的CPU创建不同的动态链接库文件,好在一行命令搞定~所有的动态链接一起打包,管他是哪个CPU,统统适用,Happy啊。
参考自: http://bbs.youkuaiyun.com/topics/390158301
过程如下:

再编译时会发现生成了对应以上四个平台的.so文件~~~


一切搞定,可以运行了!!!运行结果如下:



6、Java与C++联合调试
参见: http://blog.youkuaiyun.com/wjr2012/article/details/7993722

注意:
  • C++的调试器有几秒的延迟才能启动好,也就是程序运行了一会儿才可以开始调试,所以要调试的代码一定要是几秒钟后才能调试!!!
  • 断点设置在C++中才有效。。。


过程为:
右键点击工程文件, 在properties -> C/C++ Build中:

完了设置断点(只能在C++中)就可以启动调试了~~



好吧,,,调试怎么不灵光呢。。。再想想刚才的注意事项。。。好吧,早执行完了JNI代码了。。。
于是乎,修改MainActivity代码如下:
Java代码 复制代码  收藏代码
  1. package com.lc.testndk2;   
  2.   
  3. import java.util.Arrays;   
  4. import java.util.Timer;   
  5. import java.util.TimerTask;   
  6.   
  7. import android.annotation.SuppressLint;   
  8. import android.app.Activity;   
  9. import android.os.Bundle;   
  10. import android.os.Handler;   
  11. import android.os.Message;   
  12. import android.util.Log;   
  13. import android.widget.TextView;   
  14.   
  15. /**  
  16.  * @author LC  
  17.  *  
  18.  *完整的演示Android通过JNI调用C++代码的工程  
  19.  */  
  20. public class MainActivity extends Activity {   
  21.   
  22.     TextView tv = null;   
  23.     int count = 0;   
  24.     Timer timer;   
  25.   
  26.      @SuppressLint("HandlerLeak")   
  27.     class MyHandler extends Handler{   
  28.         @Override  
  29.         public void handleMessage(Message msg) {   
  30.             if (tv != null) {   
  31.                 tv.setText(msg.getData().getString("text"));   
  32.             }   
  33.             super.handleMessage(msg);   
  34.         }   
  35.     };   
  36.        
  37.     Handler handle=  new MyHandler();   
  38.        
  39.        
  40.     class refreshTask extends TimerTask {   
  41.   
  42.         @Override  
  43.         public void run() {   
  44.             try {   
  45.                 count++;   
  46.                 Log.i("MainActivity""before call native code,count="  
  47.                         + count);   
  48.                 int[] array = new int[] { count, -count, 2*count };   
  49.   
  50.                 String str = "第" + count + "次了\n";   
  51.   
  52.                 str += "数组,调用C++前" + Arrays.toString(array);   
  53.   
  54.                 boolean isCopyOfArrayInCpp = NativeClass.jniArrayAdd(array,1);   
  55.                 str += "\n在C++中为副本?  " + isCopyOfArrayInCpp;   
  56.                 str += "\n数组,调用C++后:" + Arrays.toString(array)+"\n\n";   
  57.   
  58.                 str+="测试在C++中创建数组:\n";   
  59.                 str +=  Arrays.toString(array)+"* 2 =";   
  60.                 str+=Arrays.toString(NativeClass.jnitArrayMul(array, 2))+"\n\n";   
  61.                    
  62.                 Message msg=new Message();   
  63.                 Bundle b=new Bundle();   
  64.                 b.putString("text", str);   
  65.                 msg.setData(b);                
  66.                 handle.sendMessage(msg);   
  67.                    
  68.                 Log.i("MainActivity""after call native code");   
  69.             } catch (Exception e) {   
  70.                 Log.i(MainActivity.class.getSimpleName(), e.toString());   
  71.                 e.printStackTrace();   
  72.             }   
  73.   
  74.         }   
  75.     };   
  76.        
  77.        
  78.     @Override  
  79.     protected void onCreate(Bundle savedInstanceState) {   
  80.         super.onCreate(savedInstanceState);   
  81.         tv = new TextView(this);   
  82.         tv.setText("我是初始值");   
  83.         setContentView(tv);   
  84.     }   
  85.   
  86.     @Override  
  87.     protected void onPause() {   
  88.         Log.i(MainActivity.class.getSimpleName(),"onPuase()");   
  89.         timer.cancel();   
  90.         timer=null;   
  91.         super.onPause();   
  92.     }   
  93.   
  94.     @Override  
  95.     protected void onResume() {   
  96.         Log.i(MainActivity.class.getSimpleName(),"onResume()");   
  97.         timer=new Timer();   
  98.         timer.scheduleAtFixedRate(new refreshTask(), 01000);   
  99.         super.onResume();   
  100.     }   
  101.   
  102. }  
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();
	}

}


运行到断点的结果:
  • 大小: 121.8 KB
  • 大小: 68.8 KB
  • 大小: 37.8 KB
  • 大小: 29.2 KB
  • 大小: 40.6 KB
  • 大小: 11.5 KB
  • 大小: 98.3 KB
  • 大小: 27.1 KB
  • 大小: 25.9 KB
  • 大小: 42 KB
  • 大小: 7.8 KB
  • 大小: 80.7 KB
  • 大小: 71.1 KB
  • 大小: 21.7 KB
  • 大小: 36.3 KB
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值