android sudio jni 调用so_UE4:UPL 与 JNI 调用的最佳实践
本篇文章搬運自我自己的博客,原文鏈接: 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腳本,注意AndroidPlugin和IOSPlugin不可修改,文件路徑可以根據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中調用到GameActivity的SingnatureTester時,就會調用到我們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。
非內置基礎類型的簽名規則為:
如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);{}#endifcom.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 调用的最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 英雄联盟封号怎么解除
- 下一篇: 肯德基app怎么点外卖(如何评价肯德基新