项目需求:
- 在Pico头显上做一个后台运行的录屏程序,因为前台我们要运行一个用户打游戏(无源码)的画面;
- 同时获取PicoNeo设备实时的位姿信息.
- PicoNeo的位姿信息,一般是通过Pico提供的UnitySDK获取的. Pvr_UnitySDKManager.SDK.HeadPose.Orientation
需求分析:
经过一些测试和调研,得到了以下结果:
- 当Unity程序在Android平台,切换到后台后,Update将不再调用;
前台运行情况下, 是每帧 Update,从 Pico 提供的 UnitySDK 中获取头盔信息发送的我们的
Android 库进行处理. - Android 的线程, 可以在后台情况下持续地跑. 它就类似于 Service 的存在.
因此, 由于 Unity 休眠了,想从 Pico 的 UnitySDK 拿数据,就是不可能成功的了.
拟尝试方案
在看 PicoSDK 源码的时候,发现了一个有趣的现象:
- 这些头盔位置信息都是从一个so库( libPvr_UnitySDK.so )中获取的.
- 核心函数为: Pvr_GetMainSensorState
- C#接口:
- so库反编译结果:
具体的方案就是:
只要我们能在 Android 里把这个 so 库用起来,应该就可以解决我们的问题
Android调用so库
这里涉及到一些Android NDK 和JNI开发的知识.
NDK Android.mk 和 Application.mk的编写
如何打印NDK日志
Android如何调用第三方SO库有两种:
- 直接调用第三方 So 满足 JNI 书写规则的要求
由于不是自己编写的 so 库, 无法解决接口问题. - 创建自己的 SO 文件,在自己的 SO 文件里调用第三方 SO.
相当于,我们将这个 libPvr_UnitySDK.so 库再进行封装,在 C++层调用这个 so 库,那么
就可以避开 JNI 接口的要求
那么现在的方案就是2.
- 需要调用自己编写的so库再调用这个库
具体做法:
- 在C++层通过
dlopen
加载第三方so库. 类似于Windows动态加载DLL - 通过
dlsym
知道对应函数. 必须知道具体API的参数
Demo
JNIEXPORT void JNICALL
Java_com_compilelife_mediacodecexample_MainActivity_init__(JNIEnv *env, jobject instance) {
LOGE("Java_com_compilelife_mediacodecexample_MainActivity_init__ begin!!!");
// https://stackoverflow.com/questions/30368096/directly-call-function-in-another-so-file-from-c-code-in-android-ndk/30368631
const char* temp;
jobject oActivity = instance;
jclass cActivity = env->GetObjectClass(oActivity);
// get the path to where android extracts native libraries to
jmethodID midActivityGetApplicationInfo = env->GetMethodID(cActivity, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
jobject oApplicationInfo = env->CallObjectMethod(oActivity, midActivityGetApplicationInfo);
jclass cApplicationInfo = env->GetObjectClass(oApplicationInfo);
jfieldID fidApplicationInfoNativeLibraryDir = env->GetFieldID(cApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;");
jstring sNativeLibraryDir = (jstring)env->GetObjectField(oApplicationInfo, fidApplicationInfoNativeLibraryDir);
temp = env->GetStringUTFChars(sNativeLibraryDir, NULL);
char *libpath = new char[1024];
strcpy(libpath, temp);
strcat(libpath, "/libPvr_UnitySDK.so");
LOGE("libpath:%s",libpath);
// 1.打开 libPvr_UnitySDK.so
// "./libPvr_UnitySDK.so"
g_handle = dlopen(libpath, RTLD_LAZY);
if(!g_handle){
LOGE("open lib error\n");
LOGE("dlopen - %sn", dlerror());
return ;
}else{
LOGE("Find libPvr_UnitySDK.so!!!\n");
}
// 2. 初始化Sensor
initMethod = (Pvr_Init)dlsym(g_handle,"Pvr_Init");
if(!initMethod){
LOGE("can not find Pvr_Init function\n");
dlclose(g_handle);
return ;
}else{
LOGE("Find Pvr_Init!!!\n");
}
if(initMethod(g_SensorIndexDefault)==0){
g_initEnable = true;
LOGE("g_initEnable is true\n");
}
// 3. 启动Sensor
startMethod = (Pvr_StartSensor)dlsym(g_handle,"Pvr_StartSensor");
if(!startMethod){
printf("can not find Pvr_StartSensor function\n");
dlclose(g_handle);
return ;
}else{
LOGE("Find Pvr_StartSensor!!!\n");
}
if(startMethod(g_SensorIndexDefault)==0){
g_startEnable = true;
LOGE("g_startEnable is true\n");
}
// 4.
restMethod = (Pvr_ResetSensor)dlsym(g_handle,"Pvr_ResetSensor");
stopMethod = (Pvr_StopSensor)dlsym(g_handle,"Pvr_StopSensor");
updateMethod = (Pvr_GetMainSensorState)dlsym(g_handle,"Pvr_GetMainSensorState");
if(!updateMethod){
LOGE("can not find Pvr_GetMainSensorState function!!!\n");
dlclose(g_handle);
return ;
}else{
LOGE("Find Pvr_GetMainSensorState!!!\n");
}
LOGE("Java_com_compilelife_mediacodecexample_MainActivity_init__ end!!!\n");
}
方案结果
方案设计:
- Unity启动Android程序;
- Android程序加载so库;
- 开辟一个线程进行打印信息.
实验结果:
打印的数据如下所示:
03-31 19:19:30.941 I/Unity (15018): jayjzchen unity 后台
该行之后的信息为 Unity 程序进入后台打印的.四元数的数值是在动态改变的.
因此,所需功能实现了.
问题
- 通过dlopen加载so库文件找不到?
Directly call function in another .so file from C++ code in Android NDK
解决方案:
You can do it this way on Android, though take care of where the shared library has been put in Android folders. It can change from a version to another. On api 17 for example, it remains in /data/app-lib/. You can hardwrite it, but the best is to make calls to Java (through JNI) to know where the libraries should be. We’re doing something like this in our project :
JNIEnv* env;
const char* temp;
jobject oActivity = state->activity->clazz;
jclass cActivity = env->GetObjectClass(oActivity);
// get the path to where android extracts native libraries to
jmethodID midActivityGetApplicationInfo = env->GetMethodID(cActivity, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
jobject oApplicationInfo = env->CallObjectMethod(oActivity, midActivityGetApplicationInfo);
jclass cApplicationInfo = env->GetObjectClass(oApplicationInfo);
jfieldID fidApplicationInfoNativeLibraryDir = env->GetFieldID(cApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;");
jstring sNativeLibraryDir = (jstring)env->GetObjectField(oApplicationInfo, fidApplicationInfoNativeLibraryDir);
temp = env->GetStringUTFChars(sNativeLibraryDir, NULL);
strcpy(libpath, temp);
strcat(libpath, "/");