前段时间写过一篇关于JNI开发的文章,不小心被删了,今天补上
NDK和JNI的介绍:
ndk(Native Development Kit)是工具的集合,帮助开发者快速开发C(C++)动态库,并将.so文件和Java应用一起打包成apk;
jni(Java Native Inteface)java本地接口,是一种互通机制,建立了Java和C(C++)互通的桥梁。
Android Studio2.0使用NDK开发
最早使用Eclipse开发Android项目时,NDK开发始终是比较让人头疼的一块,因为需要手动关联代码提示,需要手动创建jni文件,Android.mk文件,手动生成.h头文件,先编译成.so文件才可以运行。AS2.0之前也是比较麻烦的,这里介绍一下Android2.0之后的NDK开发流程:
Android Studio2.0NDK的开发主要是使用了CMake工具进行开发,这就是他的简便之处
CMake:CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。只是 CMake 的组态档取名为 CMakeLists.txt。Cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再依一般的建构方式使用。这使得熟悉某个集成开发环境(IDE)的开发者可以用标准的方式建构他的软件,这种可以使用各平台的原生建构系统的能力是 CMake 和 SCons 等其他类似系统的区别之处。感兴趣的可以上网搜一下CMake的进一步学习下
在这之前你需要做一些准备工作,下载NDK,CMake,LLDB这三个包
LLDB包是用来调试C代码的
下载完之后,在你AS的SDK目录下,会找到下载好的这三个包,把其中的ndk-bundle配置到你的环境变量path中
第一类,创建项目时候就已经确定了使用NDK开发
在创建项目的时候勾选Include C++ Support选项,然后按照正常流程创建项目
完成项目创建之后,你会发现工程结构多了一些东西如下
其中cpp文件家里面放的就是你的C++代码,并且已经帮你自动写好了一部分C++代码,其实到这一步你就已经完成了一个简单的JNI的Demo,你可以直接运行起来试试,当然如果有报错的情况,你可以Open Module Setting来看一下NDK是不是已经自动导入,如果没有,需要你手动导入一下
CMakeLists.txt里面是相关的配置,包括你的.so文件名,你的c代码路径,以及log日志的一些配置。
再来看一下gradle文件也多出了两行相应的配置,分别配置了c代码编辑和CMakeList.txt文件
。
到这里就是android studio2.0的NDK项目的创建
第二类,开发中途遇到使用NDK开发
我想这个是我们比较关心的,因为很多情况下需求是中途加上来的,在项目中使用NDK开发,才是我们需要关注的。
首先我们要创建好一个jni文件夹来放我们的C代码,当然你也可以创建cpp文件夹,放我们的C++代码
然后在该文件夹下创建我们的C文件
接下来我们回忆下刚才在创建JNI项目的时候,多出来的那个CMakeLists.txt文件和在gradle文件里面多出来的那两处配置,就可以用的上了,首先在gradle文件defaultConfig节点里配置如下代码
externalNativeBuild {
cmake {
cppFlags ""
}
}
然后在buildTypes节点的外面配置如下代码
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
那么现在还缺少CMakeLists.txt文件了,这个文件里面的东西不太好记,我是直接拷贝的(随便创建一个JNI项目就可以自动或取代CMakeLists.txt文件),将CMakeLists.txt文件copy到app目录下,同时要对这个文件进行修改,因为这个文件配置了动态链接库以及C代码的路径等(我觉得和以前的Android.mk文件差不多),具体需要修改的地方如下:
到这里已经基本完成了,接下来你要Rebuild Project一下,这时候你可以尝试着写一个native方法来测试一下,发现并没有代码提示啊,不会在C中自动创建代码啊,不要着急,光标放到你的native方法名上面,然后alt+enter键弹出相应的解决方案,选择第三个
它会自动比对一下,并且gradle几秒钟,这个时候你把光标重新移到native方法名上,alt+enter就可以看到代码提示,自动帮你在C文件中创建对应的方法,有时候会自动给你创建另一个C文件,并在里面自动写好了方法,你可以把里面代码copy到你的C文件中,把自动创建的C文件删掉,下一次再写native方法,就会自动在你的C文件中自动添加方法了。到这里项目就创建完成了,代码提示有了,还可以看相应的源码,比如.h文件的源码。
这里并未详细介绍C++使用,其实创建过程和C是一样的,只不过习惯性的不需要叫你文件夹,而是叫做cpp文件夹,对应的需要配置好CMakeLists.txt文件,cpp文件夹下创建的文件需要以.cpp为后缀。C++代码使用的JNIEnv和C代码的不一样的,是进一步的封装,感兴趣的可以自己研究一下。
另外,在CMakeLists.txt文件里面的SHARED代表的是生成动态链接库
动态链接库(SHARED):.so文件,占用的体积较小
静态链接库(STATIC):.a文件,占用的体积较大
最后生成的.so文件对应的路径在:app\build\intermediates\cmake\debug\obj
接下来我把我写的JNI练习的相关代码看一下
MainActivity的代码
package com.herenit.jnidemo05;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.lang.reflect.Method;
/**
* C代码中调用java某一个类中的方法,用到了C的反射技术
*/
public class MainActivity extends Activity {
private JavaSimple javaSimple;
private EditText et_num1,et_num2;
private TextView tv_result;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
javaSimple = new JavaSimple();
et_num1 = (EditText) findViewById(R.id.et_num1);
et_num2 = (EditText) findViewById(R.id.et_num2);
tv_result = (TextView) findViewById(R.id.tv_sum);
}
/**
* 使用反射技术调用某一个类的某个方法
* @param view
*/
public void reflectOnclick(View view){
try {
//1,获取该类的字节码文件
Class clazz = JavaSimple.class;
//2,根据获取该类的实例对象
Object javaSimple = clazz.newInstance();
//3,获取该类的某一个方法对象(Method)
Method mGetSum = clazz.getDeclaredMethod("getSum",int.class,int.class);
//4,调用对应的方法
int result = (int) mGetSum.invoke(javaSimple,3,2);
Toast.makeText(this, "反射调用成功:result="+result, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 点击按钮,用C调用java的JavaSimple的sayHello方法
* Log日志看现象
* @param view
*/
public void cCallJava_sayHello(View view){
javaSimple.cCallJava_sayHello();
}
/**
* 点击按钮,C调用java的JavaSimple的getSum方法
* 界面操作看现象
* @param view
*/
public void cCallJava_getSum(View view){
String str1 = et_num1.getText().toString().trim();
String str2 = et_num2.getText().toString().trim();
if(!TextUtils.isEmpty(str1) && !TextUtils.isEmpty(str2)){
int num1 = Integer.valueOf(str1);
int num2 = Integer.valueOf(str2);
int result = javaSimple.cCallJava_getSum(num1,num2);
tv_result.setText(""+result);
}
}
/**
* 点击按钮,C调用java的JavaSimple的LogString方法
* 从log日志看现象
* @param view
*/
public void cCallJava_LogString(View view){
javaSimple.cCallJava_LogString();
}
/**
* 点击按钮,C调用java的JavaSimple的staticFunction静态方法
* 从Log日志看现象
* @param view
*/
public void cCallJava_staticFunction(View view){
javaSimple.cCallJava_staticFunction();
}
}
JavaSimple中的代码
package com.herenit.jnidemo05;
import android.util.Log;
/**
* Created by HouBin on 2017/3/8.
*/
public class JavaSimple {
/**
* 加载动态链接库
*/
static {
System.loadLibrary("native-lib");
}
private static final String TAG = JavaSimple.class.getSimpleName();
public void sayHello() {
Log.e(TAG, "C调用Java的无参数无返回值方法:sayHello()");
}
public int getSum(int a, int b) {
return a + b;
}
public void LogString(String text) {
Log.e(TAG, "C调用Java的String参数方法:LogString(String text)---" + text);
}
public static void staticFunction(String text) {
Log.e(TAG, "C调用Java的静态String参数方法:staticFunction(String text)---" + text);
}
public native void cCallJava_sayHello();
public native int cCallJava_getSum(int num1, int num2);
public native void cCallJava_LogString();
public native void cCallJava_staticFunction();
}
native-lib.c中的代码
#include <jni.h>
#include <syslog.h>
/**
* 在这个方法中,使用C代码调用JavaSimple中的sayHello方法,使用的是C的反射技术
*/
JNIEXPORT void JNICALL
Java_com_herenit_jnidemo05_JavaSimple_cCallJava_1sayHello(JNIEnv *env, jobject instance) {
/*1,获取该类的字节码文件
* jclass (*FindClass)(JNIEnv*, const char*);
* char*参数 传入的是全类名(需要把点(.)改成斜线(/)
*
* */
jclass clazz = (*env)->FindClass(env, "com/herenit/jnidemo05/JavaSimple");
/*2,获取该类的某一个方法对象(jmethodID)
* jmethodID (*GetMethodID)(JNIEnv*, jclass, const char* str1, const char* str2);
* jclass 要调用的java类的字节码
* str1 要调用的类的方法的方法名
* str2 对应的这个方法的签名(可以在该类的.class文件所在的根目录下执行命令行:javap -s 全类名(不包括.java))得到
* */
jmethodID methodId = (*env)->GetMethodID(env, clazz, "sayHello", "()V");
// 3,根据获取该类的实例对象
jobject javaSimple = (*env)->AllocObject(env, clazz);
//4,调用对应的方法
(*env)->CallVoidMethod(env, javaSimple, methodId);
}
/**
* C调用java中的带参数的方法
*/
JNIEXPORT jint JNICALL
Java_com_herenit_jnidemo05_JavaSimple_cCallJava_1getSum(JNIEnv *env, jobject instance, jint num1,
jint num2) {
jclass clazz = (*env)->FindClass(env, "com/herenit/jnidemo05/JavaSimple");
jmethodID methodId = (*env)->GetMethodID(env, clazz, "getSum", "(II)I");
jobject javaSimple = (*env)->AllocObject(env, clazz);
int result = (*env)->CallIntMethod(env, javaSimple, methodId, num1, num2);
return result;
}
/**
* C调Java的 LogString(String text)方法
*/
JNIEXPORT void JNICALL
Java_com_herenit_jnidemo05_JavaSimple_cCallJava_1LogString(JNIEnv *env, jobject instance) {
jclass clazz = (*env)->FindClass(env, "com/herenit/jnidemo05/JavaSimple");
jmethodID methodId = (*env)->GetMethodID(env, clazz, "LogString", "(Ljava/lang/String;)V");
jobject javaSimple = (*env)->AllocObject(env, clazz);
jstring text = (*env)->NewStringUTF(env, "I am param");
(*env)->CallVoidMethod(env, javaSimple, methodId, text);
}
/**
* C调用java的静态方法static void staticFunction(String text)
*/
JNIEXPORT void JNICALL
Java_com_herenit_jnidemo05_JavaSimple_cCallJava_1staticFunction(JNIEnv *env, jobject instance) {
//1,获取该方法对应的类的字节码文件类
jclass clazz = (*env)->FindClass(env,"com/herenit/jnidemo05/JavaSimple");
//2,获取methodId
jmethodID methodId = (*env)->GetStaticMethodID(env,clazz,"staticFunction","(Ljava/lang/String;)V");
//3,调用java的静态方法
jstring text = (*env)->NewStringUTF(env,"I am from C,call Java staticFunction!");
(*env)->CallStaticVoidMethod(env,clazz,methodId,text);
}
最后给大家推荐个网址,我学习JNI看过的感觉比较有帮助的网址:
JNI技术的一些基础知识
最后给大家我的Demo源码地址
源码下载
希望各位大神多多给出评价和意见,谢谢!