发布新日志

  • Android通过JNI调用驱动程序(完全解析实例) .

    2013-03-15 13:26:44

          要达到的目的:android系统中,用JAVA写界面程序,调用jni中间库提供的接口,去操作某个驱动节点,实现read,writer ioctl等操作!这对底层驱动开发人员是很重要的一个调试通道,也是android 系统下提供一些特殊功能接口的方法!

    本文前提:我们假设已经写了一个驱动程序,它是控制LED的亮灭的,并且创建了一个节点:/dev/vib,也就是通过open这个vib节点,可以read/write/ioctl 操作驱动程序实现LED灯的亮灭控制,具体可以看我另一篇博文《android驱动例子(LED灯控制)

    开发环境 1、ubuntu下的NDK编译环境,2、Esclips开发环境

    一、编写JNI模块

    当安装好NDK编译环境后,会在它的目录下找到sample目录,它里面有一些例子,可以参考这些例子来写我们自已的模块。

    clip_image002

    1、 source文件夹下,新建“LEDSJNI”文件夹。

    2、 Source/LEDSJNI/jni/目录下,新建“vib-jni.c”

    vib-jni.c文件

    #include <string.h>
    #include <jni.h>
    #include <fcntl.h> /*包括文件操作,如open() read() close() write()等*/
    //----for output the debug log message
    #include <android/log.h>
    #define LOG_TAG "vib-jni"
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    #define DEVICE_NAME "/dev/vib" //device point
    #define VIB_ON 0x11
    #define VIB_OFF 0x22
    int fd;
    jstring
    Java_com_auly_control_vibClass_stringFromJNI( JNIEnv* env,
    jobject thiz )
    {
    return (*env)->NewStringUTF(env, "Hello from JNI--Peter for vib!");//打印字符串
    }

    jint
    Java_com_auly_control_vibClass_Init( JNIEnv* env )
    {
    LOGE("vibClass_Init() /n");
    fd = open(DEVICE_NAME,O_RDWR);//打开设备
    LOGE("vibClass_Init()-> fd = %d /n",fd);
    if(fd == -1)
    {
    LOGE("open device %s error /n ",DEVICE_NAME);//打印调试信息
    return 0;
    }
    else
    {
    return 1;
    }
    }

    jint
    Java_com_auly_control_vibClass_IOCTLVIB( JNIEnv* env, jobject thiz, jint controlcode )
    {
    int CTLCODE = controlcode;
    LOGE("IOCTLVIB() = %x --vibClass_IOCTLVIB /n",CTLCODE);
    switch(CTLCODE)
    {
    case VIB_ON:
    {
    ioctl(fd,VIB_ON);//调用驱动程序中的ioctrl接口,把命令VIB_ON传下去,实现硬件操作
    break;
    }
    case VIB_OFF:
    {
    ioctl(fd,VIB_OFF);//调用驱动程序中的ioctrl接口,把命令VIB_OFF传下去,实现硬件操作
    break;
    }
    default:break;
    }
    return 1;
    }

    3、相同目录下的新建Android.mk如下

    Android.mk文件

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := vib-jni
    LOCAL_SRC_FILES := vib-jni.c
    LOCAL_CFLAGS := -Werror
    LOCAL_LDLIBS := -llog -lGLESv2 //__android_log_print 函数
    include $(BUILD_SHARED_LIBRARY)

    可以看到,主要是修改LOCAL_SRC_FILES指向源文件的名称!

    还有一点很重要,如果要使用调试LOG 打印,也就是__android_log_print 函数。要在LOCAL_LDLIBS中添加-llog,如上面的Android.mk所示。

    4、编译JNI模块

    #cd /home/workspace/android-ndk-r4b/sources/LEDSJNI

    进到刚才写的JNI目录

     

    #ndk-build

    编译JNI,编译成功后,会在LEDSJNI文件夹下生成libs和obj两个文件夹,并在

    LEDSJNI/libs/armeabi下得到目标文件libvib-jni.so

    clip_image002[8]

    (目前LEDSJNI文件夹只有3个目录jni,libs,obj)

    二、JAVA程序

    1、Eclipse新建工程

    拷贝LEDSJNI目录到Windows下,例如C盘下。然后在它里面新建Eclipse 工程。

    clip_image002[10]
    clip_image004

    新键工程后,

    如果出现如下错误:

    ERROR: Unable to open class file C:/LEDSJNI/gen/com/auly/control/R.java: No such file or directory

    解决方法如下:

    对着该工程鼠标右键 》bulid path 》configure build path》java build path》order and Export

     

    clip_image002[12]

    把里面的android 2.1勾上,Build project,就OK了

    clip_image002[14]

    然后 Run as > Android application,就会出现android的模拟器了,里面跑个helloworld出来。

    clip_image002[16]

    2、加入button和文本输出

    程序到上面为止代码是ADT自动生成的,似乎与我们一点关系也没有。那我们来改一下代码,因为我们调用JNI接口是为了访问驱动程序操作硬件的,例如写,读,打开LED,关闭LED等等,由按钮触发的动作。

    第一步是,增加两个Button,,在main.xml里描述一下:打开Res > layout> main.xml 文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    androidrientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
    <Button android:id="@+id/led_on"/*这表示需要一个唯一的UID来作为Button的ID,它的引用名是led_on。*/
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/LEDon">/*表示这个按钮的文本是来源于另一个资源描述文件strings.xml里的文:字资源LEDon */
    <requestFocus/>
    </Button>
    <Button android:id="@+id/led_off"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/LEDoff">
    <requestFocus/>
    </Button>
    </LinearLayout>

    实际代码中,把注释去掉,否则编译不过的。

    3、加入输出字符串资源

    工程 > values > strings.xml 文件

    clip_image002[18]
    修改如下
    
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="hello">Led 控制程序</string>
        <string name="app_name">LEDAPP</string>
    <string name="LEDon">打开LED</string>
    <string name="LEDoff">关闭LED</string>
    </resources>

    上面的”打开LED”等资源,就是用在按钮上显示出来的字符串

    经过上面的修改,现在程序界面上,已经有如下效果了

    鼠标右键工程名>Run as > Android application 运行程序。

    clip_image004[11]

    4、加入按钮对应的动作

    “打开LED”按扭:调用JNI的IOCTLVIB(VIB_ON);

    “关闭LED”按钮:调用JNI的 IOCTLVIB(VIB_OFF);

    操作:

    在LEDAPP > src > com.auly.control > vibrator.java文件

    package com.auly.control;
    /**定义头文件*/
    import android.widget.TextView;
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    import android.util.Log;
    import android.app.Activity;
    import android.os.Bundle;
    public class vibrator extends Activity {
    /** 定义变量 */
    public static final int VIB_ON = 0x11;
    public static final int VIB_OFF = 0x22;
    vibClass mvibClass;/**定义类*/
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    /**----------------初始化------------- */
    mvibClass = new vibClass();/*声明类*/
    mvibClass.Init(); //调用JNI库里的初始化函数
    /**----------------按钮:打开LED------------- */
    Button btn1 = (Button)findViewById(R.id.led_on);/*这里用到的ID,就是在main.xml里定义的 led_on*/
    btn1.setOnClickListener(new View.OnClickListener()
    {
    public void onClick(View v) /**当按钮按下*/
    {
    mvibClass.IOCTLVIB(VIB_ON);
    }
    });
    /**----------------按钮:关闭LED------------- */
    Button btn2 = (Button)findViewById(R.id.led_off);/*声明按钮,id main.xml里有定义*/
    btn2.setOnClickListener(new View.OnClickListener()
    {
    public void onClick(View v) /**当按钮按下*/
    {
    mvibClass.IOCTLVIB(VIB_OFF);
    }
    });
    }
    }

    如果在保存时遇到说 save problems,无法保存,请先复制上面的代码,然后,关闭vibrator.java,提示保存时选不保存,然后在左边资源窗中再次双击打开该文件,把几日才复制下来的内容,粘贴上去,一般就能正常保存,不知道是不是Eclipse的不稳定造成的。

    5、添加类vibClass

    鼠标右键com.auly.control > new > Class

    clip_image002[20]

    填参数:

    clip_image002[22]

    Finish后,在/src/com/auly/control/得到如下的类文件

    vibClass.java

    修改如下

    package com.auly.control;
    /*Class for Vibrator --peter*/
    public class vibClass {
    static {
    System.loadLibrary("vib-jni");/*加载JNI库*/
    }
    /*声明 函数*/
    public static native String stringFromJNI();/*输出字符串
    对应于JNI里面的
    jstring Java_com_auly_control_vibClass_stringFromJNI( JNIEnv* env,jobject thiz )
    */
    public static native int Init();/*初始化函数,对应于JNI里面的
    jint Java_com_auly_control_vibClass_Init( JNIEnv* env )
    */
    public static native int IOCTLVIB(int controlcode);
    /*IO CTRL 接口
    * 对应于JNI里的
    jint Java_com_auly_control_vibClass_IOCTLVIB( JNIEnv* env, jobject thiz, jint controlcode )
    */
    }

    三、 编译运行

    鼠标右键工程名,弹出菜单,选择 Run as > Android Application 就可以看到编译过程,编译完成后,会自动调用android模拟器,看到如下效果

    clip_image002[24]

    安装到开发板:

    在C:/LEDSJNI目录下,会看到bin文件夹,里面的LEDAPP.apk就是这个程序的安装文件,可以把它安装的开发板上,运行本程序,看控制开发板上的LED灯的效果。

    步骤:

    1、开发板上跑的kenel就已经把了LED驱动编译在里面了,

    可以参考《android驱动例子(LED灯控制)》

    2、开发板android跑起来后,PC机打开串口工具例如DNW,打开与开发板连接的COM口,然后敲打回车,就会在终端里看到“#”并有光标,表面进入了开发板的命令行终端,

    输入命令:

    #chmod 777 /dev/vib 

          这是为了使得vib这个节点可以被我们写的JNI操作,不然会open失败的,因为APK安装的JNI模块,权限不够,这个节点是我们的LED驱动生成的控制节点。

    也可以在android文件系统yaffs编译时,通过init.rc 文件来实现这个操作,就是在该文件里随便一行,写上面的命令行,启动时会自动执行!这样就不用手动的改变该节点的属性了。

    3、拷贝LEDAPP.apk到开发板上,通过安装工具把它安装到开发板上,如果不会安,可以GOOGLE一下,

    4、运行程序,就能按程序上的近钮来控制开发板上的LED亮灭了!


  • Android使用JNI实现Java与C之间传递数据

    2013-03-14 15:50:04

    介绍Java如何将数据传递给C和C回调Java的方法。  java传递数据给C,在C代码中进行处理数据,处理完数据后返回给java。C的回调是Java传递数据给C,C需要用到Java中的某个方法,就需要调用java的方法。

    Android中使用JNI七个步骤:

    1.创建一个android工程

    2.JAVA代码中写声明native 方法 public native String helloFromJNI();

    3.用javah工具生成头文件

    4. 创建jni目录,引入头文件,根据头文件实现c代码

    5.编写Android.mk文件

    6.Ndk编译生成动态库

    7.Java代码load 动态库.调用native代码

    Java调用C进行数据传递

     这里分别传递整形、字符串、数组在C中进行处理。

    声明native 方法:

    1. public class DataProvider {  
    2.     // 两个java中的int 传递c 语言 ,  c语言处理这个相加的逻辑,把相加的结果返回给java   
    3.     public native int add(int x ,int y);  
    4.       
    5.     //把一个java中的字符串传递给c语言, c 语言处理下字符串, 处理完毕返回给java    
    6.     public native String sayHelloInC(String s);  
    7.   
    8.     //把一个java中int类型的数组传递给c语言, c语言里面把数组的每一个元素的值 都增加5,    
    9.     //然后在把处理完毕的数组,返回给java   
    10.     public native int[] intMethod(int[] iNum);   
    11. }  


    以上方法要在C中实现的头文件,头文件可以理解为要在C中实现的方法

    其中 JENEnv* 代表的是java环境 , 通过这个环境可以调用java的方法,jobject 表示哪个对象调用了 这个c语言的方法, thiz就表示的是当前的对象

    1. /* DO NOT EDIT THIS FILE - it is machine generated */  
    2. #include <jni.h>   
    3. /* Header for class cn_itcast_ndk3_DataProvider */  
    4.   
    5. #ifndef _Included_cn_itcast_ndk3_DataProvider   
    6. #define _Included_cn_itcast_ndk3_DataProvider   
    7. #ifdef __cplusplus   
    8. extern "C" {  
    9. #endif   
    10. /* 
    11.  * Class:     cn_itcast_ndk3_DataProvider 
    12.  * Method:    add 
    13.  * Signature: (II)I 
    14.  */  
    15. JNIEXPORT jint JNICALL Java_cn_itcast_ndk3_DataProvider_add  
    16.   (JNIEnv *, jobject, jint, jint);  
    17.   
    18. /* 
    19.  * Class:     cn_itcast_ndk3_DataProvider 
    20.  * Method:    sayHelloInC 
    21.  * Signature: (Ljava/lang/String;)Ljava/lang/String; 
    22.  */  
    23. JNIEXPORT jstring JNICALL Java_cn_itcast_ndk3_DataProvider_sayHelloInC  
    24.   (JNIEnv *, jobject, jstring);  
    25.   
    26. /* 
    27.  * Class:     cn_itcast_ndk3_DataProvider 
    28.  * Method:    intMethod 
    29.  * Signature: ([I)[I 
    30.  */  
    31. JNIEXPORT jintArray JNICALL Java_cn_itcast_ndk3_DataProvider_intMethod  
    32.   (JNIEnv *, jobject, jintArray);  
    33.   
    34. #ifdef __cplusplus   
    35. }  
    36. #endif   
    37. #endif  


    C代码出了要引用头文件外,还要引入日志信息,以方便在C 中进行调试

    1. //引入头文件   
    2. #include "cn_itcast_ndk3_DataProvider.h"   
    3. #include <string.h>   
    4. //导入日志头文件   
    5. #include <android/log.h>   
    6. //修改日志tag中的值   
    7. #define LOG_TAG "logfromc"   
    8. //日志显示的等级   
    9. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)   
    10. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)   
    11.   
    12. // java中的jstring, 转化为c的一个字符数组   
    13. char*   Jstring2CStr(JNIEnv*   env,   jstring   jstr)  
    14. {  
    15.      char*   rtn   =   NULL;  
    16.      jclass   clsstring   =   (*env)->FindClass(env,"java/lang/String");  
    17.      jstring   strencode   =   (*env)->NewStringUTF(env,"GB2312");  
    18.      jmethodID   mid   =   (*env)->GetMethodID(env,clsstring,   "getBytes",   "(Ljava/lang/String;)[B");  
    19.      jbyteArray   barr=   (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312");   
    20.      jsize   alen   =   (*env)->GetArrayLength(env,barr);  
    21.      jbyte*   ba   =   (*env)->GetByteArrayElements(env,barr,JNI_FALSE);  
    22.      if(alen   >   0)  
    23.      {  
    24.       rtn   =   (char*)malloc(alen+1);         //new   char[alen+1]; "\0"   
    25.       memcpy(rtn,ba,alen);  
    26.       rtn[alen]=0;  
    27.      }  
    28.      (*env)->ReleaseByteArrayElements(env,barr,ba,0);  //释放内存   
    29.   
    30.      return rtn;  
    31. }  
    32.   
    33. //处理整形相加   
    34. JNIEXPORT jint JNICALL Java_cn_itcast_ndk3_DataProvider_add  
    35.   (JNIEnv * env, jobject obj, jint x, jint y){  
    36.     //打印 java 传递过来的 jstring ;   
    37.     LOGI("log from c code ");  
    38.     LOGI("x= %ld",x);  
    39.     LOGD("y= %ld",y);  
    40.     return x+y;  
    41. }  
    42.   
    43. //处理字符串追加   
    44. JNIEXPORT jstring JNICALL Java_cn_itcast_ndk3_DataProvider_sayHelloInC  
    45.   (JNIEnv * env, jobject obj, jstring str){  
    46.   
    47.     char* p =  Jstring2CStr(env,str);  
    48.     LOGI("%s",p);  
    49.     char* newstr = "append string";  
    50.   
    51.     //strcat(dest, sorce) 把sorce字符串添加到dest字符串的后面   
    52.     LOGI("END");  
    53.     return (*env)->NewStringUTF(env, strcat(p,newstr));  
    54. }  
    55.   
    56. //处理数组中的每一个元素   
    57. JNIEXPORT jintArray JNICALL Java_cn_itcast_ndk3_DataProvider_intMethod  
    58.   (JNIEnv * env, jobject obj, jintArray arr){  
    59.     // 1.获取到 arr的大小   
    60.   
    61.     int len = (*env)->GetArrayLength(env, arr);  
    62.     LOGI("len=%d", len);  
    63.   
    64.     if(len==0){  
    65.         return arr;  
    66.     }  
    67.     //取出数组中第一个元素的内存地址   
    68.     jint* p = (*env)-> GetIntArrayElements(env,arr,0);  
    69.     int i=0;  
    70.     for(;i<len;i++){  
    71.         LOGI("len=%ld", *(p+i));//取出的每个元素   
    72.         *(p+i) += 5; //取出的每个元素加五   
    73.     }  
    74.     return arr;  
    75. }  

    编写Android.mk文件

    1. LOCAL_PATH := $(call my-dir)  
    2.   
    3. include $(CLEAR_VARS)  
    4.   
    5. LOCAL_MODULE    := Hello  
    6. LOCAL_SRC_FILES := Hello.c   
    7. #增加 log 函数对应的log 库  liblog.so  libthread_db.a   
    8. LOCAL_LDLIBS += -llog  
    9.   
    10. include $(BUILD_SHARED_LIBRARY)  

     Java代码load 动态库.调用native代码

    1. static{  
    2.         System.loadLibrary("Hello");  
    3.     }  
    4.     DataProvider dp;  
    5.       
    6.     @Override  
    7.     public void onCreate(Bundle savedInstanceState) {  
    8.         super.onCreate(savedInstanceState);  
    9.         setContentView(R.layout.main);  
    10.         dp = new DataProvider();  
    11.     }  
    12.       
    13.     //add对应的事件   
    14.     public void add(View view){  
    15.         //执行C语言处理数据   
    16.         int result = dp.add(35);  
    17.         Toast.makeText(this"相加的结果"+ result, 1).show();      
    18.     }  

     

    C中回调java方法

    声明native 方法:

    1. public class DataProvider{  
    2.     public native void callCcode();  
    3.     public native void callCcode1();  
    4.     public native void callCcode2();  
    5.       
    6.     ///C调用java中的空方法     
    7.     public void helloFromJava(){  
    8.         System.out.println("hello from java ");  
    9.     }  
    10.     //C调用java中的带两个int参数的方法   
    11.     public int Add(int x,int y){  
    12.         System.out.println("相加的结果为"+ (x+y));  
    13.         return x+y;  
    14.     }  
    15.     //C调用java中参数为string的方法   
    16.     public void printString(String s){  
    17.         System.out.println("in java code "+ s);  
    18.     }  
    19. }  

    头文件可以用jdk自带的javah进行自动生成,使用javap -s可以获取到方法的签名。

    C代码实现回调需要三个步骤:首先要要获取到 某个对象 , 然后获取对象里面的方法  ,最后 调用这个方法  .

    1. #include "cn_itcast_ndk4_DataProvider.h"   
    2. #include <string.h>   
    3. #include <android/log.h>   
    4. #define LOG_TAG "logfromc"   
    5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)   
    6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)   
    7.   
    8. //1.调用java中的无参helloFromJava方法   
    9. JNIEXPORT void JNICALL Java_cn_itcast_ndk4_DataProvider_callCcode  
    10.   (JNIEnv * env , jobject obj){  
    11.     // 获取到DataProvider对象   
    12.     char* classname = "cn/itcast/ndk4/DataProvider";  
    13.     jclass dpclazz = (*env)->FindClass(env,classname);  
    14.     if (dpclazz == 0) {  
    15.             LOGI("not find class!");  
    16.         } else  
    17.             LOGI("find class");  
    18.     //第三个参数 和第四个参数 是方法的签名,第三个参数是方法名  , 第四个参数是根据返回值和参数生成的   
    19.     //获取到DataProvider要调用的方法   
    20.     jmethodID methodID = (*env)->GetMethodID(env,dpclazz,"helloFromJava","()V");  
    21.     if (methodID == 0) {  
    22.             LOGI("not find method!");  
    23.         } else  
    24.             LOGI("find method");  
    25.     //调用这个方法   
    26.     (*env)->CallVoidMethod(env, obj,methodID);  
    27. }  
    28.   
    29. // 2.调用java中的printString方法传递一个字符串   
    30. JNIEXPORT void JNICALL Java_cn_itcast_ndk4_DataProvider_callCcode1  
    31.   (JNIEnv * env, jobject obj){  
    32.     LOGI("in code");  
    33.     // 获取到DataProvider对象   
    34.     char* classname = "cn/itcast/ndk4/DataProvider";  
    35.     jclass dpclazz = (*env)->FindClass(env,classname);  
    36.     if (dpclazz == 0) {  
    37.             LOGI("not find class!");  
    38.         } else  
    39.             LOGI("find class");  
    40.     // 获取到要调用的method   
    41.     jmethodID methodID = (*env)->GetMethodID(env,dpclazz,"printString","(Ljava/lang/String;)V");  
    42.     if (methodID == 0) {  
    43.             LOGI("not find method!");  
    44.         } else  
    45.             LOGI("find method");  
    46.   
    47.     //调用这个方法   
    48.     (*env)->CallVoidMethod(env, obj,methodID,(*env)->NewStringUTF(env,"haha"));  
    49. }  
    50.   
    51. // 3. 调用java中的add方法 , 传递两个参数 jint x,y   
    52. JNIEXPORT void JNICALL Java_cn_itcast_ndk4_DataProvider_callCcode2  
    53.   (JNIEnv * env, jobject obj){  
    54.     char* classname = "cn/itcast/ndk4/DataProvider";  
    55.     jclass dpclazz = (*env)->FindClass(env,classname);  
    56.     jmethodID methodID = (*env)->GetMethodID(env,dpclazz,"Add","(II)I");  
    57.     (*env)->CallIntMethod(env, obj,methodID,3l,4l);  
    58. }  
Open Toolbar