1.什么是AAR打包
这里引用deepseek的尺度回复:Android开发中用于封装和复用代码及资源的一种尺度格式,全称为Android Archive(文件扩展名为.aar)。在现实项目中,特别是车机应用中,把UE5作为第三方库,通过JNI函数的方式提供给安卓引擎函数,使安卓端不通过NDK的方式,更加灵活的使用UE5进行渲染。说得普通点,就是安卓开发也可以自界说UI、其他业务逻辑等,仅把UE作为3D渲染引擎,可以实现安卓端点击某些按钮控制UE场景结果等
2. 安卓和c++交互简介
安卓端和c++使用JNI函数进行交互:尺度格式是
JNI_METHOD void java_com_包名_类名_函数名(函数参数)
实例:
- JNI_METHOD jint Java_com_epicgames_unreal_nativeMain(JNIEnv* jenv, jobject thiz, jstring projectModule, jobjcet activity)
复制代码 JAVA端使用界说:
留意JNI函数前两个参数是安卓调用的时候自动通报的,第一个代表JAVA虚拟机对象,第二个体现当前调用类对象
- 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初始化引擎的函数
- int32 AndroidMain(struct android_app* state)
- {
- ......
- //加载安卓库
- void* Lib = dlopen("libandroid.so",0);
- if (Lib != NULL)
- {
- GetAxes = (GetAxesType)dlsym(Lib, "AMotionEvent_getAxisValue");
- }
- //调用LaunchEngineLoop.cpp中PreInit函数
- int32 PreInitResult = GEngineLoop.PreInit(0, NULL, FCommandLine::Get());
- ........
- //调用LaunchEngineLoop.cpp中Init函数
- ErrorLevel = GEngineLoop.Init();
- ........
- // 启动引擎tick
- while (!IsEngineExitRequested())
- {
- FAndroidStats::UpdateAndroidStats();
- FAppEventManager::GetInstance()->Tick();
- if (!FAppEventManager::GetInstance()->IsGamePaused())
- {
- GEngineLoop.Tick();
- }
- else
- {
- // use less CPU when paused
- FPlatformProcess::Sleep(0.10f);
- }
- }
- }
复制代码 上述代码片段,描述了初始化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接口获取
- if ((EventSource & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE)
- {
- static int32 previousButtonState = 0;
- const int32 device = AInputEvent_getDeviceId(event);
- const int32 action = AMotionEvent_getAction(event);
- const int32 actionType = action & AMOTION_EVENT_ACTION_MASK;
- int32 buttonState = AMotionEvent_getButtonState(event);
- .....
- }
复制代码 至于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
- JNI_METHOD void Java_com_epicgames_ue4_GameActivity_nativeMain(JNIEnv* jenv, jobject thiz, jstring projectModule)
- {
- dummy_state.activity = nullptr;
-
- pthread_attr_t otherAttr;
- pthread_attr_init(&otherAttr);
- pthread_attr_setdetachstate(&otherAttr, PTHREAD_CREATE_DETACHED);
- pthread_create(&G_AndroidMainThread, &otherAttr, AndroidMain, &dummy_state);
- }
复制代码 2.不依靠NDK接口,直接从安卓端把输入事故通报过来,再通过FAndroidInputInterface: ueueTouchInput绑定到UE里通报给slate UI
- JNI_METHOD jint Java_com_epicgames_ue4_GameActivity_nativeInputTouch(JNIEnv* jenv, jobject thiz, jint device, jint inType, jint inPointerId, jint inX, jint inY)
- {
- TArray<TouchInput> TouchesArray;
- TouchType type = TouchEnded;
- switch (inType)
- {
- case 0: type = TouchBegan; break;
- case 1: type = TouchMoved; break;
- case 2: type = TouchEnded; break;
- }
- ANativeWindow* Window = (ANativeWindow*)FAndroidWindow::GetHardwareWindow_EventThread();
- if (!Window)
- {
- return 0;
- }
- int32_t Width = 0;
- int32_t Height = 0;
- if (Window)
- {
- extern void* GAndroidWindowOverride;
- if (GAndroidWindowOverride != nullptr)
- {
- Width = ANativeWindow_getWidth((ANativeWindow*)GAndroidWindowOverride);
- Height = ANativeWindow_getHeight((ANativeWindow*)GAndroidWindowOverride);
- }
- else
- {
- // we are on the event thread. true here indicates we will retrieve dimensions from the current window.
- FAndroidWindow::CalculateSurfaceSize(Width, Height, true);
- }
- }
- // 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
- if (!GAndroidGPUInfoReady)
- {
- return 1;
- }
- FPlatformRect ScreenRect = FAndroidWindow::GetScreenRect(true);
- int32_t ScreenWidth = ScreenRect.Right - ScreenRect.Left;
- int32_t ScreenHeight = ScreenRect.Bottom - ScreenRect.Top;
- int32 eventX = inX - ScreenRect.Left;
- int32 eventY = inY - ScreenRect.Top;
- if (eventX < 0 || eventY < 0 || eventX >= ScreenWidth || eventY >= ScreenHeight)
- {
- return 0;
- }
- int pointerId = inPointerId;
- float x = FMath::Max<float>(FMath::Min<float>((float)eventX / Width, 1.f), 0.f) * (ScreenWidth - 1);
- float y = FMath::Max<float>(FMath::Min<float>((float)eventY / Height, 1.f), 0.f) * (ScreenHeight - 1);
- //FPlatformMisc::LowLevelOutputDebugStringf(TEXT("TOUCH: at (%d,%d) windowPos = (%d,%d), windowSize = (%d,%d), screenSize = (%d,%d), final pos: (%f,%f)"),
- // eventX, eventY, ScreenRect.Left, ScreenRect.Top, ScreenWidth, ScreenHeight, Width, Height, x, y);
- UE_LOG(LogAndroid, Verbose, TEXT("Received targeted motion event from pointer %u (id %d) action %d: (%.2f, %.2f)"), 0, pointerId, type, x, y);
- TouchInput TouchMessage;
- TouchMessage.DeviceId = device;
- TouchMessage.Handle = pointerId;
- TouchMessage.Type = type;
- TouchMessage.Position = FVector2D(x, y);
- TouchMessage.LastPosition = FVector2D(x, y);
- TouchesArray.Add(TouchMessage);
- FAndroidInputInterface::QueueTouchInput(TouchesArray);
- return 1;
- }
复制代码 3.命令控制app窗口生命周期并通知UE
- JNI_METHOD void Java_com_epicgames_ue4_GameActivity_nativeAppCommand(JNIEnv* jenv, jobject thiz, jint cmd)
- {
- extern void* GAndroidWindowOverride;
- dummy_state.window = (ANativeWindow*)GAndroidWindowOverride;
- dummy_state.pendingWindow = (ANativeWindow*)GAndroidWindowOverride;
- FPlatformMisc::LowLevelOutputDebugStringf(TEXT("OnAppCommandCB cmd: %u, tid = %d"), cmd, gettid());
- OnAppCommandCB(&dummy_state, cmd);
- }
复制代码 比方cmd通报APP_CMD_RESUME,唤醒UE窗口。
APP_CMD_PAUSE,暂停窗口渲染,制止音效等

4.通报自界说的surfaceview对象给UE进行渲染
- //This function is declared in the Java-defined class, GameActivity.java: "public native void nativeSetGlobalActivity();"
- 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*/)
- {
- if (!FJavaWrapper::GameActivityThis)
- {
- GGameActivityThis = FJavaWrapper::GameActivityThis = jenv->NewGlobalRef(thiz);
- if (!FJavaWrapper::GameActivityThis)
- {
- FPlatformMisc::LowLevelOutputDebugString(TEXT("Error setting the global GameActivity activity"));
- check(false);
- }
- // This call is only to set the correct GameActivityThis
- FAndroidApplication::InitializeJavaEnv(GJavaVM, JNI_CURRENT_VERSION, FJavaWrapper::GameActivityThis);
- .....
- }
复制代码 5.安卓重要调用片段
初始化调用nativeMain
唤醒安卓APP的时候,通过调用前面界说的JNI函数,nativeAppCommand通知UE
总结
末了总结一下,安卓与UE的交互是通过JNI函数的方式,所以安卓端可以更加灵活的控制,不依靠NDK限制,直接拉起UE,并控制UE的生命周期。比方车机端使用UE,想安卓和UE能更加深度的交互,并以安卓为主逻辑驱动,就需像AAR打包方式一样,自界说控制流程。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |