This looks like both a limitation and a bug in MonoAndroid; the java.lang.UnsatisfiedLinkError
exception occurs because the method n_getPackageName
has not yet been registered with the Mono runtime and fails to resolve. The short answer is:
- It's probably not possible to override the
PackageName
property for an Application
because of complexities in MonoAndroids startup sequence.
- File a bug report with Xamarin. This a bug in the startup routine for MonoAndroid (Outlined below).
Down The Rabbit Hole
Let's dig into the bridging Java code to determine why this happens. When you build a MonoAndroid application, a bunch of Java code is generated in the [ProjectName]/obj/[Configuration]/android/src
directory of your project. This source code is the interop layer that allows applications built in C# to execute on an Android device via Dalvik or ART. Each class in your project that is an Android component (Application
, Service
, Activity
, Fragment
etc) will have a corresponding .java source code file generated.
This is the one for your TestApp
class:
package md5cf27010e14af20e69784a5a54418b85f;
public class TestApplication
extends mono.android.app.Application
implements
mono.android.IGCUserPeer
{
static final String __md_methods;
static {
__md_methods =
"n_getPackageName:()Ljava/lang/String;:GetGetPackageNameHandler\n" +
"";
}
public TestApplication () throws java.lang.Throwable
{
super ();
}
public java.lang.String getPackageName ()
{
return n_getPackageName ();
}
private native java.lang.String n_getPackageName ();
public void onCreate ()
{
mono.android.Runtime.register ("TestApp.TestApplication, TestApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", TestApplication.class, __md_methods);
super.onCreate ();
}
java.util.ArrayList refList;
public void monodroidAddReference (java.lang.Object obj)
{
if (refList == null)
refList = new java.util.ArrayList ();
refList.add (obj);
}
public void monodroidClearReferences ()
{
if (refList != null)
refList.clear ();
}
}
The important things to note here are:
The definition of overridden methods that link into your .NET assembly:
static final String __md_methods;
static {
__md_methods =
"n_getPackageName:()Ljava/lang/String;:GetGetPackageNameHandler\n" +
"";
}
The linkup code for your PackageName
property.
public java.lang.String getPackageName ()
{
return n_getPackageName ();
}
private native java.lang.String n_getPackageName ();
And the registration code:
public void onCreate ()
{
mono.android.Runtime.register ("TestApp.TestApplication, TestApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", TestApplication.class, __md_methods);
super.onCreate ();
}
Now, the most probable reason it crashes upon the n_getPackageName()
invocation is because an android.app.Application
s getPackageName()
method is called before it's onCreate
invocation.
If then you dissect MonoPackageManager
and MonoRuntimeProvider
, you find the cause of the issue:
MonoRuntimeProvider.java
@Override
public void attachInfo (android.content.Context context, android.content.pm.ProviderInfo info)
{
// Mono Runtime Initialization {{{
android.content.pm.ApplicationInfo apiInfo = null;
String platformPackage = mono.MonoPackageManager.getApiPackageName ();
if (platformPackage != null) {
Throwable t = null;
try {
apiInfo = context.getPackageManager ().getApplicationInfo (platformPackage, 0);
} catch (android.content.pm.PackageManager.NameNotFoundException e) {
// ignore
}
if (apiInfo == null) {
try {
apiInfo = context.getPackageManager ().getApplicationInfo ("Xamarin.Android.Platform", 0);
} catch (android.content.pm.PackageManager.NameNotFoundException e) {
t = e;
}
}
if (apiInfo == null)
throw new RuntimeException ("Unable to find application " + platformPackage + " or Xamarin.Android.Platform!", t);
}
try {
android.content.pm.ApplicationInfo runtimeInfo = context.getPackageManager ().getApplicationInfo ("Mono.Android.DebugRuntime", 0);
mono.MonoPackageManager.LoadApplication (context, runtimeInfo.dataDir,
apiInfo != null
? new String[]{runtimeInfo.sourceDir, apiInfo.sourceDir, context.getApplicationInfo ().sourceDir}
: new String[]{runtimeInfo.sourceDir, context.getApplicationInfo ().sourceDir});
} catch (android.content.pm.PackageManager.NameNotFoundException e) {
throw new RuntimeException ("Unable to find application Mono.Android.DebugRuntime!", e);
}
// }}}
super.attachInfo (context, info);
}
MonoPackageManager.java
// ...
public static void LoadApplication (Context context, String runtimeDataDir, String[] apks)
{
synchronized (lock) {
if (!initialized) {
System.loadLibrary("monodroid");
Locale locale = Locale.getDefault ();
String language = locale.getLanguage () + "-" + locale.getCountry ();
String filesDir = context.getFilesDir ().getAbsolutePath ();
String cacheDir = context.getCacheDir ().getAbsolutePath ();
String dataDir = context.getApplicationInfo ().dataDir + "/lib";
ClassLoader loader = context.getClassLoader ();
Runtime.init (
language,
apks,
runtimeDataDir,
new String[]{
filesDir,
cacheDir,
dataDir,
},
loader,
new java.io.File (
android.os.Environment.getExternalStorageDirectory (),
"Android/data/" + context.getPackageName () + "/files/.__override__").getAbsolutePath (),
MonoPackageManager_Resources.Assemblies,
context.getPackageName ());
initialized = true;
}
}
}
// ...
The cause of the crash becomes clear when you walk through the startup routine:
- Android loads the
MonoRuntimeProvider
class and executes attachInfo
.
attachInfo
invokes LoadApplication
.
LoadApplication
calls uses the Application contexts getPackageName()
method.
getPackageName()
calls through to the n_getPackageName()
method.
According to the docs for android.app.Application
:
public void onCreate ()
Called when the application is starting, before any activity, service, or receiver objects (excluding content providers) have been created. Implementations should be as quick as possible (for example using lazy initialization of state) since the time spent in this function directly impacts the performance of starting the first activity, service, or receiver in a process. If you override this method, be sure to call super.onCreate().
Because an Applications onCreate is called after a ContentProviders onCreate, the native method won't have been bound by Mono and therefore can't be resolved! So there is the root cause of the java.lang.UnsatisfiedLinkError
exception.
To summarise, it's both a bug and a startup complexity.