JNI开发最佳实践

来源:互联网 发布:java web前台框架 编辑:程序博客网 时间:2024/06/11 20:14

今天逛论坛时意外发现一个很给力的小视频,嘿嘿嘿。传送门 。这是一个美国的哥们录制的一个JNI开发入门的小案例。看了一遍,还不错,把整个流程记录一下。然后补充一些知识。恩。Let’s begin.

转载请注明出处:
CSDN:天道酬勤
本文地址:http://blog.csdn.net/Hello_Chillax/article/details/50920766

软件开发,环境很重要,说一下我的编程环境:

Operation System:Mac OS 10.10.4
JDK:jdk1.8
IDE:Android Studio:2.1 Preview1


一. 举个小栗子。


1. 首先,新建工程。工程名随意。

贴一下MainActivity的代码。

public class MainAty extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        TextView tv= (TextView) findViewById(R.id.tv);        tv.setText(getStringFromNative());    }    static {        System.loadLibrary("first-jni");    }    public native String getStringFromNative();}

现在我们唯一要做的事情就是实现这个native方法。为了达到这个目的,我们需要分以下几步去做。


2. 编译MainActivity文件,生成对应的字节码文件。

AndroidStudio —> build —> make project (这一步的快捷键是command + F9)

然后我们找到这个字节码文件。如果你对编译有一些了解,应该可以猜到字节码文件在哪里。这里给出路径。

《project name》—> 《module name》—> build —>intermediates —> classes —> debug —> 《包名》—>MainActivity.class


3. 使用javah命令生成对应的.h头文件。

上面我们得到了字节码文件的路径,接下来我们要生成对应的头文件。

打开AndroidStudio 的terminal终端,然后执行以下命令:

cd app/src/main

然后执行下面一段命令,有点长,不要急,一会儿我会解释。

javah -d jni -classpath /Users/MAC/Library/Android/sdk/platforms/android-23/android.jar:../../build/intermediates/classes/debug com.mac.jjj.MainAty

其中“/Users/MAC/Library/Android/sdk/platforms/android-23/android.jar”这一段是android.jar的路径,因为涉及到了Activity,所以要加上Activity的源码,不然会报错。

然后“com.mac.jjj”是我的包名。

另外要注意个小细节:
在android.jar 的路径和包名之间,有一个英文的“:”英文冒号,如果你的系统是Windows,记得换成“;”英文分号;如果你的系统是Linux或者Mac,则用英文的“:”英文冒号。(不同的系统连接符不一样,记住就行)

执行上面的命令之后,你的工程应该变成了这样.

这里写图片描述

可以看到,我们成功得到了.h文件。打开后可以看到对应的native方法。


4. 实现native方法。

我们把上面得到的.h文件拷贝一份,实现对应的方法。

然后我们的工程多了一个.cpp文件。(我比较喜欢cpp,所以这里用cpp演示,c语言写起来差不多)

这里写图片描述

然后我们大刀阔斧,把main.app中没用的东西先删了,只留下native方法的声明,然后实现这些方法。

/* DO NOT EDIT THIS FILE - it is machine generated */#include "com_mac_jjj_MainAty.h"JNIEXPORT jstring JNICALL Java_com_mac_jjj_MainAty_getStringFromNative  (JNIEnv * env, jobject obj){        return env->NewStringUTF("Hello From Jni ! ");  }

很简单,一行代码。

(这个步骤,在Windows下有个坑,视频里提到了,Windows用户可以去看一看。。。)


4. 配置ndk。

在运行项目之前,我们需要先配置一下ndk。

先去官网下载一下ndk。传送门
需要个梯子,如果你没有梯子,可以去这里下载。AndroidDevTools

下载好后,解压。然后在local.properties文件中配置ndk。

sdk.dir=/Users/MAC/Library/Android/sdk//SDK路径ndk.dir=/Users/MAC/Library/Android/ndk//NDK路径

接下来,我们需要配置生成的.so文件名称。

更改module的build.gradle文件。
在defaultConfig下添加如下内容:

ndk{            moduleName "MyFirstSoLib"//设置.so库的名称            abiFilter "armeabi"//设置.so库的cpu支持版本。默认支持所有版本(如 arm,x86等。。)}

然后把MainActivity中加载的Library的名字改一下:

static {        System.loadLibrary("MyFirstSoLib");    }


4. 运行一下。

如果你上面的步骤都正确的话,那么你就搞定了第一个jni程序。

这里写图片描述


5. 一点小思考。

对于jni和ndk,有很多人傻傻分不清楚。jni提供了一种Java和C/C++交互的接口,而ndk是为了jni开发而开发的一组工具包。

也就是说,想用c/c++开发Android部分功能,可以没有ndk,但是必须使用jni。

使用ndk开发的好处:简单,不用手动编写Android.mk文件和Application.mk文件(通过参数进行配置就行)

其中:Android.mk文件主要用来设置.so库的名称和需要参与编译的源文件地址。Application.mk主要用来配置CPU架构平台的类型。


二. JNI的数据类型和类型签名。

上面我们虽然实现了在Java中调用native方法。但是我们实现的功能也太简单了。打印个字符串没啥用,并且我们还没说怎么在native方法中调用Java方法。


1.首先,我们需要了解一下数据类型。

jni中的数据类型分为两种:基本类型和引用类型。

基本类型对应表:

JNI类型 Java类型 jboolean jboolean jbyte byte jchar char jshort short jint int jlong long jfloat float jdouble double void void

引用类型对应表:

JNI类型 Java类型 jobject[] Object jclass Class jstring String jobjectArray Object[] jbooleanArray boolean[] jbyteArray byte[] jcharArray char[] jshortArray short[] jintArray int[] jlongArray long[] jfloatArray float[] jdoubleArray double[] jthrowable Throwable

2.jni的类型签名。

jni的类型签名表示了一个特定的Java类型,这个类型可以是方法和类,也可以是数据类型。

2.1.类的签名。

类的签名:“L + 包名 + 类名 + ; ”

栗子:java.lang.String的签名为:Ljava/lang/String

2.1.基本数据类型的签名。

基本数据类型的签名采用一个大写字母表示。

基本数据类型的签名对应表:

Java类型 签名 boolean Z long J byte B char C short S int I float F double D void V

(ps:可以看到,基本数据类型的签名是有规律的,除了boolean和long之外,其他的都是首字母大写。boolean特殊是因为字母B已经被byte占了,long特殊是因为字母L已经被类的签名占了。)

对象和数组的签名对应表:

Java类型 签名 char[] [C float[] [F double[] [D long[] [J String[] [Ljava/lang/String; Object[] [Ljava/lang/Object;

(ps:这个也有规律,对于数组来说,签名为:“[ + 类型签名”)

方法的签名对应表:

方法的签名为(参数类型签名)+ 返回值类型签名。

举个例子,规律显而易见。

方法 签名 int fun1() ()I void fun2(int i) (I)V boolean fun3(int a,double b,int[] c) (ID[C)Z boolean fun4(int a,String b,int[] c) (ILjava/lang/String;[I)Z

三. JNI调用Java方法实例。

首先我们要了解一下,从native调用Java方法的流程:

  1. 找到Java中的某个Class(需要类的全路径),
  2. 得到方法的ID(需要1中的Class和方法签名)
  3. 调用Java方法(需要1中的Class和2中的方法ID)

我们还是用上面的那个栗子,修改一下,实现native调用Java。
我们主要实现的功能为:

Java调用native方法,然后native方法反过来调用Java方法,并传递一个字符串,在Java中显示出来。


1. 首先在Java中定义一个静态方法,以供native调用。

  public static void methodCalledByJni(String msg){        tv2.setText(msg);//把结果显示到TextView上    }

2. 编译工程生成.class文件。

步骤和第一个例子一样。。。

2. 生成对应的.h头文件。

步骤和第一个例子一样。。。

3. 在对应的.cpp文件中实现native方法。

/* * Class:     com_mac_jjj_MainAty * Method:    callJavaMethod * Signature: ()V */JNIEXPORT void JNICALL Java_com_mac_jjj_MainAty_callJavaMethod        (JNIEnv * env,  jobject obj){    jclass cls=env->FindClass("com/mac/jjj/MainAty");//找到对应的class    jmethodID id=env->GetStaticMethodID(cls,"methodCalledByJni","(Ljava/lang/String;)V");//找到对应的唯一的方法id    jstring msg=env->NewStringUTF("msg send by callJavaMethod");    env->CallStaticVoidMethod(cls,id,msg);//调用Java方法}

4. 运行一下看看。

这里写图片描述


四. 写在最后。


JNI开发并不能明显提高App的整体性能,相反,这是一个很重量级的操作,我们在使用时应该权衡利弊,不能为了“JNI而JNI”,就像官网说的那样:
The NDK is not appropriate for most novice Android programmers, and has little value for many types of Android apps. It is often not worth the additional complexity it inevitably brings to the development process.

demo:传送门

0 0
原创粉丝点击