Android NDK开发之旅(1): Eclipse中NDK环境搭建与JNI开发流程

来源:互联网 发布:魔兽争霸3mac安装 编辑:程序博客网 时间:2024/05/17 12:04

Android NDK开发之旅(1):Eclipse中NDK环境搭建与JNI开发流程

(码字不易,转载请表明出处:http://blog.csdn.net/andrexpert/article/details/72626830)

前言

       看着本篇文章的标题,或许你会问现在AndroidStudio版本都更新到3.0了,从2.2开始就可以直接使用Cmake来构建NDK项目,根本没有必要再去研究NDK在Eclipse中的开发。嗯,在我使用过AndroidStudio构建NDK项目后,我也是这种想法,通过cmake编译C/C++代码来构建NDK开发框架确实是非常智能、步骤也很简单,完全可以秒杀Eclipse。但是,对于从来没有开发NDK项目的人来说,直接上AS开发可能一是无法明白这其中的原理,比如Android项目是如何构建C/C++开发环境的、Java层和C/C++层是如何映射的等等,这就是我打算写这篇文件的原因。

1. NDK/JNI简介

     AndroidFramework由基于Java语言的Java层与基于C/C++语言的C/C++层组成,在某些情况下,为了将Java(上层)与C/C++(底层)有机地联系起来,使得他们相互协调,共同完成某些任务,Android引入了Java本地接口(JNI,JavaNative interface),它允许Java代码与基于C/C++编写的应用程序、模块和库进行交互操作。在AndroidFramework中,借助JNI综合了Java语言与C/C++等本地语言的优点,使得开发者既可以利用Java语言跨平台、类库丰富、开发便捷等特点,又可以利用本地语言开发运行效率更高、更健壮、更安全的程序。使用JNI几种情况:

(1)注重处理速度(栈、堆速度区别)

     与本地代码(C/C++等)相比,Java代码的执行速度要慢一些。如果对某段程序的执行速度有较高的要求,建议使用C/C++编写代码。而后在Java中通过JNI调用基于C/C++编写的部分,常常能够获得很快的运行速度。

(2)硬件控制

     为了更好地控制硬件,硬件控制代码通常使用C语言编写。而后借助JNI将其与Java层连接起来,从而实现对硬件的控制。另外,假如搭载Android的设备上安装AndroidFramework不支持的硬件时,可以使用C语言实现设备的驱动程序,以便对设备进行控制。

(3)既有C/C++代码的复用

     在程序编写过程中,常常会使用一些已经编写好的C/C++代码,既提高了编程效率,又确保了程序的安全性与健壮性,在复用这些C/C++代码时,就要通过JNI来实现。

(4)防止被反编译,提高app安全性

2. NDK开发环境配置

* NDK:android-ndk-r10c(32位)

* JDK:jdk1.8.0_20(32位)

* NDK插件:com.android.ide.eclipse.ndk_23.0.7.2120684.jar

                        com.android.ide.eclipse.ndk.feature_23.0.7.2120684(目录)

(1) 配置环境变量

      将JDK和NDK相关路径配置到环境变量中,需要注意的是,JDK的版本要与NDK版本一致,否则会报错,比如系统是64位那么JDK和NDK应该都是64位的版本。path变量添加:;%NDK_HOME%;%JAVA_HOME%;%JAVA_HOME%\include;%JAVA_HOME%\bin;%JAVA_JRE%; ,其中,NDK_HOME 、JAVA_HOME分别为android-ndk-r10c、jdk1.8.0_20的根目录路径。然后,在cmd窗口中敲入”ndk-build”命令,如果出现以下信息,即可说明NDK环境配置成功。


(2) 配置NDK选项

     “Window->Preferences->NDK选项”中配置AndroidNDK开发环境,有可能你的Elipse没有这个NDK选项,那是因为Eclipse没有安装NDK插件,我们可以手动来进行安装,即分别将com.android.ide.eclipse.ddms_23.0.7.2120684.jar、com.android.ide.eclipse.ndk.feature_23.0.7.2120684(目录)拷贝到Eclipse安装目录下的plugins和features目录,重启Eclipse。


(3) AddAndroid Native Support,创建JNI目录

     在Eclipse中进行NDK开发,最核心的地方就是Android工程中的JNI目录,所有一切与C/C++有关的开发与配置均是在该目录下实现的。


(4)  配置javah.exe命令到Eclipse,方便生成头文件

     javah.exe命令是JDK提供的工具,位于C:\ProgramFiles\Java\jdk1.8.0_20\bin目录下,主要用于生成JNI开发所需的且与Native方法对应的头文件。通常,我们主要是通过CMD命令窗口执行javah命令行得到所需的头文件,但CMD生成头文件操作有点麻烦。这里为了方便,只需将javah配置到Eclipse即可直接在JNI目录下生成目标头文件,其中javah命令行执行相关参数为:

-v-classpath "${project_loc}/bin/classes" -d"${project_loc}/jni" -jni ${java_type_name}

说明:

       -v:启用详细输出;

       -classpath <path>:指定加载类的路径(C/C++头文件是通过.class文件生成的);

       -d <dir>:头文件输出目录,即选中工程下的jni目录;

       -jni:指定需要编译的类名,即包含native方法的Java类的类名;


javah命令使用方法:

先选中包含native方法的java类,再执行javah命令。    (注意:需要refresh下JNI目录才能显示头文件)      

(5)  为目标工程配置ndk-build命令,方便编译生成.so文件


     HelloJni命令使用方法:

     选中Android工程,执行HelloJni命令,会自动在libs目录下生成相应平台的.so文件。

2.JNI开发流程解析

(1)  JNI工程目录结构


      根据HelloJni工程结构图可知,JNI目录主要包含三个文件:HelloJni.cpp、Android.mkcom_example_hellojni_CalculateUtils.h。其中,HelloJni.cpp是C++函数实现;Android.mk是C/C++配置文件,主要用于配置动态库的名称、有哪些C/C++文件等信息;com_example_hellojni_CalculateUtils.h是使用javah命令自动生成且与Java层native方法相对应的头文件。在Eclipse中,JNI开发流程如下:

a)     Java层编写native方法,即CalculateUtils.java;

b)     使用javah工具生成C语言头文件,即com_example_hellojni_CalculateUtils.h。需要注意的是每次修改或增加了native方法,都要重新生成头文件;

c)     拷贝头文件声明的函数到HelloJni.cpp,编写C/C++代码实现功能逻辑;如果使用的c开发(xxx.c),可能会报"Method can not resolved"异常,且使用(*env)->无法补全方法,这种情况是可以正常编译的,可以通过右键工程 property->C/C++ General->Code Analysis—>配置当前工程(或者workspace)->使 method cannot be resolved  不选中(即此项不进行报错 ),C++开发就不会出现上述情况;

d)    使用ndk工具生成共享库.so,即libHelloJni.so。需要注意的是,每次修改了C/C++层函数的相关代码,都要重新生成共享库;

e)     运行Android工程;

(2)  Native本地方法创建

/** 本地方法 * Created by jiangdg on 2017/5/17. */public classCalculateUtils {// 加载动态库,其中JNITest为Lib名称// 通过Android.mk中的LOCAL_MODULE字段值可知    static {       System.loadLibrary("JNITest");} // 使用native关键字声明该方法的具体实现是在本地层,使用C/C++开发    public static native int getAddResult(inta,int b);    public static native StringgetEncryptString(String str);}

(3)  C/C++本地方法实现与日志打印

#include <jni.h>#include <string.h>#include <android/log.h>#include "com_example_hellojni_CalculateUtils.h"#define  LOG_TAG "laojiang"// 不带格式log#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,"%s",__VA_ARGS__)#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,"%s",__VA_ARGS__)#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,"%s",__VA_ARGS__)#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,"%s",__VA_ARGS__)// 带格式log#define LOG_I(format,...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,format,__VA_ARGS__)#define LOG_D(format,...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,format,__VA_ARGS__)#define LOG_W(format,...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,format,__VA_ARGS__)#define LOG_E(format,...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,format, __VA_ARGS__) JNIEXPORT jintJNICALL Java_com_example_hellojni_CalculateUtils_getAddResult  (JNIEnv *env, jclass jobj, jint numA, jintnumB){    int i,result = 0;    for (i = numA; i <= numB ; ++i) {        result += i;    }    LOGI("计算和...");    LOG_D("result = %d",result);    return result;} JNIEXPORTjstring JNICALL Java_com_example_hellojni_CalculateUtils_getEncryptString  (JNIEnv *env, jclass jobj, jstring str){       // jstring转const char*       const char* temp =env->GetStringUTFChars(str,JNI_FALSE);       LOGI("jstring转constchar*...");       LOG_D("temp =%s",temp);    // 拼接字符串    strcat((char *)temp,"jiangdongguo");    // 释放资源,防止内存溢出    jstring result =  env->NewStringUTF(temp);    env->ReleaseStringUTFChars(str,temp);    return result;}

JNIEXPORT jint JNICALL Java_com_example_hellojni_CalculateUtils_getAddResult(JNIEnv *env, jclass jobj, jint numA, jintnumB)

JNIEXPORT jint JNICALL Java_com_example_hellojni_CalculateUtils_getAddResult(JNIEnv *env, jclass jobj, jint numA, jintnumB)

解析:

     上面两个函数即为Java层native方法在C/C++层的函数映射,当在Java层调用getAddResult(inta,int b)方法时,实际最终调用的是JNIEXPORT jint JNICALL Java_com_example_hellojni_CalculateUtils_getAddResult(JNIEnv *env, jclass jobj, jint numA, jintnumB)。其实,从该函数的结构来看,它与getAddResult(inta,int b)方法都有着千丝万缕的关系:JNIEXPORT 返回值 JNICALL  Java_包名_类名_方法名(参数列表),其中,env参数表示的是JNI环境变量,我们可以通过它调用JNI相关的API实现Java与C/C++之间的转换开发,jobj参数表示Java中getAddResult方法属于的Java类对象,通过它我们可以在C/C++层轻松调用Java层该对象的其他属性和方法。

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,"%s",__VA_ARGS__)

#define LOG_I(format,...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,format,__VA_ARGS__)

解析:

      #define是宏定义,表明使用LOGI(...)用来替换后面的一串;__android_log_print函数来源于android/log.h(源码中声明为:int __android_log_print(int prio, const char *tag,  const char *fmt, ...)),C/C++层打印日志就靠它来实现,它的第一个参数是日志的等级,类似如java层中的info、debug、warn、error,第二个参数为日志打印tag,第三个参数表示输出格式,第四个参数表示若干个变量参数。

(4)  Android.mk配置文件分析

LOCAL_PATH :=$(call my-dir) include $(CLEAR_VARS)# 指定共享库名称 LOCAL_MODULE    := HelloJni # 使C/C++支持android/log.h,日志打印LOCAL_LDLIBS:=  -L$(call host-path, $(LOCAL_PATH))-lm -llog -lc# 添加实现的C/C++文件,比如我们又在jni目录下添加了一个test.c文件,那么#LOCAL_SRC_FILES:= HelloJni.cpp test.cLOCAL_SRC_FILES:= HelloJni.cppinclude$(BUILD_SHARED_LIBRARY)

3.C/C++层日志打印与最终效果

(1)日志打印


(2)效果演示


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 陌陌收到的礼物怎么办 吃了油腻的东西恶心怎么办 主播工资不发怎么办 主播工资被欠怎么办 直播平台不发工资坑主播怎么办 主播公司不发工资怎么办 梦幻月卡用完了怎么办 网易星球实名认证通过不了怎么办 认证过荔枝主播怎么办 苹果手机相机不对焦怎么办 苹果手机摄像头不能对焦了怎么办 闪电邮里面邮件太多怎么办 苹果手机和助理打不开怎么办 苹果我的世界打不开怎么办 ps试用7天到期了怎么办 皮肤锁不住水份怎么办 硫酸弄到皮肤上怎么办 直播时图像反看怎么办 快手直播权限被收回怎么办 快手直播权限被收回了怎么办 腾讯手游助手玩游戏卡怎么办 电脑直播视频打不开了怎么办 平板进水开不了机怎么办 苹果平板进水开不了机怎么办 苹果平板电脑进水了怎么办 电脑换主机以前的文件怎么办 货车卖了没过户怎么办 微交易出金不了怎么办 直播时出现央视影音客户端怎么办 qq账号暂时无法登录怎么办 饿了吃东西胃疼怎么办 早上不吃饭胃疼怎么办 孕晚期胃疼呕吐怎么办 胃疼了一晚上怎么办 微信视频图像倒立怎么办 ps链接图层锁定怎么办 慕课过时间了怎么办 异地恋又要考研怎么办 阴阳师手机绑定上限了怎么办 高考口令卡丢了怎么办 网易将军令换手机了怎么办