UE4:Android 平台开发实践指南

3 篇文章 0 订阅

前言

在使用 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 版本。

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

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

构建首个 UE 工程

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

Android 打包配置

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

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

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

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

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

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

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

然后就可以顺利打包了。

升级至 AndroidX

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

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

android.useAndroidX=true
android.enableJetifier=true
复制代码

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

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

Android AAR

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

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

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

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

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 PLATFORM_ANDROID
    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 resultCode, Intent data)
	{
		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 littleCoreMask);
	public native void nativeSetConfigRulesVariables(String[] KeyValuePairs);

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

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

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

	public native void nativeInitHMDs();

	public native void nativeResumeMainInit();

	public native void nativeOnActivityResult(GameActivity activity, int requestCode, int resultCode, Intent data);

	public native void nativeGoogleClientConnectCompleted(boolean bSuccess, String accessToken);

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

	public native void nativeOnConfigurationChanged(boolean bPortrait);

	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 PLATFORM_ANDROID
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++ 已经成功了。

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

总结

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

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


作者:HelloWorld杰少
链接:https://juejin.cn/post/7068510988378570783
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值