I'm developing a plugin for Unity Android using JNI. I create the .jar file with my Java code, and then .so file with the c++ bridge. Everything works fine when I call my library from Unity side. So, my problem is that in several parts of my Java code, I need to access to the context of the main activity (UnityPlayer3d). I need a pointer to this activity to call the function GetApplicationContext().
How can I obtain this pointer? I think about passing the pointer from Unity to my Java class as a parameter, but first I have to get it in Unity side.
Here's some code to get the activity JNI style:
var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var unityActivity = unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");
I think your logic is wrong, you don't need to pass Context pointer from Unity(C#) to Java, you can get the Context in Android Pluglin MainActivity(this is the main activity (UnityPlayer3d) you mentioned in your post) directly as follow:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this.getApplicationContext();
}
And then, you can operate the context to do stuff in JAVA intead of writing it in Unity(C#)
Hope it be helpful. :)
Related
Please note that this question is a follow-up question to another question I asked here some time ago.
What would I like to achieve?
I would like to use JNA (version 4.5.2 with Android Studio) to achieve the following:
First I would like to get a list of all callable functions from a given .so file.
Once I have this list, I would like to call a specific function
What did I do so far?
So far, the code of the file "MainActivita.java" of my test app looks as follows:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String MyLibraryString = MyLibrary.Instance.toString();
}
public interface MyLibrary extends Library
{
MyLibrary Instance = (MyLibrary ) Native.loadLibrary("nameOfMyLibraryWithoutSoExtension", MyLibrary.class);
}
}
What issues am I facing?
I used to get an error message that my library is not found on my Android device but after putting it in the "jniLibs" subfolder my app seems to be able to detect it. Unfortunately, I now get the error message
java.lang.UnsatisfiedLinkError: Native library
(com/sun/jna/android-aarch64/libjnidispatch.so) not found in resource
path (.)
whenever I try to start the app on my device. I already tried to extract the file "libjnidispatch.so" from the JNA.jar file and put it in its respective subfolders (as described here) but that doesn't work.
The folder structure of the subfolder "app\src\main\jniLibs" of my app looks as follows:
app\src\main\jniLibs\arm64-v8a\myLibrary.so
...
app\src\main\jniLibs\arm64-v8a\libjnidispatch.so
app\src\main\jniLibs\armeabi\myLibrary.so
...
app\src\main\jniLibs\armeabi\libjnidispatch.so
app\src\main\jniLibs\armeabi-v7a\myLibrary.so
...
app\src\main\jniLibs\armeabi-v7a\libjnidispatch.so
app\src\main\jniLibs\mips\myLibrary.so
...
app\src\main\jniLibs\mips\libjnidispatch.so
app\src\main\jniLibs\mips64\myLibrary.so
...
app\src\main\jniLibs\mips64\libjnidispatch.so
app\src\main\jniLibs\x86\myLibrary.so
...
app\src\main\jniLibs\x86\libjnidispatch.so
app\src\main\jniLibs\x86_64\myLibrary.so
...
app\src\main\jniLibs\x86_64\libjnidispatch.so
Any help would be highly appreciated.
I am working on an augment reality android app. I developed the app in unity using vuforia and then imported it in android studio as i wanted to design it in android studio. The activity name in generated project structure is UnityPlayerActivity which has no layout in the project. The activity is passed to UnityPlayerclass and the layout is generated. This is some code of my UnityPlayerActivity activity.
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
// Setup activity layout
#Override protected void onCreate(Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
getWindow().setFormat(PixelFormat.RGBX_8888); // <--- This makes xperia play happy
mUnityPlayer = new UnityPlayer(this);
setContentView(mUnityPlayer);
mUnityPlayer.requestFocus();
}
as it uses that layout how can i design that activity by referencing it.
It is not possible to do this by referencing the object(Java does not support call by reference).
You need to define a new function in UnityPlayer class that generates a layout and returns the LayoutParams object.
Then use the Layout inflator. Something like this:
LayoutParams mParams=mUnityPlayer.layoutGenerator() //some function to generate LayoutParams
LayoutInflator.getLayoutInflator().inflate(LayoutParams);
Finally i found an easy solution to my problem. I displayed the unity scenes in a subview using a framelayout and now i can customize the UI easily. Check here and here to see more information.
I am attempting to develop a "Dynamic" Android application.
Dynamic in the sense that I have an activity listed in the manifest that is "built" at runtime.
I can build the required activity fine, however, when I attempt to start it my application fails with...
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.research.ps/com.research.Dynamic}: java.lang.ClassNotFoundException:
Didn't find class "com.research.Dynamic" on path: DexPathList[[zip file "/data/app/com.research.ps-1/base.apk"],nativeLibraryDirectories=[/data/app/com.research.ps-1/lib/arm,
/data/app/com.research.ps-1/base.apk!/lib/armeabi-v7a, /vendor/lib, /system/lib]]
Is there an approach I can take to successfully instantiate an Android Activity at runtime?
Is there a way I can add a "temporary" or "shell" activity onto my application path? and then replace the "temporary" activity with my dynamic instance?
UPDATE
My Manifest XML contains this entry
<activity
android:name=".Dynamic"
android:label="#string/title_activity_dynamic"
android:theme="#style/AppTheme.NoActionBar" />
However, there is no Activity called "Dynamic" contained within my application.
I am using ByteBuddy to build my dynamic activity:-
final Class<? extends android.support.v7.app.AppCompatActivity> dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V8)
.subclass(android.support.v7.app.AppCompatActivity.class, IMITATE_SUPER_CLASS)
.name("com.research.Dynamic")
.make()
.load(getClass().getClassLoader(), new AndroidClassLoadingStrategy.Wrapping(this.getDir("dexgen", Context.MODE_PRIVATE)))
.getLoaded();
final Intent intent = new Intent(this, dynamicType);
startActivity(intent);
Yes you CAN start such an Activity (assuming you have a dummy manifest Activity entry). If you don't like this technique, use Fragments (they don't need entries in the manifest). Alternatively use WebView and JavaScript like Apache Cordova et-al (cross platform too !).
ByteBuddy (kudos too #Rafael Winterhalter author of Byte Buddy) looks cool, maybe a learning curve involved. Why not download the linked project and try both techniques.
Here's how to include ByteBuddy in your Android Studio Gradle project (build.gradle):
android {
compileSdkVersion 25
buildToolsVersion '25'
dependencies {
compile 'com.android.support:appcompat-v7:25'
compile 'net.bytebuddy:byte-buddy:1.7.9'
compile 'net.bytebuddy:byte-buddy-android:1.7.9'
}
}
how I can "find" my dynamically instantiate class at runtime?
External loading of DEX files (class byte code)
See my answer here and follow the links for source code and tutorial (Apache Ant {Eclipse compatible, build.xml} and Android Studio Gradle examples build.gradle of the same code, you need some custom build steps which these project's provide).
Code snippet:
// Internal storage where the DexClassLoader writes the optimized dex file to.
final File optimizedDexOutputPath = getDir(SECONDARY_DEX_INTERNAL_DIR, Context.MODE_PRIVATE);
// Initialize the class loader with the secondary dex file.
DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
optimizedDexOutputPath.getAbsolutePath(),
null,
getClassLoader());
Class libProviderClazz = null;//variable libProviderClazz of type Class
try {
// Load the library class from the class loader.
libProviderClazz = cl.loadClass(PROVIDER_CLASS);
// Cast the return object to the library interface so that the
// caller can directly invoke methods in the interface.
// Alternatively, the caller can invoke methods through reflection,
// which is more verbose and slow.
LibraryInterface lib = (LibraryInterface) libProviderClazz.newInstance();
}
catch (Exception exception)
{
// Handle exception gracefully here.
exception.printStackTrace();
}
Q: How do I add an Activity, I cannot add it to the manifest ? A: Use
Fragments, they don't need entries in the manifest.
I have managed to call a dynamically instantiated Activity and set the required layout content using ByteBuddy.
Heres how
final DynamicType.Unloaded<? extends AppCompatActivity> dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V8)
.subclass(AppCompatActivity.class)
.name(CLASS_NAME)
.method(named("onCreate").and(takesArguments(1)))
.intercept(MethodDelegation.to(TargetActivity.class).andThen(SuperMethodCall.INSTANCE))
.make();
final Class<? extends AppCompatActivity> dynamicTypeClass = dynamicType.load(getClassLoader(), new AndroidClassLoadingStrategy.Injecting(this.getDir("dexgen", Context.MODE_PRIVATE))).getLoaded();
final Intent intent = new Intent(this, dynamicTypeClass);
startActivity(intent);
The method delegation class
public class TargetActivity {
public static void intercept(Bundle savedInstanceState, #This AppCompatActivity thiz) {
thiz.setContentView(R.layout.activity_fourth);
}
}
Even though this gives the desired result it still has issues as the super.onCreate(savedInstanceState) call is made after I have called setContent (I think).
Using the excellent ByteBuddy library is a much better approach IMHO than working with DEX manipulation.
When I click a button in native UI (probably from a fragment container) , I wanna invoke a method from JavaScript, let's call it just like 'jsMethod' or other else. However, I have never handled this situation before.
I am looking up some relative codes from Facebook's document on website and can not find out the key to the problem so far. Anyone give me some suggestions?
To call a JavaScript method from Java, do something like this:
ReactInstanceManager reactInstanceManager = reactNativeHost.getReactInstanceManager();
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
CatalystInstance catalystInstance = reactContext.getCatalystInstance();
WritableNativeArray params = new WritableNativeArray();
params.pushString("Message to show using nameOfJsMethod");
catalystInstance.callFunction("JavaScriptVisibleToJava", "nameOfJsMethod", params);
The JavaScript method you want to call must be defined and made visible to Java:
import BatchedBridge from "react-native/Libraries/BatchedBridge/BatchedBridge";
export class ExposedToJava {
nameOfJsMethod(message) {
alert(message);
}
}
const exposedToJava = new ExposedToJava();
BatchedBridge.registerCallableModule("JavaScriptVisibleToJava", exposedToJava);
This sample on Github includes a working demo.
EDIT I'm told this approach is not officially documented. If your scenario allows, consider taking the advice of #ufxmeng (in the comment to the OP) instead.
I am having trouble importing the zbar library properly.
I am following this guide:
https://github.com/DushyanthMaguluru/ZBarScanner
The guide says:
Download this project and add it as a library project to your existing Android app
I downloaded this project and created a library from ZbarScannerLibary folder. I then set in the properties of my project this liabry to be used.
I then continued the guide, when I started to code the main activity I received errors that variables and imports cannot be resolved.
What am I doing wrong and how can I get the imports to be resolved ?
just create a variable in your Activity:
public static final int ZBAR_SCANNER_REQUEST = 0;
public static final int ZBAR_OR_SCANNER_REQUEST = 1;
//etc
These are just used for handling activity result. In the short run, you can also comment out the onActivityResult method to test the barcode is working. You then open the barcode with:
startActivity(intent);
Finally, be sure to read up on startActivityForResult, since it is a very important topic when developing a multi-activity app.