使用NDK编译代码主要有两种方法:
- 基于Make的ndk-build
- CMake
基于Make的ndk-build
- 准备Android.mk
Android.mk文件位于项目app/src/main/jni目录中,用于向编译系统描述源文件和共享库。
//此变量表示源文件在开发树中的位置。在这行代码中,编译系统提供的宏函数 my-dir 将返回当前目录(Android.mk 文件本身所在的目录)的路径。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
//LOCAL_MODULE 变量存储您要编译的模块的名称。请在应用的每个模块中使用一次此变量。每个模块名称必须唯一,且不含任何空格;LOCAL_SRC_FILES := hello-jni.c,会列举源文件,以空格分隔多个文件
LOCAL_SRC_FILES := Test.c
LOCAL_MODULE := Hello
#支持日志
LOCAL_LDLIBS := -llog
//帮助系统将所有内容连接到一起:
include $(BUILD_SHARED_LIBRARY)
-
Module的右键菜单中选择Link C++ Project with Gradle
image.png -
选择ndk-build,然后选择Android.mk
image.png
CMake
- 准备CMakeLists.txt,放到app目录下
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
Hello
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/jni/Test.c )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
Hello
# Links the target library to the log library
# included in the NDK.
${log-lib} )
-
选择cmake,然后选择CMakeLists.txt
image.png
构建环境通过上面两种方式任意一种配置完成即可,ndk-build编译方式有代码自动补全功能,然后在切换为cmake编译方式,cmake也就具有代码自动补全功能。然后进行下面的步骤:
- 新建JNI.java类,Java调用C方法
public class JNI {
/**
* 定义native方法
* 调用C代码对应的代码
*
* @return
*/
public native int add(int x, int y);
public native String sayHello(String s);
public native int[] increaseArrayEles(int[] intArray);
public native int checkPwd(String pwd);
}
- 自动生成c方法
在app/src/main/java目录下执行命令:javah com.qsc.ndk.demo.JNI会在java目录下生成com_qsc_ndk_demo_JNI.h,把该文件剪切到app/src/main/java/jni目录下
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_qsc_ndk_demo_JNI */
#ifndef _Included_com_qsc_ndk_demo_JNI
#define _Included_com_qsc_ndk_demo_JNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_qsc_ndk_demo_JNI
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_qsc_ndk_demo_JNI_add
(JNIEnv *, jobject, jint, jint);
/*
* Class: com_qsc_ndk_demo_JNI
* Method: sayHello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_qsc_ndk_demo_JNI_sayHello
(JNIEnv *, jobject, jstring);
/*
* Class: com_qsc_ndk_demo_JNI
* Method: increaseArrayEles
* Signature: ([I)[I
*/
JNIEXPORT jintArray JNICALL Java_com_qsc_ndk_demo_JNI_increaseArrayEles
(JNIEnv *, jobject, jintArray);
/*
* Class: com_qsc_ndk_demo_JNI
* Method: checkPwd
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_qsc_ndk_demo_JNI_checkPwd
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
- 在main/jni目录下新建c文件,如Test.c,从上一步骤中生成的文件中把方法拷贝过来重写
#include<stdio.h>
#include<stdlib.h>
#include<jni.h>
#include <string.h>
jint JNICALL Java_com_qsc_ndk_demo_JNI_add
(JNIEnv *env, jobject jobj, jint a, jint b) {
int result = a + b;
return result;
}
/**
* 将一个jstring转换成一个c语言的char* 类型.
其实可以用char *fromJava = (*env)->GetStringUTFChars(env, str, 0)代替
*/
char *_JString2CStr(JNIEnv *env, jstring jstr) {
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");
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);
return rtn;
}
jstring JNICALL Java_com_qsc_ndk_demo_JNI_sayHello
(JNIEnv *env, jobject jobj, jstring str) {
//将jstring转为char*
char *fromJava = (*env)->GetStringUTFChars(env, str, 0);
char *fromC = "And I'm from C";
strcat(fromJava, fromC);
return (*env)->NewStringUTF(env, fromJava);
}
jintArray JNICALL Java_com_qsc_ndk_demo_JNI_increaseArrayEles
(JNIEnv *env, jobject jobj, jintArray array) {
jsize size = (*env)->GetArrayLength(env, array);
jint *jarray = (*env)->GetIntArrayElements(env, array, JNI_FALSE);
int i;
for (i = 0; i < size; i++) {
*(jarray + i) += 10;
}
return array;
}
jint JNICALL Java_com_qsc_ndk_demo_JNI_checkPwd
(JNIEnv *env, jobject jobj, jstring str) {
char *origin = "123456";
char *user = _JString2CStr(env, str);
int code = strcmp(origin, user);
if (code == 0) {
return 200;
} else {
return 400;
}
}
C回调Java方法
public class JNI {
{
System.loadLibrary("C2Java");
}
public native void callbackAdd();
public native void callbackHelloFromJava();
public native void callbackPrintString();
public native void callbackSayHello();
public int add(int x,int y){
Log.e("TAG","add() x="+x+" y="+y);
return x+y;
}
public void helloFromJava(){
Log.e("TAG","helloFromJava()");
}
public void printString(String s){
Log.e("TAG","C中输入的:"+s);
}
public static void sayHello(String s){
Log.e("TAG","我是静态方法==="+s);
}
public native void unInstallListener(String packageName,int sdkVersion);
}
C方法实现
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#include <unistd.h>
#include <pthread.h>
#include "jni.h"
#define LOG_TAG "qsc"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
JNIEXPORT void JNICALL Java_com_qsc_ndk_c2java_JNI_callbackAdd
(JNIEnv *env, jobject jobj){
jclass jclazz=(*env)->FindClass(env,"com/qsc/ndk/c2java/JNI");
jmethodID methodId=(*env)->GetMethodID(env,jclazz,"add","(II)I");
jobject obj=(*env)->AllocObject(env,jclazz);
jint result=(*env)->CallIntMethod(env,obj,methodId,6,7);
LOGE("result==%d\n",result);
}
void JNICALL Java_com_qsc_ndk_c2java_JNI_callbackHelloFromJava
(JNIEnv * env, jobject jobj){
jclass jclazz=(*env)->FindClass(env,"com/qsc/ndk/c2java/JNI");
jmethodID methodId=(*env)->GetMethodID(env,jclazz,"helloFromJava","()V");
jobject obj=(*env)->AllocObject(env,jclazz);
(*env)->CallVoidMethod(env,obj,methodId);
}
void JNICALL Java_com_qsc_ndk_c2java_JNI_callbackPrintString
(JNIEnv * env, jobject jobj){
jclass jclazz=(*env)->FindClass(env,"com/qsc/ndk/c2java/JNI");
jmethodID methodId=(*env)->GetMethodID(env,jclazz,"printString","(Ljava/lang/String;)V");
jobject obj=(*env)->AllocObject(env,jclazz);
jstring str=(*env)->NewStringUTF(env,"cccccc");
(*env)->CallVoidMethod(env,obj,methodId,str);
}
void JNICALL Java_com_qsc_ndk_c2java_JNI_callbackSayHello
(JNIEnv * env, jobject jobj){
jclass jclazz=(*env)->FindClass(env,"com/qsc/ndk/c2java/JNI");
jmethodID methodId=(*env)->GetStaticMethodID(env,jclazz,"sayHello","(Ljava/lang/String;)V");
// jobject obj=(*env)->AllocObject(env,jclazz);
jstring str=(*env)->NewStringUTF(env,"hello from c");
(*env)->CallStaticVoidMethod(env,jclazz,methodId,str);
}
void *thr_fn(void *arg)
{
LOGE("new thread: ");
// int code=fork();
// LOGE("code==%d---%d\n",getpid(),code);
return NULL;
}
JNIEXPORT void JNICALL
Java_com_qsc_ndk_c2java_JNI_unInstallListener(JNIEnv *env, jobject instance, jstring packageName_, jint sdkVersion) {
/**
* >0:父进程的id
* =0:子进程的id
* <0:出错了
*/
pthread_t ntid;
int code=pthread_create(&ntid,NULL,thr_fn, NULL);
LOGE("error==%d\n",code);
if(code==0){
int flag=1;
while(flag){
sleep(5);
const char *packageName = (*env)->GetStringUTFChars(env, packageName_, 0);
LOGE("packageName==%s",packageName);
FILE* file=fopen(packageName,"r");
if(file==NULL){
execlp("am","am","start","--user","0","-a","android.intent.action.VIEW",
"-d","https://www.baidu.com",NULL);
flag=0;
(*env)->ReleaseStringUTFChars(env, packageName_, packageName);
}
}
}else if(code==1){
}else{
}
// TODO
}
在上面的C通过反射调用Java的过程中,其中GetMethodID方法需要传入方法签名参数,那方法签名如何获取呢?
查看方法签名的方法
NDKDemo\ccalljava\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes该目录右键Show In Explore,然后打开
命令窗口:javap -s com.qsc.ndk.c2java.JNI
$ javap -s com.qsc.ndk.c2java.JNI
Compiled from "JNI.java"
public class com.qsc.ndk.c2java.JNI {
public com.qsc.ndk.c2java.JNI();
descriptor: ()V
public native void callbackAdd();
descriptor: ()V
public native void callbackHelloFromJava();
descriptor: ()V
public native void callbackPrintString();
descriptor: ()V
public native void callbackSayHello();
descriptor: ()V
public int add(int, int);
descriptor: (II)I
public void helloFromJava();
descriptor: ()V
public void printString(java.lang.String);
descriptor: (Ljava/lang/String;)V
public static void sayHello(java.lang.String);
descriptor: (Ljava/lang/String;)V
}
网友评论