1.写java jni调用类文件
package com.example.android.toyvpn;
public class JniSocket {
JniSocket(ToyVpnService toyVpnService)
{
vpnService = toyVpnService;
}
static
{
System.loadLibrary("jnisocket");
}
ToyVpnService vpnService;
public native int connectServer(String ip, int port);
public boolean protectFd(int fd)
{
return vpnService.protect(fd);
}
public void print(int fd)
{
System.out.println("fd " + fd);
}
public boolean printBoolean(int fd)
{
System.out.println("fd " + fd);
return true;
}
}
2.生成本地方法头文件
2.1利用ide工具(如eclipse)生成.class文件
2.2 命令行进入工程目录,输入以下命令即可:
Javah -classpath bin\classes -d jni com.example.android.toyvpn.JniSocket
(ps: -classpath bin\classes 指定 jni调用类.class文件的路径 -d jni 指定本地方法头文件生成的目录,
如果楚翔:
错误: 找不到类android.net.VpnService。
可以换成在src目录输入如下命令:
Javah -encoding UTF-8 -d ../jni com.example.android.toyvpn.JniSocket
加入-encoding UTF-8是因为我运行这条命令是有如下错误:
错误: 编码GBK的不可映射字符
)
生成的头文件如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_android_toyvpn_JniSocket */
#ifndef _Included_com_example_android_toyvpn_JniSocket
#define _Included_com_example_android_toyvpn_JniSocket
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_android_toyvpn_JniSocket
* Method: connectServer
* Signature: (Ljava/lang/String;I)I
*/
JNIEXPORT jint JNICALL Java_com_example_android_toyvpn_JniSocket_connectServer
(JNIEnv *, jobject, jstring, jint);
int getCurrentENVInfo(JNIEnv *env);
#ifdef __cplusplus
}
#endif
#endif
其中 “int getCurrentENVInfo(JNIEnv *env);”这一行为后面添加的方法声明,其余的均为javah自动生成,
(JNIEnv *, jobject, jstring, jint);中JNIEnv* 指向虚拟机环境, jobject为调用此方法的java对象(当在本地方法中回调java非静态方法的时候会用到),后面两个为java方法所带的参数
3.实现本地方法
(jni 数据类型及基本方法可参考我转发的文章:JNI基本数据类型)
#include "com_example_android_toyvpn_JniSocket.h"
#include "javamethodctrl.h"
#include "javamacro.h"
#include <assert.h>
#include <fcntl.h>
#include<errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <android/log.h>
#include <sys/time.h>
#include <unistd.h>
#include <pthread.h>
#include <netdb.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <linux/rtc.h>
#include <setjmp.h>
#include <net/if.h>
#include <net/route.h>
#include <sys/types.h>
#include <sys/stat.h>
JavaVM *gs_jvm = NULL;
jobject g_LivingProxyNotify = NULL;
const int Ret_Success = 0;
const int Ret_Failed = 1;
//static jclass JniSocket;
//JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM * vm, void * reserved) {
// jclass tmp = env->FindClass("com/example/company/MyClass");
// myClass = (jclass)env->NewGlobalRef(tmp);
// return JNI_VERSION_1_6;
//}
static jclass JniSocket;
static jclass tmp;
static jclass print;
static jclass printBoolean;
JNIEXPORT jint JNICALL Java_com_example_android_toyvpn_JniSocket_connectServer
(JNIEnv * env, jobject obj, jstring ip, jint port)
{
int tmpSocket = -1;
do
{
if(0 != getCurrentENVInfo(env))
{
return -5;
}
const char * servIP = NULL;
servIP = env->GetStringUTFChars(ip, 0);
//对IP和端口进行转换
struct sockaddr_in stRemote;
struct sockaddr_in stLocal;
memset(&stRemote, 0x00, sizeof(sockaddr_in));
memset(&stLocal, 0x00, sizeof(sockaddr_in));
stRemote.sin_family = AF_INET;
stLocal.sin_family = AF_INET;
stRemote.sin_port = htons(port);
stLocal.sin_addr.s_addr = htonl(INADDR_ANY);
stLocal.sin_port = htons(22389);
if(inet_pton(AF_INET, servIP, &stRemote.sin_addr) <= 0)
{
return -6;
}
tmpSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(tmpSocket < 0)
{
return -8;
}
//绑定指定IP和端口
if(bind(tmpSocket, (sockaddr*)&stLocal, sizeof(sockaddr)) < 0)
{
return -9;
}
// print = env->FindClass("com/example/android/toyvpn/JniSocket");
// if(NULL == print)
// {
Koolsee_LOGDebug("FindClass com/example/android/toyvpn/JniSocket");
// return -11;
// }
// jmethodID jmethodIDprint = NULL;
// jmethodIDprint = env->GetMethodID(print, "print", "(I)V");
// if (NULL == jmethodIDprint)
// {
Koolsee_LOGDebug("get jmethodIDreceiveNotify failed!");
// return -12;
// }
return -12;
// env->CallVoidMethod(obj, jmethodIDprint, tmpSocket);
JniSocket = env->FindClass("com/example/android/toyvpn/JniSocket");
// JniSocket = (jclass)(env->NewGlobalRef(tmp));
if(NULL == JniSocket)
{
// Koolsee_LOGDebug("FindClass com/example/android/toyvpn/JniSocket");
return -11;
}
jmethodID jmethodIDNewInstance = NULL;
<pre name="code" class="cpp"> //第二个参数为方法名,第三个参数括号内为参数类型(可以有多个),括号外为返回值类型
jmethodIDNewInstance = env->GetMethodID(JniSocket, "protectFd", "(I)Z"); if (NULL == jmethodIDNewInstance) { return -12; } bool flag = env->CallBooleanMethod(obj, jmethodIDNewInstance, tmpSocket); if(!flag) { return -13; } //设置非阻塞模式 //连接服务器 // fcntl(tmpSocket, F_SETFL, fcntl(tmpSocket, F_GETFL, 0) | O_NONBLOCK); // errno = 0; if(0 != connect(tmpSocket, (sockaddr*)&stRemote, sizeof(stRemote))) { return -20; } }while(0); return 0; }int getCurrentENVInfo(JNIEnv *env){if(NULL != env){env->GetJavaVM(&gs_jvm);}if(NULL != gs_jvm){return Ret_Success;} return Ret_Failed;}
上面类似于 static jclass JniSocket; 这样的声明可以视情况而定。
4.编译成libjnisocket.so库
目录结构为:
jni 目录下有如下文件和目录:
android.mk, Application.mk, socket(为目录)
jni/android.mk:
LOCAL_PATH:= $(call my-dir)
include $(call all-subdir-makefiles)
jni/Application.mk:
# The ARMv7 is significanly faster due to the use of the hardware FPU
APP_ABI := armeabi-v7a armeabi mips x86
#APP_ABI := x86
#APP_PLATFORM := android-8
APP_ABI:希望编译低平台架构类型,一般选armeabi-v7a就ok了
socket目录下有如下文件和目录:
android.mk,socket_source(目录)
jni/socket/socket_source目录下存放所有源文件(*.cpp, *.h)
jni/socket/android.mk:
include $(CLEAR_VARS)
APP_STL := stlport_static
LOCAL_MODULE := jnisocket
LOCAL_CFLAGS := -Wall -pipe -D_POSIX_SOURCE -D_REENTRANT -D_THREAD_SAFE -D_GNU_SOURCE -DWORDS_LITTLEENDIAN -DNO_FTW_H
LOCAL_CFLAGS += -funwind-tables -DANDROID -DARMANDROID -DTIXML_USE_STL
LOCAL_CFLAGS += --sysroot=$(ANDROID_NDK_ROOT)/platforms/android-5/arch-arm
LOCAL_CPPFLAGS += -fno-rtti -DTIXML_USE_STL
LOCAL_C_INCLUDES := $(ANDROID_NDK_ROOT)/sources/cxx-stl/stlport/stlport
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_C_INCLUDES += $(ANDROID_NDK_ROOT)/platforms/android-9/arch-x86/usr/include/
LOCAL_LDFLAGS := --sysroot=$(ANDROID_NDK_ROOT)/platforms/android-9/arch-x86
LOCAL_LDLIBS := -L./socket/lib/x86
else ifeq ($(TARGET_ARCH_ABI),mips)
LOCAL_C_INCLUDES += $(ANDROID_NDK_ROOT)/platforms/android-9/arch-mips/usr/include/
LOCAL_LDFLAGS := --sysroot=$(ANDROID_NDK_ROOT)/platforms/android-9/arch-mips
LOCAL_LDLIBS := -L./socket/lib/mips
else
LOCAL_C_INCLUDES += $(ANDROID_NDK_ROOT)/platforms/android-9/arch-arm/usr/include/
LOCAL_LDFLAGS += -Wl,--fix-cortex-a8
LOCAL_LDFLAGS := --sysroot=$(ANDROID_NDK_ROOT)/platforms/android-9/arch-arm
LOCAL_LDLIBS := -L./socket/lib/armeabi
endif
LOCAL_SRC_FILES += socket/socket_source/com_example_android_toyvpn_JniSocket.cpp \
socket/socket_source/javamethodctrl.cpp
#LOCAL_STATIC_LIBRARIES := otherCommonAPI
LOCAL_LDLIBS += -L$(ANDROID_NDK_ROOT)/sources/cxx-stl/stlport/libs/$(TARGET_ARCH_ABI) \
-lstlport_static -llog
include $(BUILD_SHARED_LIBRARY)
5.然后在jni目录下输入 ndk-build 即可在jni同一级目录libs(自动生成的目录)中生成所需要的.so文件
6.在android工程目录下新建文件夹libs\armeabi-v7a 在此文件夹放入相应的动态库文件(libjnisocket.so)即可