Android JNI入门

来源:互联网 发布:设计顺序表的逆置算法 编辑:程序博客网 时间:2024/05/16 17:45

前言

先啰嗦一段,从学习Android以来一直会看到这个JNI,偶尔也看到要写c/c++的代码,其实从心里就是有些排斥的,毕竟我学的是Java,我学习一个JNI我还得学会c++,c其实是学了一遍了,但是长期不用基本也就忘了,虽然基本的都是看得懂的,但是编码并不是看得懂就行的,要自己能写,所以其实打心底是排斥JNI的.但是学习Android的时间越长,我发现JNI是支撑Android运行的一大模块,比如我们常见的那些播放音频的功能其实底层就是使用了JNI,这样子就让Java代码可以调用底层的c代码,从而可以操纵硬件的目的,所以其实JNI我们可以理解为Java和(c/c++)之间的桥梁

举例一段源码使用JNI的例子

MediaPlayer m = new MediaPlayer();m.start();
/*** Starts or resumes playback. If playback had previously been paused,* playback will continue from where it was paused. If playback had* been stopped, or never started before, playback will start at the* beginning.** @throws IllegalStateException if it is called in an invalid state*/public  void start() throws IllegalStateException {    stayAwake(true);    _start();}private native void _start() throws IllegalStateException;

这是播放音频控件的开始播放方法的有关源码,我们可以看到,在start()方法中,调用了一个_start()的方法,这个方法是用native关键字标识的,这就是我们以前经常听到的本地方法,意思就是说这个方法的具体实现是通过c/c++代码实现的,这就是JNI的简单案例

ndk环境的搭建,这里亲们就去谷歌官网去下载就行了,下载完毕解压,然后在eclipse里面关联一下即可


下面让我带大家入门

我带大家做一个简单的加法来学习JNI,首先我们得声明一个方法

private native int add(int i,int j);

方法类似于上面的_start()方法是没有方法体的,类似于接口中的方法声明对不对,没有方法体,然后我们在activity的oncreate()方法中调用这个方法来实现

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);int result = add(3,5);System.out.println("result == " + result);}

调用的时候就和普通方法一样调用就可以了,这样子就可以了?

很明显这样子不够,因为方法的实现我们还没做呢!我们说实现是需要c/c++来实现的,这里设计到书写c/c++代码了.

编写c/c++代码步骤

1.在项目根目录建立一个文件夹jni,里面用来书写c/c++代码


2.在jni文件夹下新建一个c/c++源码文件


3.里面自然需要实现我们之前定义个那个方法add了

int add(int i, int j){return i + j;}
c中写一个add方法就是上面这样子书写的,但是使用JNI的时候需要加上JNI的一些规范
首先就是方法名字,规范如下:

Java_包名_类名_方法名

所以代码就需要改成这样子

int Java_com_example_day01_MainActivity_add(int i, int j){return i + j;}
都是用下划线隔开的,请注意,然后就是参数列表,这里有两个参数是固定的,必须要写,如下:

int Java_com_example_day01_MainActivity_add(JNIEnv* e, jobject thiz, int i,int j) {return i + j;}

可以看到这里多了两个参数,一个是JNIEnv*和jobject

JNIEnv*:这里做一下解释,我们的java代码是运行在虚拟机里面的,所以利用JNI在实现功能的同时,需要用到虚拟机环境的指针,也就是需要环境的支持,并且这是一个指针,指向运行环境

jobject是调用方法的时候的对象,这里也就是我们的MainActivity对象

做完了这一切我们就实现了add方法,当然了别忘了添加一些必要的头文件,完整的c代码是

#include <stdio.h>#include <stdlib.h>#include <jni.h>jint Java_com_example_day01_MainActivity_add(JNIEnv* e, jobject thiz, int i,int j) {return i + j;}
这里有一个jni.h头文件是使用jni功能所必需的,还有这里的返回值jint,其实就是谷歌为了让代码更有可读性预定义的一些名字,这里我点进去


可以看到就是一个int,这里也做一个解释,为啥要这样子,直接适应int不是挺好的.

其实这里你用int和jint都是没有什么区别的,但是我们想一下,在c中是没有对象的概念的,加入你java里面传入的是一个数组,或者传入一个对象,在c中都是一个指针而已,指向了你传入的对象,在c中都是void*表示,这时候你写的代码过几天去看,你还知道当初的void*这里的参数,到底是数组呢还是对象,还是字符串呢?你不知道了,你只能去调用的地方去瞧瞧,很明显这里降低了代码的可阅读性,所以谷歌工程师为我们预定义了很多的关键字来表示不同的对象

typedef void*           jobject;typedef jobject         jclass;typedef jobject         jstring;typedef jobject         jarray;typedef jarray          jobjectArray;typedef jarray          jbooleanArray;typedef jarray          jbyteArray;typedef jarray          jcharArray;typedef jarray          jshortArray;typedef jarray          jintArray;typedef jarray          jlongArray;typedef jarray          jfloatArray;typedef jarray          jdoubleArray;typedef jobject         jthrowable;typedef jobject         jweak;

可以看到这些的本质的是void*,但是我们可以用jobject和jarray这些来说明参数的含义,这样子就可阅读性很强

最后我们写的c/c++代码最后都会被打包成.so文件,所以我们使用的时候需要在Avtivity里面先加载类库

public class MainActivity extends Activity {static {System.loadLibrary("hello");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);int result = add(3,5);System.out.println("result == " + result);}private native int add(int i,int j);}

我们的.so文件要打包成功还需要有一个android.mk,直接从ndk自带的例子里面复制过来就行了

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE    := helloLOCAL_SRC_FILES := hello.cinclude $(BUILD_SHARED_LIBRARY)
里面就是这几句话,只要修改hello所在的地方就可以了,你可以把上面的两个hello改成其他的任意名字

利用ndk里面的ndk-build.cmd进行打包我们的c代码

当然了用eclipse关联了ndk之后,直接运行项目就可以了,打包的工作交给eclipse


4 0
原创粉丝点击