2.本篇主要考察的是JNI层线程的创建,以及各种类型的传递。
实现需求内容如下:
java层传入两个参数,字符串和activity对象,在JNI中创建线程,间隔5ms多次通知安卓进行字符串的刷新操作。
一。流程步骤
1.实现java中的声明方法和被调用方法
2.实现JNI中的代码
3.编写测试代码进行验证
二。在java中声明引用
1.这个和上一篇一样,只是增加了一个实现native方法。
public class DynamicRegister {
static {
System.loadLibrary("DynamicRegister");
}
/**
* 拼接字符串str1和str2
* @param str1
* @param str2
* @return
*/
public native String spliceString(String str1,String str2);
/**
* 读取指定路径的文件内容
* @param path
* @return
*/
public native String readStrByPath(String path);
/**
* JNI线程通知安卓刷新
* @param path
* @param activity
*/
public native void refresh(String path, Activity activity);
}
2.在JNIActivity声明一个方法进行界面刷新,供JNI层线程调用。
这里并没有进行线程,也就是说UI刷新方法是在线程中执行的,为什么不会报错?这个可以参看我的另一篇文章,这里就不过多讲解了。
/**
* 供native方法调用通知安卓刷新
* 这里是子线程刷新UI哦,并且可以正常运行不会报错,原因可以参考TextView源码中checkForRelayout方法
*/
fun showMessage(str: String) {
LogUtil.logI("showMessage:$str")
mResult.text = str
}
三。创建JNI的方法,并动态注册
1.创建JNI中的方法。传入参数有两个,字符串和activity对象。
void nativeRefresh(JNIEnv *env, jclass clazz, jstring title, jobject activity) {
}
2.注册JNI方法。和上一章一样,直接贴代码不详细说了。
static JNINativeMethod method_table[] = {
// 第一个参数a 是java native方法名,
// 第二个参数 是native方法参数,括号里面是传入参的类型,外边的是返回值类型,
// 第三个参数 是c/c++方法参数,括号里面是返回值类型,
{"spliceString", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", (jstring *) nativeSpliceString},
{"encryptionStr", "(Ljava/lang/String;)Ljava/lang/String;", (jstring *) nativeEncryptionStr},
{"staticencryptionStr", "(Ljava/lang/String;)Ljava/lang/String;", (jstring *) nativeStaticEncryptionStr},
{"readStrByPath", "(Ljava/lang/String;)Ljava/lang/String;", (jstring *) nativeReadStrByPath},
{"refresh", "(Ljava/lang/String;Landroid/app/Activity;)V", (jstring *) nativeRefresh},
};
3.接下来我们就要取实现nativeRefresh方法了。
因为刷新操作我们要在线程中执行,所以我们要把参数传入到线程中。
所以我们先构建Param结构体:
struct Param {
const char *charTitle;
jobject activity;
jstring title;
int i;
};
4.生成线程,然后把上一步创建的结构体带入到线程中。
首先对param赋值,然后创建线程,把param带入
void nativeRefresh(JNIEnv *env, jclass clazz, jstring title, jobject activity) {
param.title = static_cast<jstring>(env->NewGlobalRef(title));
param.charTitle = env->GetStringUTFChars(title, JNI_FALSE);
param.activity = env->NewGlobalRef(activity);
param.i = 1;
pthread_t pt;
__android_log_print(ANDROID_LOG_INFO, "lxltestjni", "nativeRefresh start,tmp.c:%d", param.i);
pthread_create(&pt, NULL, freshAndroidPage, ¶m);//这里的param无用
}
freshAndroidPage是方法,创建线程时传入方法名,则线程启动后就会执行freshAndroidPage方法。
5.实现freshAndroidPage方法
freshAndroidPage方法中的代码,就是在线程中执行了。
我们定时通知安卓进行刷新,sleep5毫秒。
首先获取activity对象,然后通过对象获取activity这个类对象,通过类对象获取到方法ID。PS:这一套和反射很像。
然后把传入的字符串charTitle和次数i进行字符串拼接,生成jstring对象,然后调用CallVoidMethod的方法,调用java层的showMessage方法,进行界面刷新。
最后达到10次之后,还要释放一下线程资源。
void *freshAndroidPage(void *arg) {
JNIEnv *env;
g_vm->AttachCurrentThread(&env, nullptr);
Param tmp = param;
jobject activity = tmp.activity;
const char *title = env->GetStringUTFChars(tmp.title, JNI_FALSE);
int i = 0;
__android_log_print(ANDROID_LOG_INFO, "lxltestjni",
"freshAndroidPage start,tmp.charTitle:%s,title:%s", tmp.charTitle, title);
while (i++ < 10) {
__android_log_print(ANDROID_LOG_INFO, "lxltestjni", "i:%d,charTitle:%s", i, tmp.charTitle);
jclass cls = env->GetObjectClass(activity);
jmethodID id = env->GetMethodID(cls, "showMessage", "(Ljava/lang/String;)V");
strchr(tmp.charTitle, i);
std::string result = tmp.charTitle;
result.append(std::to_string(i));
jstring message = env->NewStringUTF(result.c_str());
env->CallVoidMethod(activity, id, message);
sleep(5);
}
g_vm->DetachCurrentThread();
}
四。CMakeLists中做好声明
1.创建文件Cmake文件,同上一章,略。
五。验证效果
1.JAVA层的调用方法比较简单,传入文本和activity对象即可
if (position == 5) {
//C层启动一个线程,主动通知java层的刷新界面。
dynamicRegister.refresh("ttA", this)
return
}
2.点击之后,我们就可以看到下面的文本框不断的进行刷新,最后的数字不断的进行累加。
附:项目地址和源码
android_all_demo/DemoClient at master · aa5279aa/android_all_demo · GitHub