Cocos2d-x is awesome! However, the framework is not designed to do EVERYTHING for you, thus occasionally we have to break out those programming skills, roll our sleeves up, and get dirty. Thankfully, its not very often. I recently found one of these instances when I attempted to open a URL from inside my app while running on an Android device.
Its a bit of a round about process to get from C++ to Java to Android to another App, especially for someone who has been spoiled by how easy it is to do on iOS devices. The very first step is getting your cocos2d-x project up and running on android. I used the following blog post the first time I did it, and it works pretty well.
http://www.supersuraccoon-cocos2d.com/2011/08/10/cocos2d-x-iphone-androidide-installation-and-setup-under-mac-os/
So I assume you have followed the above instructions and made your way back here with a fully functioning "HelloWorld". Let me be the first to congratulate you! What a pain right! Well the good news is that the hard part is over. So lets get back to trying to open a URL on android from inside our cocos2d-x app.
On iOS you would simply call:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithUTF8String:url]]];
However, since we are using the android NDK to interface from our Cocos2d-x project it takes a few more steps. The first step is to open your Eclipse Project you created using the link above. Inside this project you will find "ProjectName/src/org.cocos2dx.lib/Cocos2dxActivity.java". The beginning of that file should look something like this:
/**********Cocos2dxActivity.java**********/
package org.cocos2dx.lib;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
public class Cocos2dxActivity extends Activity{
public static int screenWidth;
public static int screenHeight;
private static Cocos2dxMusic backgroundMusicPlayer;
private static Cocos2dxSound soundPlayer;
private static Cocos2dxAccelerometer accelerometer;
private static boolean accelerometerEnabled = false;
private static Handler handler;
private final static int HANDLER_SHOW_DIALOG = 1;
private static String packageName;
private static native void nativeSetPaths(String apkPath);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// get frame size
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
accelerometer = new Cocos2dxAccelerometer(this);
// init media player and sound player
backgroundMusicPlayer = new Cocos2dxMusic(this);
soundPlayer = new Cocos2dxSound(this);
handler = new Handler(){
public void handleMessage(Message msg){
switch(msg.what){
case HANDLER_SHOW_DIALOG:
showDialog(((DialogMessage)msg.obj).title, ((DialogMessage)msg.obj).message);
break;
}
}
};
}
//CLASS METHODS REMOVED FROM EXAMPLE TO SINCE THEY DO NOT CHANGE
}
/********************/
Change the class to look like this. The lines denoted in RED are the changes.
/**********Cocos2dxActivity.java**********/
package org.cocos2dx.lib;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
public class Cocos2dxActivity extends Activity{
public static int screenWidth;
public static int screenHeight;
private static Cocos2dxMusic backgroundMusicPlayer;
private static Cocos2dxSound soundPlayer;
private static Cocos2dxAccelerometer accelerometer;
private static boolean accelerometerEnabled = false;
private static Handler handler;
private final static int HANDLER_SHOW_DIALOG = 1;
private static String packageName;
private static Activity me = null;
private static native void nativeSetPaths(String apkPath);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
me = this;
// get frame size
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
accelerometer = new Cocos2dxAccelerometer(this);
// init media player and sound player
backgroundMusicPlayer = new Cocos2dxMusic(this);
soundPlayer = new Cocos2dxSound(this);
handler = new Handler(){
public void handleMessage(Message msg){
switch(msg.what){
case HANDLER_SHOW_DIALOG:
showDialog(((DialogMessage)msg.obj).title, ((DialogMessage)msg.obj).message);
break;
}
}
};
}
public static void openURL(String url) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
me.startActivity(i);
}
//CLASS METHODS REMOVED FROM EXAMPLE TO SINCE THEY DO NOT CHANGE
}
/********************/
Ok... So what did we just do? Well, we created a self reference "me" to the activity created by Cocos2d-x when "onCreate" is called durring the program initialization phase. Then we added a method named "openURL" that will launch a child activity from "me" that sends an intent to the Android OS to parse the URL and open it with the default web browser.
So, why on earth did we go through all of that just to call "startActivity"? Well, thats because "startActivity" is not a static method, and dealing with static methods from the NDK is WAY easier than dealing with non static methods. Thus creating a reference to "me" is the next best thing. Kind of a hack, but an effective one.
So thats all the Java we need to change. Lets move on to the C++ side. Go to your iOS/Android hybrid project root and open "ProjectName/libs/cocos2dx/platform/android/Cocos2dJni.h" and add the following line to the list of method declarations:
void openURLJNI(const char* url);
Now open "ProjectName/libs/cocos2dx/platform/android/Cocos2dJni.cpp" and add the following method.
void openURLJNI(const char* url)
{
TMethodJNI t;
if (getMethodID(t
, "org/cocos2dx/lib/Cocos2dxActivity"
, "openURL"
, "(Ljava/lang/String;)V"))
{
jstring StringArg1 = t.env->NewStringUTF(url);
t.env->CallStaticVoidMethod(t.classID, t.methodID, StringArg1);
}
}
So what does that do? Well, its just a wrapper for calling the Java function we created earlier from within our C++ project. Now anywhere you want to open a URL you simply call openURLJNI("Http://someplace.com"); Please remember to use the full HTTP:// path when opening your URL since Android is REALLY picky about that.
Obviously that command will NOT work on iOS devices. So my wrapper function looks like this.
void PlatformResolver::openURL(const char* url)
{
#ifdef __APPLE__
PlatformResolver_OBJC::openURL(url);
#endif
#ifdef ANDROID
openURLJNI(url);
#endif
}
Thats it! You can now launch URL links form within your Cocos2d-x application. YAY! Questions, comments, concerns, drop a note below. Thanks!
Nice tutorial.
ReplyDeleteThanks.
Hey that's one very useful tip!
ReplyDeleteHope to see more on Android / iOS services abstraction with cocos2dx on this blog!
Thanks!
Hey I just went through your tutorial. Nice 1 if it worked for you.
ReplyDeleteI need to ask a few things.
First there was no Cocos2dJni file in libs of Cocos2dx latest Version. So where to get this file from.
Secondly, I got this file from a sample project and I placed it in libs of cocos2dx.
Now how to call this method as its showing undeclared. Do I need to include Cocos2dJni or how?
M sorry as I have just started working on it. Its about a week only.
Hope you help. :)
Thanks in advance
Exactly the same question as nilhil ! Nihil: did you solve this issue ? Or Alan, can you answer ? Thanks !!
ReplyDeleteSorry, I have not worked with the newer version of Cocos2D, but I downloaded the source this morning and it looks like they have separated the JNI functions into individual files now. Take a look at "cocos2dx/platform/android/jni" in there you will find a shared "JniHelper class" and a series of JNI based files like MessageJni, and SystemInfoJni. You should be able to create a new .h and .cpp using one of these as a template that will encapsulate the functions I created above.
ReplyDeleteI assume the Java section is still pretty much the same, but I cannot verify at this time as I do not have eclipse installed on this machine. If you still cant figure it out, please let me know and I will try to port this later this week if I can find the time. Sorry I can't be more help at the moment. Good luck!
In case I use a JniHelper ot MessageJni.. I still need to call its methods... How should I do that???
ReplyDeleteThats a major concern.... Please help me in that....
Well, JniHelper looks to be a helper class used by the other JNI files, and contains most of what would be a base class. Try looking at "showMessageBoxJNI" in MessageJni and use that as your template in which to write your own functions. Then just include your new header, and call the function directly when needed. Or am I missing a larger question?
ReplyDeleteHey m still not able to find the solution to it... Its not showing MessageJni if I include it in my scene class. :(...
ReplyDeleteHope you could help me out.. :)
Thank you very much! Finally I got this thing working.
ReplyDeleteYou're the best!
Perhaps you should add:
ReplyDeletet.env->DeleteLocalRef(StringArg1);
right after the call to the static method (line 8 of the `openURLJNI` function)
Hi,
ReplyDeletethere is no "cocos2djni.h" & "cocos2djni.cpp" in cocos2d-2.1beta3-x-2.1.0 2 version
what should i do ?
You can add them into the Java_org_cocos2dx_lib_Cocos2dxHelper.(h/cpp) in "\cocos2dx\platform\android\jni"
DeleteThis comment has been removed by the author.
ReplyDeleteThanks for the FANTASTIC post! This information is really good and thanks a ton for sharing it :-) I m looking forward desperately for the next post of yours..
ReplyDeletecocos2d game development