AndroidJNI实践(1)-使用.h头文件-静态注册JNI方法
16lz
2021-01-26
一、环境和工具:
Ubuntu14.04
java version "1.7.0_95"
IDE(Android-studio/Eclipse)
android-ndk-r10b
二、JNI 开发的基本步骤
图1 Java程序HelloWorld中开发流程
1.IDE创建一个Android工程(或图1所示Java程序中)声明本地方法。
---此处用Android-studio创建一个HelloJNI工程。
package com.android.hellojni;import android.app.Activity;import android.os.Bundle;import android.widget.TextView;public class HelloJNIActivity extends Activity { static { System.loadLibrary("HelloJNI"); } private TextView tv1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv1 = (TextView) findViewById(R.id.text1); tv1.setText(getStringFromJNI()); } public native String getStringFromJNI(); public native String unimplementedStringFromJNI();}
代码1
代码很简单,主要是调用本地方法返回一个字符串,显示在屏幕上。有两点需要针对说明一下:
static {
System.loadLibrary("HelloJNI");
}
上面这几行代码是用来加载动态库 libHelloJNI.so 。那么是在什么时候加载呢?当第一次使用到这个类的时候就会加载。
public native String getStringFromJNI();
public native String unimplementedStringFromJNI();
使用关键字 native 声明本地方法,表明这两个函数需要通过本地代码 c/c++ 实现。
2.编译java文件为.class文件
(或图1所示使用 javac 编译源文件Java程序HollowWorld.java,生成 HelloWorld.class。)
---这里直接用Android-studio编译工程代码,生成HelloJNIActivity.class文件,目录~/Code/HelloJNI/app/build/intermediates/classes/debug/com/android/hellojni/HelloJNIActivity.class
---如果是Java程序的话可以用Eclipse或者终端用javac编译
???javac编译命令
——(百度:javac命令:编译源文件;java 命令:加载运行类文件;javah生成jni头文件 。用--help来查看使用选项)
3.生成jni的头文件
现在的问题是要怎么样实现 getStringFromJNI 和 unimplementedStringFromJNI 这两个函数呢?这两个函数要怎么命名?直接用这两个名字行不行?要解决这些疑问,就要用到 javah 这个命令。
在你新建成的工程根目录下,键入以下命令:
---Android-studio下:~/Code/HelloJNI$javah -classpath app/build/intermediates/classes/debug/ -d jni com.android.hellojni.HelloJNIActivity
---Eclipse下:~/Code/HelloJNI$javah -classpath bin/classes -d jni com.android.hellojni.HelloJNIActivity
先简单说一下这个命令有什么用。这个命令是用来生成与指定 class 想对应的本地方法的头文件。
——(自行百度javah用法,以及JNI语法。javah用法小结:http://blog.csdn.net/zzhays/article/details/10514767)
-classpath:指定类的路径。
-d:输出目录名。
com.example.hellojni.HelloJNI:完整的类名。
注意:我在Android-studio下在运行的时候可能出现如下问题
错误: 无法访问android.app.Activity找不到android.app.Activity的类文件
这是因为android.jar包在sdk路径下没有在javah运行的时候引用到,没有引用到,要么设定环境变量,要么就更粗暴的方式,将对应版本的android.jar直接拷贝到-classpath指定的目录下,并解压出来。注意android.app.Activity路径在-classpath目录下,再次运行就不会报错了。
命令的结果是在本地生成一个名为 jni 的目录,里面有一个名为 com_android_hellojni_HelloJNIActivity.h 的头文件。这个文件就是我们所需要的头文件,他声明了两个函数。
......
/*
* Class: com_android_hellojni_HelloJNIActivity
* Method: getStringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_android_hellojni_HelloJNIActivity_getStringFromJNI
(JNIEnv *, jobject);
/*
* Class: com_android_hellojni_HelloJNIActivity
* Method: unimplementedStringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_android_hellojni_HelloJNIActivity_unimplementedStringFromJNI
(JNIEnv *, jobject);
......
代码2
函数名比较长,不过是有规律的,按照:java_pacakege_class_method 形式来命名。调用 getStringFromJNI() 就会执行 JNICALL Java_com_android_hellojni_HelloJNIActivity_getStringFromJNI()
还有一个地方需要注意一下,Signature: ()Ljava/lang/String;
()表示函数的参数为空(这里为空是指除了JNIEnv *,jobject 这两个参数之外没有其他参数,JNIEnv* 和 jobject 是所有 jni 函数必有的两个参数,分别表示 jni 环境和对应的 java 类(或对象)本身);Ljava/lang/String; 表示函数的返回值是 java 的 String 对象。
PS:jni部分知识可以参考<<深入理解Android系统(卷一)>>中所述的
按照 com_android_hellojni_HelloJNIActivity.h 中声明的函数名,在 jni 目录下建立一个 HelloJNI.c 文件实现其函数体。
#include
#include
#include "com_android_hellojni_HelloJNIActivity.h"
jstring JNICALL Java_com_android_hellojni_HelloJNIActivity_getStringFromJNI( JNIEnv* env,
jobject this )
{
return (*env)->NewStringUTF(env, "Hello from JNI ! It's Me");
}
代码3
5.编译生成动态库
如果你还没有下载 ndk 的话,请先下载ndk。ndk 有什么用?就是用来编译 jni 代码的。安装方法很简单,只要把 ndk-build 命令加入到环境变量 PATH 中就可以使用了。验证方法就是键入 ndk-build 命令后,不会出现 command not found 的字样。(NDK官方下载地址:http://wear.techbrood.com/tools/sdk/ndk/#Installing,下载下来的/android-ndk-r10b/samples目录里面有很多NDK编译成so示例,如hello-jni,多模块编译so等;另外如果添加Application.mk文件其中写入APP_ABI := all则表示编译成适用于arm64-v8a,x86_64,mips64,armeabi-v7a,armeabi,x86,mips等多种芯片架构平台的so,这都可以在里面直接执行ndk-build 命令来查看)
在 jni 目录下创建一个名为 Android.mk 的文件,并输入以下内容:
------
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := HelloJNI
LOCAL_SRC_FILES := HelloJNI.c
include $(BUILD_SHARED_LIBRARY)
------
代码4
/HelloJNI $ ndk-build
[armeabi] Compile thumb : HelloJNI <= HelloJNI.c
[armeabi] SharedLibrary : libHelloJNI.so
[armeabi] Install : libHelloJNI.so => libs/armeabi/libHelloJNI.so
将会在 HelloJNI/libs/armeabi 目录下生成一个名为 libHelloJNI.so 的动态库。将这so拷贝到~/Code/HelloJNI/app/libs/armeabi/下使用,编译生成 apk 时会把这个库一起打包。
6. 测试
注意:运行报错信息:E/art: dlopen("/system/lib64/libHelloJNI.so", RTLD_LAZY) failed: dlopen failed: "/system/lib64/libHelloJNI.so" is 32-bit instead of 64-bit
从出错信息分析,我觉得是这个app被认为是64 bit的了。系统主要是根据apk所使用的native library来决定要用64还是32bit vm的,所以你直接安装的时候是好的,而预装的时候,lib和app的关系不明确,所以不行。所以使用Application.mk文件其中写入APP_ABI := all编译多个平台使用的so,找到其中一个合适的so库push进去。
$ adb push '~/Code/HelloJNI/app/libs/arm64-v8a/libHelloJNI.so' /system/lib64/
再次运行工程即可解决
图2
更多相关文章
- Android(安卓)so逆向基本知识总结
- Android(安卓)ADB实现解析
- Android入门——页面跳转
- Android官方命令深入分析之dmtracedump
- Android(安卓)4 游戏高级编程(第2版)
- RK3288 Android(安卓)5.1 固件 编译
- OpenGLES 2.0 在 NDK-r15b上的编译问题
- android的文件系统结构
- Android——4.2 - 3G移植之路之usb-modeswitch (二)