本篇文章搬运自我自己的博客,原文链接: https://imzlp.me/posts/27289/ 作者: 查利鹏

在使用UE4开发Android时,有时需要获取平台相关的信息、或者执行平台相关的操作,在这种情况下,需要在代码中添加Java的代码以及在C++中调用它们。有些需求也需要在游戏中从Java侧接收一些事件,需要处理Java调用C++的流程。

本篇文章主要涉及以下几部分内容:

  • UE工程中添加Java代码
  • Java函数的签名规则
  • Java调用C++的函数
  • C++调用Java的函数

如何利用UE的UPL特性、Java的签名规则,以及在UE中进行JNI调用实现方法,会在文章中做详细的介绍。

UPL

UPL全称Unreal Plugin Language,是一个XML-Based的结构化语言,用于介入UE的打包过程(如拷贝so/编辑,添加IOS的framework/操作plist等),本篇文章主要介绍UPL在Android中的使用,UPL在IOS上的使用,在我之前的文章UE4 开发笔记:Mac/iOS 篇#UPL 在 iOS 中的应用中有介绍。

往UE项目里添加Java代码,需要通过UPL在打包时往插入代码来实现。

UPL的语法使用XML,文件也需要保存为.xml格式:

<?xml version="" encoding="utf-8"?><!--Unreal Plugin Example--><rootxmlns:android=""></root>

<root></root>中可以使用UPL提供的节点来编写逻辑(但是因为它的语法都是XML的形式来实现编程逻辑的,所以写起来循环等控制流程十分麻烦),以添加中权限请求为例(以下代码均位于<root></root>中):

<androidManifestUpdates><!--权限请求--><addPermissionandroid:name=""/><addPermissionandroid:name=""/><!--Android的全面屏支持--><addElementstag="application"><meta-dataandroid:name=""android:value="portrait|landscape"/><meta-dataandroid:name=""android:value="true"/></addElements></androidManifestUpdates>

使用androidManifestUpdates节点,可以在其中更新,UPL为IOS和Android都提供了很多平台相关的节点,在使用时需要注意,不能混用。

UPL还提供了往GameActivity类中添加Java方法的节点:gameActivityClassAdditions,通过这个节点,可以直接在UPL里编写Java代码,在构建Android包时,会自动把这些代码插入到中的GameActivity类中:

<gameActivityClassAdditions><insert>public String AndroidThunkJava_GetPackageName(){Context context = getApplicationContext();return ();}public String AndroidThunkJava_GetInstalledApkPath(){Context context = getApplicationContext();PackageManager packageManager = ();ApplicationInfo appInfo;try{appInfo = ((),);return appInfo.sourceDir;}catch (PackageManager.NameNotFoundException e){return "invalid";}}</insert></gameActivityClassAdditions>

插入之后生成的文件:

这两个函数就在中了,UPL有很多增加GameActivity内容的节点,这部分内容在UE的文档中是不全的,具体还是要去看UBT的代码:UnrealBuildTool/System/。

UPL支持对GameActivity的扩展,不仅仅只是添加函数,还可以给OnCreate/OnDestory等函数添加额外的代码,方便根据需求介入到不同的时机。

/* Engine/Source/Programs/UnrealBuildTool/System/
*   <!-- optional additions to the GameActivity imports in  -->
*   <gameActivityImportAdditions> </gameActivityImportAdditions>
*
*   <!-- optional additions to the GameActivity after imports in  -->
*  <gameActivityPostImportAdditions> </gameActivityPostImportAdditions>
*
*   <!-- optional additions to the GameActivity class implements in  (end each line with a comma) -->
*   <gameActivityImplementsAdditions> </gameActivityImplementsAdditions>
*
*   <!-- optional additions to the GameActivity class body in  -->
*   <gameActivityClassAdditions> </gameActivityOnClassAdditions>
*
*   <!-- optional additions to GameActivity onCreate metadata reading in  -->
*   <gameActivityReadMetadata> </gameActivityReadMetadata>
*
*   <!-- optional additions to GameActivity onCreate in  -->
*   <gameActivityOnCreateAdditions> </gameActivityOnCreateAdditions>
*
*   <!-- optional additions to GameActivity onDestroy in  -->
*   <gameActivityOnDestroyAdditions> </gameActivityOnDestroyAdditions>
*
*   <!-- optional additions to GameActivity onStart in  -->
*   <gameActivityOnStartAdditions> </gameActivityOnStartAdditions>
*
*   <!-- optional additions to GameActivity onStop in  -->
*   <gameActivityOnStopAdditions> </gameActivityOnStopAdditions>
*
*   <!-- optional additions to GameActivity onPause in  -->
*   <gameActivityOnPauseAdditions> </gameActivityOnPauseAdditions>
*
*   <!-- optional additions to GameActivity onResume in  -->
*   <gameActivityOnResumeAdditions> </gameActivityOnResumeAdditions>
*
*   <!-- optional additions to GameActivity onNewIntent in  -->
*   <gameActivityOnNewIntentAdditions> </gameActivityOnNewIntentAdditions>
*
*   <!-- optional additions to GameActivity onActivityResult in  -->
*   <gameActivityOnActivityResultAdditions> </gameActivityOnActivityResultAdditions>
*/

那么,写完了UPL的脚本之后,如何来使用它呢?

需要在需要添加该UPL的Module的中添加以下代码:

// for Android
if(Target.Platform==UnrealTargetPlatform.Android){PrivateDependencyModuleNames.Add("Launch");AdditionalPropertiesForReceipt.Add("AndroidPlugin",Path.Combine(ModuleDirectory,"UPL/Android/"));}// for IOS
if(Target.Platform==UnrealTargetPlatform.IOS){AdditionalPropertiesForReceipt.Add("IOSPlugin",Path.Combine(ModuleDirectory,"UPL/IOS/"));}

通过AdditionalPropertiesForReceipt来指定我们的UPL脚本,注意AndroidPluginIOSPlugin不可修改,文件路径可以根据UPL文件在项目中的位置指定。

使用这种方式就把UPL添加到了UE的构建系统中,当构建Android/IOS平台时,就会自动执行我们在脚本中的逻辑了。

Java函数签名

JNI是什么?JNI全称Java Native Interface,即Java原生接口。主要用来从Java调用其他语言代码、其他语言来调用Java的代码。

在上一节中,我们通过UPL往GameActivity中添加了Java的代码,在UE中如何通过C++去调用这些Java的函数,需要使用JNI调用来实现。

通过C++去调用Java,首先需要知道,所要调用的Java函数的签名。签名是描述一个函数的参数和返回值类型的信息。 以该函数为例:

publicStringAndroidThunkJava_GetPackageName(){return"";}

以这个函数为例,它不接受参数,返回一个Java的String值,那么它的签名是什么呢?

签名的计算是有一个规则的,暂时先按下不表,后面会详细介绍。

JDK提供的javac具有一个参数可以给Java代码生成C++的头文件,用来方便JNI调用,其中就包含了签名。

写一个测试的Java代码,用来生成JNI调用的.h:

publicclassGameActivity{publicstaticnativeStringSingnatureTester();}

生成命令:

javac -h . 

会在当前目录下生成.class.h文件,.h中的内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include<jni.h>/* Header for class GameActivity */#ifndef _Included_GameActivity
#define _Included_GameActivity
#ifdef __cplusplus
extern"C"{#endif
/*
 * Class:     GameActivity
 * Method:    SingnatureTester
 * Signature: ()Ljava/lang/String;
 */JNIEXPORTjstringJNICALLJava_GameActivity_SingnatureTester(JNIEnv*,jclass);#ifdef __cplusplus
}#endif
#endif

里面导出了GameActivity类成员SingnatureTesterJNI调用的符号信息,在注释中包含了它的签名()Ljava/lang/String;

Java_ue4game_GameActivity_SingnatureTester是当前函数可以在C/C++中实现的函数名,当我们在C++中实现了这个名字的函数,在Java中调用到GameActivitySingnatureTester时,就会调用到我们C++中的实现。

把函数声明改为:

publicclassGameActivity{publicstaticnativeStringSingnatureTester(intival,doubledval,Stringstr);}

它的签名则是:

/*
 * Class:     GameActivity
 * Method:    SingnatureTester
 * Signature: (IDLjava/lang/String;)Ljava/lang/String;
 */

经过上面的两个例子,其实就可以看出来Java函数的签名规则:签名包含两部分——参数、返回值。

其中,()中的是参数的类型签名,按照参数顺序排列,()后面的是返回值的类型签名。

那么Java中的类型签名规则是怎么样的呢?可以依据下面的Java签名对照表:JNI 调用签名对照表。

Java中的基础类型和签名对照表

根据上面的规则,void EmptyFunc(int)的签名为(I)V

非内置基础类型的签名规则为:

  1. L开头
  2. ;结尾
  3. 中间用/隔开包和类名

如Java中类类型:

  • String:Ljava/lang/String;
  • Object:Ljava/lang/Object;

给上面的例子加上package时候再测试下:

packageue4game;publicclassGameActivity{publicstaticnativeStringSingnatureTester(GameActivityactivity);}

则得到的签名为:

/*
 * Class:     ue4game_GameActivity
 * Method:    SingnatureTester
 * Signature: (Lue4game/GameActivity;)Ljava/lang/String;
 */JNIEXPORTjstringJNICALLJava_ue4game_GameActivity_SingnatureTester(JNIEnv*,jclass,jobject);

JNI:Java to C++

UE给我们的游戏生成的GameActivity中也声明了很多的native函数,这些函数是在C++实现的,在Java中执行到这些函数会自动调用到引擎的C++代码中:

publicnativeintnativeGetCPUFamily();publicnativebooleannativeSupportsNEON();publicnativevoidnativeSetAffinityInfo(booleanbEnableAffinity,intbigCoreMask,intlittleCoreMask);publicnativevoidnativeSetConfigRulesVariables(String[]KeyValuePairs);publicnativebooleannativeIsShippingBuild();publicnativevoidnativeSetAndroidStartupState(booleanbDebuggerAttached);publicnativevoidnativeSetGlobalActivity(booleanbUseExternalFilesDir,booleanbPublicLogFiles,StringinternalFilePath,StringexternalFilePath,booleanbOBBInAPK,StringAPKPath);publicnativevoidnativeSetObbFilePaths(StringOBBMainFilePath,StringOBBPatchFilePath);publicnativevoidnativeSetWindowInfo(booleanbIsPortrait,intDepthBufferPreference);publicnativevoidnativeSetObbInfo(StringProjectName,StringPackageName,intVersion,intPatchVersion,StringAppType);publicnativevoidnativeSetAndroidVersionInformation(StringAndroidVersion,StringPhoneMake,StringPhoneModel,StringPhoneBuildNumber,StringOSLanguage);publicnativevoidnativeSetSurfaceViewInfo(intwidth,intheight);publicnativevoidnativeSetSafezoneInfo(booleanbIsPortrait,floatleft,floattop,floatright,floatbottom);publicnativevoidnativeConsoleCommand(StringcommandString);publicnativevoidnativeVirtualKeyboardChanged(Stringcontents);publicnativevoidnativeVirtualKeyboardResult(booleanupdate,Stringcontents);publicnativevoidnativeVirtualKeyboardSendKey(intkeyCode);publicnativevoidnativeVirtualKeyboardSendTextSelection(Stringcontents,intselStart,intselEnd);publicnativevoidnativeVirtualKeyboardSendSelection(intselStart,intselEnd);publicnativevoidnativeInitHMDs();publicnativevoidnativeResumeMainInit();publicnativevoidnativeOnActivityResult(GameActivityactivity,intrequestCode,intresultCode,Intentdata);publicnativevoidnativeGoogleClientConnectCompleted(booleanbSuccess,StringaccessToken);publicnativevoidnativeVirtualKeyboardShown(intleft,inttop,intright,intbottom);publicnativevoidnativeVirtualKeyboardVisible(booleanbShown);publicnativevoidnativeOnConfigurationChanged(booleanbPortrait);publicnativevoidnativeOnInitialDownloadStarted();publicnativevoidnativeOnInitialDownloadCompleted();publicnativevoidnativeHandleSensorEvents(float[]tilt,float[]rotation_rate,float[]gravity,float[]acceleration);

在上一节Java签名中已经提到过,native的方法是Java调用其他语言实现,上面这些函数在UE中均有实现,用于在引擎中接收Android设备的不同逻辑,定义分布在下列文件中:

Runtime\Android\AndroidLocalNotification\Private\AndroidLocalNotification.cpp
Runtime\ApplicationCore\Private\Android\AndroidWindow.cpp
Runtime\Core\Private\Android\AndroidPlatformFile.cpp
Runtime\Core\Private\Android\AndroidPlatformMisc.cpp
Runtime\Core\Private\Android\AndroidPlatformProcess.cpp
Runtime\Launch\Private\Android\AndroidEventManager.cpp
Runtime\Launch\Private\Android\AndroidJNI.cpp
Runtime\Launch\Private\Android\

我们也可以自己在GameActivity添加native的函数,如果有一些SDK中提供了native这样的函数,也可以用以下方式来实现,我这里写一个简单的例子,使用UPL往GameActivity添加一个native函数,并在C++端实现。

<gameActivityClassAdditions><insert>publicnativevoidnativeDoTester(StringMsg);</insert></gameActivityClassAdditions>

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

#if PLATFORM_ANDROID
JNI_METHODvoidJava_com_epicgames_ue4_GameActivity_nativeDoTester(JNIEnvjenv*,jobjectthiz,jstringmsg);{}#endif

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

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

RTypeJava_PACKAGENAME_CLASSNAME_FUNCNAME(JNIEnv*,jobjectthiz,Oher...)

注意:这个函数必须是个C函数,不能参与C++的name mangling,不然签名就不对了。

在UE中可以使用JNI_METHOD宏,它定义在中。

// Runtime/Core/Public/Android/
#define JNI_METHOD          __attribute__ ((visibility ("default"))) extern "C"

也可以使用extern "C"。在C++中定义之后,如果Java端调用了该函数,就可以执行到我们在C++里写的逻辑了。

JNI:C++ to Java

通过上一节的内容,可以知道了Java中函数的签名信息,如何在UE中通过函数名和签名信息来在C++中调用到游戏中的Java代码呢。

UE在C++端封装了大量的JNI的辅助函数,可以很方便地进行JNI操作。这些函数大多定义在下面三个头文件中:

// Runtime/Launch/Public/Android
#include"Android/"// Runtime/Core/Public/Android
#include"Android/"// Runtime/Core/Public/Android
#include"Android/"

因为位于Launch模块中,所以在需要在中为Android平台添加该模块。

以第一节我们使用UPL往GameActivity类中添加的下面这个函数为例:

publicStringAndroidThunkJava_GetPackageName(){Contextcontext=getApplicationContext();returncontext.getPackageName();}

想要在UE中调用到它,首先要获取它的jmethodID,需要通过函数所属的类函数名字签名三种信息来获取:

if(JNIEnv*Env=FAndroidApplication::GetJavaEnv()){jmethodIDGetPackageNameMethodID=FJavaWrapper::FindMethod(Env,FJavaWrapper::GameActivityClassID,"AndroidThunkJava_GetPackageName","()Ljava/lang/String;",false);}

因为我们的代码是插入到GameActivity类中的,而UE对GameActivity做了封装,所以可以通过FJavaWrapper来获取,FJavaWrapper定义位于Runtime/Launch/Public/Android

得到的这个methodID,有点类似于C++的成员函数指针,想要调用到它,需要通过某个对象来执行调用,UE也做了封装:

jstringJstringResult=(jstring)FJavaWrapper::CallObjectMethod(Env,FJavaWrapper::GameActivityThis,GetPackageNameMethodID);

通过CallObjectMethod来在GameActivity的实例上调用GetPackageNameMethodID,得到的值是java中的对象,这个值还不能直接转换为UE中的字符串使用,需要进行转换的流程:

namespaceFJavaHelperEx{FStringFStringFromParam(JNIEnv*Env,jstringJavaString){if(!Env||!JavaString||Env->IsSameObject(JavaString,NULL)){return{};}constautochars=Env->GetStringUTFChars(JavaString,0);FStringReturnString(UTF8_TO_TCHAR(chars));Env->ReleaseStringUTFChars(JavaString,chars);returnReturnString;}FStringFStringFromLocalRef(JNIEnv*Env,jstringJavaString){FStringReturnString=FStringFromParam(Env,JavaString);if(Env&&JavaString){Env->DeleteLocalRef(JavaString);}returnReturnString;}}

通过上面定义的FJavaHelperEx::FStringFromLocalRef可以把jstring转换为UE的FString:

FStringFinalResult=FJavaHelperEx::FStringFromLocalRef(Env,JstringResult);

到这里,整个JNI调用的流程就结束了,能够通过C++去调用Java并获取返回值了。

结语

参考资料:

- Unreal Plugin Language

- Jni符号对照

- JNI Types and Data Structures

android sudio jni 调用so_UE4:UPL 与 JNI 调用的最佳实践相关推荐

  1. Cocos2d-x下Lua调用自定义C++类和函数的最佳实践

    关于cocos2d-x下Lua调用C++的文档看了不少,但没有一篇真正把这事给讲明白了,我自己也是个初学者,摸索了半天,总结如下: cocos2d-x下Lua调用C++这事之所以看起来这么复杂.网上所 ...

  2. Android JNI入门第七篇——C调用Java

    关键代码: java: [java] view plaincopy print? public class CCallJava { public static String getTime() { L ...

  3. 【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )

    文章目录 I . 调用 Java 方法流程 II . 获取 jclass 对象 ( GetObjectClass ) III . 获取 jclass 对象 ( FindClass ) IV . JNI ...

  4. Android JNI入门第六篇——C调用Java

    本篇将介绍在JNI编程中C调用Java实现. ×××地址:http://download.csdn.net/detail/xyz_lmn/4868265 关键代码: java: public clas ...

  5. Android NDK开发之旅(2):一篇文章搞定Android Studio中使用CMake进行NDK/JNI开发

    Android NDK开发之旅(2):一篇文章搞定android Studio中使用CMake进行NDK/JNI开发 (码字不易,转载请声明出处:http://blog.csdn.NET/andrex ...

  6. android c回调java_android使用c通过jni回调java

    很多场合都有这样的需求,由于以前都是java调用c的接口,没有做过回调,今天花了大半天时间把这个流程跑通了,记录一下,以备后用.这里发句牢骚,那些网上分享出来的代码,请问你们确实是能正常工作吗?还有查 ...

  7. 【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( JNI 传递 Bitmap | 获取位图信息 | 获取图像数据 | 图像数据过滤 | 释放资源 )

    文章目录 一.Bitmap 图像数据处理 二.Java 层 Bitmap 对象转为 JNI 层 bitmap 对象 三.获取 bitmap 中的图像数据 四.过滤 bitmap 中的图像数据 ( 获取 ...

  8. java调用jni接口,Java 中通过jni接口调用native code

    [    Java语言本身是通过Java的虚拟机解释执行的,因此对于Java中调用本地动态链接库的问题便提上了日程,为何会存在这样的需求呢?因为Java本身的机制导致一些要求高效率 在上上篇中已经介绍 ...

  9. Android之 震动(Vibrator)如何贯通Android系统 (从硬件设计 -- 驱动 -- HAL -- JNI -- Framework -- Application)

        在Android 2.3(Gingerbread) 系统的时候,我写过一篇关于"Android 震动马达系统"的文章,当时的Linux内核还是2.6版本的.写那篇文章的目的 ...

最新文章

  1. 二极管的反向恢复过程
  2. leetcode算法题--回文链表
  3. 网易2022秋季校园招聘-通用技术A卷-0821
  4. WS-Eventing、WS-Transfer Web服务标准
  5. 开源RefreshListView下拉刷新效果
  6. mongodb 无法查出数据_MongoDB,再见还是再等等?
  7. 吴恩达深度学习3.2笔记_Structuring Machine Learning Projects_机器学习策略(2)
  8. 中式装修怎么做,有哪些注意事项?
  9. bootstrap项目中无间距栅格(grid) no-gutter
  10. 我和Linux,不得不说的故事
  11. 考研计算机专业课961考什么,北航计算机考研(961)经验谈
  12. C语言n番战--数组(三)
  13. linux 中两个文档怎么对比内容是否一致
  14. 百度wz开户竞价推广如何做到降低平均点击价格
  15. win10升级补丁_微软官方给出无法安装WIN10更新的终极解决办法:覆盖安装
  16. Oracle数据库配置
  17. JavaScript中的let声明
  18. c语言三个数从小到大排序/输出
  19. linux系统编程学习_(2)进程控制-- fork函数、exec函数族、回收子进程--孤儿进程僵尸进程、wait函数
  20. 【原创】VMware安装没有引导的Ghost镜像,超级详细,步步讲解

热门文章

  1. requireJS和seajs区别?
  2. Machine Learning on Spark—— 统计基础(一)
  3. 结合电商支付业务一文搞懂DDD
  4. 最新RemObjects,您值得拥有
  5. 《Kali Linux 渗透测试技术详解》笔记之 metasploit 学习纪要
  6. git学习笔记04-将本地仓库添加到GitHub远程仓库-git比svn先进的地方
  7. 毕业后才认清的15个道理
  8. Java中UDP协议的基本原理和简单用法
  9. HDU 4020 Ads Proposal
  10. 7/7 SELECT语句:创建计算字段