注册

[Android]使用函数指针实现native层异步回调

1. 前言


在上篇关于lambda表达式实现方式的文章中,有提到一个概念叫做MethodHandle,当时的解释是类似于C/C++的函数指针,但是文章发出后咨询友人的意见,发现很多人并不清楚函数指针是怎么用的,其实我本人也是只是知道这个概念,但是并没有实际使用过。仿佛冥冥中自有天意,前几天公司的项目正好用到了函数指针来做native层的事件回调,也让我理解了函数指针的妙用。但是关于C/C++我并不是特别熟练,于是将实现过程写了个DEMO,一是为了做个记录熟悉过程,二是以备后续使用。


2. 概念


如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。


那么这个指针变量怎么定义呢?虽然同样是指向一个地址,但指向函数的指针变量同我们之前讲的指向变量的指针变量的定义方式是不同的。例如:


int(*p)(int, int);


这个语句就定义了一个指向函数的指针变量 p。首先它是一个指针变量,所以要有一个“*”,即(p);其次前面的 int 表示这个指针变量可以指向返回值类型为 int 型的函数;后面括号中的两个 int 表示这个指针变量可以指向有两个参数且都是 int 型的函数。所以合起来这个语句的意思就是:定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。p 的类型为 int()(int,int)。


所以函数指针的定义方式为:


函数返回值类型 (* 指针变量名) (函数参数列表);


“函数返回值类型”表示该指针变量可以指向具有什么返回值类型的函数;“函数参数列表”表示该指针变量可以指向具有什么参数列表的函数。这个参数列表中只需要写函数的参数类型即可。


我们看到,函数指针的定义就是将“函数声明”中的“函数名”改成“(*指针变量名)”。但是这里需要注意的是:“(*指针变量名)”两端的括号不能省略,括号改变了运算符的优先级。如果省略了括号,就不是定义函数指针而是一个函数声明了,即声明了一个返回值类型为指针型的函数。


那么怎么判断一个指针变量是指向变量的指针变量还是指向函数的指针变量呢?首先看变量名前面有没有“”,如果有“”说明是指针变量;其次看变量名的后面有没有带有形参类型的圆括号,如果有就是指向函数的指针变量,即函数指针,如果没有就是指向变量的指针变量。


3. 定义函数指针和枚举


假设native层有个耗时操作需要异步调用,我们在异步调用结束后通过回调通知业务层完成事件,那么这个时候就可以使用函数指针作为回调方法。


定义方式:



  1. 首先定义事件枚举:

enum EventEnum {
eeSleepWake,
};



  1. 其次,定义一个函数指针:

typedef void (*onSleepWake)(int code, void* sender);


这个函数指针可以指向一个返回值为void 参数分别为 int 和void型指针的函数,其中void型指针表示调用方的指针



  1. 定义一个结构体,包含函数指针和调用方的指针

struct EventData {
void* eventPointer;
void* sender;
};



  1. 注册事件持有类,使其成为单例

这个操作的部分代码:


class EventManager {
public:
static EventManager& singleton()
{
static EventManager sl;
return sl;
}
static EventManager& getInstance()
{
return singleton();
}

//注册事件
void addEvent(EventEnum eventEnum, void* event, void* sender);

EventData getEventData(EventEnum eventEnum);

private:
std::map<EventEnum, EventData> eventMap;
EventManager(){};
~EventManager(){};
};



  1. 实现事件注册函数

void EventManager::addEvent(EventEnum eventEnum, void* event, void* sender) {
if(event == nullptr || sender == nullptr) {
return;
}
EventData eventData;
eventData.eventPointer = event;
eventData.sender = sender;

eventMap.insert(std::pair<EventEnum, EventData>(eventEnum, eventData));
}



  1. 编写函数指针对应函数的具体实现

void eeSleepWakeCallback(int result, void* sender) {
JniTester *tester = (JniTester *) sender;
tester->onResultCallback(result);
}



  1. 在入口类中注册事件及其对应的枚举和函数

JniTester::JniTester() {
EventManager::getInstance().addEvent(eeSleepWake, (void*)eeSleepWakeCallback, this);
}



  1. 编写异步函数调用

···
void JniTester::getThreadResult() {
ThreadTest *test = new ThreadTest();
test->sleepThread();
}
···
耗时函数的具体实现:


void ThreadTest::sleepThread() {
std::thread cal_task(&ThreadTest::makeSleep, this);
cal_task.detach();
}

void ThreadTest::makeSleep() {
sleep(2);
}


这一步我们是通过新建一个线程,并让其等待2S来模拟异步耗时操作


4. 异步回调的实现



  1. 在java层编写java的回调方法

private OnResultCallback callback;

public void setOnResultCallback(OnResultCallback callback) {
this.callback = callback;
}

public interface OnResultCallback {
void onResult(int result);
}



  1. 在java曾编写java层回调的触发:

    public void onResult(int result) {
if (this.callback != null) {
callback.onResult(result);
}
}



  1. native层异步动作完成的通知

通过向单例的事件持有类获取对应的事件枚举,获取到其对应的函数指针,并调用该函数指针实现:


void ThreadTest::makeSleep() {
sleep(2);
EventData eventData = EventManager::singleton().getEventData(eeSleepWake);
onSleepWake wake = (onSleepWake)eventData.eventPointer;
if(wake) {
wake(12345, eventData.sender);
}
}


因为我们在第三章节第7步注册的函数指针是eeSleepWakeCallback, 因此,这里会调用到这个函数:


void eeSleepWakeCallback(int result, void* sender) {
JniTester *tester = (JniTester *) sender;
tester->onResultCallback(result);
}


通过sender确定具体的对象,调用其onResultCallback函数



  1. onResultCallback函数的实现

void JniTester::onResultCallback(int result) {
JNIEnv *env = NULL;
int status = f_jvm->GetEnv((void **) &env, JNI_VERSION_1_4);

bool isInThread = false;
if (status < 0) {
isInThread = true;
f_jvm->AttachCurrentThread(&env, NULL);
}

if (f_cls != NULL) {
jmethodID id = env->GetMethodID(f_cls, "onResult", "(I)V");
if (id != NULL) {
env->CallVoidMethod(f_obj, id, result);
}
}

if (isInThread) {
f_jvm->DetachCurrentThread();
}
}


这里因为缺少java环境,因此我们需要将该线程挂载到jvm上执行,并获取对应的JNIEnv ,通过jnienv获取java层的回调触发方法onResult并执行。


5.效果


编写测试代码:


        JniTester tester = new JniTester();
Log.d("zyl", "startTime = " + System.currentTimeMillis());
tester.setOnResultCallback(result -> {
Log.d("zyl", "endTime = " + System.currentTimeMillis());
Log.d("zyl", "result = " + result);
});
tester.requestData();


执行结果:
image.png


和预期一致,完美。


作者:dafasoft
链接:https://juejin.cn/post/6965699138163834910
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册