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.
Related
I'm modifying someone else's code to implement a new functionality and I can't do it without changing the return of one of the functions. As I've said, this is not my code, so I can't change a single line of code.
The function itself is the following:
package me.Mohamad82.MineableGems.Core;
public class DropReader {
...
public DropReader() {}
public CustomDrop readCustomDrop(ConfigurationSection section, String mined, #Nullable String sectionNumber) {
...
}
}
And I'm trying to do something like this:
package com.rogermiranda1000.mineit;
public class DropReader extends me.Mohamad82.MineableGems.Core.DropReader {
public DropReader() {
super();
}
#Override
public CustomDrop readCustomDrop(ConfigurationSection section, String mined, #Nullable String sectionNumber) {
CustomDrop drop = super.readCustomDrop(section, mined, sectionNumber);
if (drop != null) {...}
return drop;
}
}
The problem is that I have no idea where to start. I can't change their code to call the other object, and I can't change the function call either.
The object is created every time (inside the functions that uses DropReader) so Reflection won't work, and searching I found something named Javassist but it won't work if the class is already loaded. I should be able to load by code before, but even with that I don't know if I'm approaching to the problem correctly, what do you think?
Edit: I'll try to explain better the situation. There's a class named Commands that runs the command new DropReader().readCustomDrop(section, mined, sectionNumber). The problem is that if section.getString("mine") != null I need to change the readCustomDrop return (I need to add an aditional property).
I can't change Commands's code, nor DropReader. Commands MUST get the modified object.
Edit2: It should work on both Java 8 and 17.
Probably the best way to do this is Java Instrumentation, but it was too complex for me so I did the following (assuming you have the .jar, and the .jar it's not already loaded):
Open the .jar as a Zip using ZipFile class
Send the .java to ClassFileToJavaSourceDecompiler (from JD-Core) and decompile it
Change the .java code
Compile the new code running javac with Runtime.getRuntime().exec (note: you'll probably need to add the dependencies using -classpath)
Add the compiled .java to the .jar
Lets say I have an Android application built using an Android Studio project that relies on a core android class; android.text.Selection. One of the functions, removeSelection, is flagged up as a cause for crashes. I suspect that is due to 3rd party keyboards. I would like to modify that function to try and work around the issue, changing from;
/**
* Remove the selection or cursor, if any, from the text.
*/
public static final void removeSelection(Spannable text) {
text.removeSpan(SELECTION_START, Spanned.SPAN_INTERMEDIATE);
text.removeSpan(SELECTION_END);
removeMemory(text);
}
to;
/**
* Remove the selection or cursor, if any, from the text.
*/
public static final void removeSelection(Spannable text) {
Log.i("Test", "Method replaced.");
setSelection(text, 0, 0);
}
I can get the source for the class; https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/android/text/Selection.java. I also know that you can modify the class path in Java to use a modified version of a class; https://www.sourceallies.com/2010/02/replacing-and-patching-java-application-and-core-classes/. The class itself is located in the main android.jar dependencies.
How can I tweak this method, include it in my project and make my application prefer the modified version?
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.
I am trying to use a library that uses JNI.
I've tried the sample app provided by the developers and it works. So I know it's not a bug in the library.
I assume I'm doing something wrong in the process of importing the library:
copy the .so file into my libs folder (its called libjniRTSP.so)
copy the the jniRTSP.java (summarized below) into my project:
public class jniRTSP {
private volatile static jniRTSP libRTSP = null;
public static jniRTSP getInstance() {
if(null == libRTSP) {
synchronized(jniRTSP.class) {
if(null == libRTSP) {
libRTSP = new jniRTSP();
libRTSP.InitProductList();
// DEBUG
libRTSP.SetDebugView(1);
}
}
}
return libRTSP;
}
static {
try {
System.loadLibrary("jniRTSP");
} catch (Exception e) {
e.printStackTrace();
}
}
public native int GetBrandEnableRecorder();
public native int GetBrandEnableLocal();
public native int GetBrandEnableRemote();
...
then in my onCreate() I try to call one of the methods:
jniRTSP.getInstance().Init(.....)
Which returns the error:
UnsatisfiedLinkError: Native method not found com.myuniquepackage.jniRTSP.InitProductList:()I
UPDATE (FIX): instead of just copying the jniRTSP java file, I copied the whole package that contained it, keeping the same package name. I'm not sure if this fixed it because the package name was the issue, or if because it needed one of the other java files that were in that package. Although I'm pretty sure if it was a missing file, it would complain at compile time.
Fairly certain the package declarations have to be the same inside the C code as the Java code.
So the class jniRTSP should be in the com.myuniquepackage package in Java and have the native method InitProductList declared and the C code should have method declared as Java_com_myuniquepackage_jniRTSP_InitProductList
By moving the class you are probably breaking the link, change the package declaration in Java to match the demo project and see if it works, if it does you can change it back and then change it in the C code which is a bit more time consuming but easy enough.
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.