UE5安卓AAR打包

打印 上一主题 下一主题

主题 1950|帖子 1950|积分 5850

1.什么是AAR打包

这里引用deepseek的尺度回复:Android开发中用于封装和复用代码及资源的一种尺度格式,全称为Android Archive(文件扩展名为.aar)。在现实项目中,特别是车机应用中,把UE5作为第三方库,通过JNI函数的方式提供给安卓引擎函数,使安卓端不通过NDK的方式,更加灵活的使用UE5进行渲染。说得普通点,就是安卓开发也可以自界说UI、其他业务逻辑等,仅把UE作为3D渲染引擎,可以实现安卓端点击某些按钮控制UE场景结果等
2. 安卓和c++交互简介

安卓端和c++使用JNI函数进行交互:尺度格式是
JNI_METHOD void java_com_包名_类名_函数名(函数参数)
实例:
  1. JNI_METHOD jint Java_com_epicgames_unreal_nativeMain(JNIEnv* jenv, jobject thiz, jstring projectModule, jobjcet activity)
复制代码
JAVA端使用界说:
留意JNI函数前两个参数是安卓调用的时候自动通报的,第一个代表JAVA虚拟机对象,第二个体现当前调用类对象
  1. public native void nativeMain(String projectModule, Activity activity);
复制代码
3.UE5安卓端apk引擎初始化流程

安卓NDK源代码中会创建线程调用c++的全局函数android_main(struct android_app* state),所以对于UE来说,在LauchAndroid.cpp中实现了android_main函数。

这个函数重要调用AndroidMain函数,AndroidMain函数才是重要调用LaunchEngineLoop初始化引擎的函数
  1. int32 AndroidMain(struct android_app* state)
  2. {
  3.         ......
  4.         //加载安卓库
  5.         void* Lib = dlopen("libandroid.so",0);
  6.         if (Lib != NULL)
  7.         {
  8.                 GetAxes = (GetAxesType)dlsym(Lib, "AMotionEvent_getAxisValue");
  9.         }
  10.         //调用LaunchEngineLoop.cpp中PreInit函数       
  11.         int32 PreInitResult = GEngineLoop.PreInit(0, NULL, FCommandLine::Get());
  12.         ........
  13.         //调用LaunchEngineLoop.cpp中Init函数
  14.         ErrorLevel = GEngineLoop.Init();
  15.         ........
  16.         // 启动引擎tick
  17.         while (!IsEngineExitRequested())
  18.         {
  19.                 FAndroidStats::UpdateAndroidStats();
  20.                 FAppEventManager::GetInstance()->Tick();
  21.                 if (!FAppEventManager::GetInstance()->IsGamePaused())
  22.                 {
  23.                         GEngineLoop.Tick();
  24.                 }
  25.                 else
  26.                 {
  27.                         // use less CPU when paused
  28.                         FPlatformProcess::Sleep(0.10f);
  29.                 }
  30.                 }
  31. }
复制代码
上述代码片段,描述了初始化UE引擎的重要过程,这个过程中PreInit和Init之间还会死循环卡死,等待安卓端调用对应的JNI函数响应初始化流程。
EngineLoop:reInit具体实现

简单描述下:PreInitPreStartupScreen里面干了很多事情
1.加载核心CoreUobject模块,初始化反射体系基础类对象UClass,UFunction等
2.初始化全局GGameThreadId游戏线程ID
3.创建TaskGraph线程池
4.加载其他的核心模块,Engine、Renderer、SlateRHIRenderer、RenderCore、RHICore等
5.调用不同平台层preInit函数
6.调用InitGamePhys初始化物理引擎
7.创建不同平台层FSlateApplication对象,进而调用不同平台创建窗口函数
8.调用不同平台层的PlatformCreateDynamicRHI函数创建RHI对象,有的平台支持Vulkan,有的支持OpenGL,windows平台下使用DX,都是在这里处置惩罚的
9.加载插件等等
PreInitPostStartupScreen
1.调用显示开机加载动画
2.加载资源管理模块AssetRegistry
3.调用ProcessNewlyLoadedUObjects再次确保CoreUObejct核心反射模块已加载
4.InitRenderingThread()初始化rendering渲染线程
EngineLoop::Init重要作用是创建:
1.创建GEngine引擎对象,编辑器对应UEditorEngine,游戏模式对应UGameEngine,然后在GEngine初始化的时候创建GameInstance
2.初始化MoviePlayer体系等
3.FCoreDelegates::OnFEngineLoopInitComplete通知引擎初始化完
EngineLoop::Tick重要实现:
1.调用BeginFrameRenderThread(RHICmdList, CurrentFrameCounter)
2.CalculateFPSTimings()计算帧率
3.调用GEngine->Tick,这里才是游戏逻辑Tick入口,重要作用是:
1)调用TickWorldTravel检测是否是要无缝加载舆图,
2)调用UWorld::Tick( ELevelTick TickType, float DeltaSeconds ),每帧开始收网络同步包,异步加载资源,调用TickGroup(本质是Actor和ActorComponent等的Tick函数被Level::AddTickFunction函数参加Tick队列),每帧末了发网络包,GC回收检测等
3)调用FTickableGameObject::TickObjects,实现继续自FTickableGameObject类对象的Tick函数调用
apk输入事故绑定
android_main里面给事故获取单独开了个线程

现实输入事故绑定是通过给NDK注册输入回调HandleInputCB

HandleInputCB中具体代码实现,直接通过NDK接口获取
  1. if ((EventSource & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE)
  2. {
  3.         static int32 previousButtonState = 0;
  4.         const int32 device = AInputEvent_getDeviceId(event);
  5.         const int32 action = AMotionEvent_getAction(event);
  6.         const int32 actionType = action & AMOTION_EVENT_ACTION_MASK;
  7.         int32 buttonState = AMotionEvent_getButtonState(event);
  8.         .....
  9. }
复制代码
至于UE的渲染结果是怎样绑定渲染到安卓界面窗口的?涉及到AndroidEGL等类,后续有空再先容,简而言之是安卓端会通报一个surfaceview UI对象到UE,然后转换成ANativeWindow对象进行渲染。
4.APK打包流程简介

省略前面cook资源流程,着重先容下cook完后与安卓端干系的交互
1)生成安卓gradle工程

一般打包后会在Intermediate目录下生成这些内容

assets: 包含了一个main.obb.png文件,所有的蓝图,贴图,关卡资源都会被打包到这里面,末了提供给安卓端解析

gradle: 这是安卓工程,可以直接用AndroidStudio打开,里面包含了安卓端源代码,比力核心的就是GameActivity.java,里面包含了安卓端唤醒和挂起APP后,控制UE怎样响应的代码。
安卓端源代码是通过拷贝引擎准备好的模板,即引擎目录下UE_5.2\Engine\Build\Android\Java\src\com\epicgames\unreal,每次构建的时候都会拷贝模板重新生成,所以书写安卓代码实在并不方便。也就衍生了自建安卓gradle工程,以及不依靠NDK接口,安卓端控制UE拉起,即后面要先容的AAR打包调用
jni: 包含UE5的c++代码就会被打包成libUnreal.so库(注UE4叫libUE4.so)
5.AAR安卓端调用UE方式实现

这里工作量实在重要都在安卓端,UE只是提供控制的JNI接口。
1.提供初始化引擎重要入口nativeMain,不依靠安卓NDK
  1. JNI_METHOD void Java_com_epicgames_ue4_GameActivity_nativeMain(JNIEnv* jenv, jobject thiz, jstring projectModule)
  2. {
  3.         dummy_state.activity = nullptr;
  4.        
  5.         pthread_attr_t otherAttr;
  6.         pthread_attr_init(&otherAttr);
  7.         pthread_attr_setdetachstate(&otherAttr, PTHREAD_CREATE_DETACHED);
  8.         pthread_create(&G_AndroidMainThread, &otherAttr, AndroidMain, &dummy_state);
  9. }
复制代码
2.不依靠NDK接口,直接从安卓端把输入事故通报过来,再通过FAndroidInputInterface:ueueTouchInput绑定到UE里通报给slate UI
  1. JNI_METHOD jint Java_com_epicgames_ue4_GameActivity_nativeInputTouch(JNIEnv* jenv, jobject thiz, jint device, jint inType, jint inPointerId, jint inX, jint inY)
  2. {
  3.         TArray<TouchInput> TouchesArray;
  4.         TouchType type = TouchEnded;
  5.         switch (inType)
  6.         {
  7.                 case 0: type = TouchBegan; break;
  8.                 case 1: type = TouchMoved; break;
  9.                 case 2: type = TouchEnded; break;
  10.         }
  11.         ANativeWindow* Window = (ANativeWindow*)FAndroidWindow::GetHardwareWindow_EventThread();
  12.         if (!Window)
  13.         {
  14.                 return 0;
  15.         }
  16.         int32_t Width = 0;
  17.         int32_t Height = 0;
  18.         if (Window)
  19.         {
  20.                 extern void* GAndroidWindowOverride;
  21.                 if (GAndroidWindowOverride != nullptr)
  22.                 {
  23.                         Width = ANativeWindow_getWidth((ANativeWindow*)GAndroidWindowOverride);
  24.                         Height = ANativeWindow_getHeight((ANativeWindow*)GAndroidWindowOverride);
  25.                 }
  26.                 else
  27.                 {
  28.                         // we are on the event thread. true here indicates we will retrieve dimensions from the current window.
  29.                         FAndroidWindow::CalculateSurfaceSize(Width, Height, true);
  30.                 }
  31.         }
  32.         // make sure OpenGL context created before accepting touch events.. FAndroidWindow::GetScreenRect() may try to create it early from wrong thread if this is the first call
  33.         if (!GAndroidGPUInfoReady)
  34.         {
  35.                 return 1;
  36.         }
  37.         FPlatformRect ScreenRect = FAndroidWindow::GetScreenRect(true);
  38.         int32_t ScreenWidth = ScreenRect.Right - ScreenRect.Left;
  39.         int32_t ScreenHeight = ScreenRect.Bottom - ScreenRect.Top;
  40.         int32 eventX = inX - ScreenRect.Left;
  41.         int32 eventY = inY - ScreenRect.Top;
  42.         if (eventX < 0 || eventY < 0 || eventX >= ScreenWidth || eventY >= ScreenHeight)
  43.         {
  44.                 return 0;
  45.         }
  46.         int pointerId = inPointerId;
  47.         float x = FMath::Max<float>(FMath::Min<float>((float)eventX / Width, 1.f), 0.f) * (ScreenWidth - 1);
  48.         float y = FMath::Max<float>(FMath::Min<float>((float)eventY / Height, 1.f), 0.f) * (ScreenHeight - 1);
  49.         //FPlatformMisc::LowLevelOutputDebugStringf(TEXT("TOUCH: at (%d,%d) windowPos = (%d,%d), windowSize = (%d,%d), screenSize = (%d,%d), final pos: (%f,%f)"),
  50.         //        eventX, eventY, ScreenRect.Left, ScreenRect.Top, ScreenWidth, ScreenHeight, Width, Height, x, y);
  51.         UE_LOG(LogAndroid, Verbose, TEXT("Received targeted motion event from pointer %u (id %d) action %d: (%.2f, %.2f)"), 0, pointerId, type, x, y);
  52.         TouchInput TouchMessage;
  53.         TouchMessage.DeviceId = device;
  54.         TouchMessage.Handle = pointerId;
  55.         TouchMessage.Type = type;
  56.         TouchMessage.Position = FVector2D(x, y);
  57.         TouchMessage.LastPosition = FVector2D(x, y);
  58.         TouchesArray.Add(TouchMessage);
  59.         FAndroidInputInterface::QueueTouchInput(TouchesArray);
  60.         return 1;
  61. }
复制代码
3.命令控制app窗口生命周期并通知UE
  1. JNI_METHOD void Java_com_epicgames_ue4_GameActivity_nativeAppCommand(JNIEnv* jenv, jobject thiz, jint cmd)
  2. {
  3.         extern void* GAndroidWindowOverride;
  4.         dummy_state.window = (ANativeWindow*)GAndroidWindowOverride;
  5.         dummy_state.pendingWindow = (ANativeWindow*)GAndroidWindowOverride;
  6.         FPlatformMisc::LowLevelOutputDebugStringf(TEXT("OnAppCommandCB cmd: %u, tid = %d"), cmd, gettid());
  7.         OnAppCommandCB(&dummy_state, cmd);
  8. }
复制代码
比方cmd通报APP_CMD_RESUME,唤醒UE窗口。
APP_CMD_PAUSE,暂停窗口渲染,制止音效等

4.通报自界说的surfaceview对象给UE进行渲染
  1. //This function is declared in the Java-defined class, GameActivity.java: "public native void nativeSetGlobalActivity();"
  2. JNI_METHOD void Java_com_epicgames_ue4_GameActivity_nativeSetGlobalActivity(JNIEnv* jenv, jobject thiz, jboolean bUseExternalFilesDir, jboolean bPublicLogFiles, jstring internalFilePath, jstring externalFilePath, jboolean bOBBinAPK, jstring APKFilename /*, jobject googleServices*/)
  3. {
  4.         if (!FJavaWrapper::GameActivityThis)
  5.         {
  6.                 GGameActivityThis = FJavaWrapper::GameActivityThis = jenv->NewGlobalRef(thiz);
  7.                 if (!FJavaWrapper::GameActivityThis)
  8.                 {
  9.                         FPlatformMisc::LowLevelOutputDebugString(TEXT("Error setting the global GameActivity activity"));
  10.                         check(false);
  11.                 }
  12.                 // This call is only to set the correct GameActivityThis
  13.                 FAndroidApplication::InitializeJavaEnv(GJavaVM, JNI_CURRENT_VERSION, FJavaWrapper::GameActivityThis);
  14.         .....
  15. }
复制代码
5.安卓重要调用片段
初始化调用nativeMain

唤醒安卓APP的时候,通过调用前面界说的JNI函数,nativeAppCommand通知UE

总结

末了总结一下,安卓与UE的交互是通过JNI函数的方式,所以安卓端可以更加灵活的控制,不依靠NDK限制,直接拉起UE,并控制UE的生命周期。比方车机端使用UE,想安卓和UE能更加深度的交互,并以安卓为主逻辑驱动,就需像AAR打包方式一样,自界说控制流程。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

笑看天下无敌手

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表