前言

在使用 UE 开发手游时,我们免不了要跟 Android 和 iOS 进行一些数据的交互,在这种情况下,就需要在代码中添加一些操作,使得在 Android 平台下 C++ 可以调用 Java,Java 可以调用 C++;iOS 平台下也是一样,C++ 可以调用 OC,OC 可以调用 C++。由于上次我已经实现了 UE 和 iOS 之间的互相调用,今天就继续和大家来讲讲 UE 和 Android 之间的互相调用。

Android 环境搭建

根据你安装的 UE 引擎的版本,来安装对应的 Android Studio。我的引擎版本是 4.25.4,所以根据 UE 文档的指示,安装了 Android Studio 3.5.3 版本。

UE4:Android 平台开发实践指南_Android

安装完成后,查看一下 NDK 的版本,记得勾选右下脚的 “Show Package Detail”。

UE4:Android 平台开发实践指南_Android_02

如果 NDK 没有安装为指定的版本,那请先勾选对的版本后,再下载安装。

构建首个 UE 工程

Android 环境配置好了以后,咱们就开始去构建一个 UE 工程,主要的思路是在界面 UI 上添加一个按钮,点击按钮后触发一个事件,该部分我已经在之前的教程中整理出来了,

Android 打包配置

接下来,在 UE 中去配置 Android 打包,在 “项目设置” 中找到 “打包” 选项,并设置为 “开发”。

UE4:Android 平台开发实践指南_Unreal Engine_03

如果需要 Release 的包,那就在下拉列表中,设置为“发行”。

UE4:Android 平台开发实践指南_java_04

在 “项目设置” 中找到 “平台” 选项,然后配置 “Android”,将这俩个栏目都设置为同意,接受SDK证书,以及填上安卓包名称。

UE4:Android 平台开发实践指南_Android_05

UE4:Android 平台开发实践指南_java_06

在 Android SDK 中填上对应的路径。

UE4:Android 平台开发实践指南_android_07

PS:由于我是用的 Mac,所以在路径配置上和 Windows 的不同。

Android SDK 和 NDK 的的路径可以在 Android Studio 中查看到。

UE4:Android 平台开发实践指南_android_08

根据在 Android Studio 中查看到的 Android SDK 路径,找到对应 NDK 的路径。

UE4:Android 平台开发实践指南_Android_09

然后就可以顺利打包了。

UE4:Android 平台开发实践指南_java_10

升级至 AndroidX

在 UE 中完成打包后,用 Android Studio 打开你的 UE 工程目录:Intermediate -> Android -> armv7 -> gradle,

UE4:Android 平台开发实践指南_java_11

在全局的 gradle.properties 加入以下俩个配置:

android.useAndroidX=true
android.enableJetifier=true

用 Android Studio 自带的升级功能,将项目升级至 AndroidX。

UE4:Android 平台开发实践指南_c++_12

然后执行一下 gradle sync,最后将整体工程进行编译,编译成功就代表我们的工程已经升级至了 AndroidX。

Android AAR

在使用 UE4 开发 Android 时,经常需要接入第三方的库,于是就做个简单的案例吧!

在上文中我已将 UE 打包出来的 gradle 加载到了 Android Studio 中,然后依次 File -> New Module, 新建一个 AAR 库。

我暂时将这个库命名为 LoginSDK,目录结构如下:

UE4:Android 平台开发实践指南_Android_13

这时候一个简单的第三方库就创建好了。在下面的文章中,会继续教大家如何去调用这个第三方库。

C++ 调用 Java

在 UE 中如何通过 C++ 去调用 Java 的函数呢,这时候就需要使用 JNI 调用来实现。在上文中,我们创建的 UE 工程已经实现了一个按钮点击事件,于是可以在这个事件中去调用 Java 函数。

那我们的 Java 函数应该写在哪呢!

UE 在打 Android 包的时候,提供了一个 GameActivity.java 的类,通过这个类,就可以让 UE 去调用 Java 代码。

于是,在 GameActivity.java 中,我们添加一个函数 ​​public void AndroidThunkJava_InitName()​​ 实现如下:

public void AndroidThunkJava_InitName()
{
Log.debug("AndroidThunkJava_InitName");
}

通过 C++ 去调用 Java,首先需要知道,所要调用的 Java 函数的签名,关于这一知识点,我在这里就不多说了。回到我们的 C++ 代码中的按钮点击事件中,加入如下代码。

void UMyUserWidget::callLoginFunction(){
#if
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
jmethodID GetPackageNameMethodID = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "AndroidThunkJava_InitName", "()V;", false);
FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis,GetPackageNameMethodID);
}
#endif

由于我们的 C++ 代码被修改过了,所以需要重新打包安卓项目,打包完成以后,在我们的 Android Studio 上跑起来,点击按钮,控制台中就会打印相应的日志。

C++ 调用 Java 代码已经成功实现了,但是在上文中,我们新建的一个第三方库的内容还没有讲完,那就继续来讲如何去调用安卓第三方库中的函数方法。

首先,在上文创建的 LoginSDK 库中,去实现一些逻辑:

  1. 创建一个 LoginActivity,并加上 EditText 和 Button 俩个控件
  2. 在 GameActivity 中跳转到 LoginActivity
  3. 点击 LoginActivity 中的按钮后,将 EditText 控件中的值回调给 GameActivity

在 GameActivity.java 中的 AndroidThunkJava_InitName() 函数中去调用 LoginSDK,代码如下。

public void AndroidThunkJava_InitGame()
{
Log.debug("AndroidThunkJava_InitGame");

// call loginsdk
Intent intent = new Intent(this, LoginActivity.class);

String message = "UE4";
intent.putExtra("com.example.MESSAGE", message);
startActivityForResult(intent, 998);
}

在 LoginActivity 中实现如下代码:

public class LoginActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);

Intent intent = getIntent();
String message = intent.getStringExtra("com.example.MESSAGE");
Log.d("message", message);
}

public void sendMessage(View view) {
EditText editText = (EditText) findViewById(R.id.editText);
String value = editText.getText().toString();

// to C++
Intent intent = new Intent();
intent.putExtra("LOGIN", value);
setResult(998, intent);
finish();
}
}

在 GameActivity 里的 ​​AndroidThunkJava_InitGame​​​ 函数中,设置了 ​​startActivityForResult(intent, 998)​​​ 的 RequestCode 是 998,所以在回调函数 ​​protected void onActivityResult(int requestCode, int resultCode, Intent data)​​ 中只要去监听 RequestCode = 998 就可获取回调值,代码如下:

@Override
protected void onActivityResult(int requestCode, int
{
if( requestCode == DOWNLOAD_ACTIVITY_ID)
{
// ...
}
else if (requestCode == 998){
android.util.Log.d("test", "Received data from LoginSDK");
String result = data.getStringExtra("LOGIN");
android.util.Log.d("test", result);
//nativeOnLoginCallBack(result);
}
else if( IapStoreHelper != null )
{
super.onActivityResult(requestCode, resultCode, data);
}
else
{
super.onActivityResult(requestCode, resultCode, data);
}

if(InitCompletedOK)
{
nativeOnActivityResult(this, requestCode, resultCode, data);
}
}

ps: RequestCode 的值你可以自定义

Java 调用 C++

UE 给我们的游戏生成的 GameActivity 中也声明了很多的 native 函数,例如:

public native int nativeGetCPUFamily();
public native boolean nativeSupportsNEON();
public native void nativeSetAffinityInfo(boolean bEnableAffinity, int bigCoreMask, int;
public native void nativeSetConfigRulesVariables(String[] KeyValuePairs);

public native boolean nativeIsShippingBuild();
public native void nativeSetAndroidStartupState(boolean;
public native void nativeSetGlobalActivity(boolean bUseExternalFilesDir, boolean bPublicLogFiles, String internalFilePath, String externalFilePath, boolean;
public native void nativeSetObbFilePaths(String OBBMainFilePath, String OBBPatchFilePath);
public native void nativeSetWindowInfo(boolean bIsPortrait, int;
public native void nativeSetObbInfo(String ProjectName, String PackageName, int Version, int;
public native void nativeSetAndroidVersionInformation( String AndroidVersion, String PhoneMake, String PhoneModel, String PhoneBuildNumber, String OSLanguage );

public native void nativeSetSurfaceViewInfo(int width, int;
public native void nativeSetSafezoneInfo(boolean bIsPortrait, float left, float top, float right, float;

public native void nativeConsoleCommand(String commandString);
public native void nativeVirtualKeyboardChanged(String contents);
public native void nativeVirtualKeyboardResult(boolean;
public native void nativeVirtualKeyboardSendKey(int;
public native void nativeVirtualKeyboardSendTextSelection(String contents, int selStart, int;
public native void nativeVirtualKeyboardSendSelection(int selStart, int;

public native void nativeInitHMDs();

public native void nativeResumeMainInit();

public native void nativeOnActivityResult(GameActivity activity, int requestCode, int;

public native void nativeGoogleClientConnectCompleted(boolean;

public native void nativeVirtualKeyboardShown(int left, int top, int right, int;
public native void nativeVirtualKeyboardVisible(boolean;

public native void nativeOnConfigurationChanged(boolean;

public native void nativeOnInitialDownloadStarted();
public native void nativeOnInitialDownloadCompleted();

这些函数是在 C++ 中实现的,在 Java 中执行到这些函数会自动调用到引擎的 C++ 代码中, 我们可以自己在 GameActivity 添加自定义的 native 的函数。

我这里写一个简单的例子,往 GameActivity 添加一个 native 函数,并在 C++ 端实现。

public native void nativeOnLoginCallBack(String msg);

在 MyUserWidget 类的 C++ 代码中实现一个这样的函数即可:

#if
JNI_METHOD void Java_com_epicgames_ue4_GameActivity_nativeOnLoginCallBack(JNIEnv* jenv, jobject thiz, jstring msg){
FString message;
message = FJavaHelper::FStringFromParam(jenv, msg);
UE_LOG(LogTemp, Log, TEXT("Java_com_epicgames_ue4_GameActivity_nativeOnLoginCallBack=[%s]"), *message);
}
#endif

ps: PLATFORM_ANDROID 宏不可少

​com.epicgames.ue4​​​ 是 UE 生成的 ​​GameActivity.java​​​ 的包名(​​package com.epicgames.ue4;​​)。

可以看到,在 C++ 中实现 JNIMETHOD 的函数名是根据以下的规则:

RType Java_PACKAGENAME_CLASSNAME_FUNCNAME(JNIEnv*,jobject thiz,Oher...)

注意:这个实现函数是可以放在任意的 C++ 中的

然后,我们就可以在 Java 端去执行 C++ 的逻辑了,我在 GameActivity 中收到 LoginActivity 的回调后,去调用 ​​public native void nativeOnLoginCallBack(String msg);​

代码如下:

if (requestCode == 998){
android.util.Log.d("test", "Received data from LoginSDK");
String result = data.getStringExtra("LOGIN");
android.util.Log.d("test", result);
nativeOnLoginCallBack(result);
}

通过打印的日志就可以看到,Java 去调用 C++ 已经成功了。

UE4:Android 平台开发实践指南_java_14

那到这里,整个调用的流程就结束了。

总结

最后总结一下在这整个开发流程里面,我们需要关注的点:

  • Android Studio 版本
  • UE 编辑器中 sdk 路径配置
  • 打包:每次打包后 gradle 文件夹都会重置,记得第一次打包后先将 gradle 另存为,以后再打包就只要替换资源和 so 库即可
  • C++ to Java: JNI, GameActivity.java
  • Java to C++: native 函数,JNIMETHOD,PLATFORM_ANDROID 宏