一、前言
前段时间听朋友说在学习NDK开发,说现在的普通安卓程序猿马上都要失业了,吓得我赶紧去各大招聘平台搜了下,确实有不少招聘都写着要求NDK开发经验、JNI开发经验、H5/JS开发经验、cocos2d开发经验、蓝牙等底层开发经验巴拉巴拉的,还有些招安卓的还要求同时具有ios开发经验是什么鬼... 唉,安卓程序猿不好混呀,近段时间微信小程序也是火的不要不要的,天天处在要失业的阴影中。为了赚口猿粮不容易,还是花点时间来瞅瞅NDK开发(也叫JNI开发)吧。
● 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开发
1、项目需要调用底层的一些C/C++的一些东西(java无法直接访问到操作系统底层(如系统硬件等)),或者已经在C/C++环境下实现了功能代码,直接使用即可。NDK开发常用于驱动开发、无线热点共享、数学运算、实时渲染的游戏、音视频处理、文件压缩、人脸识别等。
2、为了效率更加高效些。这个可能并不能成为NDK开发的优点,因为虽然C/C++代码是高效的,但是在java与C/C++相互调用时增大了开销;
3、基于安全性的考虑。防止代码被反编译,为了安全起见,使用C/C++语言来编写重要的部分以增大系统的安全性,最后生成so库(用过第三方库的应该都不陌生)便于给人提供方便。(任何有效的代码混淆对于会smail语法反编译你apk是分分钟的事,即使你加壳也不能幸免高手的攻击)
4、便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。
二、安装与配置
首先我们在Android Studio下新建一个安卓项目。然后打开Project Structure界面,如下:
在SDK Location目录下,有SDK和NDK的路径,而这里我们暂时还未下载配置过NDK,故我们需要点击Download Android NDK来进行下载。这里Android Studio会下载最新版本的NDK进行安装,默认会下载保存在SDK的路径。我们在上图中还能看到有一段介绍文字,说SDK以及NDK的路径配置会保存在local.properties文件内,安装完成后我们刷新Project,进local.properties文件查看也能看到SDK与NDK的路径。
NDK下载配置完成之后,需要在gradle.properties文件中加上一行:
android.useDeprecatedNdk=true接下来,我们借助强大的Android Studio的插件功能,在External Tools下配置两个非常有用的插件。进入Settings-->Tools-->ExternalTools,点击+号增加。
javah -jni命令,是根据java文件生成.h头文件的,会自动根据java文件中的类名与方法名生成对应的C/C++里面的方法名。下面是参数配置及其含义:
1.Program: $JDKPath$\bin\javah.exe 这里配置的是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
使用方式:选中java文件--->右键--->External Tools--->javah-jni,将生成jni文件夹以及文件夹下的 包名.类名的.h头文件 (名字过长,我们可以自己重命名)。
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目录下去。
三、简单实例
接下来我们创建一个访问本地C/C++方法的java类。
public class JniTest { /** * 将用C++代码实现,在android代码中调用的方法:获取当前app的包名 * @param o * @return */ public static native String getPackname(Object o); /** * 加载so库或jni库 */ static { System.loadLibrary("JNI_ANDROID_TEST"); } }
注意JNI_ANDROID_TEST这个Library名字,之后还会需要用到,要保持一致。该类提供了一个static的native方法,该方法将用来获取app的包名。然后对该文件执行javah -jni操作,生成对应的.h头文件。
如图,已经根据我们的java类生成了对应的.h文件,文件名为包名_类名.h,我们可以手动改名为jnitest.h,里面只有一个方法,返回值为String(jstring),方法名为Java_类的包名_类名_方法名(包名中的分级不是用.而是_),前面两个参数是C++里面必须有的(JNIEnv代表指向JVM的指针,jclass是调用该方法的java对象),第三个就是我们java类的方法里面的参数Object。
然后我们新建一个C++文件,取名为jnitest.cpp,写上需要include的文件,从.h文件中复制方法过来(方法名、参数类型、返回值等必须一致)。
至此,.h文件和c++文件均已完成,接下来还需要在这个jni目录下增加两个文件,Android.mk和Application.mk。
Android.mk,注意LOCAL_MODULE的值与之前的名字相对应,LOCAL_SRC_FILES的值写c++文件。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := JNI_ANDROID_TEST LOCAL_SRC_FILES =: jnitest.cpp include $(BUILD_SHARED_LIBRARY)
Application.mk,注意APP_MODULES的值与之前的名字相对应。
APP_MODULES := JNI_ANDROID_TEST APP_ABI := all
接下来我们需要对C++文件执行ndk-build操作,生成相应的so文件。
如图,在main/libs目录下生成了多个so文件,名字为lib+我们指定的库名(同时还生成了obj文件夹,不知是什么东西)。
这时候我们可以在main目录下新建jniLibs文件夹,把生成的libs文件夹内的东西均复制过去,删除新生成的jni、libs、obj三个文件夹。然后在Activity中测试调用,在TextView上显示我们通过C++代码实现的方法getPackname获取app的包名了。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = (TextView) findViewById(R.id.tv_app_package_name); tv.setText("packageName: " + JniTest.getPackname(MainActivity.this)); } }
测试能正确得到包名,说明调用成功了。我们可以把JniTest类以及so文件给别人去使用,这样别人是看不到我们的代码实现的,能很好的保护我们的源码。
以上内容大部分来自网络,整理并测试过后,给出自己的一个理解以及详细图文操作步骤,本文仅供新手参考。本人也刚开始接触,文中可能存在错误之处,请谨慎。