GLBasic forum

Codesnippets => Code Snippets => Topic started by: spacefractal on 2012-Oct-09

Title: How to communicate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2012-Oct-09
I finally found a workaround from the bug that prevent me to communicate with java directly in this version. Its a bit more advanced, but when its works, then its would been much easier. This method will not break the current apps.

Compiler\platform\android\bin\sdl_main.cpp:
put below lines in the extern "C" area (can been inserted below char g_androidExtStoragePubDir[512] = "~";)
Code (glbasic) Select

char g_androidJAVACALL[512] = "~";
jclass cls;
jmethodID mid;

// * COSTUME JAVACALL *
const char* android_JAVACALL(const char* url)
{

if (cls==NULL)
{ cls = gJNIenv->FindClass("org/libsdl/app/SDLActivity");
mid = gJNIenv->GetStaticMethodID(cls,
   "glb_JAVACALL",
   "(Ljava/lang/String;)Ljava/lang/String;"); // (params;..)return_type
}

   if (mid==NULL)
{ return g_androidJAVACALL;
}

// there could be some exception handling happening here, but there isn't
jstring mystr = gJNIenv->NewStringUTF(url);
jobject retJava = gJNIenv->CallStaticObjectMethod(cls, mid, mystr);
if (retJava==NULL)
{ gJNIenv->DeleteLocalRef(mystr);
gJNIenv->DeleteLocalRef(retJava);
return g_androidJAVACALL;
}
jstring res = (jstring)retJava;
strncpy(g_androidJAVACALL, gJNIenv->GetStringUTFChars(res,  0L), 512);

gJNIenv->DeleteLocalRef(mystr);
gJNIenv->DeleteLocalRef(res);
gJNIenv->DeleteLocalRef(retJava);
res=NULL;
return g_androidJAVACALL;
}


EDIT: Please note, Fivesprite have wrote a more protection one of above code, which can been used, when the above works, its here:
http://www.glbasic.com/forum/index.php?topic=8621.msg77670#msg77670

templateproj\src\com\glbasic\test\SDLActivity.java:
can example inserted before glb_open_url() or around there
Code (glbasic) Select

    // Java functions called from C
public static String glb_JAVACALL(String url)
{ Log.i("glbasic", "calltest");
return "works";
}


test above code in glbasic with:
Code (glbasic) Select

STDOUT "IMPORT"
?IFDEF ANDROID
IMPORT "C" const char* android_JAVACALL(const char* string)
?ENDIF

LOCAL test$=android_JAVACALL("test")
STDOUT "test: "+test$


If the above works corretly, you should get a "works" string in STDOUT, that came from java code call.

With that in mind you can do fun thing with example doing attest loading your self while loading or even better, using Google License Service. When that code have been validated then I post how to use that service.

in java there is a string split command, so you could collect all required calls into that method.

etc String[] tokens = url.split(":");

EDIT:
I got succesful ported Greedy Mouse with this Java communication system over. So its works very well and its dont impact combatible at all. Later in this thread I doing how doing some stuff with that,
Title: Re: How to communcate with java in Android (working in v11.171-beta).
Post by: Falstaff on 2012-Oct-10
This looks cool, thanks for sharing!

Does this mean you can call native Android SDK stuff? Would it be possible this could be used to create libraries for say, in app-purchase, or advertisements?
Title: Re: How to communcate with java in Android (working in v11.171-beta).
Post by: mentalthink on 2012-Oct-10
Hi Thanks for this awesome Work... I donĀ“t have too much skills about Android, but this can be very interesting for calling internal things in Android... perhaps the trouble can be when they change the Android Version....

Thanks a lot...!!!
:nw: :nw:
Title: Re: How to communcate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2012-Oct-10
Yes, but there is some activity issues left. Etc glbasic close its thread completely when I'm trying create a another activity (example a WebKit), Which look its does on purpuse. Hope we can get those fixed first. When fixed then we can use ads services native or use Dropbox.

Howover you can call any java libs, example Google Licence Service works very great (much better than internal drm on google play, and you can use own assets copy so it's don't hang in startup and eventuelly crash out. I later put them here.

Title: Re: How to communcate with java in Android (working in v11.171-beta).
Post by: bigsofty on 2012-Oct-10
This is one of those "I have no use for this yet", with "yet" being the operative word. I am sure this will be VERY useful to me at a later date, thank you!  :good:
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2012-Oct-10
oh dear, spelling in the subject, fixed.

Yes this is not needed early in the development, but its handly when the project is near finished. I did need that due I did not like the default assest installing (which was the first reason to do that).
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2012-Oct-15
Here is some java calls I use

Code (glbasic) Select

public static String glb_JAVACALL(String url)
{ //Log.i("glbasic", "calltest");

String[] tokens = url.split(":");

// copy the sigle assets
if (tokens[0].equals("copy"))
{ tokens[1]="Media/"+tokens[1];
int ok=mSingleton.copyAsset(tokens[1]);
return "";
}

// RecursiveCopy
if (tokens[0].equals("dircopy"))
{ tokens[1]="Media/"+tokens[1];
mSingleton.recursiveCopy(tokens[1], 0);
}

// get device info
if (tokens[0].equals("getdevice"))
{ String AndroidInfo="OSVersion="+System.getProperty("os.version")+"|";
AndroidInfo=AndroidInfo+"OSDevice="+android.os.Build.DEVICE+"|";
AndroidInfo=AndroidInfo+"OSModel="+android.os.Build.MODEL+"|";
AndroidInfo=AndroidInfo+"OSProduct="+android.os.Build.PRODUCT+"|";
AndroidInfo=AndroidInfo+"OSManufacturer="+android.os.Build.MANUFACTURER+"|";
AndroidInfo=AndroidInfo+"DisplayMetrics="+mSingleton.getResources().getDisplayMetrics().density+"|";
return AndroidInfo;
}

if (tokens[0].equals("ScreenOrientation"))
{ Display display = ((WindowManager) mSingleton.getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
screenOrientation = display.getOrientation();
return Integer.toString(screenOrientation);
}

// check file size
if (tokens[0].equals("size"))
{ int size=-1;
tokens[1]="Media/"+tokens[1];
try {
Log.i("glbasic", tokens[1].concat(" is a file, reading..."));
InputStream is = mSingleton.getAssets().open(tokens[1]);
size=-2;
size = is.available();
is.close();
}
catch (IOException e)
{ e.printStackTrace(System.out);
}
return Integer.toString(size);
}

// a working test function.
if (tokens[0].equals("test"))
{ return "This is Working String";
}
return "";
    }

// this check a file and copy a new over on the top, if no file exist or filesize have been changed (this is the asset copy I use)
public int copyAsset(String path)
{ int succes=0;
try {
InputStream in = getAssets().open(path);
FileOutputStream out = new FileOutputStream(getFilesDir().getAbsolutePath().concat(File.separator).concat(path));
int read;
byte[] buffer = new byte[4096];
while ((read = in.read(buffer)) > 0)
{ out.write(buffer, 0, read);
}
out.close();
in.close();
}
catch (IOException e)
{ succes=-1;
e.printStackTrace();
}
return succes;
}


EXAMPLE TO USE:

This fix orientation issue.

Code (glbasic) Select

FUNCTION CallJava$: Args$
LOCAL result$
IF device$<>"a" AND device$<>"ak" THEN RETURN
?IFDEF ANDROID
result$=android_JAVACALL(Args$)
RETURN result$
?ENDIF
ENDFUNCTION

LOCAL orentation=CallJava$("screenOrientation")
DEPRINT("GetOrientation: "+orentation)
SETORIENTATION orentation // that fix orientation issues.
GETSCREENSIZE ScreenWidth, ScreenHeight


A Late edit to reflect change to this post is more clean. Its dosent breaks the follow posts.
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: MrTAToad on 2012-Oct-16
Thats because there isn't a new update :)
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2012-Oct-16
its did fixed most cases, but I guess you should look on the SETSCREEN bug.
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: Crivens on 2012-Oct-30
Nice. Could this be used to call up the Android keyboard?

Cheers
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: Crivens on 2012-Nov-01
QuoteNice. Could this be used to call up the Android keyboard?
Do you think this is possible? Would be great to get a native Android keyboard working.

Cheers 
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: r0ber7 on 2012-Nov-01
Quote from: Crivens on 2012-Nov-01
QuoteNice. Could this be used to call up the Android keyboard?
Do you think this is possible? Would be great to get a native Android keyboard working.

Cheers

Ooh ooh!

+1!

There's so much good stuff on these forums. I should make a seperate bookmark file or something.
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2012-Nov-01
should been possible, howover activity is a bit worry, but seen targetSDK max to 12 seen stabity is something much more. But have not looked into those things. I have used it to better assest loading, Getting Google License info, fixing orientation issue (XY cant been fixed, but Gernot should have fixed that in next beta) as well getting device info.

Title: How to communicate with java in Android (working in v11.171-beta).
Post by: Kitty Hello on 2012-Nov-02
Yo spacefractal. Excellent function. I'll include this dummy in my jave template.
Title: How to communicate with java in Android (working in v11.171-beta).
Post by: Crivens on 2012-Nov-02
Excellent. Would love to see an Android keyboard. Nice work!

Cheers
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: hardyx on 2012-Nov-02
Quote from: spacefractal on 2012-Oct-15
Here is some java calls I use
.................
WOW! Now I see the power of your glb_JAVACALL.
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2012-Nov-04
I have updated the first post, because there was still some strange issue I never have found out why. Its seen Java Visual Machine does NOT correctly remove reference to pointers from c++, but you need to remove them manally from c++.

This is something documents not said, which statemeted java visual machine should garbage collect it by its self. but its dosent do that with java variable, called from c++. So you need to free them using DeleteLocalRef command, which I have added.

This should ben now much more stable rather than crash out after about 120+ java calls.
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: hardyx on 2012-Nov-05
Quote from: spacefractal on 2012-Nov-04
I have updated the first post, because there was still some strange issue I never have found out why. Its seen Java Visual Machine does NOT correctly remove reference to pointers from c++, but you need to remove them manally from c++.

This is something documents not said, which statemeted java visual machine should garbage collect it by its self. but its dosent do that with java variable, called from c++. So you need to free them using DeleteLocalRef command, which I have added.

This should ben now much more stable rather than crash out after about 120+ java calls.

Maybe you must to release manually the java objects you use in the c++ part. BTW, it is called java virtual machine, not visual. Thanks for the fix.
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2012-Nov-06
yes its was required and releasing the java objects I called from c++? Its fixed in the c++ code with few lines (but took hours to find out about that). That could been why some people have cancelled some orders due this? Damn, but finally I did fixed that. I could now do 1000+ calls without crashing. So I guess even KEYPRESSES thing now can been monited, those glbasic sometimes skips? I look on the screen keyboard which could been fun to add (but AMBOS have a nice onscreen keyboard rutine to been used).

Anyway I have cleanup the post about the java calls which one I use, but its can of course expand much more. Etc I explain how to fix the ORIENTATION issues. Due that I have removed the thread from the bug section, because its not needed anymore and required to been fixed by Gernot. Its easy to fix that one by your self.

I hope Gernot add those code and the fix to your build. Fell free to do that :-D.

Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: Crivens on 2012-Nov-06
QuoteI look on the screen keyboard which could been fun to add (but AMBOS have a nice onscreen keyboard rutine to been used).
The Ampos keyboard was really good, and a lot of us make our own. Most of the time it's fine for games, but for example my latest project is an app for a company (ie. not a game) and while they like my own keyboard it was mentioned that it would be nice for the proper Android keyboard to be used. It shows that for apps (rather than games) then the actual Android/iOS keyboard is preferable. My iOS keyboard is orange (Jailbroken), for example, and it's really glaring when it looks different in certain apps (including Cydia amusingly).

So go on, you know you want to implement the keyboard! =D I would owe you a bevvy of beers and build a statue of you in my lounge  :nw: Seriously though, would be quite an addition.

Cheers
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2012-Nov-07
You can trying your self. I might look later right now, by now the biggest issue with keyboard, is GlBasic does not using the GUI, but using a SDLSurface, that is the issue. Its nothing problem to open the keyboard, but there is no commands to get input from it directly with inputMethodManager method. Also I do thinks its might have issues with languages with other than english, due glbasic not supports unicode. But I do try later this week.

By now the most important thing using java here is fixing the orientation issue that might been happens on some devices. SETORIENTATION 0 is not allways default....
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: fivesprites on 2013-Apr-03
This is really handy, but be warned:

- check for exceptions!  This could cause all sorts of strange behaviour if you don't - if you're lucky enough for your app to continue working!  You must always check and clear exceptions
- release strings you have created.  If you don't you will eventually get memory leaks and heap overflow
- there's no reason to cast retJava to another jstring (res). 
- Don't rely on strncpy in this case - I've seen memory leaks with its use.  It's better to get the length of the string you are copying and use that - then apply a null character

Here's an updated segment from the sdlmain.cpp:

Code (glbasic) Select


const char* android_JAVACALL(const char* url)
{
const char *textString = NULL;

strcpy(g_androidJAVACALL, "-1");
if (cls==NULL)
{
cls = gJNIenv->FindClass("org/libsdl/app/SDLActivity");
if (cls == NULL)
{
return "Invalid class";
}
mid = gJNIenv->GetStaticMethodID(cls,
   "glb_JAVACALL",
   "(Ljava/lang/String;)Ljava/lang/String;"); // (params;..)return_type
}

if (mid==NULL)
{
return "Invalid method";
}

jstring mystr = gJNIenv->NewStringUTF(url);
if (NULL == mystr)
{
return g_androidJAVACALL;
}
jstring retJava = (jstring)gJNIenv->CallStaticObjectMethod(cls, mid, mystr);
if (gJNIenv->ExceptionCheck()) {
gJNIenv->ExceptionClear();
gJNIenv->DeleteLocalRef(mystr);
return "Exception calling method";
}
if (retJava == NULL)
{
gJNIenv->DeleteLocalRef(mystr);
return g_androidJAVACALL;
}
textString = gJNIenv->GetStringUTFChars(retJava, 0);
if (gJNIenv->ExceptionCheck()) {
gJNIenv->ExceptionClear();
gJNIenv->DeleteLocalRef(mystr);
gJNIenv->DeleteLocalRef(retJava);
return "Exception getting textString";
}

int len = gJNIenv->GetStringLength(retJava);
if (gJNIenv->ExceptionCheck()) {
gJNIenv->ExceptionClear();
gJNIenv->ReleaseStringUTFChars(retJava, textString);
gJNIenv->DeleteLocalRef(mystr);
gJNIenv->DeleteLocalRef(retJava);
return "Exception getting length of return string";
}

strncpy(g_androidJAVACALL, textString, len);
g_androidJAVACALL[len] = 0x00;

gJNIenv->ReleaseStringUTFChars(retJava, textString);
gJNIenv->DeleteLocalRef(mystr);
gJNIenv->DeleteLocalRef(retJava);

return g_androidJAVACALL;
}


The above still isn't perfect, but should provide a bit more protection.

//Andy
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2013-Apr-04
There should not need to release them when you uses global string variables, not local (which im did not trust on that when you sharing memory with other threads), which is property why om not releasing them. Howover im are no c++ expert, so im did just want the communication to work and prevent memory sharing confict between c++ and java.

No the code was not perfect, but worked nicely and have used long time now (even the first one crashed allready abit over 60-100 calls, which im got fixed that and updated the thread after that fix).

Howover thanks for the update  :)
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: fivesprites on 2013-Apr-04
Hi Spacefractal,

Wasn't being critical of your code - it's a really helpful routine to get access to otherwise hidden Android tasks and I've recently used it for a project I'm working on.  The updated code I posted helps to prevent against some problems which you may not have detected yet.

The JNI documentation states that you must check for exceptions after calling certain JNI routines (use ExceptionCheck). You must then clear any pending exception with ExceptionClear.  As your code doesn't do this, one mistake can result in an instant crash.  (This also applies to other code in sdlmain.cpp!)

Note: if you detect an exception - don't continue, but do ensure you release any strings/local references before returning

Also, when you call:

strncpy(g_androidJAVACALL, gJNIenv->GetStringUTFChars(res,  0L), 512);

you aren't releasing (or unpinning) the reference from the GetStringUTFChars call.  Again, according to the JNI documentation, you must always call ReleaseStringUTFChars().

If you examine the Android logs (adb shell logcat) then you will most likely see that the garbage collection keeps kicking in - and often.  You can also check memory usage stats using adb - check out the (dumpsys meminfo) adb command. 

Keep up the good work  :good:

//Andy
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2013-Apr-05
I checkout later and update the thread soon. howover its was not a major issue.

If you uses other primary calls than I'm using, I can update the javacall function as well.

I'm primary created this one to do my own assets loader, so it's diddent crash under loading.

I'm happy to see what you using this code too and hope gernot add those code as well. Good luck with your project : :good:
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: r0ber7 on 2013-Apr-10
Hi. I'm trying to set this up but I've run into a problem I don't understand.

I've modified sdl_main to include android_JAVACALL:

Code (glbasic) Select

const char* android_JAVACALL(const char* url)
{
return "Well the C part works.";
        }


And that works. But when I try to set up the Java part, I get this message from adb shell logcat.

Code (glbasic) Select
I/glbasic (13754): Invalid method

Which I assume is done because mid == NULL

Code (glbasic) Select

mid = gJNIenv->GetStaticMethodID(cls,
   "glb_JAVACALL",
   "(Ljava/lang/String;)Ljava/lang/String;"); // (params;..)return_type
}

if (mid==NULL)
{
return "Invalid method";
}



But as far as I know, I've placed everything in its right place. This has been added to SDLActivity:

Code (glbasic) Select


   // Java functions called from C
public static String glb_JAVACALL(String url)
{ Log.i("glbasic", "calltest");
return "works";
}


Why doesn't it see the method? I'm not using V11 by the way. Any ideas or suggestions are welcome. Trying to get an Android keyboard up and running.
Title: Re: How to communicate with java in Android (working in v11.171-beta).
Post by: spacefractal on 2013-Apr-11
did you tried to put the java stump to the templateproj\src\com\glbasic\test folder (in glbasic folder), not in distribute\Android\src\com\glbasic\test? the templateproj\src\com\glbasic\test one should replace that one in the distribute when you change the file.

Its look like its could not find your java metode, even its seen the code was correct.

For testing you could also use the method im did, before fivesprites changed its for more protectio. That just for tetsing, but then when its works, then uses fivesprites method again, which is better than mine (just edited my main post to point to that code FiveSprite have made).