0%

JNI 编程简介

JNI,Java Native Interface,是 native code 的编程接口。JNI 使 Java 代码程序可以与 native code 交互——在 Java 程序中调用 native code;在 native code 中嵌入 Java 虚拟机调用 Java 的代码。
JNI 编程在软件开发中运用广泛,其优势可以归结为以下几点:

  1. 利用 native code 的平台相关性,在平台相关的编程中彰显优势。
  2. 对 native code 的代码重用。
  3. native code 底层操作,更加高效。

然而任何事物都具有两面性,JNI 编程也同样如此。程序员在使用 JNI 时应当认识到 JNI 编程中如下的几点弊端,扬长避短,才可以写出更加完善、高性能的代码:

  1. 从 Java 环境到 native code 的上下文切换耗时、低效。
  2. JNI 编程,如果操作不当,可能引起 Java 虚拟机的崩溃。
    3, JNI 编程,如果操作不当,可能引起内存泄漏。
Read more »

编译插桩

编译插桩的应用场景

代码生成 除了 Dagger、ButterKnife 这些常用的注解生成框架,Protocol Buffers、数据库 ORM 框架也都会在编译过程生成代码。
代码监控 除了网络监控和耗电监控,我们可以利用编译插桩技术实现各种各样的性能监控。
代码修改
代码分析
对于代码监控、代码修改以及代码分析这三个场景,一般采用操作字节码的方式。可以操作“.class”的 Java 字节码,也可以操作“.dex”的 Dalvik 字节码,这取决于我们使用的插桩方法。

字节码

avatar

编译插桩的量种方法

AspectJ 和 ASM 框架的输入和输出都是 Class 文件,它们是我们最常用的 Java 字节码处理框架。

AspectJ

AspectJ是 Java 中流行的 AOP(aspect-oriented programming)编程扩展框架。
基于AspectJ的扩展工具:
沪江
hugo

AspectJ的优势

成熟稳定: AspectJ 作为从 2001 年发展至今的框架,它已经很成熟,一般不用考虑插入的字节码正确性的问题。
**使用简单:**它可以在方法(包括构造方法)被调用的位置、在方法体(包括构造方法)的内部、在读写变量的位置、在静态代码块内部、在异常处理的位置等前后,插入自定义的代码,或者直接将原位置的代码替换为自定义的代码。

AspectJ的缺点
  1. 切入点固定:AspectJ 只能在一些固定的切入点来进行操作,如果想要进行更细致的操作则无法完成,它不能针对一些特定规则的字节码序列做操作。
  2. 正则表达式:AspectJ 的匹配规则是类似正则表达式的规则,比如匹配 Activity 生命周期的 onXXX 方法,如果有自定义的其他以 on 开头的方法也会匹配到。
  3. 性能较低:AspectJ 在实现时会包装自己的一些类,逻辑比较复杂,不仅生成的字节码比较大,而且对原函数的性能也会有所影响。
Read more »

首先JNI中的数据类型有基本数据类型和引用类型:
基本数据类型为:

Java native Size
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
lon g jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void void
Read more »

1. JNI中获取JavaVM和 JNIEnv

JavaVM是虚拟机在JNI中的表示,一个虚拟机中只有一个JavaVM对象,这个对象是线程共享的。
JNIEnv类型是一个指向全部JNI方法的指针。该指针只在创建它的线程有效,不能跨线程传递。多线程无法共享。
使用JNI_OnLoad方法,这个方法需要自己实现。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
jint JNI_OnLoad(JavaVM *vm,void *reserved){
LOGE("JNI_Onload in 1");
JNIEnv *env = NULL;
int result = -1;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
LOGE("JNI_Onload in 2");
return JNI_ERR;
}
result = register_method(env);//动态注册本地方法
savedVm = vm;//保存JavaVM ,供其他地方使用。
LOGE("JNI_Onload in 3 , result is %d", result);
return JNI_VERSION_1_6;
}

这个方法是在加载相应的.so包的时候,系统主动调用的,即:

1
2
3
static {
System.loadLibrary("native-lib");
}

这句代码调用时,JNI_OnLoad方法就会被调用。
每一个.so文件中,只能包含一个JNI_OnLoad方法,也就是在当前.so包含的文件中,只能有一个.c文件中实现这个JNI_OnLoad方法。
如上面的做法,我们可以在头文件中声明extern JavaVM *savedVm;,然后其他文件include这个头文件后,就可以使用这个JavaVM。

Read more »

在JNI代码中获取到了我们想要的值,需要返回到Java层,简单的情况下可以使用return某个值来实现。但是复杂情况下,我们需要不断地获取JNI中返回的值,我们就可以在JNI中主动去找Java中的方法,然后调用,并把JNI中的值作为Java方法的参数传入。如下:

//native方法将调用Java方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JNIEXPORT void JNICALL nativeCallJava(JNIEnv *env, jobject thiz){
object_global = (jobject)env->NewGlobalRef(thiz);
JNIEnv *env = NULL;
//先获取JNIEnv
if (savedVm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
env = NULL;
}
//再通过FindClass来加载当前需要的类
jclass clazz = env->FindClass("com/XXXX/jni/MainActivity");
//获取当前类中的静态方法
jmethodID method1 = env->GetStaticMethodID(clazz,"nativeCall","(Ljava/lang/String;)V");
jstring result = env->NewStringUTF("aaaaaaaaa");//字符串
env->CallStaticVoidMethod (clazz, method1,result);//调用静态方法
//获取普通成员方法
jmethodID method2 = env->GetMethodID(clazz,"nativeCall_nonStatic","(Ljava/lang/String;)V");
//调用成员方法,这个时候第一个参数应该是对象
env->CallVoidMethod (thiz, method2,result);
}

以上是借用JNI主动传进来的jobject ,它就代表对应的Java类的对象。然而有时候,我们没有这样的对象引用作为参数,就需要找到Java的对应的构造器获取Java类的一个对象。

Read more »

Binder架构

binder在framework层,采用JNI技术来调用native(C/C++)层的binder架构,从而为上层应用程序提供服务。 在native层中,binder是C/S架构,分为Bn端(Server)和Bp端(Client)。对于java层在命名与架构上非常相近,同样实现了一套IPC通信架构。

Read more »

概述

PackageManagerService(简称PKMS),是Android系统中核心服务之一,管理着所有跟package相关的工作,常见的比如安装、卸载应用。 PKMS服务也是通过binder进行通信,IPackageManager.aidl由工具转换后自动生成binder的服务端IPackageManager.Stub和客户端IPackageManager.Stub.Proxy,具体关系如图:

Read more »