1前言
在使用 UE 开发手游时,我们免不了要跟 Android 和 iOS 进行一些数据的交互,在这种情况下,就需要在代码中添加一些操作,使得在 Android 平台下 C 可以调用 Java,Java 可以调用 C ;iOS 平台下也是一样,C 可以调用 OC,OC 可以调用 C 。由于上次我已经实现了 UE 和 iOS 之间的互相调用,文章传送门,今天就继续和大家来讲讲 UE 和 Android 之间的互相调用。
2Android 环境搭建
根据你安装的 UE 引擎的版本,来安装对应的 Android Studio。我的引擎版本是 4.25.4,所以根据 UE 文档的指示,安装了 Android Studio 3.5.3 版本。
image
安装完成后,查看一下 NDK 的版本,记得勾选右下脚的 “Show Package Detail”。
image
如果 NDK 没有安装为指定的版本,那请先勾选对的版本后,再下载安装。
3构建首个 UE 工程
Android 环境配置好了以后,咱们就开始去构建一个 UE 工程,主要的思路是在界面 UI 上添加一个按钮,点击按钮后触发一个事件,该部分我已经在之前的教程中整理出来了,大家可以点击查看。
4Android 打包配置
接下来,在 UE 中去配置 Android 打包,在 “项目设置” 中找到 “打包” 选项,并设置为 “开发”。
image
如果需要 Release 的包,那就在下拉列表中,设置为“发行”。
image
在 “项目设置” 中找到 “平台” 选项,然后配置 “Android”,将这俩个栏目都设置为同意,接受SDK证书,以及填上安卓包名称。
image
image
在 Android SDK 中填上对应的路径。
image
PS:由于我是用的 Mac,所以在路径配置上和 Windows 的不同。
Android SDK 和 NDK 的的路径可以在 Android Studio 中查看到。
image
根据在 Android Studio 中查看到的 Android SDK 路径,找到对应 NDK 的路径。
image
然后就可以顺利打包了。
image
5升级至 AndroidX
在 UE 中完成打包后,用 Android Studio 打开你的 UE 工程目录:Intermediate -> Android -> armv7 -> gradle,
image
在全局的 gradle.properties 加入以下俩个配置:
代码语言:javascript复制android.useAndroidX=true
android.enableJetifier=true
用 Android Studio 自带的升级功能,将项目升级至 AndroidX。
image
然后执行一下 gradle sync,最后将整体工程进行编译,编译成功就代表我们的工程已经升级至了 AndroidX。
6Android AAR
在使用 UE4 开发 Android 时,经常需要接入第三方的库,于是就做个简单的案例吧!
在上文中我已将 UE 打包出来的 gradle 加载到了 Android Studio 中,然后依次 File -> New Module, 新建一个 AAR 库。
我暂时将这个库命名为 LoginSDK,目录结构如下:
image
这时候一个简单的第三方库就创建好了。在下面的文章中,会继续教大家如何去调用这个第三方库。
7C 调用 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 代码中的按钮点击事件中,加入如下代码。
代码语言:javascript复制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 库中,去实现一些逻辑:
- 创建一个 LoginActivity,并加上 EditText 和 Button 俩个控件
- 在 GameActivity 中跳转到 LoginActivity
- 点击 LoginActivity 中的按钮后,将 EditText 控件中的值回调给 GameActivity
在 GameActivity.java 中的 AndroidThunkJava_InitName() 函数中去调用 LoginSDK,代码如下。
代码语言:javascript复制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 中实现如下代码:
代码语言:javascript复制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 的值你可以自定义
8Java 调用 C
UE 给我们的游戏生成的 GameActivity 中也声明了很多的 native 函数,例如:
代码语言:javascript复制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 端实现。
代码语言:javascript复制public native void nativeOnLoginCallBack(String msg);
在 MyUserWidget 类的 C 代码中实现一个这样的函数即可:
代码语言:javascript复制#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 的函数名是根据以下的规则:
代码语言:javascript复制RType Java_PACKAGENAME_CLASSNAME_FUNCNAME(JNIEnv*,jobject thiz,Oher...)
注意:这个实现函数是可以放在任意的 C 中的
然后,我们就可以在 Java 端去执行 C 的逻辑了,我在 GameActivity 中收到 LoginActivity 的回调后,去调用 public native void nativeOnLoginCallBack(String msg);
代码如下:
代码语言:javascript复制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 已经成功了。
image
那到这里,整个调用的流程就结束了。
9总结
最后总结一下在这整个开发流程里面,我们需要关注的点:
- Android Studio 版本
- UE 编辑器中 sdk 路径配置
- 打包:每次打包后 gradle 文件夹都会重置,记得第一次打包后先将 gradle 另存为,以后再打包就只要替换资源和 so 库即可
- C to Java: JNI, GameActivity.java
- Java to C : native 函数,JNIMETHOD,PLATFORM_ANDROID 宏