项目需求:

  1. 在Pico头显上做一个后台运行的录屏程序,因为前台我们要运行一个用户打游戏(无源码)的画面;
  2. 同时获取PicoNeo设备实时的位姿信息.
  3. PicoNeo的位姿信息,一般是通过Pico提供的UnitySDK获取的. Pvr_UnitySDKManager.SDK.HeadPose.Orientation

需求分析:

经过一些测试和调研,得到了以下结果:

  1. 当Unity程序在Android平台,切换到后台后,Update将不再调用;
    前台运行情况下, 是每帧 Update,从 Pico 提供的 UnitySDK 中获取头盔信息发送的我们的
    Android 库进行处理.
  2. 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库有两种:

  1. 直接调用第三方 So 满足 JNI 书写规则的要求
    由于不是自己编写的 so 库, 无法解决接口问题.
  2. 创建自己的 SO 文件,在自己的 SO 文件里调用第三方 SO.
    相当于,我们将这个 libPvr_UnitySDK.so 库再进行封装,在 C++层调用这个 so 库,那么
    就可以避开 JNI 接口的要求

那么现在的方案就是2.

  • 需要调用自己编写的so库再调用这个库

具体做法:

  • 在C++层通过 dlopen 加载第三方so库. 类似于Windows动态加载DLL
  • 通过 dlsym 知道对应函数. 必须知道具体API的参数

c++调用so文件

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");
}

方案结果

方案设计:

  1. Unity启动Android程序;
  2. Android程序加载so库;
  3. 开辟一个线程进行打印信息.

unity 关闭后台_unity 关闭后台

实验结果:

打印的数据如下所示:
03-31 19:19:30.941 I/Unity (15018): jayjzchen unity 后台
该行之后的信息为 Unity 程序进入后台打印的.四元数的数值是在动态改变的.
因此,所需功能实现了.

unity 关闭后台_Android_02

问题

  1. 通过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, "/");