UE源码分析:修改游戏默认的数据存储路径

Modify the default data storage path for Android games

默认情况下,使用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
// Launch\Private\Android\AndroidJNI.cpp
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);

// if you have problems with stuff being missing especially in distribution builds then it could be because proguard is stripping things from java
// check proguard-project.txt and see if your stuff is included in the exceptions
GJavaVM = InJavaVM;
FAndroidApplication::InitializeJavaEnv(GJavaVM, JNI_CURRENT_VERSION, FJavaWrapper::GameActivityThis);

FJavaWrapper::FindClassesAndMethods(Env);

// hook signals
if (!FPlatformMisc::IsDebuggerPresent() || GAlwaysReportCrash)
{
// disable crash handler.. getting better stack traces from system for now
//FPlatformMisc::SetCrashHandler(EngineCrashHandler);
}

// Cache path to external storage
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);
// Copy that somewhere safe
GFilePathBase = FString(nativePathString);
GOBBFilePathBase = GFilePathBase;

// then release...
Env->ReleaseStringUTFChars(pathString, nativePathString);
Env->DeleteLocalRef(pathString);
Env->DeleteLocalRef(externalStoragePath);
Env->DeleteLocalRef(EnvClass);
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Path found as '%s'\n"), *GFilePathBase);

// Get the system font directory
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);

// Wire up to core delegates, so core code can call out to Java
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
// get context
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;");
// get File
jobject ExternalFileDir = Env->CallObjectMethod(JniEnvContext, getExternalFilesDir,nullptr);
// getPath method in File class
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
//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, 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);

// @todo split GooglePlay, this needs to be passed in to this function
FJavaWrapper::GoogleServicesThis = FJavaWrapper::GameActivityThis;
// FJavaWrapper::GoogleServicesThis = jenv->NewGlobalRef(googleServices);

// Next we check to see if the OBB file is in the APK
//jmethodID isOBBInAPKMethod = jenv->GetStaticMethodID(FJavaWrapper::GameActivityClassID, "isOBBInAPK", "()Z");
//GOBBinAPK = (bool)jenv->CallStaticBooleanMethod(FJavaWrapper::GameActivityClassID, isOBBInAPKMethod, nullptr);
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

这是Android的App私有数据路径,是内部存储,没有ROOT权限是无法访问该目录的,也意味着无法手动把pak放到该目录下,我认为如此设计的本意是防止用户获取发行版本产生的数据。

但UE也提供了一个配置,可以在Shipping时把Log输出到非内部存储中。即在DefaultEngine.ini中的[/Script/AndroidRuntimeSettings.AndroidRuntimeSettings]下添加bPublicLogFiles=True

在非Shipping打包模式下会设置为GExternalFilePath的值:

1
/storage/emulated/0/Android/data/PACKAGE_NAME/files

这就是Android App外部存储沙盒路径,会随着App卸载自动清理。

但是,问题的关键是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打包时会自动把这里的内容追加到ManifestApplication项尾部,这样就覆盖了默认的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://g.co/androidstudio/manifest-merger for more information about the manifest merger.
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://help.gradle.org
UATHelper: Packaging (Android (ASTC)):
UATHelper: Packaging (Android (ASTC)): BUILD FAILED in 10s
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.3060694s 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:namecom.epicgames.ue4.GameActivity.bUseExternalFilesDir的项给删除。

项目设置bUseExternalFilesDir选项无效分析

下面来分析一下Project Settings-Android-Use ExternalFilesDir for UE4Game Files这个选项不生效。
其实这个选项确实是控制manifest中的bUseExternalFilesDir的值的,在UBT中操作的,上面已经提到manifest文件就是在UBT中生成的。
但是,虽然UE提供了这个参数,但是目前的引擎中(4.22.3)这个选项是没有作用的,因为它被默认禁用了。
首先,UBT的构建调用栈为:

  1. AndroidPlatform(UEBuildAndroid.cs)的Deploy
  2. UEDeployAndroid(UEDeployAndroid.cs)中的PrepTargetForDeployment
  3. UEDeployAndroid(UEDeployAndroid.cs)中的MakeApk(最关键的函数)

MakeApk这个函数接收了一个特殊的控制参数bDisallowExternalFilesDir:

1
2
// UEDeployAndroid.cs
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
// UEDeployAndroid.cs
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);
// ...
}

// func UseExternalFilesDir
public bool UseExternalFilesDir(bool bDisallowExternalFilesDir, ConfigHierarchy Ini = null)
{
if (bDisallowExternalFilesDir)
{
return false;
}

// make a new one if one wasn't passed in
if (Ini == null)
{
Ini = GetConfigCacheIni(ConfigHierarchyType.Engine);
}

// we check this a lot, so make it easy
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
// UEDeployAndroid.cs
public override bool PrepTargetForDeployment(UEBuildDeployTarget InTarget)
{
//Log.TraceInformation("$$$$$$$$$$$$$$ PrepTargetForDeployment $$$$$$$$$$$$$$$$$ {0}", InTarget.TargetName);
AndroidToolChain ToolChain = new AndroidToolChain(InTarget.ProjectFile, false, InTarget.AndroidArchitectures, InTarget.AndroidGPUArchitectures);

// we need to strip architecture from any of the output paths
string BaseSoName = ToolChain.RemoveArchName(InTarget.OutputPaths[0].FullName);

// get the receipt
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)));

// make an apk at the end of compiling, so that we can run without packaging (debugger, cook on the fly, etc)
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 we made any non-standard .apk files, the generated debugger settings may be wrong
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,一个选项没作用,还把它在设置里暴露出来干嘛…

全文完,若有不足之处请评论指正。

微信扫描二维码,关注我的公众号。

本文标题:UE源码分析:修改游戏默认的数据存储路径
文章作者:查利鹏
发布时间:2020/01/22 09:10
本文字数:3.9k 字
原始链接:https://imzlp.com/posts/20367/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!