Android JNI
上篇博客介绍了JNI的基本的使用,但是不是在多进程中进行的,此片讲述是在另外一个线程中进行回调Java代码,完成回调,demo也是一个异步的回显操作,因为也是刚开始学习,仅仅做个记录。
- C++层回调Java层(多线程)
a. demo只是在C++开辟线程完成回调Java层代码,和上篇在单一线程有些不同,也需要理解一些关于Java的概念。
b. JVM,即JVM虚拟机在整个程序中只有一个,所以可以以一个全局的方式进行保存。
c. JNIEnv*则是每个线程都是不同的,可以通过VM进行获取,此处也是自己刚开始学习时犯的错误,以为可以在不同线程进行传递,后面通过看了一些资料了解到。
d. demo如下图,通过START按钮完成随机数生成并且更新UI,FINISH按钮完成线程销毁,其中更新UI是在C++线程入口进行每一秒进行一次回调Java层刷新UI,这里为了方便将jobject对象等变成全局,方便访问,实际中应进行封装。
MainActivity.java
package com.example.androidjni;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
public static TextView textView = null;
public static Button StartButten = null;
public static Button FinishButten = null;
public static String Flag ="finish";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
textView = (TextView) findViewById(R.id.sample_text);
final NativeUtils nativeUtils = new NativeUtils();
StartButten = findViewById(R.id.button);
FinishButten = findViewById(R.id.button2);
StartButten.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(Flag == "finish"){
Flag ="start";
nativeUtils.JNICALLJavaWorkBack(Flag);
}
else{
// NOTHING TO_DO
}
}
});
FinishButten.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Flag = "finish";
nativeUtils.JNICALLJavaWorkBack(Flag);
}
});
}// end onCreate
}
NativeUtils.java
package com.example.androidjni;
import android.os.Looper;
public class NativeUtils {
// C++层调用的方法
public native void JNICALLJavaWorkBack(String flag);
// Java层具体实现
public native String GetRandomNumber();
public void JNICALLJavaWork(){
//TO_DO
// 1. 首先获取随机值
// 2. 因为更新UI的原因,所以每次开次一个线程完成UI线程的更新
// 3. Handler机制,通过将Runable或者Meesage放入UI线程Looper中
final String randomNum = GetRandomNumber();
handler.post(new Runnable() {
@Override
public void run() {
MainActivity.textView.setText(randomNum);
}
});
}
}
Bridge.hpp
#ifndef ANDROIDJNI_BRIDGE_H
#define ANDROIDJNI_BRIDGE_H
#include <jni.h>
#include <thread>
struct Arg;
struct Arg{
public:
JavaVM* _vm ;
jobject _obj;
std::string _IsRunning ;
Arg(JavaVM* vm,jobject obj,std::string IsRunning):_vm(vm),_obj(obj),_IsRunning(IsRunning){}
};
#endif //ANDROIDJNI_BRIDGE_H
CmakeLists.txt
#将其他源文件进行加入,否则找不到
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp
src/main/cpp/jni-utils.cpp)
jni-lib.cpp
#include <jni.h>
#include <random>
#include <time.h>
#include <string>
#include <unistd.h>
#include <iostream>
#include <pthread.h>
#include <assert.h>
#include "Bridge.hpp"
// 为了方便直接变成全局,实际除了JavaVM都应该进行封装
static JavaVM* g_vm = NULL;
static jobject g_obj = NULL;
static pthread_t tid;
static std::string IsRunning = "satrt";
// Random
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_momo_androidjni_NativeUtils_GetRandomNumber(JNIEnv *env, jobject instance) {
// TODO
srand((unsigned)(time(NULL)));
std::string returnValue = std::to_string(rand());
return env->NewStringUTF(returnValue.data());
}
// ThreadHandler
void* ThreadHandler(void* arg){
// 1. 通过全局的jvm,获取当前线程的JNIEnv对象
JNIEnv* env;
g_vm->AttachCurrentThread((JNIEnv**)(&env),NULL);
// 2. 通过env获取jclass对象
jclass cls = env->GetObjectClass(g_obj);
jobject jobject1 = env->AllocObject(cls);
jmethodID mid = env->GetMethodID(cls,"JNICALLJavaWork","()V");
while(1){
if(IsRunning == "start"){
env->CallVoidMethod(g_obj,mid);
sleep(1);
}else if(IsRunning == "finish"){
pthread_exit(NULL);
}
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_androidjni_NativeUtils_JNICALLJavaWorkBack(
JNIEnv *env, jobject instance, jstring flag) {
// TODO(Test)
// 1. 因为env对象是每个线程都会有一个,所以需要获得jvm
env->GetJavaVM(&g_vm);
g_obj = env->NewGlobalRef(instance);
// 2. 构造调用methond的参数
jboolean iscopy;
std::string Cflag = env->GetStringUTFChars(flag, &iscopy);
// 3. 具体处理
if (Cflag == "start") {
IsRunning = "start";
pthread_create(&tid, NULL, ThreadHandler, NULL);
pthread_detach(tid);
} else if (Cflag == "finish") {
IsRunning = "finish";
} else {
assert(false);
}
}
这篇文章也是为了记录下自己在学习遇到的一些问题,如有错误可以给我私信,这里表示感谢。下面会学习GitHub上开源的一个跨平台工具,希望自己可以不断去学习嘻嘻。