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

Android NDK开发:函数指针实现native层异步回调
本文讲述了作者在跳槽过程中,重点准备Android面试的经历,特别是涉及NDK模块开发中使用函数指针实现native层异步回调的技巧,以及面试中遇到的Java和Android基础知识问题。作者强调了简历编写、技术博客和GitHub项目的重要性,并分享了Android面试的常见问题,包括Java线程、内存管理、Android组件原理等。

前言

在今年六月份,我萌生了跳槽的打算,在过去的7,8月份我经历了差不多两个月左右的面试。最终拿到了平安人寿TCL电子投哪儿网以及几个不太知名公司的offer。于是就写下这篇文章,一来记录总结自己的面试,二来帮助需要跳槽意向的大家了提供一些跳槽经验。
文末会给大家分享我整理的Android面试专题及答案(这些题我基本都刷了三遍以上,毕竟年前突然被裁心里是有点迷)

准备阶段

在面试准备阶段我大概准备了差不多两个礼拜多一点的时间。这段时间重点是做两件事,第一是准备简历,第二是大概过一下面试题。

简历的编写其实这里可能需要注意几点

  1. 标题的直接按照姓名-几年工作经验-应聘岗位格式来填写。例如 张三——三年工作经验——Android开发工程师。因为要知道,HR筛选简历是非常快的,你要在第一时间就让他看到他想要看的信息。HR一开始最关心的就是工作年限和岗位,你直接在标题栏用最大的字体写出来会比很多人介绍了半天项目最后写年限要好得多。

  2. 个人信息写最前面。 个人信息栏其实不用写很多东西,最主要的就是姓名、联系方式、性别、如果学校是名校那么就写上,如果本科学的是计算机专业也可以在这里写上,如果都不是就最好放到最后去写,因为最前面的东西永远是最先看到的内容,也是你最有竞争力和卖弄的资本。

  3. 有技术博客和github开源项目的一定写在最前面。 这是活生生的例子。我在面试平安人寿的时候面试官就特意去看了我的csdn上的博客。然后说写的还是不错的,给他留下了一个不错的印象,所以这点是绝对的加分项最好就是写在最前面。

刷面试题这块其实一开始我并没有刷特别多,也就是简单的看了下一些基础性的知识点。因为我始终觉得看的越多越记不住,而且最重要的是你根本不知道面试官会怎么问,与其盲目的看知识点倒不如先出去找几家小公司面试找找感觉,知道面试官会针对哪些地方询问,再回来针对性的看。

面试阶段

由于我不是裸辞的状态,所以面试对我来说其实不是那么着急和有压力。我陆陆续续的面试了差不多两个月左右,面了十来家公司。当然,前面面试的几家公司完全是试试水的状态;每次面试回来我都要做一些笔记让自己知道自己不足。所以这样一来二去后面的状态就越来越好了。针对android这块的面试题目其实主要还是基础性的原理之类的。

Java基础问题
  1. HashMap的内部原理。 主要是内部的hash碰撞、resize()等。还会与HashTable进行对比,说出相同与不同点。作为延伸可能还会问concurrentHashMap的分步锁问题。

  2. java中创建线程的方式有几种。

<think> 首先,用户的问题是基于之前的引用和系统指令。用户想了解在JAVA注册的回调函数能否往C注册以及实现方法。具体问题是:“请问如何将JAVA注册的回调函数注册到C,是否可以实现?” 用户提供了几个引用:[1], [2], [3], [4], [5],这些是关于Java回调函数、JNI和回调的本质的。我需要参考这些来构建回答。 关键点: - 引用[1]:解释了回调的本质,上定义接口,下实现并传入,实现灵活性和解耦。 - 引用[2]:讨论了回调的优点,如解耦、灵活性、异步处理、扩展性。 - 引用[3]:描述了回调的逆向调用本质,通过函数指针实现。 - 引用[4]:关于使用JNI从Java调用C/C++动态库,并获取JavaVM指针。 - 引用[5]:关于如何使用JNI从Java调用C++再到HAL,并回调到Java。 系统指令: - 所有行内数学表达式用$...$格式(但这里可能没有数学公式)。 - 独立公式用$$...$$(同样,可能不需要)。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾添加引用标识,如[^1]。 用户可见指令: - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 用户上一次的问题是关于Java注册回调函数到C实现。结合引用,核心是使用JNI(Java Native Interface)来实现从C回调Java函数。 实现方法: 1. **定义回调接口**:在Java定义一个接口或方法,作为回调目标。 2. **通过JNI注册**:在C/C++使用JNI函数来获取Java方法的引用,并将其存储为函数指针或类似结构。 3. **调用回调**:当C需要回调时,使用存储的引用调用Java方法。 具体步骤参考引用[4]和[5]: - 引用[4]:提到了在DLL中获取JavaVM指针,使用`env->GetJavaVM(&gs_jvm)`存储JavaVM指针。然后在另一个线程中,调用`gs_jvm->AttachCurrentThread((void **)&env, NULL);`来获取JNIEnv指针。 - 引用[5]:讨论了从APP(JAVA)调用到C++再到HAL(C)并回调到JAVA,包括方法签名。 实现细节: - 在Java,定义一个native方法,用于注册回调。 - 在JNI代码中,接收一个Java对象或方法作为参数。 - 使用JNI函数如`GetMethodID`和`CallVoidMethod`等来调用Java方法。 - 注意线程安全:JNIEnv是线程相关的,需要用JavaVM在非主线程中获取新Env。 是否可以实现? - 是的,可以实现。这在JNI中是常见实践,用于跨语言回调。 结构化回答: - 先确认可以实现。 - 然后逐步解释方法和步骤。 - 引用相关段落添加标识。 - 最后生成相关问题。 生成相关问题: - 基于主题,如JNI回调的常见问题、线程处理、错误处理等。 回答大纲: 1. 引言:是的,可以实现。 2. 实现原理:解释逆向调用本质。 3. 步骤: - Java定义回调接口。 - JNI注册回调(存储方法ID或全局引用)。 - C调用回调。 4. 注意事项:线程安全、引用管理等。 5. 生成相关问题。 确保语言中文。 </think> ### Java回调函数注册到C实现方法 是的,可以将Java注册的回调函数注册到C,并且通过Java Native Interface(JNI)技术实现。这种机制的核心是“逆向调用”:C通过函数指针调用Java定义的方法,实现了跨语言解耦和灵活性[^1][^3]。以下是清晰的实现步骤和原理说明,结合JNI规范。 #### 1. **实现原理** - **逆向调用机制**:回调的本质是C(被调方)通过函数指针调用Java(主调方)提供的函数,而非传统的正向调用(Java调用C)。C只需依赖函数原型,不关心Java的具体实现,从而实现解耦[^1][^3]。 - **JNI支持**:JNI允许C获取Java方法的引用,并将其存储为全局变量。通过JavaVM指针和JNIEnv接口,C可以在任何线程安全地调用Java方法[^4][^5]。 - 关键点:Java定义回调接口,C通过JNI注册并存储回调引用,在需要时触发调用。 #### 2. **详细实现步骤** 以下是完整实现流程,基于标准JNI实践: ##### 步骤1: Java定义回调接口和注册方法 在Java中定义一个回调接口(例如`CallbackInterface`),并提供native方法用于注册回调到C。 ```java // Java代码 public class NativeCallback { // 回调接口定义 public interface CallbackInterface { void onEvent(int eventCode, String data); } // Native方法:注册Java回调到C public static native void registerCallbackToC(CallbackInterface callback); // 加载JNI库 static { System.loadLibrary("native-lib"); } } ``` ##### 步骤2: JNI实现注册逻辑 在JNI代码中,接收Java回调对象,存储其全局引用和方法ID。 ```cpp // CJNI代码 (native-lib.cpp) #include <jni.h> // 全局变量存储回调引用 static jobject g_callbackObj = NULL; static jmethodID g_callbackMethodID = NULL; JNIEXPORT void JNICALL Java_com_example_NativeCallback_registerCallbackToC(JNIEnv *env, jclass clazz, jobject callback) { // 删除旧引用,避免内存泄漏 if (g_callbackObj != NULL) { env->DeleteGlobalRef(g_callbackObj); } // 创建全局引用(线程安全) g_callbackObj = env->NewGlobalRef(callback); // 获取回调方法ID jclass callbackCls = env->GetObjectClass(callback); g_callbackMethodID = env->GetMethodID(callbackCls, "onEvent", "(ILjava/lang/String;)V"); // 检查有效性 if (g_callbackMethodID == NULL) { // 错误处理:方法签名不匹配 } } ``` ##### 步骤3: C触发回调 在C任意位置(如事件驱动函数),调用存储的Java方法。 ```cpp // C函数示例:触发回调 void triggerEventInC(int eventCode, const char* data) { // 获取当前线程的JNIEnv(需JavaVM支持) JNIEnv *env; jint res = g_jvm->AttachCurrentThread(&env, NULL); // g_jvm为全局JavaVM指针 if (res == JNI_OK && g_callbackObj != NULL && g_callbackMethodID != NULL) { // 调用Java方法 jstring jdata = env->NewStringUTF(data); env->CallVoidMethod(g_callbackObj, g_callbackMethodID, eventCode, jdata); env->DeleteLocalRef(jdata); // 释放局部引用 } // 分离线程 if (g_jvm) { g_jvm->DetachCurrentThread(); } } ``` ##### 步骤4: 初始化JavaVM指针 在JNI_OnLoad中存储全局JavaVM引用,用于多线程回调。 ```cpp // JNI初始化模块 JavaVM* g_jvm = NULL; // 全局JavaVM指针 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { g_jvm = vm; // 存储全局引用 return JNI_VERSION_1_6; } ``` #### 3. **关键注意事项** - **线程安全**:JNIEnv是线程相关的。在非Java线程(如C事件循环)中调用回调时,必须通过`AttachCurrentThread`获取JNIEnv[^4]。否则会导致崩溃。 - **引用管理**:使用`NewGlobalRef`存储全局对象引用,避免被垃圾回收;用后需`DeleteGlobalRef`释放,防止内存泄漏[^4][^5]。 - **方法签名**:Java方法签名(如`"(ILjava/lang/String;)V"`)必须与接口一致。可使用`javap -s`命令验证签名[^5]。 - **动态库支持**:函数指针在运行时确定,支持动态库扩展(如`dlopen`加载)[^3]。 - **性能影响**:JNI调用有一定开销,频繁回调需优化批处理或异步机制。 #### 4. **是否可以实现?** 是的,完全可实现。JNI标准支持这种逆向调用模式,广泛应用于Android HAL、事件驱动系统等场景,以实现Java与C/C++的解耦交互[^4][^5]。优点包括: - **解耦性**:Java与C独立开发维护,减少依赖[^2]。 - **灵活性**:C通过函数指针泛化调用,支持多种Java实现[^3]。 - **异步处理**:C耗时操作完成后回调通知Java,避免阻塞[^2]。 ####
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值