JNI和NDK编程入门

JNI本意(Java Native Interface)Java本地接口,它是为了方便Java调用C,C++等本地代码所封装的一层接口。我们知道,Java的优点就是跨平台,但是作为优点的同时,其在本地交互的时候就出现了短板。Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,于是Java提供JNI专门用于和本地代码交互,这样就增强了Java语言本地交互能力。通过JavaJNI,用户可以调用用C,C++所编写的本地代码。
NDK是Android所提供的一个工具集合,通过NDK可以在Android中更加方便地通过JNI来访问本地代码,比如C或者C++.NDK还提供了交叉编译器,开发人员只需要修改mk文件就可以生成特定CPU平台的动态库。使用NDK的好处如下:

  1. 提高代码安全性。由于so库反编译比较困难,因此NDK提高了Android程序的安全性。
  2. 可以很方便地使用目前已有的C/C++开源库。
  3. 便于平台间移植。通过C/C++实现的动态库可以很方便地在其他平台上使用。
  4. 提高程勋在某些特定环境下的执行效率,但是并也不能明显提升Android程序的性能。

JNI和NDK比较适合在Linux环境下开发。

一、JNI的开发流程

1.在java中 声明native方法

package com.aspsine.mobi.chapter14;

/**
 * Created by hzf 2017/4/8 0008 on 上午 10:31.
 * description :
 */

public class JniTest {
     static {
         System.loadLibrary("jni-test");
     }

     public static void main(String args[]){
         JniTest test = new JniTest();
         System.out.println(test.getStringFromJNI());
         test.setStringToJNI("hello jni");
     }
     public native String getStringFromJNI();
     public native void setStringToJNI(String s);
}

2.编译java源文件得到class文件,然后通过javah命令到处JNI的头文件

打开cmd

  • javac 后面写JniTest文件的绝对路径
  • javac D:LearnPlus/chapter14/src/main/java/包名.JniTest.java
  • 对于javah要先进入src/main/java文件夹再执行指令
  • javah 包名.JniTest

下面是文件生成的头文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_aspsine_mobi_chapter14_JniTest */

#ifndef _Included_com_aspsine_mobi_chapter14_JniTest
#define _Included_com_aspsine_mobi_chapter14_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_aspsine_mobi_chapter14_JniTest
 * Method:    getStringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_aspsine_mobi_chapter14_JniTest_getStringFromJNI
  (JNIEnv *, jobject);

/*
 * Class:     com_aspsine_mobi_chapter14_JniTest
 * Method:    setStringToJNI
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_aspsine_mobi_chapter14_JniTest_setStringToJNI
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

上面代码函数名遵循如下规则:

  • Java_包名类名方法名
  • 例:JNIEXPORT jstring JNICALL Java_com_aspsine_mobi_chapter14_JniTest_getStringFromJNI
    (JNIEnv *, jobject);

其他参数
1. JNIEXPORT 和JNICALL :他们是JNI中所定义的宏,可以在jni.h这个文件中 查找到。
2. JNIEnv *表示一个指向JNI环境的指针,可以通过它来访问JNI提供的接口方法。
3. jobject:表示java对象中的this.

3.实现JNI方法
JNI方法是指java中声明的native方法,这里可以用C或者C++来实现,他们实现过程类似,只有少量区别。
首先,在工程的主目录下创建一个子目录,名字随意,然后将之前的通过javah生成是文件复制到这个目录下,接着创建test.cpp和test.c两个文件

c++实现

//
// Created by Administrator on 2017/4/8 0008.
//test.cpp c++来实现
#include "com_aspsine_mobi_chapter14_JniTest.h"
#include 

JNIEXPORT jstring JNICALL Java_com_aspsine_mobi_chapter14_JniTest_getStringFromJNI
  (JNIEnv *env, jobject thiz){
   printf("invoke get in c++\n");
   return env->NewStringUTF("HOLLE FROM JNI");
}

JNIEXPORT void JNICALL Java_com_aspsine_mobi_chapter14_JniTest_setStringToJNI
  (JNIEnv *env, jobject thiz, jstring string){
    printf("invoke set from C++\n");
    char* str = (char*)env->GetStringUTFChars(string,NULL);
    printf("%s\n", str);
    env->ReleaseStringUTFChars(string, str);


}

c实现

//
// Created by Administrator on 2017/4/8 0008.
//test.c c来实现
#include "com_aspsine_mobi_chapter14_JniTest.h"
#include 

JNIEXPORT jstring JNICALL Java_com_aspsine_mobi_chapter14_JniTest_getStringFromJNI
  (JNIEnv *env, jobject thiz){
    printf("invoke get from C\n");
    return (*env)->NewStringUTF(env, "Hello from JNI !");
}

JNIEXPORT void JNICALL Java_com_aspsine_mobi_chapter14_JniTest_setStringToJNI
  (JNIEnv *env, jobject thiz, jstring string) {
    printf("invoke set from C\n");
    char* str = (char*)(*env)->GetStringUTFChars(env,string,NULL);
    printf("%s\n", str);
    (*env)->ReleaseStringUTFChars(env, string, str);
}

不同之处:

  • C++: env->NewStringUTF(“HOLLE FROM JNI”);
  • C: (*env)->NewStringUTF(env, “Hello from JNI !”);

4.编译so库并在java中调用

二、NDK的开发流程

NDK的开发是基于JNI的,主要由以下几个步骤
1.下载并配置NDK
打开AS的SDK Manager,安装NDK插件:

整个NDK比较大,解压缩完2个G,自动安装到配置的sdk目录下:

安装完毕后,点开structure,配置NDK的路径:

配置NDK的环境变量:


验证是否配置成功:

在命令行输入ndk-build,如果显示以上内容,表示成功。

2.创建Android项目,并声明所需要的native方法

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    static {
        System.loadLibrary("jni-test");
    }
    Button get ,set;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        get = (Button) findViewById(R.id.btn_get);
        set = (Button) findViewById(R.id.btn_set);
        get.setOnClickListener(this);
        set.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
      switch (v.getId()){
          case R.id.btn_get:
              break;
          case R.id.btn_set:
              break;
      }
    }
    /**
     * 声明get方法
     *  - 作用是从本地方法返回一个String
     * @return 返回一个字符串
     */
    public native String getStringFromJNI();

    /**
     * 声明set方法
     *  - 作用是向本地方法传递一个String
     */
    public native void setStringToJNI(String s);




}

3.实现Android项目中所声明的native方法

//
// Created by Administrator on 2017/4/8 0008.
//
#include 
#include 

#ifdef __cplusplus
extern "C" {
#endif

jstring Java_com_aspsine_mobi_chapter142_MainActivity_getStringFromJNI(JNIEnv *env, jobject thiz) {
    printf("invoke get in c++\n");
    callJavaMethod(env, thiz);
    return env->NewStringUTF("Hello from JNI in libjni-test.so !");
}

void Java_com_aspsine_mobi_chapter142_MainActivity_setStringToJNI(JNIEnv *env, jobject thiz, jstring string) {
    printf("invoke set from C++\n");
    char* str = (char*)env->GetStringUTFChars(string,NULL);
    printf("%s\n", str);
    env->ReleaseStringUTFChars(string, str);
}

#ifdef __cplusplus
}
#endif

Android.mk

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := jni-test//模块的名称
LOCAL_SRC_FILES := test.cpp//要参与编译的源文件

include $(BUILD_SHARED_LIBRARY)

Application.mk

APP_ABI := armeabi//CPU架构的类型

4.切换到jni目录的父目录,然后通过ndk_build命令编译产生的so库

为了让Android studio自动编译JNI代码,首先需要在app的build.gradle的defalutConfig区域添加NDK选项,其中moduleName指定模块的名称,这个名称指定了打包后so库的文件名。

defaultConfig {
        applicationId "com.aspsine.mobi.chapter142"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        ndk{
            moduleName "jni-test"
        }
    }

接着需要将JNI代码放在app/src/main/jni目录下,注意存放JNI代码的目录必须为jni,如果不用想用这个可以用以下方式指定JNI的代码路径

sourceSets {
        main {

            jniLibs.srcDirs = ['src/main/jni']
        }
    }

这样Android Studio就可以自动编译JNI代码了,但是这种情况下是默认打包所有CPU平台的so库,我们可以自己设置

productFlavors{
        arm{
            ndk{
                abiFilters "armeabi"
            }
        }
        x86{
            ndk{
                abiFilters "x86"
            }
        }
    }

三、JNI的数据类型和类型签名

见书484

四、JNI调用java方法的流程

见书486

http://blog.csdn.net/kevindgk/article/details/52813258
http://blog.csdn.net/jkan2001/article/details/54316375

你可能感兴趣的:(书刊学习)