The gradle build of Gluon plugin (in Netbeans 8.0.2) for porting JavaFX to Android creates the following directory structures:
Source Packages [Java]
Android/Java Packages
Desktop/Java Packages
Ios/Java Packages
Each of these directories contain java packages inside them. Generally Gluon build would create the "main" class for us in one java package inside "Source Packages" directory [the name Packages with "Source Packages" might be misleading since it is not a Java package, its just a file system directory]. This main class extends Javafx Application class and thus is the entry point into our application.
The Android API is accessible only in Android/Java Packages directory for any java package created inside it. Say, the android.Vibrator class is only referr-able here.
The problem is, we cannot refer to a class created inside any Java package inside Android/Java directory to any java package created inside Source Packages [Java] directory!! If this is the case then how would we take the application forward from the start() method of javafx.Application into say android.Vibrator.
The gluon project structure has a snapshot at: How to reference android.jar in Gluon Project
As you know, JavaFXPorts project allows deploying JavaFX apps in Desktop, Android and iOS devices. When it's just pure JavaFX code, the project is added on the Main scope, and from there it will be visible in all those platforms.
Only in the case you need some platform specific code, you should add it on the corresponding package.
As you have mentioned, by default from the Main package you won't see the added code in a platform package, so you have to provide a way for that.
If you check the HelloPlatform sample on JavaFXPorts repository, you will find a PlatformService class to load the packages using ServiceLoader.
Another possibility is using Class.forName() to load dynamically the classes at runtime, once we know the platform where the app is running.
I suggest you have a look at the Gluon Down project, that manages for you several platform specific services, and provides you with uniform, platform-independent API.
For those services not available yet in Down (feel free to contribute), you can implement them like in this simple app created using Gluon Plugin.
Source Packages [Java]
First, create a getPlatform() method, and add the referred classes to each specific platform. For instance, add org.gluonoss.vibrator.GluonAndroidPlatform.java on Android package.
public class GluonPlatformFactory {
public static GluonPlatform getPlatform() {
try {
String platform = System.getProperty("javafx.platform", "desktop");
String path = "org.gluonoss.vibrator.GluonDesktopPlatform";
if(platform.equals("android")) {
path = "org.gluonoss.vibrator.GluonAndroidPlatform";
} else if(platform.equals("ios")) {
path = "org.gluonoss.vibrator.GluonIosPlatform";
}
return (GluonPlatform) Class.forName(path).newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
System.out.println("Platform Error "+e.getMessage());
}
return null;
}
}
Now, create an interface, with the method you want on all your platforms:
public interface GluonPlatform {
void vibrate();
}
Finally, on your main class retrieve the platform and call your method:
#Override
public void start(Stage stage) {
final Button button = new Button("Click me!");
button.setOnAction(e-> GluonPlatformFactory.getPlatform().vibrate());
StackPane root = new StackPane(button);
Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
Scene scene = new Scene(root, visualBounds.getWidth(), visualBounds.getHeight());
stage.setScene(scene);
stage.show();
}
Desktop/Java Packages
Add the vibrate method. For now leave it empty, but you could add a Timeline to move the button, for instance.
public class GluonDesktopPlatform implements GluonPlatform {
#Override
public void vibrate() {
System.out.println("Vibrating!");
}
}
Android/Java Packages
Add the vibrate method. Notice that we have to use FXActivity, which is the bridge between the JavaFX thread and the Android activity.
public class GluonAndroidPlatform implements GluonPlatform {
#Override
public void vibrate() {
Vibrator v = (Vibrator) FXActivity.getInstance().getSystemService(Context.VIBRATOR_SERVICE);
v.vibrate(500);
}
}
Don't forget to add the required permission on your AndroidManifest file (you will find it under src/android/AndroidManifest.xml.
Now you can deploy the project and run it on Desktop (gradlew run) and it will work, and install it on Android (gradlew androidInstall), and it will work too.
Related
So I want to develop a small game engine. This has the main engine classes like a Vector and Actor for the user to use. Since I only need the engine once, I want to make the games use the same engine, and to avoid having all games in the same jar, I intend to have them in separate folders, one with the engine, and then folders for each game. The engine should then be able to load the eg. Player class from the other folder and utilize it.
I thought one solution could be compiling the game folder at runtime. But then the problem would be that the files depend on each other and on compiled classes already loaded in the JVM. For this approach:
So as an example, we have three classes: An Actor from the engine, a Player extending the engines actor class written by the user, and a third class, the item, written by the user, spawned in Player, but again needs the Player to be compiled, meaning they can't be compiled one after the other.
To my understanding, the Actor will already be compiled in the JVM when we run the program. Now we know a folder with all the classes to compile, where the Player depends on the compiled class in the JVM and the uncompiled class in the folder, which depends on the Player.
Now I want to compile the Player class, whereby we must also compile the Item, and then instantiate the Player so we can move around and spawn items.
Here a basic example of what I mean:
// already compiled in eg. executing jar file
class MainStuff
{
public static void main(String args[])
{
String FolderOfUncompiledClasses = "Some/folder/to/src/";
Class<?>[] CompiledClasses = CompileFolderContents(FolderOfUncompiledClasses);
// iterating through compiled classes
for (Class<?> C : CompiledClasses)
{
// if we have an Actor class, we create a new instance
if (C.isAssignableFrom(Actor.class))
{
try
{
C.getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e)
{
e.printStackTrace();
}
}
}
}
// should compile all the files and returns the classes of the compiled java files
private static Class<?>[] CompileFolderContents(String Folder)
{
File[] JavaFiles = new File(Folder).listFiles();
Class<?>[] CompiledClasses = new Class<?>[JavaFiles.length];
for (int i = 0; i < JavaFiles.length; i++)
{
Class<?> CompiledClass = DoCompilationStuff(JavaFiles[i]);
CompiledClasses[i] = CompiledClass;
}
return CompiledClasses;
}
// this should effectively compile the class which it can both use non compiled
// java files in the folder and already compiled classes
private static Class<?> DoCompilationStuff(File ToCompile)
{
return null;
}
}
// already compiled in eg. executing jar file
class Actor
{
int X, Y;
}
In a folder somewhere on the drive:
// not compiled
class Player extends Actor
{
public Player()
{
// uses other non compiled class
new Item();
}
}
// not compiled
class Item
{
// Also uses Actor so we can't compile them in series
public Item(Player P)
{
}
}
I've tried using the javac command but I can't get it to work with an entire folder structure somehow.
I hope I explained it in a logical way and if this approach doesn't make sense. This was just an idea, if you have a better approach I would be very happy to hear it.
Thank you very much!
If you really have to use javac keep the classes in the same package and directory. This will simplify the build process, instead of having to use -cp argument to specify the class-path existing across multiple different directories.
I'd recommend that instead of compiling manually you should setup a project using a build system e.g. Gradle. If you look at Building Java Applications Gradle docs it should take around 10 minutes and you should have everything you need: build, tests and packaging.
Before modular application organization, I had one main JavaFX application that load custom created multiple libraries for different options and possibilities in main app.
In old way of implementation, I just send new library to update, main application reads all libraries from folder and it works like a charm. But in a modular system, if my application wants to use new modular library that I send, it needs to update its module-info file, apropos I need to send updates for modular library and for main application.
Just imagine, it would be like, chrome need to send browser update for every new plugin that is created. As I can see, with Java modularity system you can't create modular applications.
Is there a way import new module without updating main application or some other way around?
Java has a class for that: ServiceLoader.
If we assume you have a “service provider” interface named PluginProvider, other modules can declare themselves to provide that service by putting this in their respective module-info.java descriptors:
provides com.john.myapp.PluginProvider with com.library.MyProvider;
Your application would then state that it uses that service in its own module-info:
uses com.john.myapp.PluginProvider;
And your application’s code would create a ModuleFinder that looks in the directory (or directories) where you expect those plugin modules to reside, then pass that ModuleFinder to a Configuration which can be used to create a ModuleLayer for the ServiceLoader:
public class PluginLoader {
private final ServiceLoader<PluginProvider> loader;
public PluginLoader() {
Path pluginDir = Paths.get(System.getProperty("user.home"),
".local", "share", "MyApplication", "plugins");
ModuleLayer layer = PluginProvider.class.getModule().getLayer();
layer = layer.defineModulesWithOneLoader(
layer.configuration().resolveAndBind(
ModuleFinder.of(),
ModuleFinder.of(pluginDir),
Collections.emptySet()),
PluginProvider.class.getClassLoader());
loader = ServiceLoader.load(layer, PluginProvider.class);
}
public Stream<PluginProvider> getAll() {
return loader.stream();
}
public void reload() {
loader.reload();
}
}
You might even want to watch the plugin directory for new or removed files:
try (WatchService watch = pluginDir.getFileSystem().newWatchService()) {
pluginDir.register(watch,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.OVERFLOW);
WatchKey key;
while ((key = watch.take()).isValid()) {
loader.reload();
key.reset();
}
}
I have a question.
How does one initialize the JavaFX toolkit with the method I found in an earlier StackOverflow Question? The topic can be found here: JavaFX 2.1: Toolkit not initialized
I am trying to use a solution similar to this solution from that thread:
Problem: Non-trivial Swing GUI application needs to run JavaFX components. Application's startup process initializes the GUI after starting up a dependent service layer.
Solutions
Subclass JavaFX Application class and run it in a separate thread e.g.:*
public class JavaFXInitializer extends Application {
#Override
public void start(Stage stage) throws Exception {
// JavaFX should be initialized
someGlobalVar.setInitialized(true);
}
}
The only problem I have is: What do I do with
someGlobalVar.setInitialized(true); ?
I don't know what to fill in there, and some tips would be appreciated :)
I am writing this answer for the comment you have done about how to support (.mp3,.wav,.flac,.ogg) etc in java.For .mp3 you can use JLayer http://www.javazoom.net/projects.html search on web for examples.
About (.mp3,.wav.flac,.ogg) and some more you can use JavaZoom BasicPlayer which uses some external libraries to support them you can download the zip folder here(download without installer and you open the zip folder).
Then go on the folder lib and copy all the .jars except kj_dsp which can be used for visual representation on audio data and contains also a class about fast fourier transform(FFT).Also change MP3_SPI1.9.3 with MP3SPI1.9.4
Then add these .jars into your project libraries and just use:
BasicPlayer player = new BasicPlayer();
The whole thing uses Service Provider Interface (SPI) mechanism.
It runs on a separate thread so you don't have to worry.It works really well but the project is a little bit old.It's a good start!About docs check the website.
import com.sun.javafx.application.PlatformImpl;
public class JavaFXInitializer
{
public JavaFXInitializer()
{
initFx();
}
private synchronized static void initFx() {
PlatformImpl.startup(() -> {
});
}
}
I am making my first Android plugin by extending UnityPlayerNativeActivity. The package name is com.inno.myPlugin and that name is registered in Manifest.xml in Plugins/Android folder.
The class that extends "com.inno.myPlugin" is "MyClass".
The plugin is compiled as .jar file and it uses "AndroidJavaClass" and "AndroidJavaObject" on Unity side to call functions and is placed in Plugins/Android folder.
Each time I compile my example, on my android device, the app package name is always "com.inno.myPlugin". Even when I change the package name from Unity settings, the default package name is still the package name from the jar plugin which is "com.inno.myPlugin". The package name can only be renamed from the source code of the jar file...
I want to publish the plugin to Unity Asset store but I have a question that is bugging me.
If the package name "com.inno.myPlugin" is already used on the Android store, will this prevent the user of my plugin from uploading their app with this plugin?
How do I solve this problem of package naming?
This is one of the larger issues with Unity Android Plugins.
There's a few possibilities here. Use the below to get to the right answer
a) Does your plugin require that the Main Activity (i.e. Launcher activity) be "com.inno.myPlugin.MyClass"?
b) Do you override OnActivityResult in MyClass?
c) Neither of the above
If you answered yes to (a), then there's very little possibility that anyone but yourself can use this plugin, for a variety of reasons ranging from others being unable to tweak your code to conflicting packages.
If you answered yes to (b), then there's a decent possibility that others can use it, but they cannot use it if ANOTHER plugin requires that privilege as well
If you answered yes to (c) you should be good.
Easiest way to test it is to use your plugin in a fresh project, with a different package name, and check to see if it works.
Also, I'd advice that you stay away from extending UnityPlayerNativeActivity. The only reasons to extend UnityPlayerNativeActivity are
To get the currentActivity (i.e. UnityPlayer.currentActivity)
To use unitySendMessage
You can get both of these using reflection. currentActivity is a field, and unitySendMessage is a method. I won't go into detail here since it's not directly relevant to your question, but feel free to ping if you need some details.
Edit Adding some example code on request by the OP
Here's a sample plugin I made without extending UnityPlayerActivity or UnityPlayerNativeActivity. Note that I've just typed this straight in here, so there are probably errors in the code. One thing I know for sure is that the native code requires try-catch blocks, but any IDE should show up the error when you paste the code in.
Native Android Code
package com.myCompany.myProduct;
public class MyClass {
private Class<?> unityPlayerClass;
private Field unityCurrentActivity;
private Method unitySendMessage;
public void init () {
//Get the UnityPlayer class using Reflection
unityPlayerClass = Class.forName("com.unity3d.player.UnityPlayer");
//Get the currentActivity field
unityCurrentActivity= unityPlayerClass.getField("currentActivity");
//Get the UnitySendMessage method
unitySendMessage = unityPlayerClass.getMethod("UnitySendMessage", new Class [] { String.class, String.class, String.class } );
}
//Use this method to get UnityPlayer.currentActivity
public Activity currentActivity () {
Activity activity = (Activity) unityCurrentActivity.get(unityPlayerClass);
return activity;
}
public void unitySendMessageInvoke (String gameObject, String methodName, String param) {
//Invoke the UnitySendMessage Method
unitySendMessage.invoke(null, new Object[] { gameObject, methodName, param} );
}
public void makeToast (final String toastText, final int duration) {
currentActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getActivity(), toastText, duration).show();
}
});
}
}
Unity C# code
AndroidJavaObject myClass = null;
public void Init () {
if(myClass == null)
myClass = new AndroidJavaObject("com.myCompany.myProduct.MyClass");
if(myClass != null)
myClass.Call("init", new object[0]);
}
public void MakeToast (string toastText, int duration) {
if(myClass != null)
myClass.Call("makeToast", new object[] { toastText, duration } );
}
For the Manifest, change it to whatever you're calling the package name to be. Note that it DOES NOT have to match the package for the plugin! So, for example, here our plugin's package name is "com.myCompany.myProduct", you can use a package name like "com.someOtherCompany.someNewProduct"
How to test this plugin.
1. Add the native code above to "MyClass.java", and create a jar.
2. Copy this jar into Assets/Plugins/Android folder in your Unity Project.
3. Ensure that the package name is not the default package name in Unity
4. Copy the Unity C# code above into a new script
5. Put in some OnGUI code to call the "Init" & "MakeToast" methods in C#
Source - My expertise making plugins for Unity.
We're using an Android Library Project to share core classes and resources across different builds (targets) of our Android application. The Android projects for each specific target reference the Core library project (behind the scenes, Eclipse creates and references a jar from the referenced library project).
Overriding resources such as images and XML layouts is easy. Resource files placed in the target project, such as the app icon or an XML layout, automatically override the core library's resources with the same name when the app is built. However, sometimes a class needs to be overridden to enable target-specific behavior. For example, the Amazon target preferences screen cannot contain a link to the Google Play app page, requiring a change in the Amazon project's preferences.xml and preferences Activity class.
The goal is to reduce the amount of duplicate code among target projects while removing as much target-specific code from the Core library as possible. We've come up with a couple of approaches to implement logic specific to different targets:
Write the target-specific functions within Core library classes and use if/switch blocks to select behavior based on product SKU. This approach is not very modular and bloats the Core library codebase.
Extend the particular Core class in a target project and override the base (Core) class functions as needed. Then keep a reference to the base-class object in the Core library and instantiate it with an extended class object (from How to override a class within an Android library project?)
Are there other strategies to override or extend an Android library project class? What are some of the best practices for sharing and extending common classes among Android app targets?
Library project is referenced as a raw project dependency (source-based mechanism), not as a compiled jar dependency (compiled-code based library mechanism).
#yorkw this is not true for the latest versions of ADT Plugin for Eclipse
http://developer.android.com/sdk/eclipse-adt.html
From version 17 Change log
New build features
Added feature to automatically setup JAR dependencies. Any .jar files in the /libs folder are added to the build configuration (similar to how the Ant build system works). Also, .jar files needed by library projects are also automatically added to projects that depend on those library projects. (more info)
More info http://tools.android.com/recent/dealingwithdependenciesinandroidprojects
Before that, update overwriting of the Activity from Library project was easy, just exclude the class. Now the library is included as jar file, and there is no way to exclude class file from jar dependency.
EDIT:
My solution to overwrete/extend Activity from library jar:
I created a simple util class:
public class ActivityUtil {
private static Class getActivityClass(Class clazz) {
// Check for extended activity
String extClassName = clazz.getName() + "Extended";
try {
Class extClass = Class.forName(extClassName);
return extClass;
} catch (ClassNotFoundException e) {
e.printStackTrace();
// Extended class is not found return base
return clazz;
}
}
public static Intent createIntent(Context context, Class clazz) {
Class activityClass = getActivityClass(clazz);
return new Intent(context, activityClass);
}
}
In order to overwrite a library's "SampleActivity" class it a the project which depends on that library, create a new class with the name SampleActivityExtended in the project in the same package and add the new activity to your AndroidManifest.xml.
IMPORTANT: all intents referencing overwritten activities should be created through the util class in the following manner:
Intent intent = ActivityUtil.createIntent(MainActivity.this, SampleActivity.class);
...
startActivity(intent);
behind the scenes, Eclipse creates and references a jar from the referenced library project.
This is not quite accurate. Library project is referenced as a raw project dependency (source-based mechanism), not as a compiled jar dependency (compiled-code based library mechanism). Currently Android SDK does not support exporting a library project to a self-contained JAR file. The library project must always be compiled/built indirectly, by referencing the library in the dependent application and building that application. When build dependent project, the compiled source and raw resources that need to be filtered/merged from Library project are copied and properly included in the final apk file. Note that Android team had started revamping the whole Library Project design (move it from ource-based mechanism to compiled-code based library mechanism) since r14, as mentioned in this earlier blog post.
What are some of the best practices for sharing and extending common classes among Android app targets?
The solution given by Android is Library Project.
The solution given by Java is Inheritance and Polymorphism.
Come together, the best practice IMO is the second option you mentioned in the question:
2.Extend the particular Core class in a target project and override the base (Core) class functions as needed. Then keep a reference to the base-class object in the Core library and instantiate it with an extended class object (from Android library project - How to overwrite a class?)
From my personal experience, I always use Android Library Project (Sometimes with Regular Java Project, for implementing/building common-lib.jar that contains only POJO) manage common code, for instance SuperActivity or SuperService, and extends/implements proper classes/interfaces in the dependent project for Polymorphism.
Solution based on PoisoneR's solution and Turbo's solution.
public static Class<?> getExtendedClass(Context context, String clsName) {
// Check for extended activity
String pkgName = context.getPackageName();
Logger.log("pkgName", pkgName);
String extClassName = pkgName + "." + clsName + "Extended";
Logger.log("extClassName", extClassName);
try {
Class<?> extClass = Class.forName(extClassName);
return extClass;
} catch (ClassNotFoundException e) {
e.printStackTrace();
// Extended class is not found return base
return null;
}
}
The benefits of this is that
The extended class can be in the project's package, not the library's package. Thanks to Turbo for this part.
By taking a String as an argument instead of a Class object, this method is able to be used even with ProGuard. getName() is where the problem is with ProGuard, as that will return something like "a" instead of the name of the original class. So in the original solution instead of looking for ClassExtended it will look for aExtended instead, something which does not exist.
What about using a callback approach here? (Okay, callback is a little bit misleading but I currently have no other word for it:
You could declare an interface in every Activity which should/may be expanded by the user. This interface will have methods like List<Preference> getPreferences(Activity activity) (pass whatever parameters you need here, I would use an Activity or at least a Context to be futureproof).
This approach could give you what you want when I have understood it correctly. While I haven't done this before and don't know how other people handle this I would give it a try and see if it works.
Could you, please, clarify what is different in Kindle and regular Android?
I think - they are the same.
What you need is different resources for Kindle and other devices. Then use appropriate resource.
For example I use 2 links to store:
<string name="appStore"><a href=http://market.android.com/details?id=com.puzzle.jigsaw>Android Market</a> or <a href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw>Amazon Appstore</a> <br>http://market.android.com/details?id=com.puzzle.jigsaw <br>href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw</string>
<string name="appStore_amazon"><a href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw>Amazon Appstore</a> <br>href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw</string>
and use appStore for all none Amazone product and appStore_amazon for Kindle.
How to determine where are you on run time - that would be another question which was answered here many times.
I was inspired by PoinsoneR's answer to create a Utility class to do the same thing for Fragments - override a fragment in an android Library. The steps are similar to his answer so I won't go into great detail, but here is the class:
package com.mysweetapp.utilities;
import android.support.v4.app.Fragment;
public class FragmentUtilities
{
private static Class getFragmentClass(Class clazz)
{
// Check for extended fragment
String extClassName = clazz.getName() + "Extended";
try
{
Class extClass = Class.forName(extClassName);
return extClass;
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
// Extended class is not found return base
return clazz;
}
}
public static Fragment getFragment(Class clazz)
{
Class fragmentClass = getFragmentClass(clazz);
Fragment toRet = null;
try
{
toRet = (Fragment)fragmentClass.newInstance();
return toRet;
}
catch (InstantiationException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IllegalAccessException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return toRet;
}
}
Usage:
FragmentUtilities.getFragment(MySpecialFragment.class)
You can also use an Activity factory if you need to provide extended activitys for differnt build variants and have your library deal with the abstract factory alone. This can be set in your build variants Application file.