✨
AndroidSummary
  • Introduction
  • 漫品客户端技术总结
    • 小说模块介绍
      • Android不规则布局的实现(万能公式)
      • Android So动态加载原理分析与优雅实现
  • Android总结
  • Android基础
    • Android Activity启动模式
    • Android 之 ThreadLocal简析
    • Android之JNI开发总结
    • 堆和栈的区别
    • java中==,equals,hashcode
    • Java基础数据类型和引用类型的区别
    • Java内部类详解
    • Android 8.0之后Service使用问题
    • 初探RxJava以及结合Retrofit的使用
    • 深入理解--Android Loader
    • 异步线程大师Handler(源码+图+demo+常见问题)
  • [Android AIDL跨进程通信]
    • service的隐式启动和显示启动
    • 如何绕过 Android 8.0 startService 限制
  • Android内存管理
    • Android内存管理(官方概览)
    • Android内存管理(操作系统基础)
    • Android内存管理(内存管理基础)
    • Android内存管理(linux内存管理机制)
    • Android内存管理(Android的内存管理机制简析)
    • Android内存管理(Android对Linux系统的内存管理机制进行的优化)
    • Android内存管理(垃圾回收算法相关)
    • Android内存管理(JVM 、DVM(dalvik) 、ART联系与区别)
    • 命令行提取hprof文件
  • Android安全
    • Android签名攻与防
    • Smalidea+IntelliJ IDEA/Android Studio无源码调试
    • keystore CSR CER PKCS7之间的区别与联系
    • 公钥、私钥、数字签名(签名)、数字证书(证书) 的关系(图文)
    • KeyStore 和 TrustStore区别与联系
    • Android 安全分析和漏洞挖掘|工具集
  • JAVA基础
    • Java 流(Stream)、文件(File)和IO
    • [线程共享和协作]
      • 线程基础、线程之间的共享和协作
      • [synchronized local volatile Threadlocal如何实现线程共享]
        • Synchronized实现原理
        • CAS原理分析
        • Java并发编程:Callable、Future和FutureTask
    • [深入理解Java泛型]
      • 泛型的作用与定义
      • 通配符与嵌套
      • Java泛型擦除及其相关内容
    • [注解深入浅出]
      • 注解是什么,如何理解
      • 自定义注解与元注解
      • 注解的使用场景
    • [并发编程]
      • 线程共享和协作
Powered by GitBook
On this page
  • 一.静态注册和动态注册
  • 二.C反射JAVA 的各种方法
  • 三.NewStringUTF函数请慎用
  • 四.推荐几种修改过的类型转换
  • 五. 内存管理

Was this helpful?

  1. Android基础

Android之JNI开发总结

PreviousAndroid 之 ThreadLocal简析Next堆和栈的区别

Last updated 5 years ago

Was this helpful?

Android Ndk开发常用网站收集,真正的高手并不是掌握所有的API而是需要的时候可以快速的找到要使用的API。

基础知识请移步:

JNI动态加载:

JNI中C调用java的方法:

JNI读取应用签名:

NDK开发之日志打印:

以上是学习和使用jni常用的几种方式,上述文章内容并不完全正确,稍加修改可正常使用,有需要的可以收藏下。 这篇文章主要介绍JNI开发中遇到的坑以及解决的方法。

一.静态注册和动态注册

为什么需要注册?其实就是给Java的native函数找到底层C,C++实现的函数指针。

  • 静态注册:

    通过包名类名一致来确认,Java有一个命令javah,专门生成某一个JAVA文件所有的native函数的头文件(h文件),步骤如下,我们只说Android项目下如何实施,其实理解了都一样

    静态方法注册JNI有哪些缺点?

1.必须遵循某些规则 2.名字过长 3,多个class需Javah多遍, 4.运行时去找效率不高

  • 动态注册 :

    在JNi层实现的,JAVA层不需要关心,因为在system.load时就会去掉JNI_OnLoad,有就注册,没就不注册。

  • 区别:

静态注册是用到时加载,动态注册一开始就加载好了,这个可以从DVM的源代码看出来。

二.C反射JAVA 的各种方法

TestClass类包一个构造方法、一个成员方法,一个静态方法,一个内部类,大多数的类都是由这三种方法组成的。下面要做的就是怎么在JNI调用这些方法。

package com.lb6905.jnidemo;

import android.util.Log;

public class TestClass {
    private final static String TAG = "TestClass";

    public TestClass(){
        Log.i(TAG, "TestClass");
    }

    public void test(int index) {
        Log.i(TAG, "test : " + index);
    }

    public static void testStatic(String str) {
        Log.i(TAG, "testStatic : " + str);
    }

    public static class InnerClass {
        private int num;
        public InnerClass() {
            Log.i(TAG, "InnerClass");
        }

        public void setInt(int n) {
            num = n;
            Log.i(TAG, "setInt: num = " + num);
        }
    }
}

查看方法签名

进入到classpath目录下: 命令: cd app/build/intermediates/classes/debug

查看外部类的签名 javap -s -p com.lb6905.jnidemo.TestClass

查看内部类的签名 javap -s -p com.lb6905.jnidemo.TestClass$InnerClass

结果如下:

F:\Apps\jniDemo\JNIDemo\app\build\intermediates\classes\debug>javap -s -p com.lb6905.jnidemo.TestClass
Compiled from "TestClass.java"
public class com.lb6905.jnidemo.TestClass {
  private static final java.lang.String TAG;
    descriptor: Ljava/lang/String;
  public com.lb6905.jnidemo.TestClass();
    descriptor: ()V

  public void test(int);
    descriptor: (I)V

  public static void testStatic(java.lang.String);
    descriptor: (Ljava/lang/String;)V
}

F:\Apps\jniDemo\JNIDemo\app\build\intermediates\classes\debug>javap -s -p com.lb6905.jnidemo.TestClass$InnerClass
Compiled from "TestClass.java"
public class com.lb6905.jnidemo.TestClass$InnerClass {
  private int num;
    descriptor: I
  public com.lb6905.jnidemo.TestClass$InnerClass();
    descriptor: ()V

  public void setInt(int);
    descriptor: (I)V
}

在JNI中反射调用上述方法

JNIEXPORT void JNICALL Java_com_lb6905_jnidemo_MainActivity_JNIReflect
(JNIEnv *env, jobject thiz)
{
    //实例化Test类
    jclass testclass = (*env)->FindClass(env, "com/lb6905/jnidemo/TestClass");
    //构造函数的方法名为<init>
    jmethodID testcontruct = (*env)->GetMethodID(env, testclass, "<init>", "()V");
    //根据构造函数实例化对象
    jobject testobject = (*env)->NewObject(env, testclass, testcontruct);

    //调用成员方法,需使用jobject对象
    jmethodID test = (*env)->GetMethodID(env, testclass, "test", "(I)V");
    (*env)->CallVoidMethod(env, testobject, test, 1);

    //调用静态方法
    jmethodID testStatic = (*env)->GetStaticMethodID(env, testclass, "testStatic", "(Ljava/lang/String;)V");
    //创建字符串,不能在CallStaticVoidMethod中直接使用"hello world!",会报错的
    jstring str = (*env)->NewStringUTF(env, "hello world!");
    //调用静态方法使用的是jclass,而不是jobject
    (*env)->CallStaticVoidMethod(env, testclass, testStatic, str);

    //实例化InnerClass子类
    jclass innerclass = (*env)->FindClass(env, "com/lb6905/jnidemo/TestClass$InnerClass");
    jmethodID innercontruct = (*env)->GetMethodID(env, innerclass, "<init>", "()V");
    jobject innerobject = (*env)->NewObject(env, innerclass, innercontruct);

    //调用子类的成员方法
    jmethodID setInt = (*env)->GetMethodID(env, innerclass, "setInt", "(I)V");
    (*env)->CallVoidMethod(env, innerobject, setInt, 2);
}

此处需要注意 在C中: (*env)->方法名(env,参数列表) 在C++中: env->方法名(参数列表)

三.NewStringUTF函数请慎用

经常在使用 jstring (NewStringUTF)(JNIEnv, const char*);函数的过程中遇到如下错误 (1) .JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8: illegal start byte 0x80

JNI调用newStringUTF时遇到不认识的字符串就直接出错退出~~,网上原因是dalvik/vm/CheckJni.c里面的checkUtfString函数检查通不过.

遇到问题时请排查以下几种问题

  • 1. 检查包名反射引用是否正确

  • 2. 检查方法签名,参数签名是否正确

  • 3. 将char 定义更换为const char

(2). JNI DETECTED ERROR IN APPLICATION: use of deleted weak global reference 0xb305f57b

string 对象和jstring对象从java传值或者新new 的字符串不能够直接引用,必须经过NewStringUTF进行转换,才可

四.推荐几种修改过的类型转换

(1)jstring转换为char*

//jstring转为char* NewStringUTF所需要的内容位char*格式
const char *jstringTochar(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = (*env)->FindClass(env, "java/lang/String");
    jstring strencode = (*env)->NewStringUTF(env, "utf-8");
    jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid, strencode);
    jsize alen = (*env)->GetArrayLength(env, barr);
    jbyte *ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char *) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    (*env)->ReleaseByteArrayElements(env, barr, ba, 0);
    return rtn;
}

(2)char * 转为jstring

jstring chartoJstring(JNIEnv *env, const char *pat) {
    jclass strClass = (*env)->FindClass(env, "java/lang/String");
    jmethodID ctorID = (*env)->GetMethodID(env, strClass, "<init>", "([BLjava/lang/String;)V");
    jbyteArray bytes = (*env)->NewByteArray(env, (jsize) strlen(pat));
    (*env)->SetByteArrayRegion(env, bytes, 0, (jsize) strlen(pat), (jbyte *) pat);
    jstring encoding = (*env)->NewStringUTF(env, "utf-8");
    return (jstring) (*env)->NewObject(env, strClass, ctorID, bytes, encoding);
}

(3)jstring 转为jbyte*

// java中的jstring, 转化为c的一个字符数组
jbyte *Jstring2Jbyte(JNIEnv *env, jstring jstr) {
    jclass clsstring = (*env)->FindClass(env, "java/lang/String");
    jstring strencode = (*env)->NewStringUTF(env, "UTF-8");
    jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");

    jbyte *barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid,
                                                        strencode); // String .getByte("UTF-8");
    return barr;
}

以上格式转换大家看到基本上都c反射java进行的格式转换,所以反射这块还是要多多了解的。

五. 内存管理

1、什么需要释放?

什么需要什么呢 ? JNI 基本数据类型是不需要释放的 , 如 jint , jlong , jchar 等等 。 我们需要释放是引用数据类型,当然也包括数组家族。如:jstring ,jobject ,jobjectArray,jintArray 等等。 当然,大家可能经常忽略掉的是 jclass ,jmethodID , 这些也是需要释放的哦

2、如何去释放?

1) 释放String

jstring jstr = NULL;
char* cstr = NULL;
//调用方法
jstr = (*jniEnv)->CallObjectMethod(jniEnv, mPerson, getName);
cstr = (char*) (*jniEnv)->GetStringUTFChars(jniEnv,jstr, 0);
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "getName  ---- >  %s",cstr );
//释放资源
(*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, cstr);
(*jniEnv)->DeleteLocalRef(jniEnv, jstr);

2) 释放 类 、对象、方法

(*jniEnv)->DeleteLocalRef(jniEnv, XXX);//“XXX” 代表 引用对象

3) 释放 数组家族

jobjectArray arrays = NULL;
jclass jclsStr = NULL;
jclsStr = (*jniEnv)->FindClass(jniEnv, "java/lang/String");
arrays = (*jniEnv)->NewObjectArray(jniEnv, len, jclsStr, 0);
(*jniEnv)->DeleteLocalRef(jniEnv, jclsStr);  //释放String类
(*jniEnv)->DeleteLocalRef(jniEnv, arrays); //释放jobjectArray数组

native method 调用 DeleteLocalRef() 释放某个 JNI Local Reference 时,首先通过指针 p 定位相应的 Local Reference 在 Local Ref 表中的位置,然后从 Local Ref 表中删除该 Local Reference,也就取消了对相应 Java 对象的引用(Ref count 减 1)。

end:以上就是开发中对jni的一些总结,有错误的地方请及时支出。本文仅供参考学习,转载请注明出处。谢谢

关注公众号:喘口仙氣 实时关注更新状态

JNI内存管理请参考: JNI 编程实现了 native code 和 Java 程序的交互,因此 JNI 代码编程既遵循 native code 编程语言的编程规则,同时也遵守 JNI 编程的文档规范。在内存管理方面,native code 编程语言本身的内存管理机制依然要遵循,同时也要考虑 JNI 编程的内存管理。

https://www.ibm.com/developerworks/cn/java/j-lo-jnileak/
https://androidsummary.gitbook.io/androidsummary/
http://blog.csdn.net/xyang81/article/details/41777471
http://www.cnblogs.com/skywang12345/archive/2013/05/23/3092491.html
http://www.cnblogs.com/xitang/p/4174619.html
https://www.pocketdigi.com/20141129/1398.html
http://blog.csdn.net/u012702547/article/details/48222859