Android Studio平台下JNI开发:入门使用及常见错误

来源:互联网 发布:七月算法 邹博 编辑:程序博客网 时间:2024/06/05 19:47

我们公司app要把核心功能进行封装,封装成SDK方便以后得开发和集成。在开发SDK时可能要用到JNI的开发。而且之前道长写的JNI还是用的eclipse。现在使用Android Studio要重新熟悉一下,发现有很多容易忽视的小细节。造成编译不成功等。现在和小伙伴们分享一下道长踩的坑(重点是记录一下,万一在掉坑里那不丢人了)。

一、概念介绍

在我们开始之前我们首先要了解一下几个概念:

1.JNI

  • JNI的定义
    JNI 是本地编程接口(Java Native Interface,简称JNI),允许Java代码和其他语言的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++设计的,但是它并不妨碍使用其他语言,只要调用约定受支持就可以了。也就是说JNI实际上就是一套协议。
  • JNI的必要性
    1)与底层交互。Java的虚拟机的“一处编译,到处运行”使得编译和跨平台运行大大简化,但是也隔绝了Java与底层C语言的交互。JNI使得在 Java 虚拟机内部运行的 Java 代码能够与用其它语言(如 C、C++)编写的代码进行交互。
    2)代码的保护。由于apk的java代码很容易被反编译,而C/C++库反汇难度较大,所以一般app的核心代码要使用C/C++编写。
    3)提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。
    4)特殊的业务场景,比如电视,车载系统,微波炉等与硬件直接相关的开发。

2.NDK

  • NDK定义
    NDK是本地开发工具包(Native Development Kit,简称NDK),NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。NDK集成了交叉编译器(交叉编译器需要UNIX或LINUX系统环境),并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。

  • .so文件
    编译生成 .so文件是一个C/C++的函数库。在android的JNI中,要先将相应的C语言打包成so库,然后导入到lib文件夹中供java调用。每个不同架构的芯片都有不同的 .so文件。

二、NDK安装

这里首先说一下Android Studio版本,开始的时候道长使用的是3.0 Canary 5版本的。生成.so文件什么的没有问题,但是最后把项目编译到手机上是会报错。(截图丢了-_-!)。然后道长又使用Android Studio版本是2.2.1的,如果有需要其他版本的Studio点击传送门:android studio 下载(记得翻墙)

这里NDK的安装有两个方法:

1.方式一:通过Studio直接下载安装

  • 点击打开,方法如下图所示
    这里写图片描述

  • 在弹窗中选择标红位置,如下图所示
    这里写图片描述

  • 点击上图Apply下载安装,重启Android Studio以完成安装

  • 注意:使用这个方法只能安装最新版本的NDK,如果最新版本的NDK不支持当前版本的Android Studio,就用方式二下载合适的版本的NDK或者更换Android Studio版本

2.方式二:在网站上下载安装

  • 下载NDK
    点击传送门去官网下载NDK,官网上有各个版本的NDK。
    这里写图片描述

  • 下载完成后,点击Project Structure设置NDK
    这里写图片描述

  • 选择NDK解压后的根目录
    这里写图片描述

  • 注意这里的设置才是使用的NDK,假如已经通过方式一下载安装完成NDKa,然后又使用方式二下载安装NDKb,最终起作用的是NDKb

3.校验NDK是否安装成功

  • 配置环境变量
    因为生成.so文件时需要使用ndk-build,所以要配置环境变量,直接写根目录就可以了。
    这里写图片描述

  • 在Studio上的Terminal中输入ndk-build
    这里写图片描述
    基本上弹出上面内容就说明ndk安装成功。

三、JNI开发

1.配置项目参数

  • 项目的结构
    这里写图片描述

  • 在项目gradle.properties写如下代码

android.useDeprecatedNdk=true
  • 在项目local.properties中修改代码

一般NDK/SDK的路径可以自动生成,不需要修改,但是使用方式二安装NDK时有必要修改NDK的路径(下面的是道长自己的路径,只是个示例):

ndk.dir=D\:\\AS\\android-ndk-r12bsdk.dir=D\:\\Eclipse\\sdk_r10_eclipse
  • 在app文件夹下的build.gradle中的defaultConfig里加入如下代码
ndk{       moduleName "helloJNI"       //生成的so文件名字,调用C程序的代码中会用到该名字       abiFilters "armeabi", "armeabi-v7a", "x86" //输出指定三种平台下的so库}sourceSets.main {      jni.srcDirs = ['libs']}

示例图如下:
这里写图片描述

  • 创建Application.mk
    在上面gradle文件中有指定输出不同平台的so库,也可以通过Application.mk中指定:
    #指定CPU架构    APP_ABI := armeabi    #指定NDK的版本     APP_PLATFORM :=android-8

注意:如果没有配置sourceSets编译.so文件时就会报错:

Execution failed for task ':app:compileDebugNdk'

2.生成相应文件

  • 创建一个jni文件夹
    这里写图片描述

  • 在MainActivity中输入如下代码

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        TextView textView = (TextView) findViewById(R.id.tv);        textView.setText(getStringFromNative());    }    //需要用native声明方法  使这个方法成为本地方法;     //本地方法它没有实现,它的实现在本地库(本地函数)里面    public native String getStringFromNative();}
  • 生成 .h文件

打开Android Studio底部的Terminal,默认命令行窗口路径已经在当前项目,输入以下命令:

cd app/src/main/javajavah -jni 包名+类名

注意:如果编码规则不是UTF-8的会报错:

错误: 编码GBK的不可映射字符 错误: 编码GBK的不可映射字符 

如果编码规则不是UTF-8就输入以下指令:

cd app/src/main/javajavah -jni -encoding UTF-8 包名+类名

示例如下:
这里写图片描述

便会在java文件夹下生成一个com_yushan_jnidemo_MainActivity.h。内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_yushan_jnidemo_MainActivity */#ifndef _Included_com_yushan_jnidemo_MainActivity#define _Included_com_yushan_jnidemo_MainActivity#ifdef __cplusplusextern "C" {#endif/* * Class:     com_yushan_jnidemo_MainActivity * Method:    getStringFromNative * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_yushan_jnidemo_MainActivity_getStringFromNative  (JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif

然后把.h文件剪切到jni文件加下。

  • 创建.c文件
    这里写图片描述

输入内容如下:
本地函数命名规则 - 返回类型 Java_包名类名方法名(JNIEnv* env,jobject thiz)
本地函数必须俩个参数 - JNIEnv* env,jobject thiz

//JNIEnv  是结构体 struct JNINativeInterface*一级指针//JNIenv* 是结构体 struct JNINativeInterface*二级指针//env     是JNIEnv的一级指针//env     是JNIEnv*的二级指针//引入上面生成的头文件,并实现头文件中声明的方法#include "com_yushan_jnidemo_MainActivity.h"JNIEXPORT jstring JNICALL Java_com_yushan_jnidemo_MainActivity_getStringFromNative        (JNIEnv *env, jobject obj){    char *str="String from native C";    //jstring     (*NewStringUTF)(JNIEnv*, const char*);    //jstring jstr=(**env).NewStringUTF(env,cstr);    //jstring jstr=(*env)->NewStringUTF(env,cstr);    //return jstr;    return (*env)->NewStringUTF(env, str);}

注意:如果.c文件和.h文件不是在jni文件的根目录下生成.so文件时有可能会报错:

make: *** No rule to make target `D:/ASProjects/JNIDemo/app/src/main/jni/helloC.c', needed by `D:/ASProjects/JNIDemo/app/src/main/obj/local/arm64-v8a/objs/helloJNI/helloC.o'.  Stop.
  • 在jni目录下创建Android.mk文件
    这里写图片描述

Android.mk文件内容如下:

#返回当前JNI目录LOCAL_PATH := $(call my-dir)#清除上一次的编译信息include $(CLEAR_VARS)#指定源文件生成的动态连接库的名称LOCAL_MODULE    := helloJNI#指定源文件LOCAL_SRC_FILES := helloC.c#告诉编译系统生成动态连接库 include $(BUILD_SHARED_LIBRARY)

参数讲解:
LOCAL_MODULE:就是生成的.so文件的名字,要和在app文件夹下的build.gradle中的一致。
LOCAL_SRC_FILES:便是指定C语言的文件,关于.c文件的创建稍后再说。

注意:如果没有创建Android.mk生成.so文件时便会报错:

Android NDK: Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk

所有生成文件后示例如下:
这里写图片描述

三、.so文件的生成与使用

1.so文件的生成

在Terminal下执行 ndk-build(注意:要在/app/src/main/java路径下使用,否则不起作用)
这里写图片描述
目前我们的.so库就全部生成了,生成的so文件都在src/main/libs目录下。

2.so文件的使用

  • 在main文件中创建jniLibs 将我们的libs下的so文件拷贝到下面去
    这里写图片描述

  • 在MainActivity中添加代码块

    //固定写法,表示我们要加载的资源文件为libhelloJNI.so    static {        //System.loadLibrary("LOCAL_MODULE指定的名称");        System.loadLibrary("helloJNI");    }
  • 运行程序
    结果如下:
    这里写图片描述

好了。到这这里基本上已经完毕了,希望能给小伙伴们提供一些帮助。


原创粉丝点击