AndroidJNI实践(1)-使用.h头文件-静态注册JNI方法


一、环境和工具:
  Ubuntu14.04
  java version "1.7.0_95"
  IDE(Android-studio/Eclipse)
  android-ndk-r10b

二、JNI 开发的基本步骤

AndroidJNI实践(1)-使用.h头文件-静态注册JNI方法_第1张图片
图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


上面代码中的 JNIEXPORT 和 JNICALL 是 jni 的宏,在 android 的 jni 中不需要,当然写上去也不会有错。
函数名比较长,不过是有规律的,按照: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系统(卷一)>>中所述的


4.编写c/c++代码
按照 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


这里只实现了Java_com_android_hellojni_HelloJNIActivity_getStringFromJNI(),函数很简单,返回一个字符串。但是由于函数定义中的返回值是 java 的 String 类,所以不能简单的返回一个字符串,需要通过JNI函数 NewStringUTF 在本地创建一个新的 java.lang.String 对象。这个新创建的字符串对象拥有一个与给定的 UTF-8 编码的 C 类型字符串内容相同的 Unicode 编码字符串。这里抛出了一个问题,就是说我们在写 JNI 的时候,有些数据类型是需要做转换的。

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


然后在 jni 目录下输入 ndk-build 命令就可以编译了。

/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/
再次运行工程即可解决

AndroidJNI实践(1)-使用.h头文件-静态注册JNI方法_第2张图片
图2


你可能感兴趣的:(Android系统定制开发)