Thank you very much for this library, I was able to get it working in my Win32 OpenGL app without any issues. However, now I was trying to do the same for Android and I am stuck. I am very new to Android programming, so most likely it's my fault that I cannot get it to work.
I am using the OpenGL C++ Android template generated by Visual Studio. It uses NativeActivity (C++) instead of Java. The Android integration in this library did not support loading the HttpClientRequest java classes in such case, so I've added:
jclass FindClassFromNativeActivity(HCInitArgs* args, JNIEnv* jniEnv, const char* className)
{
jobject nativeActivity = args->applicationContext;
jclass acl = jniEnv->GetObjectClass(nativeActivity);
jmethodID getClassLoader = jniEnv->GetMethodID(acl, "getClassLoader", "()Ljava/lang/ClassLoader;");
jobject cls = jniEnv->CallObjectMethod(nativeActivity, getClassLoader);
jclass classLoader = jniEnv->FindClass("java/lang/ClassLoader");
jmethodID findClass = jniEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
jstring strClassName = jniEnv->NewStringUTF(className);
jclass flurryClass = (jclass)(jniEnv->CallObjectMethod(cls, findClass, strClassName));
jniEnv->DeleteLocalRef(strClassName);
return flurryClass;
}
And I replaced the code to load these classes:
jclass localHttpRequest = FindClassFromNativeActivity(args, jniEnv, "com.xbox.HttpClientRequest");
if (localHttpRequest == nullptr)
{
HC_TRACE_ERROR(HTTPCLIENT, "Could not find HttpClientRequest class");
return E_FAIL;
}
This works and gets a reference to the HttpClientRequest instance. As for adapting the Win32 samples, I did:
std::vector<std::vectorstd::string> ExtractAllHeaders(In HCCallHandle call)
{
uint32_t numHeaders = 0;
HCHttpCallResponseGetNumHeaders(call, &numHeaders);
std::vector< std::vector<std::string> > headers;
for (uint32_t i = 0; i < numHeaders; i++)
{
const char* str;
const char* str2;
std::string headerName;
std::string headerValue;
HCHttpCallResponseGetHeaderAtIndex(call, i, &str, &str2);
if (str != nullptr) headerName = str;
if (str2 != nullptr) headerValue = str2;
std::vector<std::string> header;
header.push_back(headerName);
header.push_back(headerValue);
headers.push_back(header);
}
return headers;
}
neosmart::neosmart_event_t g_stopRequestedHandle;
neosmart::neosmart_event_t g_workReadyHandle;
neosmart::neosmart_event_t g_completionReadyHandle;
neosmart::neosmart_event_t g_exampleTaskDone;
int g_targetNumThreads = 2;
pthread_t g_hActiveThreads[10] = { };
int g_defaultIdealProcessor = 0;
int g_numActiveThreads = 0;
void* background_thread_proc(void* lpParam)
{
neosmart::neosmart_event_t hEvents[3] =
{
g_workReadyHandle,
g_completionReadyHandle,
g_stopRequestedHandle
};
JNIEnv* jni;
JavaVM* vm = (JavaVM*)lpParam;
vm->AttachCurrentThread(&jni, NULL);
XTaskQueueHandle queue;
XTaskQueueDuplicateHandle(g_queue, &queue);
bool stop = false;
while (!stop)
{
int index = 0;
int dwResult = WaitForMultipleEvents(hEvents, 3, false, -1, index);
switch (index)
{
case 0: // work ready
if (XTaskQueueDispatch(queue, XTaskQueuePort::Work, 0))
{
// If we executed work, set our event again to check next time.
neosmart::SetEvent(g_workReadyHandle);
}
break;
case 1: // completed
// Typically completions should be dispatched on the game thread, but
// for this simple XAML app we're doing it here
if (XTaskQueueDispatch(queue, XTaskQueuePort::Completion, 0))
{
// If we executed a completion set our event again to check next time
neosmart::SetEvent(g_completionReadyHandle);
}
break;
default:
stop = true;
break;
}
}
XTaskQueueCloseHandle(queue);
return NULL;
}
void CALLBACK HandleAsyncQueueCallback(
In void* context,
In XTaskQueueHandle queue,
In XTaskQueuePort type
)
{
//UNREFERENCED_PARAMETER(context);
//UNREFERENCED_PARAMETER(queue);
switch (type)
{
case XTaskQueuePort::Work:
neosmart::SetEvent(g_workReadyHandle);
break;
case XTaskQueuePort::Completion:
neosmart::SetEvent(g_completionReadyHandle);
break;
}
}
void StartBackgroundThread(JavaVM* vm)
{
g_stopRequestedHandle = neosmart::CreateEvent(true, false);
g_workReadyHandle = neosmart::CreateEvent(false, false);
g_completionReadyHandle = neosmart::CreateEvent(false, false);
g_exampleTaskDone = neosmart::CreateEvent(false, false);
for (uint32_t i = 0; i < g_targetNumThreads; i++)
{
pthread_create(&g_hActiveThreads[i], NULL, background_thread_proc, (void*)vm);
}
g_numActiveThreads = g_targetNumThreads;
}
void ShutdownActiveThreads()
{
neosmart::SetEvent(g_stopRequestedHandle);
for (int i = 0; i < g_numActiveThreads; i++) {
pthread_join(g_hActiveThreads[i], NULL);
}
neosmart::ResetEvent(g_stopRequestedHandle);
}
struct SampleHttpCallAsyncContext
{
HCCallHandle call;
bool isJson;
std::string filePath;
};
void DoHttpCall(std::string url, std::string requestBody, bool isJson, std::string filePath)
{
std::string method = "GET";
bool retryAllowed = true;
std::vector<std::vectorstd::string> headers;
std::vector< std::string > header;
header.clear();
header.push_back("TestHeader");
header.push_back("1.0");
headers.push_back(header);
HCCallHandle call = nullptr;
HCHttpCallCreate(&call);
HCHttpCallRequestSetUrl(call, method.c_str(), url.c_str());
HCHttpCallRequestSetRequestBodyString(call, requestBody.c_str());
HCHttpCallRequestSetRetryAllowed(call, retryAllowed);
for (auto& header : headers)
{
std::string headerName = header[0];
std::string headerValue = header[1];
HCHttpCallRequestSetHeader(call, headerName.c_str(), headerValue.c_str(), true);
}
printf("Calling %s %s\r\n", method.c_str(), url.c_str());
SampleHttpCallAsyncContext* hcContext = new SampleHttpCallAsyncContext{ call, isJson, filePath };
XAsyncBlock* asyncBlock = new XAsyncBlock;
ZeroMemory(asyncBlock, sizeof(XAsyncBlock));
asyncBlock->context = hcContext;
asyncBlock->queue = g_queue;
asyncBlock->callback = [](XAsyncBlock* asyncBlock)
{
const char* str;
HRESULT networkErrorCode = S_OK;
uint32_t platErrCode = 0;
uint32_t statusCode = 0;
std::string responseString;
std::string errMessage;
SampleHttpCallAsyncContext* hcContext = static_cast<SampleHttpCallAsyncContext*>(asyncBlock->context);
HCCallHandle call = hcContext->call;
bool isJson = hcContext->isJson;
std::string filePath = hcContext->filePath;
HRESULT hr = XAsyncGetStatus(asyncBlock, false);
if (FAILED(hr))
{
// This should be a rare error case when the async task fails
printf("Couldn't get HTTP call object 0x%0.8x\r\n", hr);
HCHttpCallCloseHandle(call);
return;
}
HCHttpCallResponseGetNetworkErrorCode(call, &networkErrorCode, &platErrCode);
HCHttpCallResponseGetStatusCode(call, &statusCode);
HCHttpCallResponseGetResponseString(call, &str);
if (str != nullptr) responseString = str;
std::vector<std::vector<std::string>> headers = ExtractAllHeaders(call);
if (!isJson)
{
size_t bufferSize = 0;
HCHttpCallResponseGetResponseBodyBytesSize(call, &bufferSize);
uint8_t* buffer = new uint8_t[bufferSize];
size_t bufferUsed = 0;
HCHttpCallResponseGetResponseBodyBytes(call, bufferSize, buffer, &bufferUsed);
/*HANDLE hFile = CreateFileA(filePath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD bufferWritten = 0;
WriteFile(hFile, buffer, (DWORD)bufferUsed, &bufferWritten, NULL);
CloseHandle(hFile);*/
delete[] buffer;
}
HCHttpCallCloseHandle(call);
printf("HTTP call done\r\n");
printf("Network error code: 0x%0.8x\r\n", networkErrorCode);
printf("HTTP status code: %d\r\n", statusCode);
int i = 0;
for (auto& header : headers)
{
printf("Header[%d] '%s'='%s'\r\n", i, header[0].c_str(), header[1].c_str());
i++;
}
if (isJson && responseString.length() > 0)
{
// Returned string starts with a BOM strip it out.
uint8_t BOM[] = { 0xef, 0xbb, 0xbf, 0x0 };
if (responseString.find(reinterpret_cast<char*>(BOM)) == 0)
{
responseString = responseString.substr(3);
}
web::json::value json = web::json::value::parse(utility::conversions::to_string_t(responseString));;
}
if (responseString.length() > 200)
{
std::string subResponseString = responseString.substr(0, 200);
printf("Response string:\r\n%s...\r\n", subResponseString.c_str());
}
else
{
printf("Response string:\r\n%s\r\n", responseString.c_str());
}
neosmart::SetEvent(g_exampleTaskDone);
delete asyncBlock;
};
HCHttpCallPerformAsync(call, asyncBlock);
neosmart::WaitForEvent(g_exampleTaskDone, -1ul);
}
And my android_main:
void android_main(struct android_app* state) {
struct engine engine;
JNIEnv* jni;
JavaVM* vm = state->activity->vm;
vm->AttachCurrentThread(&jni, NULL);
HCInitArgs args;
args.javaVM = vm;
args.applicationContext = state->activity->clazz;
HCInitialize(&args);
XTaskQueueCreate(XTaskQueueDispatchMode::Manual, XTaskQueueDispatchMode::Manual, &g_queue);
XTaskQueueRegisterMonitor(g_queue, nullptr, HandleAsyncQueueCallback, &g_callbackToken);
HCTraceSetTraceToDebugger(true);
StartBackgroundThread(vm);
std::string url1 = "https://.../tiger.svg";
DoHttpCall(url1, "", false, "tiger.svg");`
This creates the threads as in the Win32 sample, but to be able to call into Java code I had to call JavaVM->AttachCurrentThread in my background threads.
The issue with this approach seems to be that the HCInitialize is called from one thread, but the Http requests are processed from other threads, internally sharing the HttpClientRequest java object instance from the HCInitialize call cross-threads - and the JNI apparently does not support it, so I get SIGKill and my app is terminated once I attempt to access HttpClientRequest from another thread.
Putting HCInitialize on the same thread that calls into HttpClientRequest solves the issue, but then I get a deadlock right after executing the HTTP call...
I am thinking that perhaps my approach is completely wrong and this is not how the library is supposed to be used on Android. Could you please provide some working example of an Android NativeActivity implementation using this library?