最近博主在研究JNI,在Android Studio中开发JNI中遇到一些坑,记录下来,希望给需要的人提供一些解决方法.
JNI(Java Native Interface) Java本地接口.其实就是一种协议,只要实现这种协议,就可以实现Java,C代码的互相调用
提供了Java与其他的语言的进行交互的能力,增强Java的功能(适用场景):
1.使用C语言的优秀开源框架 ffmpeg(视频) opengl(图像)等.
2.Java操作硬件的效率问题.
3.安全性,java代码反编译太过简单,可以直接看到Java代码(在C语言加密等,用户检验)等等.
但是:java语言不在跨平台,因为不同的平台遵循的c语言的标准是不同的,如果要使用的话,需要在不同的平台重新编译java本地方法,生成不同的so文件.armeabi.armeabi-v7a.x86.x86_64等等.
那么Android Studio如何创建一个JNI项目呢.
先看下最终的目录结构:
接下来:
1.创建一个含本地的方法的Java类,不推荐在MainActivity中直接创建,一是javah生成头文件的时候会提示MainActivity的父类无法找到(因为MainActivity的父类的class文件并不再我们的项目中)二是增加代码的耦合性.
package com.example.administrator.yinhangapp;
/**
* Created by Administrator Youngkaka on 2016/8/18.
* 我的心愿是:世界和平
*/
public class NdkUtils {
public native int getResYinHangC(int pass,int word);
}
Build-->Rebuild Project),这里我们可以看到Rebuild后,在app/build/intermediates/classes/debug/生成NdkUtils的.class文件,我们进入Terminal模式使用javah生成.h的头文件(这里要注意的是Jdk1.8 生成.h文件的时候,需要指定classpath的位置)
目录结构
使用javah生成头文件,命令执行完毕后,会在debug的目录下,生成 包名_类名_本地方法名 的头文件,这时我们在/src/main目录下,新建一个jni目录下,将生成的.h文件拷贝该文件夹下.
生成的头文件内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_administrator_yinhangapp_NdkUtils */
#ifndef _Included_com_example_administrator_yinhangapp_NdkUtils
#define _Included_com_example_administrator_yinhangapp_NdkUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_administrator_yinhangapp_NdkUtils
* Method: getResYinHangC
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_example_administrator_yinhangapp_NdkUtils_getResYinHangC
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
我们今天先不过多讨论Jni协议里面的内容,现在只要清楚,我们生成的头文件中,有一个的本地C方法(参数,返回值同时也包括在内)
3.在jni目录下,新建一个my.c 实现这个本地c方法.
// Created by Administrator on 2016/8/18.
//
#include "com_example_administrator_yinhangapp_NdkUtils.h"
int login(int num,int pass){
if(num==1234 && pass==1234){
return 1;
}else{
return 0;
}
}
JNIEXPORT jint JNICALL Java_com_example_administrator_yinhangapp_NdkUtils_getResYinHangC
(JNIEnv * env, jobject obj, jint num, jint pass){
jint res=login(num,pass); //调用本地的C方法.在C语言验证用户名和密码
return res;
}
4.接下来就是坑的开始,因为Android Studio使用Gradle编译,因此我们要配置Gradle,同时还要手动配置ndk_build的工作路径,我们先在/src/main/新建一个文件夹jniLibs
我的build.gradle内容如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.1"
defaultConfig {
applicationId "com.example.administrator.yinhangapp"
minSdkVersion 15
targetSdkVersion 24
versionCode 1
versionName "1.0"
ndk {
moduleName "JniLibName" //生成的so名字
abiFilters "armeabi", "x86", "armeabi-v7a" //输出指定三种abi体系结构下的so库。目前可有可无。
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets { main {
jni.srcDirs = []
jniLibs.srcDirs=['src/main/jniLibs']
} }
tasks.withType(JavaCompile) { //这里指定了ndkBuild的工作命令,以及拷贝so的命令
compileTask -> compileTask.dependsOn 'ndkBuild', 'copyJniLibs'
}
}
task ndkBuild(type: Exec) {//设置新的so的生成目录
def ndkBuildingDir = project.plugins.findPlugin('com.android.application').sdkHandler.getNdkFolder().absolutePath
commandLine ndkBuildingDir + "/ndk-build.cmd", '-C', 'src/main/jni',
"NDK_OUT=$buildDir/intermediates/ndk/obj",
"NDK_APP_DST_DIR=$buildDir/intermediates/ndk/libs/\$(TARGET_ARCH_ABI)"
}
task copyJniLibs(type: Copy) {//将新生成的so拷贝到jniLibs目录
from fileTree(dir: file(buildDir.absolutePath + '/intermediates/ndk/libs'), include: '**/*.so')
into file('src/main/jniLibs')
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.1'
}
NDK-Build编译C文件生成.so文件需要Android.mk文件以及Application.mk文件,我在build.gradle文件中已经指定我的.mk在/src/main/jni文件下,因此我们要在jni目录下,新建
5.Android.mk文件,Application.mk文件内容如下:
Andorid.mk内容:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JniLibName //build.gradle指定的modulename
LOCAL_SRC_FILES := my.c //我的C文件名称
include $(BUILD_SHARED_LIBRARY)
Application.mk内容:
APP_ABI := all
APP_PLATFORM:= android-19
APP_OPTIM := release
重新Rebuild后会在jniLibs文件夹中生成so文件,如何没有的话,从app/build/intermediates/ndk/libs拷贝一个.
6.最后在我们的MainActivity中加入静态代码块
static {
System.loadLibrary("JniLibName");
}
7
.运行
----------------------------------------------------------------------------------------------------------------------------华丽的分割线
可能会遇到的问题:
答:你的ndk版本不适合
答:删除你的检查的你的c源代码,以及Android.mk文件,C文件名称是否错误,检查无误后,重新编译
答:你的so文件并没有生成,或者没有存在在/src/main/jniLibs/目录下,拷贝并且检查你的build.gradle相关是否正确.
答:手动使用ndk-build时候,需要进入到jni目录下,并且设置Android.mk文件,然后使用ndk-build命令,或者使用配置gradle自动编译
答:jdk1.8的问题,手动设置classpath以及不能再MainActivity的中编写native方法.
我目前就遇到这些问题,欢迎互相交流.
反向编译后,只能看到native的声明调用,但是看不到本地方法具体实现.