Wrappers callable do Android para Xamarin.Android

Os ACWs (Wrappers Callable) do Android são necessários sempre que o runtime do Android invoca o código gerenciado. Esses wrappers são necessários porque não há como registrar classes com ART (o runtime do Android) em runtime. (Especificamente, a função DefineClass() JNI não é compatível com o runtime do Android.} Os Wrappers Callable do Android compõem, portanto, a falta de suporte ao registro de tipo de runtime.

Todas as vezes O código do Android precisa executar um virtual método de interface ou que seja overridden ou implementado no código gerenciado, o Xamarin.Android deve fornecer um proxy Java para que esse método seja enviado para o tipo gerenciado apropriado. Esses tipos de proxy Java são código Java que tem a classe base "mesma" e a lista de interfaces Java que o tipo gerenciado, implementando os mesmos construtores e declarando qualquer classe base e métodos de interface substituídos.

Os wrappers callable do Android são gerados pelo programa monodroid.exe durante o processo de build: eles são gerados para todos os tipos que (direta ou indiretamente) herdam Java.Lang.Object.

Nomenclatura do Wrapper Callable do Android

Os nomes de pacote para Wrappers Callable do Android são baseados no MD5SUM do nome qualificado para assembly do tipo que está sendo exportado. Essa técnica de nomenclatura possibilita que o mesmo nome de tipo totalmente qualificado seja disponibilizado por assemblies diferentes sem introduzir um erro de empacotamento.

Devido a esse esquema de nomenclatura MD5SUM, você não pode acessar diretamente seus tipos por nome. Por exemplo, o comando a seguir adb não funcionará porque o nome my.ActivityType do tipo não é gerado por padrão:

adb shell am start -n My.Package.Name/my.ActivityType

Além disso, você poderá ver erros como o seguinte se tentar fazer referência a um tipo por nome:

java.lang.ClassNotFoundException: Didn't find class "com.company.app.MainActivity"
on path: DexPathList[[zip file "/data/app/com.company.App-1.apk"] ...

Se você precisar de acesso a tipos por nome, poderá declarar um nome para esse tipo em uma declaração de atributo. Por exemplo, aqui está o código que declara uma atividade com o nome My.ActivityTypetotalmente qualificado :

namespace My {
    [Activity]
    public partial class ActivityType : Activity {
        /* ... */
    }
}

A ActivityAttribute.Name propriedade pode ser definida para declarar explicitamente o nome dessa atividade:

namespace My {
    [Activity(Name="my.ActivityType")]
    public partial class ActivityType : Activity {
        /* ... */
    }
}

Depois que essa configuração de propriedade for adicionada, my.ActivityType poderá ser acessada pelo nome do código externo e dos adb scripts. O Name atributo pode ser definido para muitos tipos diferentes, incluindo Activity, Application, Service, BroadcastReceivere ContentProvider:

A nomenclatura ACW baseada em MD5SUM foi introduzida no Xamarin.Android 5.0. Para obter mais informações sobre a nomenclatura de atributo, consulte RegisterAttribute.

Implementando interfaces

Há momentos em que talvez seja necessário implementar uma interface do Android, como Android.Content.IComponentCallbacks. Como todas as classes e interface do Android estendem a interface Android.Runtime.IJavaObject , surge a pergunta: como implementamos IJavaObject?

A pergunta foi respondida acima: o motivo pelo qual todos os tipos android precisam implementar IJavaObject é para que o Xamarin.Android tenha um wrapper callable do Android para fornecer ao Android, ou seja, um proxy Java para o tipo especificado. Como monodroid.exe procura Java.Lang.Object apenas subclasses e Java.Lang.Object implementa IJavaObject, a resposta é óbvia: subclasse Java.Lang.Object:

class MyComponentCallbacks : Java.Lang.Object, Android.Content.IComponentCallbacks {

    public void OnConfigurationChanged (Android.Content.Res.Configuration newConfig)
    {
        // implementation goes here...
    } 

    public void OnLowMemory ()
    {
        // implementation goes here...
    }
}

Detalhes da implementação

O restante desta página fornece detalhes de implementação sujeitos a alterações sem aviso prévio (e é apresentado aqui apenas porque os desenvolvedores estarão curiosos sobre o que está acontecendo).

Por exemplo, dada a seguinte fonte C#:

using System;
using Android.App;
using Android.OS;

namespace Mono.Samples.HelloWorld
{
    public class HelloAndroid : Activity
    {
        protected override void OnCreate (Bundle savedInstanceState)
        {
            base.OnCreate (savedInstanceState);
            SetContentView (R.layout.main);
        }
    }
}

O programamandroid.exe gerará o seguinte Wrapper Callable do Android:

package mono.samples.helloWorld;

public class HelloAndroid
    extends android.app.Activity
{
    static final String __md_methods;
    static {
        __md_methods = "n_onCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n" + "";
        mono.android.Runtime.register (
            "Mono.Samples.HelloWorld.HelloAndroid, HelloWorld, Version=1.0.0.0, 
            Culture=neutral, PublicKeyToken=null", HelloAndroid.class, __md_methods);
    }

    public HelloAndroid ()
    {
        super ();
        if (getClass () == HelloAndroid.class)
            mono.android.TypeManager.Activate (
                "Mono.Samples.HelloWorld.HelloAndroid, HelloWorld, Version=1.0.0.0, 
                Culture=neutral, PublicKeyToken=null", "", this, new java.lang.Object[] {  });
    }

    @Override
    public void onCreate (android.os.Bundle p0)
    {
        n_onCreate (p0);
    }

    private native void n_onCreate (android.os.Bundle p0);
}

Observe que a classe base é preservada e native as declarações de método são fornecidas para cada método substituído no código gerenciado.