(转载)Android studio3.0 JNI/NDK开发流程

(转载)https://www.jianshu.com/p/a37782b56770

一、前言

  • NDK

Native Development Kit(NDK)是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C/C++的动态库,并能自动将so和java一起打包成apk。

  • JNI

Java Native Interface(JNI)标准是java平台的一部分,JNI是Java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用C/C++代码,C/C++的代码也可以调用java代码。

  • JNI与NDK的关系

NDK可以为我们生成了C/C++的动态链接库,JNI是java和C/C++沟通的接口,两者与android没有半毛钱关系,只因为安卓是java程序语言开发,然后通过JNI又能与C/C++沟通,所以我们可以使用NDK+JNI来实现“Java+C”的开发方式。

NDK开发好处

NDK开发具有以下优点:

  1. 项目需要调用底层的一些C/C++的一些东西(java无法直接访问到操作系统底层(如系统硬件等)),或者已经在C/C++环境下实现了功能代码(大部分现存的开源库都是用C/C++代码编写的。),直接使用即可。
  2. 为了效率更加高效些。将要求高性能的应用逻辑使用C/C++开发,从而提高应用程序的执行效率。但是C/C++代码虽然是高效的,在java与C/C++相互调用时却增大了开销;
  3. 基于安全性的考虑。防止代码被反编译,为了安全起见,使用C/C++语言来编写重要的部分以增大系统的安全性,最后生成so库(用过第三方库的应该都不陌生)便于给人提供方便
  4. 便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。

二、配置

  • 下载安装
    首先我们在Android Studio下新建一个安卓项目。然后打开Project Structure界面,如下:

在SDK Location目录下,有SDK和NDK的路径,我这里已经安装NDK。如果还没有安装配置过NDK,Android Studio会提示下载Android NDK,如下图:

点击Download Android NDK来进行下载。这里Android Studio会下载最新版本的NDK进行安装,默认会下载保存在SDK的路径下。我们在上图中还能看到有一段介绍文字,说SDK以及NDK的路径配置会保存在local.properties文件内,安装完成后我们刷新Project,进local.properties文件查看也能看到SDK与NDK的路径。

注意:以前有些版本需在需要在gradle.properties文件中加上一行,3.0版本不再支持了:

android.useDeprecatedNdk=true
  • 添加插件
    为省去控制台输入命令麻烦。我们借助强大的Android Studio的插件功能,在External Tools下配置两个非常有用的插件。进入Settings–>Tools–>ExternalTools,点击+号增加。

    • javah -jni命令

       

javah -jni命令,根据java文件生成.h头文件的,会自动根据java文件中的类名(包含包名)与方法名生成对应的C/C++里面的方法名。下面是参数配置及其含义:

  1. Program: $JDKPath$\bin\javah.exe 这里配置的是JDK目录下的javah.exe的路径。

  2. Parametes: -classpath . -jni -d $ModuleFileDir$/src/main/jni $FileClass$ 这里$FileClass$指的是要执行操作的类名(即我们操作的文件),$ModuleFileDir$/src/main/jni表示生成的文件保存在这个module目录的src/main/jni目录下。

  3. Working: $ModuleFileDir$\src\main\java 表示module目录下的src\main\java目录。
    使用方式:选中java文件—>右键—>External Tools—>javah-jni,将生成jni文件夹以及文件夹下的 包名.类名的.h头文件 (名字有点长,可以自己重命名)。

    • ndk -build命令

       

ndk -build命令,是根据C/C++文件生成so文件的。下面是参数配置及其含义:

  1. Program: F:\apk\sdk\ndk-bundle\ndk-build.cmd 这里配置的是ndk下的ndk-build.cmd的路径(根据实际情况填写)。
  2. Working: $ModuleFileDir$\src\main\
    使用方式:选中C/C++文件—>右键—>ExternalTools—>ndk-build,将在main文件夹下生成libs文件夹以及多个so文件,我们可以移动至jniLibs目录下去。

三、简单实例

  • 创建java类
    随意创建一个访问本地C/C++方法的java类。
public class JniTest {

    /**
     * 将用C++代码实现,在android代码中调用的方法:获取一段文字
     */
    public static native String getStrFromC();

    /**
     * 配置加载的so库的文件名字===>如 :libmyLib.so
     */
    static {
        System.loadLibrary("myLib");
    }
}

该类的getStrFromC会报错提示,不用管。

  • 生成.h头文件
    然后对该文件执行javah -jni操作,生成对应的.h头文件。

如图,已经根据我们的java类在jni文件夹下生成了对应的.h文件,文件名为包名类名.h,我们可以手动改.h文件名,里面只有一个方法,返回值为String(jstring),方法名为Java类的包名类名方法名(包名中的分级不是用.而是_),前面两个参数是C++里面必须有的(JNIEnv代表指向JVM的指针,jclass是调用该方法的java对象),第三个jobject就是我们java类的方法里面的参数Object。注意,这是java函数与C++函数对应的静态注册方法,即通过特定的规则来写。此处方法名可以随意起名字,然后还可以用动态注册的方式关联两个方法。

  • 创建c或c++文件
    在jni文件夹下,新建一个.c(c语言)或者.cpp(c++)的文件,来实现.h文件里声明的方法:
    把.h文件里面声明的方法拷贝到新建的c文件里面,然后在文件里面引入.h文件,如下图:

至此,.h文件和c++文件已完成,想生成so文件还需要在这个jni目录下增加两个文件,Android.mk和Application.mk。

  • Android.mk
    注意LOCAL_MODULE的值与之前的java类中设置要生成so库的名字相对应,LOCAL_SRC_FILES的值写c++文件的名字,这两个值成对设置,可设置多组。(:=是赋值的意思,$是引用某变量的值。)
LOCAL_PATH := $(call my-dir)     // 设置当前的编译目录(Android.mk所在的目录) 
include $(CLEAR_VARS)            // 清除LOCAL_XX变量(LOCAL_PATH除外)
LOCAL_MODULE := myLib // 指定当前编译模块的名称  
LOCAL_SRC_FILES := jnittest.c    // 编译模块需要的源文件
include $(BUILD_SHARED_LIBRARY) // 指定编译出的库类型,BUILD_SHARED_LIBRARY:动态库;BUILD_STATIC_LIBRARY:静态库, BUILD_EXECUTEABLE指:可执行文件

在一个Android.mk文件中配置多个Module的方式如下(include$(CLEAR_VARS)、include $(BUILD_SHARED_LIBRARY)两个语句也需要加上)

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := myLib
LOCAL_SRC_FILES := jnittest.c
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := myLib2
LOCAL_SRC_FILES := jnittest2.c
include $(BUILD_SHARED_LIBRARY)
  • Application.mk
    Application.mk,APP_ABI有四种类型(默认armeabi),armeabi、armeabi-v7a、x86、mips,设置时以空格隔开,all表示所有。该文件中有个可选配置的APP_MODULES,类似于上面Android.mk文件中的LOCAL_MODULE,以空格隔开,且会覆盖掉Android.mk文件中的LOCAL_MODULE设置(比如Android.mk文件中的写了两个jni库的配置,LOCAL_MODULE := JNI1、LOCAL_MODULE := JNI2,而Application.mk中设置的APP_MODULES := JNI1,则只能生成JNI1的so文件,要生成JNI2的so文件的时候会报错,除非写成APP_MODULES := JNI1 JNI2,这里我们直接省略默认使用Android.mk中的)
APP_ABI := all
  • 生成so文件
    对C++文件执行ndk-build操作,生成相应的so文件

     

如图,在main/libs目录下生成了多个so文件,名字为lib+我们指定的库名(同时还生成了obj文件夹,不知是什么东西)。 这时候我们可以生成的main/libs文件夹内的东西复制到app/libs下,并删除main下新生成的jni、libs、obj三个文件夹。

  • 配置app/gradle
android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.ndkjnitestdemo"
        minSdkVersion 14
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
    }
    sourceSets {
        main {
            //让AS识别libs下的.so第三方包
            jniLibs.srcDirs =['libs']
        }
    }
}
  • 调用
    在Activity中测试调用,在TextView上显示我们通过C++代码实现的方法getStrFromC获取字符串。
public class MainActivity extends AppCompatActivity {
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.ndk_text);
        JniTest jniTest = new JniTest();
        textView.setText(jniTest.getStrFromC(""));
    }
}

测试能正确获取到字符串,说明调用成功。我们可以把JniTest类以及so文件给别人去使用,这样别人是看不到我们的代码实现的,能很好的保护我们的源码。



//----------------------------------------------------------------------------------------------------------------

(1)由于开始的时候,新建工程时选择了include c++ suppurt,先把下面红色框中生成或者配置文件去掉。

(2)Android.mk文件中

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := mylib
LOCAL_SRC_FILES := jnitest.c
include $(BUILD_SHARED_LIBRARY)

不要加注释,否则编译报错。

(3)当生成了相关的so库之后,进行生成apk,点击Run。把jni文件夹移走,否则会出现如下报错。

猜你喜欢

转载自blog.csdn.net/zhuhai__yizhi/article/details/82347778