目录
本文通过一个例子,一步步介绍如何在windows下将第三方库打包到一个Android应用中。
一、准备一个UE4工程
在这个工程中建一个Widget:BP_Input,并在这个Widget中放入一个TextBox:TextBox_Input。这个TextBox用于显示从第三方库中获取的信息。
并给这个Widget添加一个用于设置TextBox内容的函数:SetInputText
二、准备一个简单的C++代码,作为第三方库使用
share.h
#ifdef HELLO_API_EXPORTS
#define HELLO_API __declspec(dllexport)
#elif defined(HELLO_API_IMPORTS)
#define HELLO_API __declspec(dllimport)
#else
#define HELLO_API
#endif
Hello.h
#include <string>
#include "share.h"
class HELLO_API Hello
{
public:
std::string sayHello(std::string msg);
};
Hello.cpp
#include "Hello.h"
std::string Hello::sayHello(std::string msg)
{
return "Hello " + msg;
};
三、将以上代码编译成库
1、编译成Windows动态库,用于在Windows上做开发和测试用
在编译时,设置编译参数:HELLO_API_EXPORTS,编译后得到如下两个文件:
hello.dll
hello.lib
2、编译成Android上的共享库,用于打包到Android应用中
(1)、安装android-ndk,android-sdk,apache-ant,jdk等
使用C:\Epic Games\UE_4.18\Engine\Extras\AndroidWorks\Win64\CodeWorksforAndroid-1R6u1-windows.exe进行安装。默认都会安装到C:\NVPACK目录下。
(2)、生成toolchain工具
由于是在windows下生成能在Android系统中使用的库,所以需要使用toolchain工具。
可以在C:\NVPACK\android-ndk-r12b\build\tools下找到make_standalone_toolchain.py和make_standalone_toolchain.sh,它们是用来生成toolchain工具的。toolchain工具可以编译C++程序,并生成指定CPU架构的库文件。
make_standalone_toolchain.py --arch arm --api 24 --package-dir C:\NVPACK
--arch 表示指定CPU的架构,这里使用的是arm。如果还要生成其它CPU架构的库可指定其它值。
--api 表示目录Android API的Level
--package-dir 表示toolchain工具生成后存放的目录
关于make_standalone_toolchain.py的详细参数,可以通过make_standalone_toolchain.py --help查到:
make_standalone_toolchain.py --help
usage: make_standalone_toolchain.py [-h] --arch
{arm,arm64,mips,mips64,x86,x86_64}
[--api API]
[--stl {gnustl,libc++,stlport}] [--force]
[-v]
[--package-dir PACKAGE_DIR | --install-dir INSTALL_DIR]Creates a toolchain installation for a given Android target. The output of
this tool is a more typical cross-compiling toolchain. It is indended to be
used with existing build systems such as autotools.optional arguments:
-h, --help show this help message and exit
--arch {arm,arm64,mips,mips64,x86,x86_64}
--api API Target the given API version (example: "--api 24").
--stl {gnustl,libc++,stlport}
C++ STL to use.
--force Remove existing installation directory if it exists.
-v, --verbose Increase output verbosity.
--package-dir PACKAGE_DIR
Build a tarball and install it to the given directory.
If neither --package-dir nor --install-dir is
specified, a tarball will be created and installed to
the current directory.
--install-dir INSTALL_DIR
Install toolchain to the given directory instead of
packaging.
执行完成后将会生成文件:C:\NVPACK\arm-linux-androideabi.zip。解压后就可以使用它来编译C++程序了。
(3)、使用toolchain编译C++代码
C:\NVPACK\arm-linux-androideabi\bin\arm-linux-androideabi-g++.exe share.h Hello.h Hello.cpp -shared -fPIC -o libhello.so
-shared 表示生成共享库
-fPIC 表示生成与路径无关的库
四、在UE4工程中使用这些库
在AndroidProject目录下创建ThirdParty目录及其子目录,并将C++代码和生成的库文件按如下目录结构存放:
AndroidProject-+
|-Source
|-ThirdParty-+
|-hello-+
|-android-+
|-libhello.so
|-include-+
|-Hello.h
|-share.h
|-x64-----+
|-hello.lib
|-hello.dll
修改AndroidProjectGameMode.h和AndroidProjectGameMode.cpp
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "Blueprint/UserWidget.h"
#include "AndroidProjectGameMode.generated.h"
UCLASS(minimalapi)
class AAndroidProjectGameMode : public AGameModeBase
{
GENERATED_BODY()
private:
UPROPERTY()
UUserWidget* InputWidget;
public:
AAndroidProjectGameMode();
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UMG")
TSubclassOf<UUserWidget> InputWidgetClass;
void BeginPlay();
};
#include "AndroidProjectGameMode.h"
#include "AndroidProjectCharacter.h"
#include "UObject/ConstructorHelpers.h"
#include "Hello.h"
AAndroidProjectGameMode::AAndroidProjectGameMode()
{
// set default pawn class to our Blueprinted character
static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter"));
if (PlayerPawnBPClass.Class != NULL)
{
DefaultPawnClass = PlayerPawnBPClass.Class;
}
}
void AAndroidProjectGameMode::BeginPlay()
{
Super::BeginPlay();
InputWidget = CreateWidget<UUserWidget>(GetWorld(), InputWidgetClass);
if (InputWidget != nullptr)
{
UFunction* SetInputText = InputWidget->FindFunction(FName("SetInputText"));
if (SetInputText) {
Hello hello;
std::string msg = hello.sayHello("Tom");
FString InputText = FString(msg.c_str());
UE_LOG(LogTemp, Log, TEXT("InputText=%s"), *InputText);
InputWidget->ProcessEvent(SetInputText, &InputText);
}
InputWidget->AddToViewport();
}
};
可以从代码中看到,使用FindFunction查找InputWidget中定义的函数,使用ProcessEvent调用InputWidget中的函数。同时也调用了第三方库的sayHello方法,并将调用结果显示在InputWidget上。
五、使这些库能在UE4工程中编译并运行
1、修改AndroidProject.Build.cs文件
using UnrealBuildTool;
using System;
using System.IO;
public class AndroidProject : ModuleRules
{
private string ThirdPartyPath
{
get
{
return Path.GetFullPath(Path.Combine(ModuleDirectory, "../../ThirdParty"));
}
}
public AndroidProject(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "UMG" });
string helloPath = ThirdPartyPath + "/hello";
PublicIncludePaths.Add(helloPath + "/include");
if (Target.Platform == UnrealTargetPlatform.Win64)
{
Definitions.AddRange(new string[] { "HELLO_API_IMPORTS" });
PublicLibraryPaths.Add(helloPath + "/x64");
PublicAdditionalLibraries.Add("hello.lib");
}
else if (Target.Platform == UnrealTargetPlatform.Android)
{
AdditionalPropertiesForReceipt.Add(new ReceiptProperty("AndroidPlugin", Path.Combine(helloPath, "My_APL_armv7.xml")));
PublicLibraryPaths.Add(Path.Combine(helloPath, "android"));
PublicAdditionalLibraries.Add("hello");
}
else if (Target.Platform == UnrealTargetPlatform.IOS)
{
PublicLibraryPaths.Add(helloPath + "/IOS");
}
}
}
由于使用了Widget,所以要在PublicDependencyModuleNames中添加UMG。
上述代码根据编译目标平台(Target.Platform)的不同设置不同的配置:
(1)Windows平台下,由于导入DLL时需要使用__declspec(dllimport),所以添加HELLO_API_IMPORTS参数。添加导出库hello.lib用作编译。
(2)Android平台下,添加库名hello,toolchain在编译时会自动加上前缀lib和后缀.so,最终生成的库名为libhello.so;如果此处直接使用库名libhello.so而不用库名hello,编译时反而会找不到库。
My_APL_armv7.xml放在AndroidProject\ThirdParty\hello目录下,文件的内容如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- steps to add to build additions -->
<root xmlns:android="http://schemas.android.com/apk/res/android">
<!-- init section is always evaluated once per architecture -->
<init>
<setBool result="bSupported" value="false"/>
<isArch arch="armeabi-v7a">
<setBool result="bSupported" value="true"/>
</isArch>
</init>
<!-- optional files or directories to copy to Intermediate/Android/APK -->
<resourceCopies>
<isArch arch="armeabi-v7a">
<copyFile src="$S(BuildDir)/../../../ThirdParty/hello/android/libhello.so"
dst="$S(BuildDir)/libs/armeabi-v7a/libhello.so" />
</isArch>
</resourceCopies>
<!-- optional libraries to load in GameActivity.java before libUE4.so -->
<soLoadLibrary>
<if condition="bSupported">
<true>
<loadLibrary name="hello" failmsg="Failed to load myso library" />
</true>
</if>
</soLoadLibrary>
</root>
它的作用是:
(1)在打包Android应用时,将AndroidProject/ThirdParty/hello/android/libhello.so文件拷贝到AndroidProject/Intermediate/Android/APK/libs/armeabi-v7a/目录下。这样就不用手动拷贝了。
(2)在AndroidProject\Intermediate\Android\APK\src\com\epicgames\ue4\GameActivity.java文件中生成加载hello库的代码:
try
{
System.loadLibrary("hello");
}
catch (java.lang.UnsatisfiedLinkError e)
{
Log.debug(e.toString());
Log.debug("Failed to load hello library");
}
2、将hello.dll拷贝到AndroidProject\Binaries\Win64目录下。这样就可以从VS中启动UE4了。
六、在UE4工程中创建组件并配置参数
1、启动UE4后,创建Blueprint
在AndroidProjectGameMode上点右键,创建AndroidProjectGameMode对应的Blueprint:BP_AndroidProjectGameMode,打开BP_AndroidProjectGameMode后,设置Input Widget Class:
2、配置GameMode
将GameModeOverride配置成BP_AndroidProjectGameMode
3、查看运行结果
点击“Play”后,就可以看到界面右上方文本框中正确显示了调用第三方库函数后返回的结果。但这只是说明了在Windows上调用第三方库运行正常,接下来要将第三方库打包到Android应用中去。
七、在UE4工程中配置打包Android应用所需要的参数
1、打开Editor -> Project Settings,其中必须的配置如下:
其它可选的优化配置:
Project -> Packaging -> Packaging -> Create compressed cooked packages (Enable)
Project -> Packaging -> Packaging -> Full Rebuild (Enable)
Project -> Packaging -> Packaging -> Exclude editor content when cooking (Enable)Engine -> Network -> Libcurl -> Verify peer (Disable)
Engine -> Input -> Mouse Properties -> Use Mouse for touch (Disable)
Engine -> Input -> Moblie -> Always show touch interface (Disable)
Engine -> Rendering -> Moblie -> Max Dynamic Point Lights = 0Platforms -> Android -> APKPackaging -> Enable Gradle instead of Ant (Disable)
Platforms -> Android -> APKPackaging -> Package game data inside apk? (Enable)Plugins -> OculusVR -> Splash Screen -> Auto Enable (Disable)
Plugins -> TCP Messaging-> Transport -> Enable Transport (Disable)
Plugins -> UDP Messaging-> Transport -> Enable Transport (Disable)
2、然后进行打包操作。选择ETC1是因为所有Android设备都支持它:
3、最后会在AndroidProject\Android_ETC1下生成:AndroidProject-armv7-es2.apk。这就是最终可以放到Android设置安装并运行的文件。
4、如果想知道在打包过程中执行了哪些命令,这此命令都的哪些参数的话,可以在C:\Epic Games\UE_4.18\Engine\Source\Programs\UnrealBuildTool\System\ActionGraph.cs文件中的如下方法中
public static bool ExecuteActions(BuildConfiguration BuildConfiguration, List<Action> ActionsToExecute,
bool bIsRemoteCompile, out string ExecutorName, string TargetInfoForTelemetry, EHotReload HotReload)
对ActionsToExecute加上打印语句:
using (StreamWriter writer = new StreamWriter("D:/Temp/log.txt", true))
{
foreach (Action action in ActionsToExecute)
{
writer.WriteLine("action=" + action.ToString());
}
writer.Close();
}
就可以查看到如下日志:
action=C:/NVPACK/android-ndk-r12b\toolchains/llvm\prebuilt/windows-x86_64\bin/clang++.exe - @"D:\projects\AndroidProject\Intermediate\Build\Android\AndroidProject\Development\AndroidProject\AndroidProjectGameMode.cpp-armv7-es2.o_37AAC25F.rsp"
action=C:/NVPACK/android-ndk-r12b\toolchains/llvm\prebuilt/windows-x86_64\bin/clang++.exe - -nostdlib -Wl,-shared,-Bsymbolic -Wl,--no-undefined -Wl,-gc-sections -target armv7-none-linux-androideabi --sysroot="C:/NVPACK/android-ndk-r12b/platforms/android-24/arch-arm" -gcc-toolchain "C:/NVPACK/android-ndk-r12b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64" -march=armv7-a -Wl,--fix-cortex-a8 -Wl,-soname,libUE4.so -o "D:/projects/AndroidProject/Binaries/Android/AndroidProject-armv7-es2.so" @"D:/projects/AndroidProject/Intermediate/Build/Android/AndroidProject/Development/AndroidProject-armv7-es2.so.response"
action=C:\Microsoft\Visual Studio\2017\Professional\VC\Tools\MSVC\14.15.26726\bin\HostX64\x64\cl.exe - @"D:\projects\AndroidProject\Intermediate\Build\Win64\UE4Editor\Development\AndroidProject\AndroidProjectGameMode.cpp.obj.response"
action=C:\Microsoft\Visual Studio\2017\Professional\VC\Tools\MSVC\14.15.26726\bin\HostX64\x64\link.exe - @"D:\projects\AndroidProject\Plugins\tanx\Intermediate\Build\Win64\UE4Editor\Development\UE4Editor-tanx-4779.dll.response"
action=C:\Microsoft\Visual Studio\2017\Professional\VC\Tools\MSVC\14.15.26726\bin\HostX64\x64\link.exe - @"D:\projects\AndroidProject\Intermediate\Build\Win64\UE4Editor\Development\UE4Editor-AndroidProject-7604.dll.response"
action=C:\Microsoft\Visual Studio\2017\Professional\VC\Tools\MSVC\14.15.26726\bin\HostX64\x64\lib.exe - @"D:\projects\AndroidProject\Plugins\tanx\Intermediate\Build\Win64\UE4Editor\Development\UE4Editor-tanx-4779.lib.response"
action=C:\Microsoft\Visual Studio\2017\Professional\VC\Tools\MSVC\14.15.26726\bin\HostX64\x64\lib.exe - @"D:\projects\AndroidProject\Intermediate\Build\Win64\UE4Editor\Development\UE4Editor-AndroidProject-7604.lib.response"
action=C:\Microsoft\Visual Studio\2017\Professional\VC\Tools\MSVC\14.15.26726\bin\HostX64\x64\link.exe - @"D:\projects\AndroidProject\Intermediate\Build\Win64\UE4Editor\Development\UE4Editor-AndroidProject.dll.response"
其中,D:\projects\AndroidProject\Intermediate\Build\Android\AndroidProject\Development\AndroidProject\AndroidProjectGameMode.cpp-armv7-es2.o_37AAC25F.rsp中存放编译参数:
-target armv7-none-linux-androideabi --sysroot="C:/NVPACK/android-ndk-r12b/platforms/android-24/arch-arm" -gcc-toolchain "C:/NVPACK/android-ndk-r12b/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64" -c -fdiagnostics-format=msvc -Wall -Wdelete-non-virtual-dtor -Wno-unused-variable -Wno-unused-function -Wno-switch -Wno-tautological-compare -Wno-unused-private-field -Wno-local-type-template-args -Wno-return-type-c-linkage -Wno-reorder -Wno-unknown-pragmas -Wno-invalid-offsetof -Wno-logical-op-parentheses -Wshadow -Wno-error=shadow -Wundef -Wno-error=undef -Wno-undefined-bool-conversion -Wno-gnu-string-literal-operator-template -Wno-unused-local-typedef -Wno-inconsistent-missing-override -g2 -gdwarf-4 -O3 -fno-exceptions -fno-rtti -funwind-tables -fstack-protector -fno-strict-aliasing -fpic -fno-short-enums -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -ffunction-sections -fdata-sections -fsigned-char -Werror -DPLATFORM_64BITS=0 -DPLATFORM_ANDROID_ARM=1 -I"C:/NVPACK/android-ndk-r12b/sources/cxx-stl/gnu-libstdc++/4.9/include" -I"C:/NVPACK/android-ndk-r12b/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/include" -I"C:/NVPACK/android-ndk-r12b/sources/android/native_app_glue" -I"C:/NVPACK/android-ndk-r12b/sources/android/cpufeatures" -I"C:/Epic Games/UE_4.18/Engine/Source" -I"D:/projects/AndroidProject/ThirdParty/hello/include" -I"D:/projects/AndroidProject/Source" -I"D:/projects/AndroidProject/Intermediate/Build/Android/UE4/Inc/AndroidProject" -I"D:/projects/AndroidProject/Source/AndroidProject" -I"Runtime/Core/Public" -I"Runtime/Core/Public/Internationalization" -I"Runtime/Core/Public/Async"
。。。。。。。
-x c++ -std=c++14 -O3 -include "C:/Epic Games/UE_4.18/Engine/Intermediate/Build/Android/UE4/Development/Engine/SharedPCH.Engine-armv7-es2.h" -include "D:/projects/AndroidProject/Intermediate/Build/Android/AndroidProject/Development/AndroidProject/Definitions.AndroidProject.h" -o "D:/projects/AndroidProject/Intermediate/Build/Android/AndroidProject/Development/AndroidProject/AndroidProjectGameMode.cpp-armv7-es2.o" "D:/projects/AndroidProject/Source/AndroidProject/AndroidProjectGameMode.cpp"
D:/projects/AndroidProject/Intermediate/Build/Android/AndroidProject/Development/AndroidProject-armv7-es2.so.response中存放链接参数:
"AndroidProject/AndroidProject.cpp-armv7-es2.o"
"AndroidProject/AndroidProjectCharacter.cpp-armv7-es2.o"
"AndroidProject/AndroidProjectGameMode.cpp-armv7-es2.o"
"AndroidProject/AndroidProject.init.gen.cpp-armv7-es2.o"
"AndroidProject/AndroidProjectCharacter.gen.cpp-armv7-es2.o"
"AndroidProject/AndroidProjectGameMode.gen.cpp-armv7-es2.o"
"tanx/Module.tanx.cpp-armv7-es2.o"
"UELinkerFixups/UELinkerFixups.cpp-armv7-es2.o"
-L"C:/NVPACK/android-ndk-r12b/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/Android/cxa_demangle/armeabi-v7a" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/ICU/icu4c-53_1/Android/ARMv7/lib" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/FreeType2/FreeType2-2.4.12/Lib/Android/ARMv7" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/HarfBuzz/harfbuzz-1.2.4/Android/ARMv7/Release/" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/libPNG/libPNG-1.5.27/lib/Android/ARMv7" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/PhysX/Lib/Android/ARMv7" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/libcurl/lib/Android/ARMv7" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/Ogg/libogg-1.2.2/lib/Android/ARMv7" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/Vorbis/libvorbis-1.3.2/lib/Android/ARMv7" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/Vorbis/libvorbis-1.3.2/Lib/Android/ARMv7" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/GooglePlay/gpg-cpp-sdk.v2.3/gpg-cpp-sdk/android/lib/gnustl/armeabi-v7a/" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/libOpus/opus-1.1/Android/ARMv7/" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/Oculus/OVRPlugin/OVRPlugin/Lib/armeabi-v7a/" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/Oculus/OVRPlugin/OVRPlugin/ExtLibs/" -L"C:/Epic Games/UE_4.18/Engine/Source/ThirdParty/PhysX/Lib/Android/ARMv7" -L"D:/unreal_projects/AndroidProject/ThirdParty/hello/android" -Wl,--start-group "-lgnustl_shared" "-lgcc" "-lz" "-lc" "-lm" "-llog" "-ldl" "-lGLESv2" "-lEGL" "-lOpenSLES" "-landroid" "-lcxa_demangle" "-lz" "-licudata" "-licuuc" "-licui18n" "-licule" "-liculx" "-licuio" "-lfreetype2412" "-lharfbuzz" "-lpng" "-lPhysX3PROFILE" "-lPhysX3ExtensionsPROFILE" "-lPhysX3CookingPROFILE" "-lPhysX3CommonPROFILE" "-lPxFoundationPROFILE" "-lPxPvdSDKPROFILE" "-lPsFastXmlPROFILE" "-lcurl" "-logg" "-lvorbis" "-lvorbisfile" "-lgpg" "-lopus" "-lspeex_resampler" "-lOVRPlugin" "-lvrapi" "-lPhysX3VehiclePROFILE" "-lPhysX3CookingPROFILE" "-lhello" "C:/Epic Games/UE_4.18/Engine/Binaries/Android/UE4-Launch-armv7-es2.a"。。。。。。
-Wl,--end-group
在链接参数中可以看到,使用的都是不含前缀lib和后缀.so的库名。
参考文档
How to add a shared library (.so) in android project
Packaging Android Projects
Reducing APK Package Size
Android Development Reference
Android SO文件的兼容和适配
Packaging Projects