交叉编译:
在一个平台下,编译另外一个平台能够执行二进制的代码
windows下编译 成android 平台可以运行的汇编
工具: ndk
cdt: Eclipse开发c/c++插件 , 如果eclipse没有安装需要安装,查看是否安装该插件
##NDK目录结构
* docs:帮助文档: HOW TO:相当于index
* build/tools:linux的批处理文件: .sh文件
* platforms:编译c代码需要使用的头文件和类库
D:android-ndk-r9d\platforms\android-18\arch-arm\usr\include: 类型对应、env中方法
* prebuilt:预编译使用的二进制可执行文件,相当于window下的exe
* sample:jni的使用例子
* source:ndk的源码
* toolchains:工具链
* ndk-build.cmd:编译打包c代码的一个指令
2.1. 新建一个jni文件夹,编写c代码
public native String Hello(); // 定义native方法,c实现,Java中
***** javah的使用,如果嫌弃写c实现方法名称太长,可以使用javah
1.7:在src目录下执行javah 包名.类名
javah com.example.hava.MainActivity
然后在同级目录下找到生成文件类,拷贝对应方法即可
hello.c代码实现:
#include
#include
#include
JNIEXPORT jstring JNICALL Java_com_example_hava_MainActivity_Hello // 返回值+ 全类名
(JNIEnv * env, jobject obj){
char* cstr = "hello from a";
// env 是二级指针,NewStringUTF 到 jni.h中去找 对应函数
jstring jstr = (*env)->NewStringUTF(env, cstr);
return jstr;
}
2.2 定义 Android.mk 文件,指定编译源,和hello.c同级别目录
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS += -llog
#编译生成的文件的类库叫什么名字
LOCAL_MODULE := hello
#要编译的c文件
LOCAL_SRC_FILES := hello.c
include $(BUILD_SHARED_LIBRARY)
3. 编译:
在jni目录下执行, ndk-build 在libs生产so库 , 需要配置ndk环境变量,使用ndk-build.cmd 命令
4. 调用调用使用
public class MainActivity extends Activity {
Button button1;
static {
// 加载打包完毕的so类库,掐头lib 无尾.so
System.loadLibrary("hello");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
String str = Hello();
Toast.makeText(MainActivity.this, str, Toast.LENGTH_LONG)
.show();
}
});
}
public native String Hello(); // 定义native方法,c实现
}
5. Application.mk 文件内容,在hello.c目录下
APP_ABI := armeabi x86
#APP_ABI := armeabi armeabi-v7a x86
# 添加所有支持架构
#APP_ABI := all
3.添加本地支持,自动生成 jni 目录、添加代码提示,每次运行,自动部署
3.1. eclipse配置ndk
2. 给项目添加NDK本地支持(自动给项目生成jni目录,.mk文件)
3. 给JNI添加源码 (添加以后写代码有提示)
代码提示如下:
如果一直项目报错,不能编译,难么直接右键把错误删除即可
4.实现Java 字符串Stirng 加密功能(传递字符串)
4.1. hello.c代码实现
// 工具方法:把java的string 转化为 c语言的 char*
char* Jstring2CStr(JNIEnv* env, jstring jstr)
{
// jstring --> char *
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env,"java/lang/String");
jstring strencode = (*env)->NewStringUTF(env,"GB2312");
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B");
// 转换 为char*
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312");
jsize alen = (*env)->GetArrayLength(env,barr);
jbyte* ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE);
if(alen > 0)
{
rtn = (char*)malloc(alen+1); //"\0"
memcpy(rtn,ba,alen);
rtn[alen]=0;
}
(*env)->ReleaseByteArrayElements(env,barr,ba,0); //
// jstring --> char *
return rtn;
}
// 加密
JNIEXPORT jstring JNICALL Java_com_example_hava_MainActivity_arrString // 返回值+ 全类名
(JNIEnv * env, jobject obj,jstring jstr){
char* cstr = Jstring2CStr(env, jstr); // char* 在 Jstring2CStr 中malloc了
int i=0;
for (i = 0; i NewStringUTF(env, cstr);
}
// 解密
JNIEXPORT jstring JNICALL Java_com_example_hava_MainActivity_decodearrString // 返回值+ 全类名
(JNIEnv * env, jobject obj,jstring jstr){
char* cstr = Jstring2CStr(env, jstr); // char* 在 Jstring2CStr 中malloc了
int i=0;
for (i = 0; i NewStringUTF(env, cstr);
}
native代码实现:
public native String arrString(String arr);
public native String decodearrString(String arr)
4.1传递数组,(数组无效果,值没有变)
JAVA:
public native void arrayEncode(int[] arr);
for(int i=0;i<3;i++){
Log.e("denganzhi1", "数组元素是:"+a[i]);
}
C 代码:
JNIEXPORT void JNICALL Java_com_example_hava_MainActivity_arrayEncode
(JNIEnv * env, jobject obj, jintArray jintarr){
//拿到整型数组的长度以及第0个元素的地址
//jsize (*GetArrayLength)(JNIEnv*, jarray);
int length = (*env)->GetArrayLength(env, jintarr);
//jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
// 获取整形数组首数据指针, 这些到 jni.h中去找
int* arrp = (*env)->GetIntArrayElements(env, jintarr, 0);
LOGI("arrayEncode--%d",length);
int i;
for(i = 0;i < length; i++){
// LOGD("debug !!!");
*(arrp + i) += 10;
}
LOGI("arrp--%d",*(arrp));
}
5. c 调用 java
5.1.Android控制台日志打印
#include
#include
#include
#include
#include
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEFAULT, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
/* ...替换__VA_ARGS__,LOG_TAG过滤标识 */
JNIEXPORT jstring JNICALL Java_com_example_hava_MainActivity_Hello // 返回值+ 全类名
(JNIEnv * env, jobject obj){
char* cstr = "hello from a iiiii";
// env 是二级指针,NewStringUTF 到 jni.h中去找 对应函数
jstring jstr = (*env)->NewStringUTF(env, cstr);
LOGI("hello ... jni"); // c语言打印log输出控制台
return jstr;
}
5.2.C通过放射调用Java
通过放射,通过全类名获取类的字节码,获取类方法签名,然后调用类方法
#include
#include
#include
#include
#include
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEFAULT, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
/* ...替换__VA_ARGS__,LOG_TAG过滤标识 */
JNIEXPORT jstring JNICALL Java_com_example_hava_MainActivity_Hello // 返回值+ 全类名
(JNIEnv * env, jobject obj){
char* cstr = "hello from a iiiii";
// env 是二级指针,NewStringUTF 到 jni.h中去找 对应函数
jstring jstr = (*env)->NewStringUTF(env, cstr);
LOGI("hello ... jni");
//jclass (*FindClass)(JNIEnv*, const char*);
// 包名换斜杠
jclass clazz = (*env)->FindClass(env, "com/example/hava/MainActivity"); // 找到字节码
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
// 第三个参数是方法名 Sinarate
// 最后一个参数是show方法签名:javap -s 全类名 即可 在bin\classes\目录下,执行命令
// 如果是系统的方法 javap -s java.util.Date
// show 方法签名
jmethodID methodID = (*env)->GetMethodID(env, clazz, "sayHelloJava", "(Ljava/lang/String;)V");
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
// CallObjectMethod():有返回值的
// obj:方法的对象 ,c代码中是gbk,android是utf-u,转化为u8
(*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "是时候再黑一波小志了"));
return jstr;
}
Java代码:
package com.example.hava;
public class MainActivity extends Activity {
public void sayHelloJava(String s){
Toast.makeText(MainActivity.this, s, Toast.LENGTH_LONG)
.show();
}
}
如何使用别人的so库,把别人的App破解,找到so库,找到jni类,直接调用jni方法即可
6. Java调用c++
6.1.源码分析:jni.h中:
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
如何是c++,使用的是JNIEnv 结构体,结构体源码
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jstring NewStringUTF(const char* bytes)
{ return functions->NewStringUTF(this, bytes); }
}
_JNIEnv 结构体中和 C中 JNINativeInterface ,对比 C++ 中 函数封装了C函数已经传递了 this, 那么c++ 调用函数的时候不用传递this
6.2. 开发流程
1.Java代码:
package com.example.oo;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
static{
System.loadLibrary("hello");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void show(View view){
String str=Hello();
Toast.makeText(this, str, 1).show();
}
public native String Hello();
}
在src目录下javah com.example.oo.MainActivity 生产com.example.oo.MainActivity.h头文件拷贝到jni目录下
2. hello2.cpp代码实现
#include
#include
#include
#include "com_example_oo_MainActivity.h"
jstring Java_com_example_oo_MainActivity_Hello
(JNIEnv * env, jobject obj){
char* cstr = "hello from c++";
// 如果是 c++ env 就是指针,不用传递对象
jstring jstr = (env)->NewStringUTF(cstr);
return jstr;
}
3. 配置文件Application.mk
APP_ABI := armeabi x86
#APP_ABI := armeabi armeabi-v7a x86
# 添加所有支持架构
#APP_ABI := all
3. 配置文件Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#编译生成的文件的类库叫什么名字
LOCAL_MODULE := hello
#要编译的c++文件
LOCAL_SRC_FILES := hello2.cpp
include $(BUILD_SHARED_LIBRARY)
4. 在jni目录下 ndk-build, 生产 so文件
5. 运行项目
总结:
java 调用c++
1. 把c文件后缀名换成cpp
2. Android.mk文件中的hello.c也要换成hello.cpp
3. c++的使用的环境变量结构体中,访问了c使用的结构体的函数指针,函数名全部都是一样的,只是参数去掉了结构体指针
4. 访问函数指针时,把env前面的*号去掉,因为此时env已经是一级指针
5. clean,清除之前编译的残留文件
6. 把声明函数的h文件放入jni文件夹中,include该h文件
作者:小置同学