Android-JNI-03JNI深入

更新时间:2023-05-26 15:26:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

JNI深入

宝贵建议请发送至:-编程,始于黑马

Android课程同步笔记

Beta0.01版

By阳哥

JNI深入

Android-JNI-03JNI深入

1.JNI开发中常见错误(★★)

1.1动态库名称写错,或者不存在

static{

System.loadLibrary("hell0");

}

当我们在写上面代码的时候如果不小心将hello写成了hell0。或者libhello.so动态库不存在,那么系统启动时会

报如下异常。

1.2Android.mk配置文件写错

如果改配置文件中的某个参数名称被写错,那么我们在调用ndk-build.cmd

命令的时候很可能报如下异常。出现这类异常,就需要我们检查我们的Android.mk文件,看是否写错。

JNI深入

1.3目标文件名画蛇添足导致的错误

如果我们将Android.mk中的目标文件名:LOCAL_MODULE:

=hello写成了LOCAL_MODULE:=hello.so,那么当我们使用ndk进行编译的时候会出现如下错误。

上面的异常告诉我们,LOCAL_MODULE不能有文件拓展名。

1.4源文件名写错

如果我们将Android.mk中的源文件名:LOCAL_SRC_FILES

:=hello.c

写成了LOCAL_SRC_FILES:=helo.c(少些了一个单词),那么当使用ndk进行编译的时候会出现如下错误:

1.5平台使用错误

如果我们编译的时候用的是arm平台,但是将目标文件运行在了x86平台上,那么会产生如下错误。

JNI深入

如果想让我们编译的动态库既支持arm平台又支持x86平台,那么我们可以在工程中的jni目录下添加Application.mk文件。关于Application.mk的配置在本人的上一个文档中有说明,这里就不再介绍。

1.6C语言中被Java调用的方法名写错比如C语言中的方法jstringJava_com_itheima_jnihello_MainActivity_helloC

把helloC写成了heloC,那么将会报如下错误。

2.自动生成JNI头文件(★★)

对于一些特殊的Java方法名,我们很难写出其对应的JNI方法名,比如:publicnative

Stringa_b__c_d();这时候我们可以通过我们的JDK工具自动生成头文件。该工具位于JDK中,如果我们给电脑配置JAVA_HOME则可以直接在命令行中使用,使用法则很简单。如下图所示:

JNI深入

下面演示如何使用javah工具生成我们MainActivity.java中native方法的头文件。

将命令行控制台切换到我们MainActivity.class(注意:是.class不是.java)所在目录。本人的目录如下:执行javah命令:javahcom.itheima.jnihello.MainActivity,发现报了如下错误:

:出现上面的错误时因为我用的是jdk7版本,jdk7在编译MainActivity.class类的时候会查找其所有的父类,但是在当前命令行中是不可能有MainActivity类的父类路径的。有两种办法可以解决上述问题:

1)改成jdk6版本

2)将native方法定义在另外一个独立的类中

本人在这里采用第二种方法来解决该问题,因此我创建一个类,将所有的native方法都定义在该类中。JNIMethod.java代码清单如下:

packagecom.itheima.jnihello.jni;

publicclassJNIMethod{

publicnativeStringa_b__c_d();

}

再次执行javah命令:javahcom.itheima.jnihello.jni.JNIMethod

JNI深入

这次发现没有报错,生成的头文件在如下位置:

打开生成的头文件,将生成的方法签名拷贝出来添加到我们的hello.c源文件中即可。

3.JNI的值传递(★★★)

本章我们通过一个案例来介绍几种常见的JNI值传递场景。

JNI深入

创建一个新的Android工程《JNI值传递》。

在工程中创建com.itheima.jnipassdata.DataProvider类。在该类中定义三个native方法。代码清单如下:publicclassDataProvider{

/**

*模拟用C语言计算一些负责算法

*@paramx

*@paramy

*@return

*/

publicnativeintadd(intx,inty);

/**

*模拟用C语言加密字符串

*@paramstr

*@return

*/

publicnativeStringencideString(Stringstr);

/**

*模拟用C语言进行一些图像算法

*@paramcolorArray

*@return

*/

publicnativeint[]changeColor(int[]colorArray);

}

使用javah工具生成头文件

生成的头文件在相对于命令行的当前目录下。

在工程中创建jni目录,在改jni目录中创建Hello.c文件,将上一步生成的方法拷贝到Hello.c文件中,并实现里面的方法。Hello.c代码清单如下。

JNI深入

#include<stdio.h>

#include<malloc.h>

#include"jni.h"

//把java的string转化成c的字符串

char*Jstring2CStr(JNIEnv*env,jstringjstr)

{

char*rtn=NULL;

jclassclsstring=(*env)->FindClass(env,"java/lang/String");

//String

jstringstrencode=(*env)->NewStringUTF(env,"GB2312");//"gb2312"jmethodIDmid=(*env)->GetMethodID(env,clsstring,"getBytes",

"(Ljava/lang/String;)[B");//getBytes(Str);

jbyteArraybarr=

(jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode);//

String.getByte("GB2312");

jsizealen=(*env)->GetArrayLength(env,barr);

jbyte*ba=(*env)->GetByteArrayElements(env,barr,JNI_FALSE);

if(alen>0)

{

rtn=(char*)malloc(alen+1);//"\0"

memcpy(rtn,ba,alen);

rtn[alen]=0;

}

(*env)->ReleaseByteArrayElements(env,barr,ba,0);//释放内存空间

returnrtn;

}

jintJava_com_ithiema_jnipassdata_DataProvider_add

(JNIEnv*env,jobjectobj,jintx,jinty){

returnx+y;

};

jstringJava_com_ithiema_jnipassdata_DataProvider_encideString

(JNIEnv*env,jobjectobj,jstringjstr){

char*str=Jstring2CStr(env,jstr);

char*hello="hello";

strcat(str,hello);

//下面的两种写法是一样的效果

//return(*(*env)).NewStringUTF(env,str);

return(*env)->NewStringUTF(env,str);

};

jintArrayJava_com_ithiema_jnipassdata_DataProvider_changeColor

JNI深入

(JNIEnv*env,jobjectobj,jintArray

jarray){

intsize=(*env)->GetArrayLength(env,jarray);

int*arr=(*env)->GetIntArrayElements(env,jarray,0);

inti;

for(i=0;i<size;i++){

arr[i]+=10;

}

returnjarray;

使用NDK工具将上面的C代码编译成动态库文件,首先得在工程的jni目录下添加Android.mk和Application.mk文件

在MainActivity类中调用C语言,MainActivity.java代码清单如下。

JNI深入

布局文件比较简单,就不再给出。运行上面代码,效果图如下:

3.1让C语言输出Log日志

让我们的C语言也能在LogCat中输出一些信息是一个很简单但也很常见的实际需求。为了方便演示我们直接在本章节中创建的工程中演示如何让C语言打印LogCat日志。

在Android.mk中添加如下属性:

LOCAL_LDLIBS:=-llog

在C源码头部引入如下头文件和宏定义

#include<android/log.h>

#defineLOG_TAG"System.out"

#defineLOGD(...)__android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)#defineLOGI(...)__android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)

在C代码中可以将LOGD()或者LOGI()函数当做C语言中的printf()函数使用。

JNI深入

这里我将add方法添加添加了一条日志输入。

jintJava_com_ithiema_jnipassdata_DataProvider_add

(JNIEnv*env,jobjectobj,jintx,

jinty){

LOGI("x+y=%d",x+y);

returnx+y;

};

重新编译hello.so动态库,既然我们修改了Android.mk和C源码文件,那么一定要记得重新使用NDK编译C语言为动态库,否则日志不会输入而且也不报异常。再次部署程序到模拟器,调用add方法,发现在LogCat中成功输出了如下信息:

4.案例-调用美图秀秀的动态库(★★)

在美图秀秀1.0版本的时候程序员对其代码并没有加密以及反反编译等处理,因此我们可以将其apk反编译出来。里面关于图形的核心算法都是通过so库来实现的,我们可以拿过来直接使用。

声明:本文档中使用的美图秀秀只用于学习交流Android技术,禁止用于其他目的!

美图秀秀1.0版本下载地址:

将下载好的mtxx.apk进行反编译,将反编译好的资源作为原料备用。关于如何反编译美图秀秀请见本文档最后一章。

:解压好的目录打开lib包,发现只有armeabi一个文件夹,说明该so文件只能在arm架构的CPU上运行。创建一个新的Android工程《黑马美图秀秀》。将反编译好的libmtimage-jni.so拷贝到工程的libs->armeabi(该目录需要手动创建)目录下。通过反编译的jar包发现美图秀秀的jni方法都定义在JNI.java类中,我们用jd-gui工具将反编译的jar包打开,找到JNI.java,然后拷贝其中的native方法到我们的工程中。本人的工程目录结构如下所示:

JNI深入

:我们使用了美图秀秀的JNI源码,那么我们的JNI.java名字以及其所在的包名必须严格跟原美图秀秀保持一致,不然程序从so动态库中是找不到目标方法的。原因很简单,因为C中方法名是根据JNI.java中的方法全限定名生成的。

编写activity_main.xml布局文件,布局文件清单如下:

<LinearLayoutxmlns:android="/apk/res/android"

xmlns:tools="/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical"

tools:context=".MainActivity">

<ImageView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:id="@+id/iv"

/>

<Button

android:layout_gravity="right"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="lomo"

android:onClick="click"

/>

</LinearLayout>

在工程的res->drawable-hdpi目录下放入一张图片。

JNI深入

编写MainActivity.java代码,在改类中实现核心方法

publicclassMainActivityextendsActivity{

privateImageViewiv;

privateBitmapbitmap;

static{

System.loadLibrary("mtimage-jni");

}

@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(yout.activity_main);

bitmap=BitmapFactory.decodeResource(getResources(),

R.drawable.beautiful);

iv=(ImageView)findViewById(R.id.iv);

iv.setImageBitmap(bitmap);

}

publicvoidclick(Viewview){

JNIjni=newJNI();

//获取Bitmap的宽和高

intwidth=bitmap.getWidth();

intheight=bitmap.getHeight();

//创建一个整型数组,存放每个像素点

int[]pixels=newint[width*height];

/**

*获取bitmap的像素数组

*第一个参数是int[]

*第二个参数是第获取一个像素的偏移量,这里是0,也就是从第一个像素开始获取

*第三个参数每行获取多少个像素,当然bitmap的宽度就是每行的像素个数

*第四个参数、第五个参数分别是开始获取像素的x、y坐标,这个坐标是相对于bitmap本身来说的,因此是0,0

*第六、七个参数分别是每行、每列获取像素的个数

*/

bitmap.getPixels(pixels,0,width,0,0,width,height);

//调用美图秀秀API

JNI深入

jni.StyleLomoB(pixels,

width,height);

//重新根据pixels数组创建一个Bitmap对象

BitmapnewBitmap=Bitmap.createBitmap(pixels,width,height,

bitmap.getConfig());

iv.setImageBitmap(newBitmap);

}

}

运行上面打代码,效果如下:

1)处理前

2)处理后

JNI深入

5.C语言调用Java代码(★★★)

之前我们学习的JNI都是Java代码调用C代码,本章节中演示C代码如何调用Java

代码。

创建一个新的Android工程《C语言调用Java》,在工程中创建jni目录,在改目录下放置Android.mk和jni.h

文件(从老工程中拷贝)。工程目录结构如下:

编写DataProvider.java文件,在该文件中编写native方法,DataProvider.java代码清单如下:

publicclassDataProvider{

//C代码调用该方法发送短信

publicvoidmethodInJava(){

System.out.println("我是Java中的方法,我被调用了");

SmsManagermanager=SmsManager.getDefault();

manager.sendTextMessage("5556",null,"helloI'mfromJava",null,null);}

//C代码调用该方法传递一个字符串,获取一个字符串

publicStringmethodInJava2(Stringstr){

return"hello:"+str;

JNI深入

}

//C代码调用一个空参返回值为void的实例方法

publicvoidmethodInJava3(){

System.out.println("我是Java中的方法3,我被调用了");

}

//C语言调用静态方法

publicstaticvoidmethodInJava4(){

System.out.println("我是静态方法。被调用了。");

}

//定义四个native方法,这些方法在MainActivity中被调用,这些方法在C代码中回调上面的Java代码

publicnativevoidcallCMethod();

publicnativeStringcallCMethod2(Stringstr);

publicnativevoidcallCMethod3();

publicnativevoidcallCMethod4();

}

在jni中创建calljava.c文件,在该文件中实现调用java的方法。代码清单如下。

#include<jni.h>

#include<stdio.h>

voidJava_com_itheima_callJava_DataProvider_callCMethod(JNIEnv*env,jobjectobj){

//类似java的反射,获取java对象

jclassclazz=(*env)->FindClass(env,"com/itheima/callJava/DataProvider");//根据方法签名获取目标方法

jmethodIDmethodID=(*env)->GetMethodID(env,clazz,"methodInJava","()V");//调用目标方法

(*env)->CallVoidMethod(env,obj,methodID);

}

jstringJava_com_itheima_callJava_DataProvider_callCMethod2(JNIEnv*env,jobjectobj,jstringstr){

jclassclazz=(*env)->FindClass(env,"com/itheima/callJava/DataProvider");jmethodIDmethodID=

(*env)->GetMethodID(env,clazz,"methodInJava2","(Ljava/lang/String;)Ljava/lang/String;");

jstringjstr=(*env)->CallObjectMethod(env,obj,methodID,str);

returnjstr;

}

voidJava_com_itheima_callJava_MainActivity_callCMethod3(JNIEnv*env,jobjectobj){

jclassclazz=(*env)->FindClass(env,"com/itheima/callJava/DataProvider");

JNI深入

jmethodIDmethodID

=(*env)->GetMethodID(env,clazz,"methodInJava3","()V");jobjecto=(*env)->AllocObject(env,clazz);

(*env)->CallVoidMethod(env,o,methodID);

}

voidJava_com_itheima_callJava_DataProvider_callCMethod4(JNIEnv*env,jobjectobj){

jclassclazz=(*env)->FindClass(env,"com/itheima/callJava/DataProvider");jmethodIDmethodID=

(*env)->GetStaticMethodID(env,clazz,"methodInJava4","()V");

(*env)->CallStaticVoidMethod(env,clazz,methodID);

}

使用NDK工具,将calljava.c编译成动态库文件。(NDK的使用在上一篇文档中有详细的介绍,这里就不再说明)

在MainActivity.java中调用C语言,代码清单如下:

布局文件比较简单这里就不再给出。

JNI深入

6.用C++实现JNI(★★)

C++语言是面向对象的编程语言,源于C语言,部分语法通用。本章节中主要介绍如何使用C++完成简单的JNI

开发。

创建一个新Android工程《cpp实现jni》,创建jni包,在该包下创建JNI.java文件,在该类中写naive方法。因为我们是C++项目,因此需要给当前工程添加nativeSupport。右键点击项目,在弹出的对话框中选择AndroidTools,然后选择Addnative

Support,弹出如下对话框:

这个名字是C++源文件名,因为没有实际的业务意义一次我们这里就随便输入一个了。

在工程中创建com.itheima.cppjni.jni包,在该包下创建JNI.java,JNI.java代码清单如下:

publicclassJNI{

publicnativevoidhelloFormCPP();

}

JNI深入

使用javah工具生成头文件,并将生成的头文件拷贝到工程根目录下的jni目录中。

为了让编译器提示C++语言,我们需要给工程添加C++库。

右键点击工程,选择

Properties,在弹出的对话框中选择C/C++General->PathsAndSymbols,弹出如下对话框:

点击图中红色框中的Add按钮,在弹出的对话框(如下图)中点击File

system,然后选择ndk安装目录,选择如下目录:D:\software\ndkr9\android-ndk-r9b\platforms\android-19\arch-arm\usr\include

JNI深入

然后点击OK。

:上面目录中红色的是本人的ndk所在目录,大家找到自己的ndk所在目录即可。然后随便选择一个platform即可,但是推荐大家选择一个高版本的平台。

在gaga.cpp中引入上面生成的头文件。同时编辑gaga.cpp,代码清单如下:

#include<jni.h>

#include"com_itheima_cppjni_jni_JNI.h"

JNIEXPORTjstringJNICALLJava_com_itheima_cppjni_jni_JNI_helloFromCPP(JNIEnv*env,jobjectobj){

returnenv->NewStringUTF("gagafromcpp");

};

编写MainActivity.java,在该方法中加载动态库

publicclassMainActivityextendsActivity{

static{

System.loadLibrary("gaga");

}

@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(yout.activity_main);

}

publicvoidclick(Viewview){

JNIjni=newJNI();

StringhelloFromCPP=jni.helloFromCPP();

Toast.makeText(this,helloFromCPP,1).show();

}

}

将上面工程部署到一个arm架构的模拟器上。在部署的时候观察控制台,发现控制台输出如下信息

****BuildofconfigurationDefaultforprojectcpp代码jni开发****

D:\software\ndkr9\android-ndk-r9b\ndk-build.cmdall

C:\Users\thinkpad\workspace\cpp代码jni开发>remThisisaWindowscmd.exescriptusedtoinvoketheNDK-specificGNUMakeexecutable

JNI深入

C:\Users\thinkpad\workspace\cpp代码jni开发>call

"D:\software\ndkr9\android-ndk-r9b\find-win-host.cmd"NDK_WIN_HOST

AndroidNDK:WARNING:

APP_PLATFORMandroid-19islargerthan

android:minSdkVersion8in./AndroidManifest.xml

[armeabi]Compile++thumb:gaga<=gaga.cpp

[armeabi]StaticLibrary:libstdc++.a

[armeabi]SharedLibrary:libgaga.so

[armeabi]Install:libgaga.so=>libs/armeabi/libgaga.so

****BuildFinished****

:通过控制台,我们发现当我们的工程添加本地支持以后,当我们在部署的时候eclipse会自动的完成动态库的编译工作。而且我们还发现在生成动态之前先生成了libstdc++.a静态库然后才生成了动态库。

上面代码运行效果如下图所示:

7.案例-锅炉压力监测(★★★)

需求:硬件设备可以监测锅炉的压力,监测代码逻辑是用C语言编写。客户端用java代码每一秒调用一次C语言,

本文来源:https://www.bwwdw.com/article/i2o4.html

Top