一、JNI(Java Native Interface)

       1、什么是JNI:
              JNI(Java Native Interface):java本地开发接口
              JNI是一个协议,这个协议用来沟通java代码和外部的本地代码(c/c++)

              外部的c/c++代码也可以调用java代码
       2、为什么使用JNI
              效率上 C/C++是本地语言,比java更高效
              代码移植,如果之前用C语言开发过模块,可以复用已经存在的c代码
              java反编译比C语言容易,一般加密算法都是用C语言编写,不容易被反编译
       3、Java基本数据类型与C语言基本数据类型的对应
              
       4、引用类型对应
              
       5、堆内存和栈内存的概念
              栈内存:系统自动分配和释放,
                      保存全局、静态、局部变量,
                      在站上分配内存叫静态分配,
                      大小一般是固定的
              堆内存:程序员手动分配(malloc/new)和释放(free/java不用手动释放,由GC回收),
                      在堆上分配内存叫动态分配,
                      一般硬件内存有多大堆内存就有多大
 
二、交叉编译
       1、交叉编译的概念
          交叉编译即在一个平台,编译出另一个平台能够执行的二进制代码
          主流平台有: Windows、 Mac os、 Linux
          主流处理器: x86、 arm、 mips
       2、交叉编译的原理
          即在一个平台上,模拟其他平台的特性
          编译的流程: 源代码-->编译-->链接-->可执行程序
       3、交叉编译的工具链
          多个工具的集合,一个工具使用完后接着调用下一个工具
       4、常见的交叉编译工具
          NDK(Native Development Kit): 开发JNI必备工具,就是模拟其他平台特性类编译代码的工具
          CDT(C/C++ Development Tools): 是Eclipse开发C语言的一个插件,高亮显示C语言的语法
          Cygwin: 一个Windows平台的Unix模拟器(可以参考之前博客Cygwin简介及使用
       5、NDK的目录结构(可以在Google官网下载NDK开发工具,需要FQ)
          docs: 帮助文档
          build/tools:linux的批处理文件
          platforms:编译c代码需要使用的头文件和类库
          prebuilt:预编译使用的二进制可执行文件
          sample:jni的使用例子
          source:ndk的源码
          toolchains:工具链
          ndk-build.cmd:编译打包c代码的一个指令,需要配置系统环境变量
 
三、JNI的第一个例子
          好了,准备知识已经完毕,下面开始我们的一个JNI例子。
        1、新建一个Android项目,在根目录下创建 jni文件夹,用于存放 C源码。
        2、在java代码中,创建一个本地方法 getStringFromC 本地方法没有方法体。
  1. private native String getStringFromC();

  3、在jni中创建一个C文件,定义一个函数实现本地方法,函数名必须用使用 本地方法的全类名,点改为下划线。

  1. 1 #include <stdio.h>
  2. 2 #include <stdlib.h>
  3. 3 #include <jni.h>
  4. 4 //方法名必须为本地方法的全类名点改为下划线,穿入的两个参数必须这样写,
  5. 5 //第一个参数为Java虚拟机的内存地址的二级指针,用于本地方法与java虚拟机在内存中交互
  6. 6 //第二个参数为一个java对象,即是哪个对象调用了这个 c方法
  7. 7 jstring Java_com_mwp_jnihelloworld_MainActivity_getStringFromC(JNIEnv* env,
  8. 8 jobject obj){
  9. 9 //定义一个C语言字符串
  10. 10 char* cstr = "hello form c";
  11. 11 //返回值是java字符串,所以要将C语言的字符串转换成java的字符串
  12. 12 //在jni.h 中定义了字符串转换函数的函数指针
  13. 13 //jstring (*NewStringUTF)(JNIEnv*, const char*);
  14. 14 //第一种方法:很少用
  15. 15 jstring jstr1 = (*(*env)).NewStringUTF(env, cstr);
  16. 16 //第二种方法,推荐
  17. 17 jstring jstr2 = (*env) -> NewStringUTF(env, cstr);
  18. 18 return jstr2;
  19. 19 }

4、在jni中创建 Android.mk文件,用于配置 本地方法

  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. #编译生成的文件的类库叫什么名字
  4. LOCAL_MODULE := hello
  5. #要编译的c文件
  6. LOCAL_SRC_FILES := Hello.c
  7. include $(BUILD_SHARED_LIBRARY)

    5、在jni目录下执行 ndk-build.cmd指令,编译c文件

         6、在java代码中加载编译后生成的so类库,调用本地方法,将项目部署到虚拟机上之后就会发现toast弹出的C代码定义的字符串,第一个例子执行成功了。
  1. static{
  2. //加载打包完毕的 so类库
  3. System.loadLibrary("hello");
  4. }

7、jni打包的C语言类库默认仅支持 arm架构,需要在jni目录下创建 Android.mk 文件添加如下代码可以支持x86架构

  1. APP_ABI := armeabi armeabi-v7a x86

四、JNI常见错误

         1、findLibrary returned null:
                CPU平台不匹配 或者 在加载类库时,类库名字写错了
         2、本地方法找不到:
                忘记加载类库了 或者 C代码中方法名写错了
   
五、javah工具与javap工具
         1、javah:  生成本地方法头文件
            需要在C/C++模块下才能生效
            在JDK1.7中,在src目录下执行javah 全类名
            在JDK1.6中,在bin/classes目录下执行
         2、javap:  打印方法签名
            在C语言中调用java的方法需要用到反射,C语言的反射需要一个方法签名,使用javap能够生成方法签名,很熟练的话也可以自己写方法签名
            在bin/classes目录下执行 javap -s 全类名
            
六、使用本地方法加密字符串的一个小例子
      C语言字符串与Java中的字符串类型不同,所以需要进行字符串类型转换。
      一个重要的思想:C语言计算字符串的长度不方便,但是java很方便,只需要调用一个length()方法就可以,所以像这种需求,那个语言有优势就用哪个语言算,算完当做参数传递给另一种语言就ok。
                      混合语言编程这应该是一种非常有用的思想。
     Java非常容易被反编译,所以加密都是用 c语言写的 
  1. #include <jni.h>
  2. #include <string.h>
  3. //将java字符串转换为c语言字符串(工具方法)
  4. char* Jstring2CStr(JNIEnv* env, jstring jstr)
  5. {
  6. char* rtn = NULL;
  7. jclass clsstring = (*env)->FindClass(env,"java/lang/String");
  8. jstring strencode = (*env)->NewStringUTF(env,"GB2312");
  9. jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B");
  10. jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312");
  11. jsize alen = (*env)->GetArrayLength(env,barr);
  12. jbyte* ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE);
  13. if(alen > 0)
  14. {
  15. rtn = (char*)malloc(alen+1); //"\0"
  16. memcpy(rtn,ba,alen);
  17. rtn[alen]=0;
  18. }
  19. (*env)->ReleaseByteArrayElements(env,barr,ba,0); //
  20. return rtn;
  21. }
  22. JNIEXPORT jstring JNICALL Java_com_mwp_encodeanddecode_MainActivity_encode
  23. (JNIEnv * env, jobject obj, jstring text, jint length){
  24. char* cstr = Jstring2CStr(env, text);
  25. int i;
  26. for(i = 0;i<length;i++){
  27. *(cstr+i) += 1; //加密算法,将字符串每个字符加1
  28. }
  29. return (*env)->NewStringUTF(env,cstr);
  30. }
  31. JNIEXPORT jstring JNICALL Java_com_mwp_encodeanddecode_MainActivity_decode
  32. (JNIEnv * env, jobject obj, jstring text, jint length){
  33. char* cstr = Jstring2CStr(env, text);
  34. int i;
  35. for(i = 0;i<length;i++){
  36. *(cstr+i) -= 1;
  37. }
  38. return (*env)->NewStringUTF(env, cstr);
  39. }
七、JNI操作一个数组(引用传递)
          传递数组其实是传递一个堆内存的数组首地址的引用过去,所以实际操作的是同一块内存,
          当调用完方法,不需要返回值,实际上参数内容已经改变,
          Android中很多操作硬件的方法都是这种C语言的传引用的思路
  1. 1 public class MainActivity extends Activity {
  2. 2
  3. 3 static{
  4. 4 System.loadLibrary("encode");
  5. 5 }
  6. 6 int[] array = {1,2,3,4,5};
  7. 7 @Override
  8. 8 protected void onCreate(Bundle savedInstanceState) {
  9. 9 super.onCreate(savedInstanceState);
  10. 10 setContentView(R.layout.activity_main);
  11. 11 }
  12. 12
  13. 13 public void click(View v){
  14. 14 encodeArray(array);
  15. 15 //不需要返回值,实际操作的是同一块内存,内容已经发生了改变
  16. 16 for (int i : array) {
  17. 17 System.out.println(i);
  18. 18 }
  19. 19 }
  20. 20
  21. 21 //传递数组其实是传递一个堆内存的数组首地址的引用过去,所以实际操作的是同一块内存,
  22. 22 //当调用完方法,不需要返回值,实际上参数内容已经改变,
  23. 23 //Android中很多操作硬件的方法都是这种C语言的传引用的思路,要非常熟练
  24. 24 private native void encodeArray(int[] arr);
  25. 25 }
  1. 1 #include <jni.h>
  2. 2 /*
  3. 3 * Class: com_mwp_jniarray_MainActivity
  4. 4 * Method: encodeArray
  5. 5 * Signature: ([I)V
  6. 6 */
  7. 7 JNIEXPORT void JNICALL Java_com_mwp_jniarray_MainActivity_encodeArray
  8. 8 (JNIEnv * env, jobject obj, jintArray arr){
  9. 9 //拿到整型数组的长度以及第0个元素的地址
  10. 10 //jsize (*GetArrayLength)(JNIEnv*, jarray);
  11. 11 int length = (*env)->GetArrayLength(env, arr);
  12. 12 // jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
  13. 13 int* arrp = (*env)->GetIntArrayElements(env, arr, 0);
  14. 14 int i;
  15. 15 for(i = 0;i<length;i++){
  16. 16 *(arrp + i) += 10; //将数组中的每个元素加10
  17. 17 }
  18. 18 }
八、美图秀秀的C语言本地类库加深JNI的理解
    项目中不需要有c代码,只需要有一个编译过后的类库供Java调用就可以了。
    将美图秀秀的apk文件解压缩,将lib目录下C类库导入自己的项目,
    反编译美图秀秀的apk文件,将其本地方法类 JNI.java复制到自己的项目
    根据本地方法名和参数猜函数的作用及如何使用,
    下例调用了美图的一个LOMO美化效果

  1. 1 public class MainActivity extends Activity {
  2. 2
  3. 3 static{
  4. 4 //加载美图秀秀的类库
  5. 5 System.loadLibrary("mtimage-jni");
  6. 6 }
  7. 7 private ImageView iv;
  8. 8 private Bitmap bitmap;
  9. 9 @Override
  10. 10 protected void onCreate(Bundle savedInstanceState) {
  11. 11 super.onCreate(savedInstanceState);
  12. 12 setContentView(R.layout.activity_main);
  13. 13
  14. 14 iv = (ImageView) findViewById(R.id.iv);
  15. 15
  16. 16 bitmap = BitmapFactory.decodeFile("sdcard/aneiyi.jpg");
  17. 17 iv.setImageBitmap(bitmap);
  18. 18 }
  19. 19
  20. 20 public void click(View v){
  21. 21
  22. 22 int width = bitmap.getWidth();
  23. 23 int height = bitmap.getHeight();
  24. 24
  25. 25 //用于保存所有像素信息的数组
  26. 26 int[] pixels = new int[width*height];
  27. 27 //获取图片的像素颜色信息,保存至pixels
  28. 28 bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
  29. 29
  30. 30 JNI jni = new JNI();
  31. 31 //调用美图秀秀本地库中的美图方法,靠猜
  32. 32 //arg0:保存了所有像素颜色信息的数组
  33. 33 //arg1:图片的宽
  34. 34 //arg2:图片的高
  35. 35 //此方法是通过改变pixels的像素颜色值来实现美化效果,传递一个数组参数是不需要返回值的
  36. 36 jni.StyleLomoB(pixels, width, height);
  37. 37
  38. 38 Bitmap bmNew = Bitmap.createBitmap(pixels, width, height, bitmap.getConfig());
  39. 39 iv.setImageBitmap(bmNew);
  40. 40 }
  41. 41 }
九、在C语言中调用java方法(反射)
        1、有时需要在C语言中调用java的方法,如刷新UI显示加载资源进度
           在本地方法C语言代码中打印 Android的Logcat日志输出,Google已经帮我们封装好了方法,只需要调用一下就可以
           如果要输出中文的话,必须将C语言的文件编码改成 utf-8,否则乱码
           在C语言中调用java的方法需要用到反射,C语言的反射需要一个方法签名,使用javap能够生成方法签名,很熟练的话也可以自己写方法签名
           在bin/classes目录下执行 javap -s 全类名
  1. 1 public class MainActivity extends Activity {
  2. 2 static{
  3. 3 System.loadLibrary("hello");
  4. 4 }
  5. 5
  6. 6 @Override
  7. 7 protected void onCreate(Bundle savedInstanceState) {
  8. 8 super.onCreate(savedInstanceState);
  9. 9 setContentView(R.layout.activity_main);
  10. 10 }
  11. 11
  12. 12 public void click(View v){
  13. 13 cLog();
  14. 14 }
  15. 15
  16. 16 public native void cLog();
  17. 17
  18. 18 public void show(String message){
  19. 19 Builder builder = new Builder(this);
  20. 20 builder.setTitle("标题");
  21. 21 builder.setMessage(message);
  22. 22 builder.show();
  23. 23 }
  24. 24
  25. 25 }
  1. #include <jni.h>
  2. #include <android/log.h>
  3. #define LOG_TAG "System.out"
  4. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
  5. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  6. JNIEXPORT void JNICALL Java_com_mwp_ccalljava2_MainActivity_cLog
  7. (JNIEnv * env, jobject obj){
  8. //打印log输出
  9. LOGD("我是C语言打印的debug日志");
  10. LOGI("我是C语言打印的info日志");
  11. //通过反射来调用java的方法,需要知道方法签名,使用javap得到方法签名
  12. //在bin/classes目录下执行 javap -s 全类名
  13. //1、得到类的字节码对象
  14. //jclass (*FindClass)(JNIEnv*, const char*);
  15. jclass clazz = (*env)->FindClass(env, "com/mwp/ccalljava2/MainActivity");
  16. //jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
  17. jmethodID methodID = (*env)->GetMethodID(env, clazz, "show", "(Ljava/lang/String;)V");
  18. //void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  19. (*env)->CallVoidMethod(env,obj,methodID, (*env)->NewStringUTF(env, "这是弹窗的内容"));
  20. }
  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_LDLIBS += -llog
  4. LOCAL_MODULE := hello
  5. LOCAL_SRC_FILES := log.c
  6. include $(BUILD_SHARED_LIBRARY)

十、模拟监测压力传感器

        传感器的原理是使用敏感电阻如(光敏电阻,热敏电阻)等监测电流电压的变化
        Android程序只需要处理传感器传递的数据,并将其显示在界面上就可以。
        下面模拟一个压力传感器来练习JNI编程

  1. 1 public class MainActivity extends Activity {
  2. 2 static{
  3. 3 System.loadLibrary("monitor");
  4. 4 }
  5. 5 private MyProgressBar mpb;
  6. 6 @Override
  7. 7 protected void onCreate(Bundle savedInstanceState) {
  8. 8 super.onCreate(savedInstanceState);
  9. 9 setContentView(R.layout.activity_main);
  10. 10
  11. 11 mpb = (MyProgressBar) findViewById(R.id.mpb);
  12. 12 mpb.setMax(100);
  13. 13 }
  14. 14
  15. 15 public void start(View v){
  16. 16 new Thread(){
  17. 17 public void run() {
  18. 18 startMonitor();
  19. 19 };
  20. 20 }.start();
  21. 21 }
  22. 22
  23. 23 public void stop(View v){
  24. 24 stopMonitor();
  25. 25 }
  26. 26
  27. 27 public native void startMonitor();
  28. 28 public native void stopMonitor();
  29. 29
  30. 30 //供本地方法调用刷新UI
  31. 31 public void show(int pressure){
  32. 32 mpb.setPressure(pressure);
  33. 33 }
  34. 34 }
  1. #include <jni.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. //模拟压力传感其传递数据
  5. int getPressure(){
  6. return rand()%101;
  7. }
  8. //用于控制循环的开关
  9. int monitor;
  10. JNIEXPORT void JNICALL Java_com_mwp_monitor_MainActivity_startMonitor
  11. (JNIEnv * env, jobject obj){
  12. monitor = 1;
  13. int pressure;
  14. jclass clazz;
  15. jmethodID methodid;
  16. while(monitor){
  17. //本地方法获取传感器数据
  18. pressure= getPressure();
  19. //使用反射调用java方法刷新界面显示
  20. //jclass (*FindClass)(JNIEnv*, const char*);
  21. clazz= (*env)->FindClass(env, "com/mwp/monitor/MainActivity");
  22. //jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
  23. methodid= (*env)->GetMethodID(env, clazz, "show","(I)V");
  24. // void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  25. (*env)->CallVoidMethod(env, obj, methodid, pressure);
  26. sleep(1);
  27. }
  28. }
  29. JNIEXPORT void JNICALL Java_com_mwp_monitor_MainActivity_stopMonitor
  30. (JNIEnv * env, jobject obj){
  31. //结束循环
  32. monitor = 0;
  33. }
十一、使用C++代码实现本地方法
         1、把c文件后缀名换成cpp
         2、Android.mk文件中的hello.c也要换成hello.cpp
         3、c++的使用的环境变量结构体中,访问了c使用的结构体的函数指针,函数名全部都是一样的,只是参数去掉了结构体指针
         4、访问函数指针时,把env前面的*号去掉,因为此时env已经是一级指针
         5、clean,清除之前编译的残留文件
         6、把声明函数的h文件放入jni文件夹中,include该h文
  1. #include <jni.h>
  2. #include "com_mwp_cplusplus_MainActivity.h"
  3. JNIEXPORT jstring JNICALL Java_com_mwp_cplusplus_MainActivity_helloC
  4. (JNIEnv * env, jobject obj){
  5. char* cstr = "hello from c";
  6. //return (*env)->NewStringUTF(env, cstr);
  7. return env->NewStringUTF(cstr);
  8. }

JNI(Java Native Interface)的更多相关文章

  1. 【详解】JNI(Java Native Interface)(一)

    前言: 一提到JNI,多数编程者会下意识地感受到一种无法言喻的恐惧.它给人的第一感觉就是"难",因为它不是单纯地在JVM环境内操作Java代码,而是跳出虚拟机与其他编程语言进行交互 ...

  2. +Java中的native关键字浅析(Java+Native+Interface)++

    JNI是Java Native Interface的 缩写.从Java 1.1开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的 ...

  3. 杂项-Maven-jna:JNA(Java Native Access)

    ylbtech-杂项-Maven-jna:JNA(Java Native Access) JNA(Java Native Access )提供一组Java工具类用于在运行期间动态访问系统本地库(nat ...

  4. android 学习随笔二十七(JNI:Java Native Interface,JAVA原生接口 )

    JNI(Java Native Interface,JAVA原生接口) 使用JNI可以使Java代码和其他语言写的代码(如C/C++代码)进行交互. 问:为什么要进行交互? 首先,Java语言提供的类 ...

  5. 【详解】JNI (Java Native Interface) (四)

    案例四:回调实例方法与静态方法 描述:此案例将通过Java调用的C语言代码回调Java方法. 要想调用实例对象的方法,需要进行以下步骤: 1. 通过对象实例,获取到对象类的引用  => GetO ...

  6. 【详解】JNI (Java Native Interface) (三)

    案例三:C代码访问Java对象的实例变量   获取对象的实例变量的步骤: 1. 通过GetObjectClass()方法获得此对象的类引用 2. 通过类引用的GetFieldID()方法获得实例变量的 ...

  7. 【详解】JNI (Java Native Interface) (二)

    案例二:传递参数给C代码,并从其获取结果 注:这里传递的参数是基本类型的参数,在C代码中有直接的映射类型. 此案例所有生成的所有文件如下: (1)编写案例二的Java代码,如下: 这里我们定义了一个n ...

  8. Java Native Interface 基于JNI的嵌入式手机软件开发实例

    1.通过JNI和c/c++的库组件.其他代码交互 2.java和c不能互通的原因时数据类型问题 Introduction https://docs.oracle.com/javase/8/docs/t ...

  9. Java Native Interface 六JNI中的异常

    本文是<The Java Native Interface Programmer's Guide and Specification>读书笔记 在这里只讨论调用JNI方法可能会出现的异常, ...

随机推荐

  1. Go视频教程整理转

    Go视频教程整理 [Go Web基础]01博客项目设计 |Go视频教程|Go语言基础 http://www.tudou.com/programs/view/gXZb9tGNsGU/ [Go Web基础 ...

  2. Ubuntu 16.04安装Jetty Web服务器

    一.下载 http://www.eclipse.org/jetty/download.html 二.安装 tar -zxvf jetty-distribution-9.4.7.v20170914.ta ...

  3. [Testing] Config jest to test Javascript Application -- Part 1

    Transpile Modules with Babel in Jest Tests Jest automatically loads and applies our babel configurat ...

  4. java utf8字符 导出csv 文件的乱码问题。

    在输出的格式为UTF-8的格式,但是打开CSV文件一直为乱码,后来参考了这里的代码,搞定了乱码问题,原文请参考:http://hbase.iteye.com/blog/1172200 private ...

  5. 【转载】分布式系统理论基础 - 一致性、2PC和3PC

    引言 狭义的分布式系统指由网络连接的计算机系统,每个节点独立地承担计算或存储任务,节点间通过网络协同工作.广义的分布式系统是一个相对的概念,正如Leslie Lamport所说[1]: What is ...

  6. 史上最全的CSS hack方式一览 jQuery 图片轮播的代码分离 JQuery中的动画 C#中Trim()、TrimStart()、TrimEnd()的用法 marquee 标签的使用详情 js鼠标事件 js添加遮罩层 页面上通过地址栏传值时出现乱码的两种解决方法 ref和out的区别在c#中 总结

    史上最全的CSS hack方式一览 2013年09月28日 15:57:08 阅读数:175473 做前端多年,虽然不是经常需要hack,但是我们经常会遇到各浏览器表现不一致的情况.基于此,某些情况我 ...

  7. System V IPC相关函数

    System V IPC 将一个已保存的路径名和一个整数标识符转换成一个key_t值,称为IPC键key_t:System V IPC(System V消息队列.System V信号量.System ...

  8. OpenStack源码系列---nova-conductor

    nova-conductor启动的也是一个rpc server,代码框架和nova-compute类似,所以我也懒得再详细分析一遍服务启动的过程.nova-api那篇文章的最后我说"cctx ...

  9. Deep Learning for Robotics 资源汇总

    1 前言 在最新Nature的Machine Intelligence 中Lecun.Hinton和Bengio三位大牛的Review文章Deep Learning中.最后谈The Future Of ...

  10. 命令行添加PATH

    如何设置PATH 命令:echo "export PATH=xxxxxx:$PATH" >> ~/.bash_profile 解释:把"export PATH ...