默认情况下,使用 UE 打包出游戏的 Apk 并在手机上安装之后,启动游戏会在 /storage/emulated/0/UE4Game/
下创建游戏的数据目录 (也就是内部存储器的根目录下)。按照 Google 的规则,每个 APP 的数据文件最好都是放在自己的私有目录,所以我想要把 UE 打包出来的游戏的数据全放到/storage/emulated/0/Android/data/PACKAGE_NAME
目录中 (不管是 log、ini、还是 crash 信息)。 一个看似简单的需求,有几种不同的方法,涉及到了 UE4 的路径管理 /JNI/Android Manifest 以及对 UBT 的代码的分析。
默认的路径:
有两种方法,一种是改动引擎代码实现对 GFilePathBase
的修改,另一种是不改动引擎只添加项目设置中的 manifest
就可以,当然不改动引擎是最好的,不过既然是分析,我就两个都来搞一下,顺便从 UBT 代码分析一下 Project Setting
-Android
-Use ExternalFilesDir for UE4Game Files
选项没有作用的原因。
改动引擎代码实现 翻了一下引擎代码,发现路径的这部分代码是写在这里的:AndroidPlatformFile.cpp#L946 ,它是在 GFilePathBase
然后组合 UE4Game
+PROJECT_NAME
的路径。
在 UE4.22 及之前的引擎版本中是在 AndroidFile.cpp
文件中的,4.23+ 是在 AndroidPlatformFile.cpp
中的。 基础路径 GFilePathBase
的初始化是在 Launch\Private\Android\AndroidJNI.cpp
中:
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 JNIEXPORT jint JNI_OnLoad (JavaVM* InJavaVM, void * InReserved) { FPlatformMisc::LowLevelOutputDebugString (TEXT ("In the JNI_OnLoad function" )); JNIEnv* Env = NULL ; InJavaVM->GetEnv ((void **)&Env, JNI_CURRENT_VERSION); GJavaVM = InJavaVM; FAndroidApplication::InitializeJavaEnv (GJavaVM, JNI_CURRENT_VERSION, FJavaWrapper::GameActivityThis); FJavaWrapper::FindClassesAndMethods (Env); if (!FPlatformMisc::IsDebuggerPresent () || GAlwaysReportCrash) { } jclass EnvClass = Env->FindClass ("android/os/Environment" ); jmethodID getExternalStorageDir = Env->GetStaticMethodID (EnvClass, "getExternalStorageDirectory" , "()Ljava/io/File;" ); jobject externalStoragePath = Env->CallStaticObjectMethod (EnvClass, getExternalStorageDir, nullptr ); jmethodID getFilePath = Env->GetMethodID (Env->FindClass ("java/io/File" ), "getPath" , "()Ljava/lang/String;" ); jstring pathString = (jstring)Env->CallObjectMethod (externalStoragePath, getFilePath, nullptr ); const char *nativePathString = Env->GetStringUTFChars (pathString, 0 ); GFilePathBase = FString (nativePathString); GOBBFilePathBase = GFilePathBase; Env->ReleaseStringUTFChars (pathString, nativePathString); Env->DeleteLocalRef (pathString); Env->DeleteLocalRef (externalStoragePath); Env->DeleteLocalRef (EnvClass); FPlatformMisc::LowLevelOutputDebugStringf (TEXT ("Path found as '%s'\n" ), *GFilePathBase); jstring fontPath = (jstring)Env->CallStaticObjectMethod (FJavaWrapper::GameActivityClassID, FJavaWrapper::AndroidThunkJava_GetFontDirectory); const char * nativeFontPathString = Env->GetStringUTFChars (fontPath, 0 ); GFontPathBase = FString (nativeFontPathString); Env->ReleaseStringUTFChars (fontPath, nativeFontPathString); Env->DeleteLocalRef (fontPath); FPlatformMisc::LowLevelOutputDebugStringf (TEXT ("Font Path found as '%s'\n" ), *GFontPathBase); DECLARE_DELEGATE_OneParam (FAndroidLaunchURLDelegate, const FString&); extern CORE_API FAndroidLaunchURLDelegate OnAndroidLaunchURL; OnAndroidLaunchURL = FAndroidLaunchURLDelegate::CreateStatic (&AndroidThunkCpp_LaunchURL); FPlatformMisc::LowLevelOutputDebugString (TEXT ("In the JNI_OnLoad function 5" )); char mainThreadName[] = "MainThread-UE4" ; AndroidThunkCpp_SetThreadName (mainThreadName); return JNI_CURRENT_VERSION; }
我们的目的就是要改动 GFilePathBase
的值,因为默认引擎里是通过调用 getExternalStorageDirectory
得到的,其是外部存储的目录即 /storage/emulated/0/
,再拼接上UE4Game
就是默认平时我们看到的路径。
因为 getExternalStorageDirectory
这些都是 Environment 的静态成员,没有我们想要获取的路径的方法,但是 Context
中有,UE 的代码中并没有获取到,所以我们要像一个办法得到 App 的 Context。
可以通过下列方法从 JNI 获取 Context,:
1 2 3 4 5 6 7 8 9 10 jobject JniEnvContext; { jclass activityThreadClass = Env->FindClass ("android/app/ActivityThread" ); jmethodID currentActivityThread = FJavaWrapper::FindStaticMethod (Env, activityThreadClass, "currentActivityThread" , "()Landroid/app/ActivityThread;" , false ); jobject at = Env->CallStaticObjectMethod (activityThreadClass, currentActivityThread); jmethodID getApplication = FJavaWrapper::FindMethod (Env, activityThreadClass, "getApplication" , "()Landroid/app/Application;" , false ); JniEnvContext = FJavaWrapper::CallObjectMethod (Env, at, getApplication); }
之后可以使用 Context
下的函数 getExternalFilesDir
获取到我们想要的路径:
注意 getExternalFilesDir
的原型是:File getExternalFilesDir(String)
,在使用 JNI 获取 jmehodID 时一定注意签名要传对,不然会 Crash,其签名是(Ljava/lang/String;)Ljava/io/File;
。
1 2 3 4 5 6 7 jmethodID getExternalFilesDir = Env->GetMethodID (Env->GetObjectClass (JniEnvContext), "getExternalFilesDir" , "(Ljava/lang/String;)Ljava/io/File;" ); jobject ExternalFileDir = Env->CallObjectMethod (JniEnvContext, getExternalFilesDir,nullptr ); jmethodID getFilePath = Env->GetMethodID (Env->FindClass ("java/io/File" ), "getPath" , "()Ljava/lang/String;" ); jstring pathString = (jstring)Env->CallObjectMethod (ExternalFileDir, getFilePath, nullptr ); const char *nativePathString = Env->GetStringUTFChars (pathString, 0 );
得到的 nativePathString
的值为:
1 /storage/emulated/0 /Android/data/com.imzlp.GWorld/files
其中的 com.imzlp.GWorld
是你的 App 的包名。
然后将其赋值给 GFilePathBase
即可,打开编辑器重新打包 Apk,安装上之后该 APP 所有的数据就会在 /storage/emulated/0/Android/data/PACKAGE_NAME/files
下了。
在 UE 中调用和操作 JNI 以及 Android 存储路径相关的链接:
使用 Manifest 控制 OK,关于分析引擎中修改GFilePathBase
的大致写完了,其实有个不改动引擎的办法,就是在项目设置中添加minifest
。
其实原理也在 AndoidJNI.cpp
里了,AndroidJNI.cpp
中有以下代码:
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 JNI_METHOD void Java_com_epicgames_ue4_GameActivity_nativeSetGlobalActivity (JNIEnv* jenv, jobject thiz, jboolean bUseExternalFilesDir, jstring internalFilePath, jstring externalFilePath, jboolean bOBBinAPK, jstring APKFilename ) { if (!FJavaWrapper::GameActivityThis) { GGameActivityThis = FJavaWrapper::GameActivityThis = jenv->NewGlobalRef (thiz); if (!FJavaWrapper::GameActivityThis) { FPlatformMisc::LowLevelOutputDebugString (TEXT ("Error setting the global GameActivity activity" )); check (false ); } FAndroidApplication::InitializeJavaEnv (GJavaVM, JNI_CURRENT_VERSION, FJavaWrapper::GameActivityThis); FJavaWrapper::GoogleServicesThis = FJavaWrapper::GameActivityThis; GOBBinAPK = bOBBinAPK; const char *nativeAPKFilenameString = jenv->GetStringUTFChars (APKFilename, 0 ); GAPKFilename = FString (nativeAPKFilenameString); jenv->ReleaseStringUTFChars (APKFilename, nativeAPKFilenameString); const char *nativeInternalPath = jenv->GetStringUTFChars (internalFilePath, 0 ); GInternalFilePath = FString (nativeInternalPath); jenv->ReleaseStringUTFChars (internalFilePath, nativeInternalPath); const char *nativeExternalPath = jenv->GetStringUTFChars (externalFilePath, 0 ); GExternalFilePath = FString (nativeExternalPath); jenv->ReleaseStringUTFChars (externalFilePath, nativeExternalPath); if (bUseExternalFilesDir) { #if UE_BUILD_SHIPPING GFilePathBase = GInternalFilePath; #else GFilePathBase = GExternalFilePath; #endif FPlatformMisc::LowLevelOutputDebugStringf (TEXT ("GFilePathBase Path override to'%s'\n" ), *GFilePathBase); } FPlatformMisc::LowLevelOutputDebugStringf (TEXT ("InternalFilePath found as '%s'\n" ), *GInternalFilePath); FPlatformMisc::LowLevelOutputDebugStringf (TEXT ("ExternalFilePath found as '%s'\n" ), *GExternalFilePath); } }
在引擎启动的时候会从 JNI 调过来,其中有一个参数 bUseExternalFilesDir
用来控制修改 GFilePathBase
的值,如果它为 ture,在 Shipping 打包的模式下就会把 GFilePathBase
设置为 GInternalFilePath
的值,也就是下列路径:
1 /data/user/PACKAGE_NAME/files
在非 Shipping 打包模式下会设置为 GExternalFilePath
的值:
1 /storage/emulated/0 /Android/data/PACKAGE_NAME/files
但是,问题的关键是 bUseExternalFilesDir
这个从 JNI 调过来的参数我们又如何控制呢?
问题的答案是添加 manifest
信息!本来以为是 ProjectSettings
-Android
-UseExternalFilesDirForUE4GameFiles
这个选项,但是选中没有任何效果,原因后面会分析。
在详细解释怎么通过 manifest
控制 bUseExternalFilesDir
这个变量之前,需要先知道,UE4 打包出来的 APK 的 Manifest
中默认有什么。
下列是我解包出来的 APK 中的 Manifest 文件:
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 <?xml version="1.0" encoding="utf-8" standalone="no"?> <manifest xmlns:android ="http://schemas.android.com/apk/res/android" android:installLocation ="internalOnly" package ="com.imzlp.TEST" platformBuildVersionCode ="29" platformBuildVersionName ="10" > <application android:debuggable ="true" android:hardwareAccelerated ="true" android:hasCode ="true" android:icon ="@drawable/icon" android:label ="@string/app_name" > <activity android:debuggable ="true" android:label ="@string/app_name" android:launchMode ="singleTask" android:name ="com.epicgames.ue4.SplashActivity" android:screenOrientation ="landscape" android:theme ="@style/UE4SplashTheme" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > <activity android:configChanges ="density|keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|uiMode" android:debuggable ="true" android:label ="@string/app_name" android:launchMode ="singleTask" android:name ="com.epicgames.ue4.GameActivity" android:screenOrientation ="landscape" android:theme ="@style/UE4SplashTheme" > <meta-data android:name ="android.app.lib_name" android:value ="UE4" /> </activity > <activity android:configChanges ="density|keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|uiMode" android:name =".DownloaderActivity" android:screenOrientation ="landscape" android:theme ="@style/UE4SplashTheme" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.EngineVersion" android:value ="4.22.3" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.EngineBranch" android:value ="++UE4+Release-4.22" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.ProjectVersion" android:value ="1.0.0.0" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.DepthBufferPreference" android:value ="0" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.bPackageDataInsideApk" android:value ="true" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.bVerifyOBBOnStartUp" android:value ="false" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.bShouldHideUI" android:value ="false" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.ProjectName" android:value ="Mobile422" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.AppType" android:value ="" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.bHasOBBFiles" android:value ="true" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.BuildConfiguration" android:value ="Development" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.CookedFlavors" android:value ="ETC2" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.bValidateTextureFormats" android:value ="true" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.bUseExternalFilesDir" android:value ="false" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.bUseDisplayCutout" android:value ="false" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.bAllowIMU" android:value ="true" /> <meta-data android:name ="com.epicgames.ue4.GameActivity.bSupportsVulkan" android:value ="false" /> <meta-data android:name ="com.google.android.gms.games.APP_ID" android:value ="@string/app_id" /> <meta-data android:name ="com.google.android.gms.version" android:value ="@integer/google_play_services_version" /> <activity android:configChanges ="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" android:name ="com.google.android.gms.ads.AdActivity" /> <service android:name ="OBBDownloaderService" /> <receiver android:name ="AlarmReceiver" /> <receiver android:name ="com.epicgames.ue4.LocalNotificationReceiver" /> <receiver android:exported ="true" android:name ="com.epicgames.ue4.MulticastBroadcastReceiver" > <intent-filter > <action android:name ="com.android.vending.INSTALL_REFERRER" /> </intent-filter > </receiver > <meta-data android:name ="android.max_aspect" android:value ="2.1" /> </application > <uses-feature android:glEsVersion ="0x00030000" android:required ="true" /> <uses-permission android:name ="android.permission.INTERNET" /> <uses-permission android:name ="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name ="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name ="android.permission.WAKE_LOCK" /> <uses-permission android:name ="com.android.vending.CHECK_LICENSE" /> <uses-permission android:name ="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name ="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name ="android.permission.VIBRATE" /> </manifest >
该文件在 UnrealBuildTool\Platform\Android\UEDeployAdnroid.cs
中的 GenerateManifest
函数中生成。
其中控制了 APK 安装后的权限要求、属性配置等等,可以看到其中有一条:
1 <meta-data android:name ="com.epicgames.ue4.GameActivity.bUseExternalFilesDir" android:value ="false" />
bUseExternalFilesDir
的值为 false!,那么怎么把它设置为 true 呢?
需要打开 Project Settings
-Android
-Advanced APK Packaging
,找到Extra Tags for<application> node
,因为<meta-data />
是在 Application
下的,所以需要在这个选项下添加。
添加内容为:
1 <meta-data android:name ="com.epicgames.ue4.GameActivity.bUseExternalFilesDir" android:value ="true" />
没错!直接把 meta-data
这一行直接粘贴过来改一下值就可以了,UE 打包时会自动把这里的内容追加到 Manifest
的Application
项尾部,这样就覆盖了默认的 false
的值。
然后再打包就可以看到 bUseExternalFilesDir
这个选项起作用了。
UPL 控制 bUseExternalFilesDir 因为 UE 默认会给 AndroidManifest.xml
添加了 com.epicgames.ue4.GameActivity.bUseExternalFilesDir
项,如果我们想要手动控制,直接添加的话会产生错误,提示已经存在:
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 12 13 14 15 <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 > <addElements tag ="application" > <meta-data android:name ="com.epicgames.ue4.GameActivity.bUseExternalFilesDir" android:value ="true" /> </addElements > </androidManifestUpdates >
就是去遍历 AndroidManfest.xml
中已经存在 meta-data
中,android:name
为 com.epicgames.ue4.GameActivity.bUseExternalFilesDir
的项给删除。
项目设置 bUseExternalFilesDir 选项无效分析 下面来分析一下 Project Settings
-Android
-Use ExternalFilesDir for UE4Game Files
这个选项不生效。 其实这个选项确实是控制 manifest
中的 bUseExternalFilesDir
的值的,在 UBT 中操作的,上面已经提到 manifest
文件就是在 UBT 中生成的。但是 ,虽然 UE 提供了这个参数,但是目前的引擎中(4.22.3) 这个选项是没有作用的,因为它被默认禁用了。 首先,UBT 的构建调用栈为:
AndroidPlatform
(UEBuildAndroid.cs)的Deploy
UEDeployAndroid
(UEDeployAndroid.cs)中的PrepTargetForDeployment
UEDeployAndroid
(UEDeployAndroid.cs)中的MakeApk
(最关键的函数)
MakeApk
这个函数接收了一个特殊的控制参数bDisallowExternalFilesDir
:
1 2 private void MakeApk (AndroidToolChain ToolChain, string ProjectName, TargetType InTargetType, string ProjectDirectory, string OutputPath, string EngineDirectory, bool bForDistribution, string CookFlavor, bool bMakeSeparateApks, bool bIncrementalPackage, bool bDisallowPackagingDataInApk, bool bDisallowExternalFilesDir) ;
它用来控制是否启用项目设置中的 Use ExternalFilesDir for UE4Game Files
选项。
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 private void MakeApk (AndroidToolChain ToolChain, string ProjectName, TargetType InTargetType, string ProjectDirectory, string OutputPath, string EngineDirectory, bool bForDistribution, string CookFlavor, bool bMakeSeparateApks, bool bIncrementalPackage, bool bDisallowPackagingDataInApk, bool bDisallowExternalFilesDir) { bool bUseExternalFilesDir = UseExternalFilesDir (bDisallowExternalFilesDir); } public bool UseExternalFilesDir (bool bDisallowExternalFilesDir, ConfigHierarchy Ini = null) { if (bDisallowExternalFilesDir) { return false ; } if (Ini == null) { Ini = GetConfigCacheIni (ConfigHierarchyType.Engine); } bool bUseExternalFilesDir; Ini.GetBool ("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" , "bUseExternalFilesDir" , out bUseExternalFilesDir); return bUseExternalFilesDir; }
可以看到,如果 bDisallowExternalFilesDir
为 true 的话,就完全不会去读项目设置里的配置。
而关键的地方就在于,在 PrepTargetForDeployment
中调用 MakeApk
的时候,给了默认参数 true:
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 30 31 public override bool PrepTargetForDeployment (UEBuildDeployTarget InTarget) { AndroidToolChain ToolChain = new AndroidToolChain (InTarget.ProjectFile, false , InTarget.AndroidArchitectures, InTarget.AndroidGPUArchitectures); string BaseSoName = ToolChain.RemoveArchName (InTarget.OutputPaths[0 ].FullName); UnrealTargetPlatform Platform = InTarget.Platform; UnrealTargetConfiguration Configuration = InTarget.Configuration; string ProjectBaseName = Path.GetFileName (BaseSoName).Replace ("-" + Platform, "" ).Replace ("-" + Configuration, "" ).Replace (".so" , "" ); FileReference ReceiptFilename = TargetReceipt.GetDefaultPath (InTarget.ProjectDirectory, ProjectBaseName, Platform, Configuration, "" ); Log.TraceInformation ("Receipt Filename: {0}" , ReceiptFilename); SetAndroidPluginData (ToolChain.GetAllArchitectures (), CollectPluginDataPaths (TargetReceipt.Read (ReceiptFilename, UnrealBuildTool.EngineDirectory, InTarget.ProjectDirectory))); string RelativeEnginePath = UnrealBuildTool.EngineDirectory.MakeRelativeTo (DirectoryReference.GetCurrentDirectory ()); MakeApk (ToolChain, InTarget.TargetName, InTarget.ProjectDirectory.FullName, BaseSoName, RelativeEnginePath, bForDistribution: false , CookFlavor: "" , bMakeSeparateApks: ShouldMakeSeparateApks (), bIncrementalPackage: true , bDisallowPackagingDataInApk: false , bDisallowExternalFilesDir: true ); if (ShouldMakeSeparateApks () && (InTarget.OutputPaths.Count > 1 || !InTarget.OutputPaths[0 ].FullName.Contains ("-armv7-es2" ))) { Console.WriteLine ("================================================================================================================================" ); Console.WriteLine ("Non-default apk(s) have been made: If you are debugging, you will need to manually select one to run in the debugger properties!" ); Console.WriteLine ("================================================================================================================================" ); } return true ; }
这真是好坑的一个点…我看 UE4.18 UBT 的源码中是一样的,都是默认关闭的。明明有这个选项,却默认给关闭了,但是还没有任何的提示,这真是比较蛋疼的事情。
总结 其实改动引擎代码和使用 manifest
各有好处:
改动代码的好处是可以任意指定路径(当然不一定合理),但缺点是需要源码版引擎;
使用 Manifest
的好处是不需要源码版引擎,但是只能使用 InternalFilesDir
(Shipping) 或者ExternalFilesDir
(not-shipping);
顺道吐槽一下 UE,一个选项没作用,还把它在设置里暴露出来干嘛…