Eclipse开发NDK

Tesia ·
更新时间:2024-09-20
· 707 次阅读

1. 概念了解:

   交叉编译:
 在一个平台下,编译另外一个平台能够执行二进制的代码
 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.  NDK开发流程:

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文件


作者:小置同学



ndk Eclipse

需要 登录 后方可回复, 如果你还没有账号请 注册新账号