可以参考:JNI基础
JNI数据类型
- JNI系统类型:JNIEnv(线程上下文)
- 基本数据类型
- 引用类型
- 数组
基本数据类型:
java类型 | Jni.h | 在C中的定义 |
---|---|---|
boolean | jboolean | typedef unsigned char |
byte | jbyte | typedef signed char |
char | jchar | typedef unsigned short |
short | jshort | typedef short |
int | jint | typedef int |
long | jlong | typedef long long |
float | jfloat | typedef float |
double | jdouble | typedef double |
String | jstring | typedef jobject |
Object | jobject | typedef void* |
Class | jclass | -- |
数组:
Java类型 | Jni.h |
---|---|
boolean[] | jbooleanArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double[] | jdoubleArray |
String[] | jstringArray |
Object[] | jobjectArray |
Class[] | jclassArray |
jstring->char[]
:
const char* charStr = (*env)->GetStringUTFChars(env, str, NULL);
printf("%s", charStr);
char[]->jstring
:
const char* jniChars = "I am jni chars";
jstring str = (*env)->NewStringUTF(env, jniChars);
return str;
Unicode
编码:
const char* unicodeCharStr = (*env)->GetStringChars(env, str, NULL):
- 对象的创建
- 字段的读写
- 静态字段
- 实例字段
- 方法的调用
- 静态方法
- 实例方法
Java方法:
public native void printStr(String str);
jni方法:
/*
* Class: com_sty_ne_jnilogin_md5_encrypt_MainActivity
* Method: printStr
* Signature: (Ljava/lang/Strig;)V <-- 方法名+返回值
*/
JNIEXPORT jstring JNICALL
Java_com_sty_ne_jnilogin_md5_encrypt_MainActivity_printStr(
JNIEnv *env,
jobject,
jstring);
JNI内部描述与Java对照表:
java类型 | JNI描述 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
Object | L全路径名称; |
type[] | [Type |
method | (参数类型)返回值 |
//将message.what的值修改为-1
jclass cls = (*env)->GetObjectClass(env, msg);
jfieldID whatField = (*env)->GetFieldID(env, cls, "what", "I");
(*env)->SetIntField(env, msg, whatField, -1);
//得到message.what的值
int iWhat = (*env)->GetIntField(env, msg, whatField);
//invoke Message.obtain()
jclass cls = (*env)->FindClass(env, "android/os/Message");
jmethodID obtainMethod = (*env)->GetStaticMethodID(env, cls, "obtain", "()Landroid/os/Message;");
return (*env)->CallStaticObjectMethod(env, cls, obtainMethod);
//new Message()
jclass cls = (*env)->FindClass(env, "android/os/Message");
jmethodID constructMethod = (*env)->GetMethodID(env, cls, "<init>", "()V");
return (*env)->NewObject(env, cls, constructMethod);
Message[] messages;
for(int index = 0; i < messages.length; index++) {
messages[i].what = -2;
}
jsize length = (*env)->GetArrayLength(env, objArray);
int index = 0;
for(; index < length; index++) {
jobject element = (*env)->GetObjectArrayElement(env, objArray, index);
(*env)->SetIntField(env, element, whatField, -2);
}
Jni.h | 在C中的定义 |
---|---|
jobject | typedef void* |
jclass | typedef jobject |
jstring | typedef jobject |
jarray | typedef jobject |
jobjectArray | typedef jarray |
jbooleanArray | typedef jarray |
各种引用的区别:
JNI Reference | 特点 |
---|---|
局部引用(LocalRef) | 本地方法栈内有效 |
全局引用(GlobalRef) | 虚拟机全局生效,不会被GC回收 |
弱全局引用(WeakGlobalRef) | 虚拟机全局生效,GC时被回收 |
注意:
如果没有对jobject对象做全局引用或弱全局引用的转换,则默认为局部引用!!
如果循环次数过多,可能报“局部引用表溢出”的异常,从而导致崩溃。需要及时释放不用的局部引用。
删除一个局部变量的引用方法如下:
(*env)->DeleteLocalRef(env, element);
删除多个局部变量的引用方法如下:
(*env)->PushLocalRef(env, 3); # 预测的帧数(可以不等于实际帧数)
//中间要删除的局部引用帧
(*env)->PopLocalRef(env, 3);
if((*env)->EnsureLocalCapacity(env, 3) < 0) {
//TODO 异常处理
}
jclass globalCls;
jclass cls = (*env)->GetObjectClass(env, msg);
globalCls = (*env)->NewGlobalRef(env, cls);
删除全局引用需要调用如下方法:
(*env)->DeleteGlobalRef(env, globalCls);
例外:
jmethodID 和 jfieldID 不是 jobject,采用如下方式声明为全局引用:
jfieldID whatField;
jmethodID obtainMethod;
if(whatField == NULL) {
whatField = (*env)->GetFieldID(env, cls, "what", "I");
}
if(obtainMethod == NULL) {
obtainMethod = (*env)->GetStaticMethodID(env, cls, "obtain", "()Landroid/os/Message;");
}
创建和删除方式如下:
//创建
jclass globalWeakCls = (*env)->NewWeakGlobalRef(env, cls);
//删除
(*env)->DeleteWeakGlobalRef(env, globalWeakCls);
native方法命名必须遵循一定的规则才能被解析,registerNative
则可以将两者关联起来而不必遵循命名规则。
该方法除了在JNI_ONLoad
方法中调用外,还可以在任意地方调用,动态关联。
注意:
上面讲的规范内容比较陈旧,仅可做思路上的参考,实操部分会有一些写法上的改变。
#ifndef NEJNILOGINMD5ENCRYPT_ANDROIDLOG_H
#define NEJNILOGINMD5ENCRYPT_ANDROIDLOG_H
#include <android/log.h>
#define TAG "STY"
//#define LOGE2(...) __android_log_print(ANDROID_LOG_ERROR, TAG, ##__VA_ARGS__) //error: format string is not a string literal (potentially insecure)
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR, TAG, FORMAT, ##__VA_ARGS__)
#define LOGV(FORMAT,...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, FORMAT, ##__VA_ARGS__)
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO, TAG, FORMAT, ##__VA_ARGS__)
#define LOGW(FORMAT,...) __android_log_print(ANDROID_LOG_WARN, TAG, FORMAT, ##__VA_ARGS__)
#define LOGD(FORMAT,...) __android_log_print(ANDROID_LOG_DEBUG, TAG, FORMAT, ##__VA_ARGS__)
#endif //NEJNILOGINMD5ENCRYPT_ANDROIDLOG_H
#include <jni.h>
#include <string>
#include <android/asset_manager.h>
#include "AndroidLog.h"
/**
* Class: com_sty_ne_jnilogin_md5_encrypt_UserInfo
* Method: nativeEncrypt
* Signature: ()V
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_sty_ne_jnilogin_md5_encrypt_UserInfo_nativeEncrypt(JNIEnv *env, jobject instance) {
jclass cls = env->GetObjectClass(instance);
jfieldID pwdField = env->GetFieldID(cls, "userPwd", "Ljava/lang/String;");
jstring strPwd = static_cast<jstring>(env->GetObjectField(instance, pwdField));
jclass clsMd5 = env->FindClass("com/sty/ne/jnilogin/md5/encrypt/MD5Helper");
jmethodID md5Method = env->GetStaticMethodID(clsMd5, "getMD5", "(Ljava/lang/String;)Ljava/lang/String;");
jstring strNewPwd = static_cast<jstring>(env->CallStaticObjectMethod(clsMd5, md5Method, strPwd));
//日志打印测试
const char *buff = env->GetStringUTFChars(strNewPwd, 0);
LOGE("加密后的密码是:%s", buff);
env->ReleaseStringUTFChars(strNewPwd, buff);
env->SetObjectField(instance, pwdField, strNewPwd);
}
public class LoginActivity extends AppCompatActivity {
private EditText edtUser;
private EditText edtPwd;
private Button btnLogin;
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
}
private void initView() {
edtUser = findViewById(R.id.edt_user);
edtPwd = findViewById(R.id.edt_pwd);
btnLogin = findViewById(R.id.btn_login);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
login();
}
});
}
private void login() {
String strUser = edtUser.getText().toString().trim();
String strPwd = edtPwd.getText().toString();
if(TextUtils.isEmpty(strUser) || TextUtils.isEmpty(strPwd)) {
Toast.makeText(this, "用户名或密码为空", Toast.LENGTH_SHORT).show();
} else {
UserInfo userInfo = new UserInfo(strUser, strPwd);
userInfo.nativeEncrypt();
Toast.makeText(this, "密码: " + userInfo.getUserPwd(), Toast.LENGTH_SHORT).show();
doMockLogin(strUser, userInfo.getUserPwd());
saveUserAndPwd(this, strUser, userInfo.getUserPwd());
// doMockLogin(strUser, MD5Helper.getMD5(strPwd));
// saveUserAndPwd(this, strUser, MD5Helper.getMD5(strPwd));
}
}
private void saveUserAndPwd(Context context, String strUser, String pwd) {
//todo 保存用户名和密码
}
private void doMockLogin(String userName, String pwd) {
//todo 登录
}
}