在 UE 中为移动端添加第三方模块或者修改配置文件时经常会用到 AdditionalPropertiesForReceipt
,里面创建ReceiptProperty
传入的 xml
文件就是 UE 的 Unreal Plugin Language
脚本。
ReceiptProperty
的平台名称在 IOS 和 Android 上是固定的,分别是 IOSPlugin
和AndroidPlugin
,不可以指定其他的名字(详见代码 UEDeployIOS.cs#L1153 和UEDeployAndroid.cs#L4303 )。
1 AdditionalPropertiesForReceipt.Add(new ReceiptProperty("AndroidPlugin" , Path.Combine(ThirdPartyPath, "Android/PlatformUtils_UPL_Android.xml" )));
Android 项目中所有的 UPL 可以在项目路径下Intermediate/Android/ActiveUPL.xml
,里面列出了当前项目中所有的 UPL 文件路径:
1 2 3 4 5 6 7 Plugins\Online\Android\OnlineSubsystemGooglePlay\Source\OnlineSubsystemGooglePlay_UPL.xml Plugins\Runtime\AndroidPermission\Source\AndroidPermission\AndroidPermission_APL.xml Plugins\Runtime\GoogleCloudMessaging\Source\GoogleCloudMessaging\GoogleCloudMessaging_UPL.xml Plugins\Runtime\GooglePAD\Source\GooglePAD\GooglePAD_APL.xml Plugins\Runtime\Oculus\OculusVR\Source\OculusHMD\OculusMobile_APL.xml Source\Runtime\Online\Voice\AndroidVoiceImpl_UPL.xml Source\ThirdParty\GoogleGameSDK\GoogleGameSDK_APL.xml
删除 AndroidManifest.xml 中的项 因为 UE 默认会给 AndroidManifest.xml
添加项,如果其中的项我们想要手动控制,直接添加的话会产生错误,提示已经存在:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 UATHelper: Packaging (Android (ASTC)): > Task :app:processDebugManifest FAILED UATHelper: Packaging (Android (ASTC)): UATHelper: Packaging (Android (ASTC)): Z:\app\src\main\AndroidManifest.xml:47 :5 -106 Error: UATHelper: Packaging (Android (ASTC)): Element meta-data#com.epicgames.ue4.GameActivity.bUseExternalFilesDir at AndroidManifest.xml:47:5-106 duplicated with element declared at AndroidManifest.xml:27:5-107 UATHelper: Packaging (Android (ASTC)): Z:\app\src\main\AndroidManifest.xml Error: UATHelper: Packaging (Android (ASTC)): Validation failed, exiting UATHelper: Packaging (Android (ASTC)): UATHelper: Packaging (Android (ASTC)): FAILURE: Build failed with an exception. UATHelper: Packaging (Android (ASTC)): UATHelper: Packaging (Android (ASTC)): * What went wrong: UATHelper: Packaging (Android (ASTC)): Execution failed for task ':app:processDebugManifest' . UATHelper: Packaging (Android (ASTC)): > Manifest merger failed with multiple errors, see logs UATHelper: Packaging (Android (ASTC)): UATHelper: Packaging (Android (ASTC)): See http: UATHelper: Packaging (Android (ASTC)): UATHelper: Packaging (Android (ASTC)): * Try: UATHelper: Packaging (Android (ASTC)): Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. UATHelper: Packaging (Android (ASTC)): UATHelper: Packaging (Android (ASTC)): * Get more help at https: UATHelper: Packaging (Android (ASTC)): UATHelper: Packaging (Android (ASTC)): BUILD FAILED in 10 s UATHelper: Packaging (Android (ASTC)): 189 actionable tasks: 1 executed, 188 up-to-date UATHelper: Packaging (Android (ASTC)): ERROR: cmd.exe failed with args /c "C:\Users\lipengzha\Documents\Unreal Projects\GCloudExample\Intermediate\Android\armv7\gradle\rungradle.bat" :app:assembleDebug PackagingResults: Error: cmd.exe failed with args /c "C:\Users\lipengzha\Documents\Unreal Projects\GCloudExample\Intermediate\Android\armv7\gradle\rungradle.bat" :app:assembleDebug UATHelper: Packaging (Android (ASTC)): Took 13.3060694 s to run UnrealBuildTool.exe, ExitCode=6 UATHelper: Packaging (Android (ASTC)): UnrealBuildTool failed. See log for more details. (C:\Users\lipengzha\AppData\Roaming\Unreal Engine\AutomationTool\Logs\C+Program+Files+Epic+Games+UE_4.25 \UBT-.txt) UATHelper: Packaging (Android (ASTC)): AutomationTool exiting with ExitCode=6 (6 ) UATHelper: Packaging (Android (ASTC)): BUILD FAILED PackagingResults: Error: Unknown Error
如果想要修改或者删除 UE 默认生成的 AndroidManifest.xml
中的项,可以通过先删除再添加的方式。
以删除以下项为例:
1 <meta-data android:name="com.epicgames.ue4.GameActivity.bUseExternalFilesDir" android:value="false" />
在 UPL 的 androidManifestUpdates
中编写以下代码:
1 2 3 4 5 6 7 8 9 10 11 <androidManifestUpdates > <loopElements tag ="meta-data" > <setStringFromAttribute result ="ApplicationSectionName" tag ="$" name ="android:name" /> <setBoolIsEqual result ="bUseExternalFilesDir" arg1 ="$S(ApplicationSectionName)" arg2 ="com.epicgames.ue4.GameActivity.bUseExternalFilesDir" /> <if condition ="bUseExternalFilesDir" > <true > <removeElement tag ="$" /> </true > </if > </loopElements > </androidManifestUpdates >
就是去遍历 AndroidManfest.xml
中已经存在 meta-data
中,android:name
为 com.epicgames.ue4.GameActivity.bUseExternalFilesDir
的项给删除。
JNI 调用接收 ActivityResult 有时需要通过 startActivityForResult
来创建 Intent
来执行一些操作,如打开摄像头、打开相册选择图片等。
但是 Android 做这些操作的时候不是阻塞在当前的函数中的,所以不能直接在调用的函数里接收这些数据。而通过 startActivityForResult
执行的 Action 的结果都会调用到 Activity 的 onActivityResult
中。
1 2 3 @Override protected void onActivityResult (int requestCode, int resultCode, Intent data) {}
UE 在 UPL 中提供了往 OnActivityResult 追加 Java 代码的用法:
1 2 <gameActivityOnActivityResultAdditions > </gameActivityOnActivityResultAdditions >
使用这种方式添加的 Java 代码会追加到 OnActivityResult
函数的末尾,但是这种方式有一个问题,那就是执行了自己追加到 OnActivityResult
的代码之后,还要处理接收到的结果,并且传递到 UE 端来,有点麻烦。
经过翻阅代码,发现 UE 提供了 Java 端的 OnActivityResult
的多播代理事件,这样就可以直接在 UE 里用 C++ 来监听 OnActivityResult
的事件,自己做处理。
1 2 3 4 5 6 DECLARE_MULTICAST_DELEGATE_SixParams (FOnActivityResult, JNIEnv *, jobject, jobject, jint, jint, jobject);static FOnActivityResult OnActivityResultDelegate;
在 UE 侧就可以通过绑定这个多播代理来监听 Java 端的 OnActivityResult
调用,可以在其中做分别的处理。
它由 AndroidJNI.cpp
中的 Java_com_epicgames_ue4_GameActivity_nativeOnActivityResult
函数从 Java 那边调用过来,调用机制在上个笔记中有记录。
Java 调 C++ 有些需求和实现需要从 Java 调到 C++ 这边,可以通过下面这种方式: 首先,在 GameActivity 中新建一个 native
的 java 函数声明(不需要定义):
1 public native void nativeOnActivityResult (GameActivity activity, int requestCode, int resultCode, Intent data) ;
然后在 C++ 端按照下面的规则定义一个 C++ 函数:
1 2 3 4 JNI_METHOD void Java_com_epicgames_ue4_GameActivity_nativeOnActivityResult (JNIEnv* jenv, jobject thiz, jobject activity, jint requestCode, jint resultCode, jobject data) { FJavaWrapper::OnActivityResultDelegate.Broadcast (jenv, thiz, activity, requestCode, resultCode, data); }
可以看到函数名字的规则为:
函数前需要加 JNI_METHOD
修饰,它是一个宏__attribute__ ((visibility ("default"))) extern "C"
函数名需要以 Java_
开头,并且后面跟上 com_epicgames_ue4_GameActivity_
,标识是定义在GameActivity
中的
然后再跟上 java 中的函数名
接受参数的规则:
第一个参数是 Java 的 Env
第二个是 java 里的 this
后面的参数以此是从 java 里传递参数
AndroidP 的全面屏适配 在 UE4 打包的时候,会给项目生成 GameActivity.java 文件,里面的 OnCreate 具有适配全面屏的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public void onCreate (Bundle savedInstanceState) { if (UseDisplayCutout) { WindowManager.LayoutParams params = getWindow().getAttributes(); params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; getWindow().setAttributes(params); } }
Android P 和之后的系统不支持,所以就要自己写 Jni 调用来强制让 P 和之后系统版本支持。 在 UE4.23+ 以后的引擎版本,支持了通过 UPL 往 OnCreate 函数添加代码,就可以直接把代码插入到 GameActivity.java 了:
1 2 3 4 5 6 7 8 9 10 <gameActivityOnCreateFinalAdditions> <insert> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { WindowManager.LayoutParams lp = this .getWindow().getAttributes(); lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; this .getWindow().setAttributes(lp); } </insert> </gameActivityOnCreateFinalAdditions>
在 UE4.23 之前不可以给 OnCreate
添加代码,只能自己写个 JNI 调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void AndroidThunkJava_SetFullScreenDisplayForP () { final GameActivity Activity = this ; runOnUiThread(new Runnable() { private GameActivity InActivity = Activity; public void run () { WindowManager windowManager = InActivity.getWindowManager(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { WindowManager.LayoutParams lp = InActivity.getWindow().getAttributes(); lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; InActivity.getWindow().setAttributes(lp); Log.debug("call AndroidThunkJava_SetFullScreenDisplayForP" ); } } }); }
将其在 UPL 中通过 <gameActivityClassAdditions>
添加到 GameActivity.java 中,在游戏启动时通过 JNI 调用即可。
注:
layoutInDisplayCutoutMode
的可选项为:
外部资料
Android 重启 App 当游戏更新完毕之后有时候需要重启 App 才可以生效,在 UE 中可以使用 UPL 写入以下 java 代码:
1 2 3 4 5 6 7 8 9 10 public void AndroidThunkJava_AndroidAPI_RestartApplication () { Context context = getApplicationContext(); PackageManager pm = context.getPackageManager(); Intent intent = pm.getLaunchIntentForPackage(context.getPackageName()); int delayTime = 500 ; AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); PendingIntent restartIntent = PendingIntent.getActivity(context, 0 , intent, PendingIntent.FLAG_CANCEL_CURRENT); alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + delayTime, restartIntent); System.exit(0 ); }
在需要重启的时候通过 jni 调用来触发:
1 2 3 4 5 6 7 8 9 void RestartApplication () {#if PLATFORM_ANDROID if (JNIEnv* Env = FAndroidApplication::GetJavaEnv (true )) { FJavaWrapper::CallVoidMethod (Env, FJavaWrapper::GameActivityThis, AndroidThunkJava_AndroidAPI_RestartApplication); } #endif }