Created
July 25, 2023 18:33
-
-
Save ianrice07/36d8731f0d1af10af4803288c7c86c10 to your computer and use it in GitHub Desktop.
Assets required to integrate Backtrace Unreal for Android
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<root xmlns:android="http://schemas.android.com/apk/res/android"> | |
<init> | |
<log text="Backtrace Android"/> | |
</init> | |
<resourceCopies> | |
</resourceCopies> | |
<gradleProperties> | |
<insert> | |
android.bundle.enableUncompressedNativeLibs=false | |
</insert> | |
</gradleProperties> | |
<proguardAdditions> | |
<insert> | |
-keep class com.google.gson.**.* { *; } | |
</insert> | |
<insert> | |
-keep class backtraceio.library.**.* { *; } | |
</insert> | |
</proguardAdditions> | |
<!-- AAR dependencies --> | |
<AARImports> | |
<insertValue value="com.github.backtrace-labs.backtrace-android,backtrace-library,3.7.6" /> | |
<insertNewline/> | |
</AARImports> | |
<gameActivityImportAdditions> | |
<insert> | |
import backtraceio.library.BacktraceClient; | |
import backtraceio.library.BacktraceCredentials; | |
import backtraceio.library.BacktraceDatabase; | |
import backtraceio.library.base.BacktraceBase; | |
import backtraceio.library.enums.database.RetryBehavior; | |
import backtraceio.library.enums.database.RetryOrder; | |
import backtraceio.library.models.BacktraceExceptionHandler; | |
import backtraceio.library.models.database.BacktraceDatabaseSettings; | |
import backtraceio.library.models.json.BacktraceReport; | |
</insert> | |
</gameActivityImportAdditions> | |
<gameActivityClassAdditions> | |
<insert> | |
private BacktraceClient backtraceClient; | |
private final int anrTimeout = 3000; | |
public void initializeBacktraceClient(Map<String, Object> attributes, List<String> attachments) { | |
Context context = getApplicationContext(); | |
BacktraceCredentials credentials = new BacktraceCredentials("https://submit.backtrace.io/{universe}/{token}/json"); | |
String dbPath = context.getFilesDir().getAbsolutePath(); | |
BacktraceDatabaseSettings settings = new BacktraceDatabaseSettings(dbPath); | |
settings.setMaxRecordCount(100); | |
settings.setMaxDatabaseSize(1000); | |
settings.setRetryBehavior(RetryBehavior.ByInterval); | |
settings.setAutoSendMode(true); | |
settings.setRetryOrder(RetryOrder.Queue); | |
BacktraceDatabase database = new BacktraceDatabase(context, settings); | |
backtraceClient = new BacktraceClient(context, credentials, database, attributes, attachments); | |
backtraceClient.enableBreadcrumbs(context); | |
BacktraceExceptionHandler.enable(backtraceClient); | |
// Uncomment this to send a test report | |
// backtraceClient.send("Android Unreal Engine Test"); | |
// Enable handling of native crashes | |
database.setupNativeIntegration(backtraceClient, credentials); | |
// Enable ANR detection | |
backtraceClient.enableAnr(anrTimeout); | |
} | |
</insert> | |
</gameActivityClassAdditions> | |
<gameActivityOnCreateAdditions> | |
<insert> | |
</insert> | |
</gameActivityOnCreateAdditions> | |
</root> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <unordered_map> | |
#include <string> | |
#include <vector> | |
#if PLATFORM_ANDROID | |
#include "Android/AndroidApplication.h" | |
#include <jni.h> | |
#endif | |
DECLARE_LOG_CATEGORY_EXTERN(Backtrace, Log, All); | |
DEFINE_LOG_CATEGORY(Backtrace); | |
namespace BacktraceIO | |
{ | |
#if PLATFORM_ANDROID | |
// Pointer representing Java HashMap class | |
jclass mapClassGlobalRef = nullptr; | |
// HashMap::init method | |
jmethodID initMap = nullptr; | |
// HashMap::put method | |
jmethodID putMap = nullptr; | |
// Pointer representing Java List class | |
jclass listClassGlobalRef = nullptr; | |
// List::init method | |
jmethodID initList = nullptr; | |
// List::add method | |
jmethodID addList = nullptr; | |
void FInitializeStlStringStringMapToJavaHashMap(JNIEnv* Env) | |
{ | |
mapClassGlobalRef = Env->FindClass("java/util/HashMap"); | |
FAndroidApplication::CheckJavaException(); | |
initMap = Env->GetMethodID(mapClassGlobalRef, "<init>", "()V"); | |
FAndroidApplication::CheckJavaException(); | |
putMap = Env->GetMethodID(mapClassGlobalRef, "put", | |
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); | |
FAndroidApplication::CheckJavaException(); | |
} | |
void FInitializeStlVectorStringToJavaListString(JNIEnv* Env) | |
{ | |
listClassGlobalRef = Env->FindClass("java/util/ArrayList"); | |
FAndroidApplication::CheckJavaException(); | |
initList = Env->GetMethodID(listClassGlobalRef, "<init>", "()V"); | |
FAndroidApplication::CheckJavaException(); | |
addList = Env->GetMethodID(listClassGlobalRef, "add", "(Ljava/lang/Object;)Z"); | |
FAndroidApplication::CheckJavaException(); | |
} | |
// Attribution: https://stackoverflow.com/a/53624436/15063264 | |
jobject FStlStringStringMapToJavaHashMap(JNIEnv* env, | |
const std::unordered_map<std::string, std::string>& map) | |
{ | |
if (mapClassGlobalRef == nullptr || initMap == nullptr || putMap == nullptr) { | |
UE_LOG(Backtrace, Error, TEXT("Required object(s) are null")); | |
return nullptr; | |
} | |
if (env == nullptr) { | |
UE_LOG(Backtrace, Error, TEXT("JNI env is null")); | |
return nullptr; | |
} | |
jobject hashMap = env->NewObject(mapClassGlobalRef, initMap); | |
std::unordered_map<std::string, std::string>::const_iterator citr = map.begin(); | |
for (; citr != map.end(); ++citr) { | |
jstring keyJava = env->NewStringUTF(citr->first.c_str()); | |
jstring valueJava = env->NewStringUTF(citr->second.c_str()); | |
env->CallObjectMethod(hashMap, putMap, keyJava, valueJava); | |
env->DeleteLocalRef(keyJava); | |
env->DeleteLocalRef(valueJava); | |
} | |
jobject hashMapGlobal = static_cast<jobject>(env->NewGlobalRef(hashMap)); | |
env->DeleteLocalRef(hashMap); | |
jboolean flag = env->ExceptionCheck(); | |
if (flag) { | |
env->ExceptionDescribe(); | |
env->ExceptionClear(); | |
UE_LOG(Backtrace, Error, TEXT("Detected JNI Exception")); | |
return nullptr; | |
} | |
return hashMapGlobal; | |
} | |
jobject FStlVectorStringToJavaListString(JNIEnv* env, | |
const std::vector<std::string>& vector) | |
{ | |
if (listClassGlobalRef == nullptr || initList == nullptr || addList == nullptr) { | |
UE_LOG(Backtrace, Error, TEXT("Required object(s) are null")); | |
return nullptr; | |
} | |
if (env == nullptr) { | |
UE_LOG(Backtrace, Error, TEXT("JNI env is null")); | |
return nullptr; | |
} | |
jobject list = env->NewObject(listClassGlobalRef, initList); | |
std::vector<std::string>::const_iterator citr = vector.begin(); | |
for (; citr != vector.end(); ++citr) { | |
jstring stringJava = env->NewStringUTF(citr->c_str()); | |
env->CallBooleanMethod(list, addList, stringJava); | |
env->DeleteLocalRef(stringJava); | |
} | |
jobject listGlobal = static_cast<jobject>(env->NewGlobalRef(list)); | |
env->DeleteLocalRef(list); | |
jboolean flag = env->ExceptionCheck(); | |
if (flag) { | |
env->ExceptionDescribe(); | |
env->ExceptionClear(); | |
UE_LOG(Backtrace, Error, TEXT("Detected JNI Exception")); | |
return nullptr; | |
} | |
return listGlobal; | |
} | |
std::unordered_map<std::string, std::string> ConvertTMapToStdMap(const TMap<FString, FString>& Map) | |
{ | |
std::unordered_map<std::string, std::string> Result; | |
for (const auto& Pair : Map) | |
{ | |
std::string Key = std::string(TCHAR_TO_UTF8(*(Pair.Key))); | |
std::string Value = std::string(TCHAR_TO_UTF8(*(Pair.Value))); | |
Result[Key] = Value; | |
} | |
return Result; | |
} | |
std::vector<std::string> ConvertTArrayToStdVector(const TArray<FString>& Array) | |
{ | |
std::vector<std::string> Result; | |
for (const auto& String : Array) | |
{ | |
std::string StdString = std::string(TCHAR_TO_UTF8(*(String))); | |
Result.push_back(StdString); | |
} | |
return Result; | |
} | |
#endif | |
// Attribution: https://forums.unrealengine.com/development-discussion/android-development/106221-custom-java-method-called-from-c-causes-classnotfoundexception | |
bool FInitializeBacktraceClient(const TMap<FString, FString>& Attributes, const TArray<FString>& Attachments) | |
{ | |
FString MethodName = "initializeBacktraceClient"; | |
FString MethodSignature = "(Ljava/util/Map;Ljava/util/List;)V"; | |
#if PLATFORM_ANDROID | |
JNIEnv* Env = FAndroidApplication::GetJavaEnv(false); | |
FAndroidApplication::CheckJavaException(); | |
FInitializeStlStringStringMapToJavaHashMap(Env); | |
FInitializeStlVectorStringToJavaListString(Env); | |
jobject Activity = FAndroidApplication::GetGameActivityThis(); | |
FAndroidApplication::CheckJavaException(); | |
if (Activity != nullptr) | |
{ | |
jclass ActivityClass = Env->GetObjectClass(Activity); | |
FAndroidApplication::CheckJavaException(); | |
if (ActivityClass != nullptr) | |
{ | |
jmethodID Method = Env->GetMethodID(ActivityClass, TCHAR_TO_ANSI(*MethodName), TCHAR_TO_ANSI(*MethodSignature)); | |
FAndroidApplication::CheckJavaException(); | |
if (Method != nullptr) | |
{ | |
auto StdAttributes = ConvertTMapToStdMap(Attributes); | |
auto StdAttachments = ConvertTArrayToStdVector(Attachments); | |
jobject JavaAttributes = FStlStringStringMapToJavaHashMap(Env, StdAttributes); | |
jobject JavaAttachments = FStlVectorStringToJavaListString(Env, StdAttachments); | |
if (JavaAttributes != nullptr && JavaAttachments != nullptr) | |
{ | |
Env->CallVoidMethod(Activity, Method, JavaAttributes, JavaAttachments); | |
FAndroidApplication::CheckJavaException(); | |
Env->DeleteGlobalRef(JavaAttributes); | |
FAndroidApplication::CheckJavaException(); | |
Env->DeleteGlobalRef(JavaAttachments); | |
FAndroidApplication::CheckJavaException(); | |
} | |
else | |
{ | |
UE_LOG(Backtrace, Error, TEXT("Could not create arguments for Java initializeBacktraceClient method")); | |
return false; | |
} | |
} | |
else | |
{ | |
UE_LOG(Backtrace, Error, TEXT("Could not get method ID for Java initializeBacktraceClient method")); | |
return false; | |
} | |
} | |
else | |
{ | |
UE_LOG(Backtrace, Error, TEXT("Could not get Java GameActivity class to extract initializeBacktraceClient method")); | |
return false; | |
} | |
} | |
else | |
{ | |
UE_LOG(Backtrace, Error, TEXT("Could not get GameActivity object")); | |
return false; | |
} | |
return true; | |
#else | |
return false; | |
#endif | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment