Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Android and iOS ports available for testing  (Read 339838 times)

0 Members and 1 Guest are viewing this topic.

Mario

  • SFML Team
  • Hero Member
  • *****
  • Posts: 879
    • View Profile
Re: Android and iOS ports available for testing
« Reply #360 on: May 12, 2015, 10:29:28 am »
Identified the sound loading issue, which might also cause the sigsegv... still not sure how that got in there.

Edit/Update:

Please test this commit:

https://github.com/SFML/SFML/commit/4284332b75b4e4b7d0061022bbc0430e0810b297

And comment in this pull request:

https://github.com/SFML/SFML/pull/887
« Last Edit: May 12, 2015, 10:43:23 am by Mario »

R23MJ

  • Jr. Member
  • **
  • Posts: 50
    • View Profile
Re: Android and iOS ports available for testing
« Reply #361 on: May 20, 2015, 07:53:24 pm »
I have searched around a bit and can't find anything, so I'll ask here. I'm not sure how to build an app with the android version, I downloaded and built it and everything, but I'm not sure what project type to build (I'm using VS13 with the NVIDIA Tegra thing) do I build an android project or a C++ project? Also, how do I link the .so files, the same as usual? I tried on an android project but got somewhere around 107 errors, the top ones said that the .so files (libsfml-activity.so etc.) didn't exist.

Is there a project setup tutorial around here anywhere? Thanks.

AlexxanderX

  • Full Member
  • ***
  • Posts: 128
    • View Profile
    • AlexanderX
Re: Android and iOS ports available for testing
« Reply #362 on: May 22, 2015, 07:24:13 am »
After you compiled SFML for Android( here) just go in the folder of the sfml example and run ndk-build and then open normally the project in Eclipse or Android Studio.
Here you can find my blog and tutorials about SFML - http://alexanderx.net/ (died...) - http://web.archive.org/web/20160110002847/http://alexanderx.net/

AlexAUT

  • Sr. Member
  • ****
  • Posts: 396
    • View Profile
Re: Android and iOS ports available for testing
« Reply #363 on: May 22, 2015, 09:20:30 am »
Project setup is very easy, you just select in eclipse-adt "Android porject with exisiting source", then navigate to the SFML directory, examples, android. Then you can check the checkbox beneath something like "copy project into workspace" so you won't modify the SFML-android example for future projects.

(I'm not quite sure, Kroniax-android is a long time ago  ;), but I think you have to add native support to your project, rightclick on the project->android tools->Add Native support.


(I haven't seen the respone from AlexxanderX, before I posted this  ::) )

AlexAUT

dontworry

  • Newbie
  • *
  • Posts: 2
    • View Profile
    • Email
Re: Android and iOS ports available for testing
« Reply #364 on: May 23, 2015, 11:12:37 pm »
Any news on the other bug I was having?

05-11 22:12:43.950: E/libEGL(863): called unimplemented OpenGL ES API

Mario

  • SFML Team
  • Hero Member
  • *****
  • Posts: 879
    • View Profile
Re: Android and iOS ports available for testing
« Reply #365 on: May 24, 2015, 11:03:57 am »
Not yet, some lack of time and still have to find out whether I'm actually able to reproduce it. Are there any other logcat lines nearby that might be related?

ChronicRat

  • Sr. Member
  • ****
  • Posts: 327
  • C++ programmer
    • View Profile
    • My blog
Re: Android and iOS ports available for testing
« Reply #366 on: May 28, 2015, 08:35:54 am »
About WindowImplAndroid::getUnicode.
There new Java object is created and I think it can cause Java stackoverflow without delete of local reference.
Add    lJNIEnv->DeleteLocalRef(ObjectKeyEvent); before detach of Java thread.
You have to delete local refs manually for all created Java objects, even strings.

Mario

  • SFML Team
  • Hero Member
  • *****
  • Posts: 879
    • View Profile
Re: Android and iOS ports available for testing
« Reply #367 on: May 28, 2015, 09:01:24 am »
Yeah, there are quite a few never freed Java objects anyway. Haven't read too deep into it, but I'm aware that those are most likely not freed right now.

ChronicRat

  • Sr. Member
  • ****
  • Posts: 327
  • C++ programmer
    • View Profile
    • My blog
Re: Android and iOS ports available for testing
« Reply #368 on: May 28, 2015, 09:25:07 am »
I just started to work hard with JNI at my job two monthes ago. =) Btw, it is useful to create RAII class to attach Java thread (so we need JavaVM only, pointer to JNIEnv we'll receive after attaching to the thread). And check for exceptions after retreiving of method ids or classes.
I use macros for it:
#define JAVA_EXCEPTION_CHECK_AND_DO(env, doNext)\
                if (env->ExceptionCheck())\
                {\
                        jthrowable throwable = env->ExceptionOccurred();\
                        if (throwable != NULL)\
                        {\
                                Java::LogException(env, throwable);\
                                doNext;\
                        }\
                }
This allow us to not to crash each time if something goes wrong with Java.

Class for attach to Java thread:

namespace Java
{

class Thread
{
public:

        explicit Thread();
        explicit Thread(JavaVM* jvm, JNIEnv*& env);
        ~Thread();

        void Attach(JavaVM* jvm, JNIEnv*& env);
        void Detach();
        bool IsAttached() const { return m_Attached; }

private:

        bool            m_Attached;
        JavaVM*         m_JavaVM;
};

} // namespace Java
 

Implementation:
namespace Java
{

Thread::Thread():
        m_Attached(false),
        m_JavaVM(NULL)
{
}



Thread::Thread(JavaVM* jvm, JNIEnv*& env):
        m_Attached(false),
        m_JavaVM(NULL)
{
        Attach(jvm, env);
}



void Thread::Attach(JavaVM* jvm, JNIEnv*& env)
{
        assert(jvm);
        assert(!m_Attached && "Must be detached before call this function!");
        m_JavaVM = jvm;
        int status = m_JavaVM->GetEnv((void **)&env, JNI_VERSION_1_6);

        if (status == JNI_EDETACHED)
        {
                jint res = m_JavaVM->AttachCurrentThread(&env, NULL);
                m_Attached = res == JNI_OK;
        }
}



Thread::~Thread()
{
        Detach();
}



void Thread::Detach()
{
        if (m_Attached)
        {
                m_Attached = false;
                jint res = m_JavaVM->DetachCurrentThread();
                assert(res == 0);
        }
}

} // namespace Java
 

Using:
JNIEnv* env = NULL;
Java::Thread attach(m_JavaVM, env);
env->FindClass(....
 
And we don't bother where to exit from the function.

And another one note: use variables to hold method ids or java classes. It will improve performance greatly.
« Last Edit: May 28, 2015, 09:35:02 am by ChronicRat »

ChronicRat

  • Sr. Member
  • ****
  • Posts: 327
  • C++ programmer
    • View Profile
    • My blog
Re: Android and iOS ports available for testing
« Reply #369 on: May 28, 2015, 11:28:11 am »
For example, how to use JNI a little bit faster and much more safer or vice versa =) (based on SFML "setVirtualKeyboardVisible"):
#ifndef _ANDROID_VIRTUAL_KEYBOARD_H_
#define _ANDROID_VIRTUAL_KEYBOARD_H_



#include <jni.h>



class VirtualKeyboard
{
public:
        VirtualKeyboard();
        explicit VirtualKeyboard(JavaVM* vm, jobject activityObject);
        ~VirtualKeyboard();

        void Init(JavaVM* vm, jobject activityObject);
        bool SetVisible(bool b) const;
        int GetUnicodeValue(int deviceId, int keyCode, int meta) const;

private:

        void Clear();

        JavaVM*         m_JavaVM;
        jobject         m_InputMethodManager;
        jobject         m_DecorView;
        jclass          m_KeyCharacterMapClass;
        jmethodID       m_HideSoftInput;
        jmethodID       m_ShowSoftInput;
        jmethodID       m_GetWindowToken;
        jmethodID       m_LoadKeyMap;
        jmethodID       m_KeyMapGet;
};


#endif // _ANDROID_VIRTUAL_KEYBOARD_H_
 

Implementation:

#include "virtual_keyboard.h"
#include <android/native_activity.h>



static const char* CONTEXT_PATH = "android/content/Context";
static const char* INPUT_METHOD_MANAGER_PATH = "android/view/inputmethod/InputMethodManager";
static const char* WINDOW_PATH = "android/view/Window";
static const char* VIEW_PATH = "android/view/View";
static const char* KEY_CHARACTER_MAP_PATH = "android/view/KeyCharacterMap";



VirtualKeyboard::VirtualKeyboard():
        m_JavaVM(NULL),
        m_InputMethodManager(NULL),
        m_DecorView(NULL),
        m_KeyCharacterMapClass(NULL),
        m_HideSoftInput(0),
        m_ShowSoftInput(0),
        m_GetWindowToken(0),
        m_LoadKeyMap(0),
        m_KeyMapGet(0)
{
}



VirtualKeyboard::VirtualKeyboard(JavaVM* vm, jobject activityObject):
        m_JavaVM(NULL),
        m_InputMethodManager(NULL),
        m_DecorView(NULL),
        m_KeyCharacterMapClass(NULL),
        m_HideSoftInput(0),
        m_ShowSoftInput(0),
        m_GetWindowToken(0),
        m_LoadKeyMap(0),
        m_KeyMapGet(0)
{
        Init(vm, activityObject);
}



VirtualKeyboard::~VirtualKeyboard()
{
        Clear();
}



void VirtualKeyboard::Clear()
{
        if (!m_JavaVM)
        {
                return;
        }

        JNIEnv* env = NULL;
        Java::Thread attach(m_JavaVM, env);
        env->DeleteWeakGlobalRef(m_InputMethodManager);
        env->DeleteWeakGlobalRef(m_DecorView);
        env->DeleteWeakGlobalRef(m_KeyCharacterMapClass);
        m_JavaVM = NULL;
        m_InputMethodManager = NULL;
        m_DecorView = NULL;
        m_KeyCharacterMapClass = NULL;
        m_HideSoftInput = 0;
        m_ShowSoftInput = 0;
        m_GetWindowToken = 0;
        m_LoadKeyMap = 0;
        m_KeyMapGet = 0;
}



void VirtualKeyboard::Init(JavaVM* vm, jobject activityObject)
{
        if (m_JavaVM)
        {
                Clear();
        }

        if (!vm || !activityObject)
        {
                return;
        }

        m_JavaVM = vm;
        JNIEnv* env = NULL;
        Java::Thread attach(m_JavaVM, env);
        jclass activityClass = env->GetObjectClass(activityObject);
        jclass contextClass = env->FindClass(CONTEXT_PATH);
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);

        jfieldID fieldINPUT_METHOD_SERVICE = env->GetStaticFieldID(contextClass,
                "INPUT_METHOD_SERVICE", "Ljava/lang/String;");
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);

        jobject INPUT_METHOD_SERVICE = env->GetStaticObjectField(contextClass,
                fieldINPUT_METHOD_SERVICE);
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);
        env->DeleteLocalRef(contextClass);

        jclass inputMethodManagerClass =
                env->FindClass(INPUT_METHOD_MANAGER_PATH);
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);

        jmethodID getSystemServiceMethod = env->GetMethodID(activityClass,
                "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);

        jobject inputMethodManagerObject = env->CallObjectMethod(activityObject,
                getSystemServiceMethod, INPUT_METHOD_SERVICE);
        env->DeleteLocalRef(INPUT_METHOD_SERVICE);

        jmethodID getWindowMethod = env->GetMethodID(activityClass,
                "getWindow", "()Landroid/view/Window;");
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);
        env->DeleteLocalRef(activityClass);

        jobject windowObject = env->CallObjectMethod(activityObject, getWindowMethod);

        jclass windowClass = env->FindClass(WINDOW_PATH);
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);

        jmethodID getDecorViewMethod = env->GetMethodID(windowClass,
                "getDecorView", "()Landroid/view/View;");
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);
        env->DeleteLocalRef(windowClass);

        jobject decorViewObject = env->CallObjectMethod(windowObject, getDecorViewMethod);
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);
        env->DeleteLocalRef(windowObject);

        jmethodID showSoftInputMethod = env->GetMethodID(inputMethodManagerClass,
                "showSoftInput", "(Landroid/view/View;I)Z");
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);

        jclass viewClass = env->FindClass(VIEW_PATH);
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);

        jmethodID getWindowTokenMethod = env->GetMethodID(viewClass,
                "getWindowToken", "()Landroid/os/IBinder;");
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);
        env->DeleteLocalRef(viewClass);

        jmethodID hideSoftInputMethod = env->GetMethodID(inputMethodManagerClass,
                "hideSoftInputFromWindow", "(Landroid/os/IBinder;I)Z");
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);
        env->DeleteLocalRef(inputMethodManagerClass);

        jclass keyCharacterMapClass = env->FindClass(KEY_CHARACTER_MAP_PATH);
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);

        jmethodID loadMethod = env->GetStaticMethodID(keyCharacterMapClass,
                "load", "(I)Landroid/view/KeyCharacterMap;");
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);

        jmethodID getMethod = env->GetMethodID(keyCharacterMapClass,
                "get", "(II)I");
        JAVA_EXCEPTION_CHECK_AND_DO(env, m_JavaVM = NULL; return);

        m_InputMethodManager = env->NewWeakGlobalRef(inputMethodManagerObject);
        env->DeleteLocalRef(inputMethodManagerObject);
        m_DecorView = env->NewWeakGlobalRef(decorViewObject);
        env->DeleteLocalRef(decorViewObject);
        m_KeyCharacterMapClass = (jclass) env->NewWeakGlobalRef(keyCharacterMapClass);
        env->DeleteLocalRef(keyCharacterMapClass);
        m_HideSoftInput = hideSoftInputMethod;
        m_ShowSoftInput = showSoftInputMethod;
        m_GetWindowToken = getWindowTokenMethod;
        m_LoadKeyMap = loadMethod;
        m_KeyMapGet = getMethod;
}



bool VirtualKeyboard::SetVisible(bool b) const
{
        if (!m_JavaVM)
        {
                return false;
        }
        JNIEnv* env = NULL;
        Java::Thread attach(m_JavaVM, env);
        bool res = false;
        int flags = 0;

        if (b)
        {
                res = env->CallBooleanMethod(m_InputMethodManager,
                        m_ShowSoftInput, m_DecorView, flags);
        }
        else
        {
                jobject binderObject = env->CallObjectMethod(m_DecorView,
                        m_GetWindowToken);
                res = env->CallBooleanMethod(m_InputMethodManager,
                        m_HideSoftInput, binderObject, flags);
                env->DeleteLocalRef(binderObject);
        }
        return res;
}



int VirtualKeyboard::GetUnicodeValue(int deviceId, int keyCode, int meta) const
{
        if (!m_JavaVM || keyCode == 0)
        {
                return 0;
        }
        JNIEnv* env = NULL;
        Java::Thread attach(m_JavaVM, env);
        jobject kcmObject = env->CallStaticObjectMethod(m_KeyCharacterMapClass, m_LoadKeyMap, deviceId);
        JAVA_EXCEPTION_CHECK_CONTINUE(env);

        if (!kcmObject)
        {
                return 0;
        }

        int unicode = env->CallIntMethod(kcmObject, m_KeyMapGet, keyCode, meta);
        JAVA_EXCEPTION_CHECK_CONTINUE(env);
        env->DeleteLocalRef(kcmObject);
        return unicode;
}

 

And you have to call Init function each time "ANativeActivity_onCreate" called.
« Last Edit: May 31, 2015, 11:03:28 pm by ChronicRat »

ChronicRat

  • Sr. Member
  • ****
  • Posts: 327
  • C++ programmer
    • View Profile
    • My blog
Re: Android and iOS ports available for testing
« Reply #370 on: May 28, 2015, 01:40:51 pm »
About this part of code:
        // This requires some special treatment, since this might represent
        // a repetition of key presses or a complete sequence
        if (key == AKEYCODE_UNKNOWN)
        {
            // This is a unique sequence, which is not yet exposed in the NDK
            // http://code.google.com/p/android/issues/detail?id=33998
        }
 
In this case you have to return value as Unhandled event. This will allow to catch this event in Java and use "KeyEvent.getCharacters" method. Now your "handled" variable is always set to 1, and user can't catch this event in "dispatchEvent" method.

ChronicRat

  • Sr. Member
  • ****
  • Posts: 327
  • C++ programmer
    • View Profile
    • My blog
Re: Android and iOS ports available for testing
« Reply #371 on: May 31, 2015, 09:20:50 pm »
Btw, object returned by (JNIEnv) GetObjectClass requires to delete local ref to it after use. Or you can get this error: "E dalvikvm: JNI ERROR (app bug): local reference table overflow (max=512)"
I just ran stress-test for previously posted VirtualKeyboard to initialize it per frame. So, we need to delete local reference to EVERY jobject (jclass, jstring, jarray) - CallObjectMethod, GetObjectClass, FindClass... for all of them.
I updated post with VirtualKeyboard class. There are fixed leaks of local refs. Pay attention to other way to get unicode value. I think it is better (no proofs =)).
« Last Edit: May 31, 2015, 11:01:30 pm by ChronicRat »

Mario

  • SFML Team
  • Hero Member
  • *****
  • Posts: 879
    • View Profile
Re: Android and iOS ports available for testing
« Reply #372 on: June 04, 2015, 05:00:57 pm »
Yeah, those references... wanted to have a look at them for a long time now.

I've created a new commit/branch here: https://github.com/SFML/SFML/commit/40248b65c309c5b95e57dd4c21f74668be89b4da

Would be great if you could have a look, since I'm not really familiar with the different reference types (local vs. global vs. weak global).

This also fixes the unhandled input event.

ChronicRat

  • Sr. Member
  • ****
  • Posts: 327
  • C++ programmer
    • View Profile
    • My blog
Re: Android and iOS ports available for testing
« Reply #373 on: June 05, 2015, 08:12:22 am »
Well, local reference will be broken as only you detach from the thread, but it is required to be deleted before detach anyway.
Weak global is good to transfer object through several functions where Java thread attached/dettached several times. I did stress-test for it, and most weak references were active whole app runtime, but one of them was broken after 1000-1500 cycles. So, it is bad to hold them whole app lifecycle, nobody knows when it will become broken.
And global reference is reliable and good to hold never changing objects. So, it is better to use in previously posted VirtualKeyboard class, for example.
I hadn't notice change of JavaVM pointer in "ANativeActivity_onCreate", I don't know how constant this value is. Sure, it is good idea to reinit all Java RAII objects in the case if activity->vm will be changed.
About code - looks like you go in the right way. =)

Mario

  • SFML Team
  • Hero Member
  • *****
  • Posts: 879
    • View Profile
Re: Android and iOS ports available for testing
« Reply #374 on: June 05, 2015, 08:22:23 am »
Don't think the JVM ever changes while the app is running (would be odd IMO), so I'm still toying with the idea of just having one static "JavaEnvironment" class or something like that handling all the references, lifetime, etc.