JNI开发笔记二

作者: BlainPeng | 来源:发表于2016-02-18 17:26 被阅读881次

    上一篇文章简单的记录了JNI的入门情况,今天继续练习JNI其他的开发知识。用JNI来验证用户名和密码是否正确。看效果:


    GIF.gif

    验证用户名和密码是否正确,可以把验证逻辑直接放在android端,但这次我们把验证的逻辑放在C语言中, 再通过本地方法来确定是否正确。

    在C语言中定义一个方法:用来验证用户名和密码是否正确
     /** 
      * 判断用户名和密码是否正确
      */
     int check(char *name, char *password) {
          if (strcmp(name, "LiSi") == 0) {  //用户名正确     
              if (strcmp(password, "123") == 0) {//密码正确
                          return 200;       
               } else {
                          return 202;       
                       }    
          } else {        
               return 201;    
          }
      }
    
    定义一个两个本地方法
    public class JNIUtil {    
          public static native int checkLoginOne(byte[] name, byte[] password);    
          public static native int checkLoginTwo(String name, String password);
    }
    

    这两个方法都是用来调用C中的验证逻辑的,但所传的参数不一样,区别在哪里,请继续往下看。通过上一篇文章介绍的JNI开发步骤,得到如下两个方法:

    JNIEXPORT jint JNICALL Java_com_pbl_checklogin_JNIUtil_checkLoginOne (JNIEnv     
    *env, jclass clazz, jbyteArray jbyteArrayName, jbyteArray jbyteArrayPassword);
    
      JNIEXPORT jint JNICALL Java_com_pbl_checklogin_JNIUtil_checkLoginTwo(JNIEnv     
    *env, jclass clazz, jstring jstringName, jstring jstringPassword);
    

    在这两个方法里面,我们将会调用之前写好的check方法,而check方法里面的参数是两个char*类型的,所以需要把从java传过来的数据进行转换

    • 先看checkLoginOne方法:从Java传过来的是byte数组,所以需要先获取数组中的内容,然后将其转换成C中的字符数组

      /** 
        * 将Java中的字节数组转化成C语言中的字符数组,jbyte就是signed char 
        */
      char *jbyte2Char(JNIEnv *env, jbyteArray array) {
          //通过GetXXXArrayElements函数把简单类型的数组转化成本地类型的数组,并返回其    
           //数组的指针,然后通过该指针来对数组进行拷贝处理。
          jbyte *datas = (*env)->GetByteArrayElements(env, array, 0);    
          //获取数组的长度    
          jsize len = (**env).GetArrayLength(env, array);    
          //申请内存时别忘了加字符串的结束标记的内存    
          char *cstr = malloc(len + 1);    
          memcpy(cstr, datas, len);    
          cstr[len] = '\0';    
          //对拷贝数组处理完后,通过ReleaseXXXArrayElements函数把修改后的拷贝数组的反
          //射到java数组,然后释放所有相关的资源   
         (*env)->ReleaseByteArrayElements(env, array, datas, 0);    
          return cstr;
       }
      
    • 再看checkLoginTwo方法:从Java传过来的直接是String,那么如何把这个Java中的String转换成C语言中的char数组了?上面已经说过了,jbyte就是signed char,那么第一步就是把String转换所字节数组,若是在java环境下,String和byte之间的转换非常容易,但是,现在是在C语言环境下,此时,我们就应该用到另外一个知识了:反射。通过反射,我们可以在C/C++中调用Java中的方法

      /** 
      * 通过反射将Java中的字符串转换成C中的字符 
      */
      char *string2Char(JNIEnv *env, jstring str) {    
            //获取class   
            jclass clazz = (*env)->FindClass(env, "java/lang/String");    
           //获取Method   
            jmethodID mid = (*env)->GetMethodID(env, clazz, "getBytes", "(Ljava/lang/String;)[B");    
          //获取对象,不需要去获取,就是传过来的str    
           //jobject     (*NewObject)(JNIEnv*, jclass, jmethodID, ...);    
          //以utf-8编码格式进行调用,而这里的utf-8是C语言中的char,需把它转化java中的      
          //字符串   
           jstring jencode = (*env)->NewStringUTF(env, "utf-8");    
           jbyteArray array = (*env)->CallObjectMethod(env, str, mid, jencode); 
          //这里和第一种方法一样了   
           jbyte *datas = (*env)->GetByteArrayElements(env, array, 0);    
           jsize len = (**env).GetArrayLength(env, array);    
           char *cstr = malloc(len + 1);    
           memcpy(cstr, datas, len);    
           cstr[len] = '\0';    
           (*env)->ReleaseByteArrayElements(env, array, datas, 0);    
           return cstr;
         }
      
    • 最后整个JNI方法变成:

      JNIEXPORT jint JNICALL Java_com_pbl_checklogin_JNIUtil_checkLoginOne(JNIEnv 
      *env, jclass clazz, jbyteArray jbyteArrayName, jbyteArray jbyteArrayPassword) {      
          char *cname = jbyte2Char(env, jbyteArrayName);    
          char *cpassword = jbyte2Char(env, jbyteArrayPassword);    
          return check(cname, cpassword);
      }
      
      JNIEXPORT jint JNICALL Java_com_pbl_checklogin_JNIUtil_checkLoginTwo(JNIEnv 
      *env, jclass clazz, jstring jstringName, jstring jstringPassword) {    
          char *cname = string2Char(env, jstringName);    
          char *cpwd = string2Char(env, jstringPassword);    
          return check(cname, cpwd);
      }
      
    开始测试
      public void checkLoginOne(View view) {    
              String username = mUserName.getText().toString().trim();    
              String password = mPassword.getText().toString().trim();    
              if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {              
              Snackbar.make(view, "密码或验证码不能为空", Snackbar.LENGTH_LONG).setAction("Action", null).show();        
                  return;    
              }    
              try {        
                      int res = JNIUtil.checkLoginOne(username.getBytes("utf-8"), password.getBytes("utf-8"));       
                       switch (res) {            
                              case 200:                
                                    Snackbar.make(view, "验证成功", Snackbar.LENGTH_LONG).setAction("Action", null).show();                
                              break;            
                              case 201:                
                                    Snackbar.make(view, "用户名错误", Snackbar.LENGTH_LONG).setAction("Action", null).show();                
                              break;            
                              case 202:                
                                    Snackbar.make(view, "密码错误", Snackbar.LENGTH_LONG).setAction("Action", null).show();                
                              break;        
                        }    
               } catch (UnsupportedEncodingException e) {       
                           e.printStackTrace();    
               }
      }
      
      public void checkLoginTwo(View view) {    
              String username = mUserName.getText().toString().trim();    
              String password = mPassword.getText().toString().trim();    
              if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {              
              Snackbar.make(view, "密码或验证码不能为空", Snackbar.LENGTH_LONG).setAction("Action", null).show();        
                  return;    
              }    
             
              int res = JNIUtil.checkLoginTwo(username, password);       
              switch (res) {            
                     case 200:                
                           Snackbar.make(view, "验证成功", Snackbar.LENGTH_LONG).setAction("Action", null).show();                
                     break;            
                     case 201:                
                            Snackbar.make(view, "用户名错误", Snackbar.LENGTH_LONG).setAction("Action", null).show();                
                      break;            
                      case 202:                
                             Snackbar.make(view, "密码错误", Snackbar.LENGTH_LONG).setAction("Action", null).show();                
                      break;        
               }           
      }
    

    今天的重点主要是C/C++调用Java的代码,需要用到反射的原理。在string2Char方法中,获取MethodId时,需要注意第三个参数:方法的签名。大家都知道,Java中的方法是可以重载的,但仅仅根据方法名是没法找到具体方法的,为了解决这个问题,JNI技术中就将参数类型和返回值类型的组合作为了一个方法的签名信息,有了签名信息和方法名,就能很顺利地找到Java中的方法
    查找方法的签名信息,可以通过:javap -s -p xxx
    其中xxx为编译后的class文件,s表示输出内部数据类型的签名信息,p表示打印所有方法和成员的签名信息,默认只会打印public成员和方法的签名信息
    在C语言中调用Java中的String转找换成byte,调用了getBytes,看看下面getBytes(String string)的签名:

    QQ截图20160218172431.png QQ截图20160218172538.png

    好了。今天就到这里了。

    相关文章

      网友评论

        本文标题:JNI开发笔记二

        本文链接:https://www.haomeiwen.com/subject/tximkttx.html