Skip to content
This repository was archived by the owner on Oct 20, 2022. It is now read-only.

Commit d3900c4

Browse files
committed
Deprecated tools docs.
1 parent 6c47645 commit d3900c4

File tree

74 files changed

+3271
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+3271
-0
lines changed
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
---
2+
title: "Callbacks on Android"
3+
ms.prod: xamarin
4+
ms.assetid: F3A7A4E6-41FE-4F12-949C-96090815C5D6
5+
author: davidortinau
6+
ms.author: daortin
7+
ms.date: 11/14/2017
8+
---
9+
10+
# Callbacks on Android
11+
12+
Calling to Java from C# is somewhat a *risky business*. That is to say there is a *pattern* for callbacks from C# to Java; however, it is more complicated than we would like.
13+
14+
We'll cover the three options for doing callbacks that make the most sense for Java:
15+
16+
- Abstract classes
17+
- Interfaces
18+
- Virtual methods
19+
20+
## Abstract Classes
21+
22+
This is the easiest route for callbacks, so I would recommend using `abstract` if you are just trying to get a callback working in the simplest form.
23+
24+
Let's start with a C# class we would like Java to implement:
25+
26+
```csharp
27+
[Register("mono.embeddinator.android.AbstractClass")]
28+
public abstract class AbstractClass : Java.Lang.Object
29+
{
30+
public AbstractClass() { }
31+
32+
public AbstractClass(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) { }
33+
34+
[Export("getText")]
35+
public abstract string GetText();
36+
}
37+
```
38+
39+
Here are the details to make this work:
40+
41+
- `[Register]` generates a nice package name in Java--you will get an auto-generated package name without it.
42+
- Subclassing `Java.Lang.Object` signals to .NET Embedding to run the class through Xamarin.Android's Java generator.
43+
- Empty constructor: is what you will want to use from Java code.
44+
- `(IntPtr, JniHandleOwnership)` constructor: is what Xamarin.Android will use for creating the C#-equivalent of Java objects.
45+
- `[Export]` signals Xamarin.Android to expose the method to Java. We can also change the method name, since the Java world likes to use lower case methods.
46+
47+
Next let's make a C# method to test the scenario:
48+
49+
```csharp
50+
[Register("mono.embeddinator.android.JavaCallbacks")]
51+
public class JavaCallbacks : Java.Lang.Object
52+
{
53+
[Export("abstractCallback")]
54+
public static string AbstractCallback(AbstractClass callback)
55+
{
56+
return callback.GetText();
57+
}
58+
}
59+
```
60+
61+
`JavaCallbacks` could be any class to test this, as long as it is a `Java.Lang.Object`.
62+
63+
Now, run .NET Embedding on your .NET assembly to generate an AAR. See the [Getting Started guide](~/tools/dotnet-embedding/get-started/java/android.md) for details.
64+
65+
After importing the AAR file into Android Studio, let's write a unit test:
66+
67+
```java
68+
@Test
69+
public void abstractCallback() throws Throwable {
70+
AbstractClass callback = new AbstractClass() {
71+
@Override
72+
public String getText() {
73+
return "Java";
74+
}
75+
};
76+
77+
assertEquals("Java", callback.getText());
78+
assertEquals("Java", JavaCallbacks.abstractCallback(callback));
79+
}
80+
```
81+
82+
So we:
83+
84+
- Implemented the `AbstractClass` in Java with an anonymous type
85+
- Made sure our instance returns `"Java"` from Java
86+
- Made sure our instance returns `"Java"` from C#
87+
- Added `throws Throwable`, since C# constructors are currently marked with `throws`
88+
89+
If we ran this unit test as-is, it would fail with an error such as:
90+
91+
```csharp
92+
System.NotSupportedException: Unable to find Invoker for type 'Android.AbstractClass'. Was it linked away?
93+
```
94+
95+
What is missing here is an `Invoker` type. This is a subclass of `AbstractClass` that forwards C# calls to Java. If a Java object enters the C# world and the equivalent C# type is abstract, then Xamarin.Android automatically looks for a C# type with the suffix `Invoker` for use within C# code.
96+
97+
Xamarin.Android uses this `Invoker` pattern for Java binding projects among other things.
98+
99+
Here is our implementation of `AbstractClassInvoker`:
100+
101+
```csharp
102+
class AbstractClassInvoker : AbstractClass
103+
{
104+
IntPtr class_ref, id_gettext;
105+
106+
public AbstractClassInvoker(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer)
107+
{
108+
IntPtr lref = JNIEnv.GetObjectClass(Handle);
109+
class_ref = JNIEnv.NewGlobalRef(lref);
110+
JNIEnv.DeleteLocalRef(lref);
111+
}
112+
113+
protected override Type ThresholdType
114+
{
115+
get { return typeof(AbstractClassInvoker); }
116+
}
117+
118+
protected override IntPtr ThresholdClass
119+
{
120+
get { return class_ref; }
121+
}
122+
123+
public override string GetText()
124+
{
125+
if (id_gettext == IntPtr.Zero)
126+
id_gettext = JNIEnv.GetMethodID(class_ref, "getText", "()Ljava/lang/String;");
127+
IntPtr lref = JNIEnv.CallObjectMethod(Handle, id_gettext);
128+
return GetObject<Java.Lang.String>(lref, JniHandleOwnership.TransferLocalRef)?.ToString();
129+
}
130+
131+
protected override void Dispose(bool disposing)
132+
{
133+
if (class_ref != IntPtr.Zero)
134+
JNIEnv.DeleteGlobalRef(class_ref);
135+
class_ref = IntPtr.Zero;
136+
137+
base.Dispose(disposing);
138+
}
139+
}
140+
```
141+
142+
There is quite a bit going on here, we:
143+
144+
- Added a class with the suffix `Invoker` that subclasses `AbstractClass`
145+
- Added `class_ref` to hold the JNI reference to the Java class that subclasses our C# class
146+
- Added `id_gettext` to hold the JNI reference to the Java `getText` method
147+
- Included a `(IntPtr, JniHandleOwnership)` constructor
148+
- Implemented `ThresholdType` and `ThresholdClass` as a requirement for Xamarin.Android to know details about the `Invoker`
149+
- `GetText` needed to lookup the Java `getText` method with the appropriate JNI signature and call it
150+
- `Dispose` is just needed to clear the reference to `class_ref`
151+
152+
After adding this class and generating a new AAR, our unit test passes. As you can see this pattern for callbacks is not *ideal*, but doable.
153+
154+
For details on Java interop, see the amazing [Xamarin.Android documentation](~/android/platform/java-integration/working-with-jni.md) on this subject.
155+
156+
## Interfaces
157+
158+
Interfaces are much the same as abstract classes, except for one detail: Xamarin.Android does not generate Java for them. This is because before .NET Embedding, there are not many scenarios where Java would implement a C# interface.
159+
160+
Let's say we have the following C# interface:
161+
162+
```csharp
163+
[Register("mono.embeddinator.android.IJavaCallback")]
164+
public interface IJavaCallback : IJavaObject
165+
{
166+
[Export("send")]
167+
void Send(string text);
168+
}
169+
```
170+
171+
`IJavaObject` signals to .NET Embedding that this is a Xamarin.Android interface, but otherwise this is exactly the same as an `abstract` class.
172+
173+
Since Xamarin.Android will not currently generate the Java code for this interface, add the following Java to your C# project:
174+
175+
```java
176+
package mono.embeddinator.android;
177+
178+
public interface IJavaCallback {
179+
void send(String text);
180+
}
181+
```
182+
183+
You can place the file anywhere, but make sure to set its build action to `AndroidJavaSource`. This will signal .NET Embedding to copy it to the proper directory to get compiled into your AAR file.
184+
185+
Next, the `Invoker` implementation will be quite the same:
186+
187+
```csharp
188+
class IJavaCallbackInvoker : Java.Lang.Object, IJavaCallback
189+
{
190+
IntPtr class_ref, id_send;
191+
192+
public IJavaCallbackInvoker(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer)
193+
{
194+
IntPtr lref = JNIEnv.GetObjectClass(Handle);
195+
class_ref = JNIEnv.NewGlobalRef(lref);
196+
JNIEnv.DeleteLocalRef(lref);
197+
}
198+
199+
protected override Type ThresholdType
200+
{
201+
get { return typeof(IJavaCallbackInvoker); }
202+
}
203+
204+
protected override IntPtr ThresholdClass
205+
{
206+
get { return class_ref; }
207+
}
208+
209+
public void Send(string text)
210+
{
211+
if (id_send == IntPtr.Zero)
212+
id_send = JNIEnv.GetMethodID(class_ref, "send", "(Ljava/lang/String;)V");
213+
JNIEnv.CallVoidMethod(Handle, id_send, new JValue(new Java.Lang.String(text)));
214+
}
215+
216+
protected override void Dispose(bool disposing)
217+
{
218+
if (class_ref != IntPtr.Zero)
219+
JNIEnv.DeleteGlobalRef(class_ref);
220+
class_ref = IntPtr.Zero;
221+
222+
base.Dispose(disposing);
223+
}
224+
}
225+
```
226+
227+
After generating an AAR file, in Android Studio we could write the following passing unit test:
228+
229+
```java
230+
class ConcreteCallback implements IJavaCallback {
231+
public String text;
232+
@Override
233+
public void send(String text) {
234+
this.text = text;
235+
}
236+
}
237+
238+
@Test
239+
public void interfaceCallback() {
240+
ConcreteCallback callback = new ConcreteCallback();
241+
JavaCallbacks.interfaceCallback(callback, "Java");
242+
assertEquals("Java", callback.text);
243+
}
244+
```
245+
246+
## Virtual Methods
247+
248+
Overriding a `virtual` in Java is possible, but not a great experience.
249+
250+
Let's assume you have the following C# class:
251+
252+
```csharp
253+
[Register("mono.embeddinator.android.VirtualClass")]
254+
public class VirtualClass : Java.Lang.Object
255+
{
256+
public VirtualClass() { }
257+
258+
public VirtualClass(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) { }
259+
260+
[Export("getText")]
261+
public virtual string GetText() { return "C#"; }
262+
}
263+
```
264+
265+
If you followed the `abstract` class example above, it would work except for one detail: _Xamarin.Android won't lookup the `Invoker`_.
266+
267+
To fix this, modify the C# class to be `abstract`:
268+
269+
```csharp
270+
public abstract class VirtualClass : Java.Lang.Object
271+
```
272+
273+
This is not ideal, but it gets this scenario working. Xamarin.Android will pick up the `VirtualClassInvoker` and Java can use `@Override` on the method.
274+
275+
## Callbacks in the Future
276+
277+
There are a couple of things we could to do improve these scenarios:
278+
279+
1. `throws Throwable` on C# constructors is fixed on this [PR](https://github.com/xamarin/java.interop/pull/170).
280+
1. Make the Java generator in Xamarin.Android support interfaces.
281+
- This removes the need for adding Java source file with a build action of `AndroidJavaSource`.
282+
1. Make a way for Xamarin.Android to load an `Invoker` for virtual classes.
283+
- This removes the need to mark the class in our `virtual` example `abstract`.
284+
1. Generate `Invoker` classes for .NET Embedding automatically
285+
- This is going to be complicated, but doable. Xamarin.Android is already doing something similar to this for Java binding projects.
286+
287+
There is a lot of work to be done here, but these enhancements to .NET Embedding are possible.
288+
289+
## Further Reading
290+
291+
- [Getting Started on Android](~/tools/dotnet-embedding/get-started/java/android.md)
292+
- [Preliminary Android Research](~/tools/dotnet-embedding/android/index.md)
293+
- [.NET Embedding Limitations](~/tools/dotnet-embedding/limitations.md)
294+
- [Error codes and descriptions](~/tools/dotnet-embedding/errors.md)

0 commit comments

Comments
 (0)