Arquitetura

Os aplicativos Xamarin.Android são executados no ambiente de execução Mono. Esse ambiente de execução é executado lado a lado com a máquina virtual ART (Android Runtime). Ambos os ambientes de runtime são executados sobre o kernel do Linux e expõem várias APIs ao código do usuário que permite que os desenvolvedores acessem o sistema subjacente. O runtime mono é escrito na linguagem C.

Você pode usar o Sistema, System.IO, System.Net e o restante das bibliotecas de classes do .NET para acessar as instalações subjacentes do sistema operacional Linux.

No Android, a maioria dos recursos do sistema, como Áudio, Gráficos, OpenGL e Telefonia, não estão disponíveis diretamente para aplicativos nativos, eles são expostos apenas por meio das APIs Java do Android Runtime que residem em um dos namespaces Java.* ou nos namespaces Android.*. A arquitetura é mais ou menos assim:

Diagrama de Mono e ART acima do kernel e abaixo de associações .NET/Java +

Os desenvolvedores do Xamarin.Android acessam os vários recursos no sistema operacional chamando as APIs do .NET que eles conhecem (para acesso de baixo nível) ou usando as classes expostas nos namespaces do Android que fornecem uma ponte para as APIs Java expostas pelo Android Runtime.

Para obter mais informações sobre como as classes android se comunicam com as classes do Android Runtime, consulte o documento Design de API .

Pacotes de Aplicativos

Os pacotes de aplicativos Android são contêineres ZIP com uma extensão de arquivo .apk . Os pacotes de aplicativos Xamarin.Android têm a mesma estrutura e layout que os pacotes normais do Android, com as seguintes adições:

  • Os assemblies de aplicativo (contendo IL) são armazenados descompactados dentro da pasta assemblies . Durante a inicialização do processo no Release, o .apk é mmap() inserido no processo e os assemblies são carregados da memória. Isso permite uma inicialização de aplicativo mais rápida, pois os assemblies não precisam ser extraídos antes da execução.

  • Nota: Informações de local do assembly, como Assembly.Location e Assembly.CodeBase, não podem ser usadas em builds de versão. Elas não existem como entradas distintas do sistema de arquivos e não têm um local utilizável.

  • Bibliotecas nativas que contêm o runtime Mono estão presentes no .apk . Um aplicativo Xamarin.Android deve conter bibliotecas nativas para as arquiteturas do Android desejadas/direcionadas, por exemplo, armeabi , armeabi-v7a , x86 . Os aplicativos Xamarin.Android não podem ser executados em uma plataforma, a menos que contenham as bibliotecas de runtime apropriadas.

Os aplicativos Xamarin.Android também contêm Wrappers Callable do Android para permitir que o Android chame o código gerenciado.

Callable Wrappers do Android

  • Os wrappers callable do Android são uma ponte JNI que é usada sempre que o runtime do Android precisa invocar o código gerenciado. Os wrappers chamáveis do Android são como os métodos virtuais podem ser substituídos e as interfaces Java podem ser implementadas. Consulte o documento Visão geral da integração java para obter mais informações.

Wrappers callable gerenciados

Os wrappers callable gerenciados são uma ponte JNI que é usada sempre que o código gerenciado precisa invocar o código Android e fornecer suporte para substituir métodos virtuais e implementar interfaces Java. Todos os namespaces android.* e relacionados são wrappers callable gerenciados gerados por meio da associação .jar. Os wrappers chamáveis gerenciados são responsáveis pela conversão entre tipos gerenciados e Android e pela invocação dos métodos subjacentes da plataforma Android por meio do JNI.

Cada wrapper callable gerenciado criado contém uma referência global java, que é acessível por meio da propriedade Android.Runtime.IJavaObject.Handle . Referências globais são usadas para fornecer o mapeamento entre instâncias Java e instâncias gerenciadas. As referências globais são um recurso limitado: os emuladores permitem que apenas 2.000 referências globais existam por vez, enquanto a maioria dos hardwares permite que mais de 52.000 referências globais existam por vez.

Para acompanhar quando as referências globais são criadas e destruídas, você pode definir a propriedade do sistema debug.mono.log para conter gref.

As referências globais podem ser liberadas explicitamente chamando Java.Lang.Object.Dispose() no wrapper callable gerenciado. Isso removerá o mapeamento entre a instância java e a instância gerenciada e permitirá que a instância java seja coletada. Se a instância java for acessada novamente do código gerenciado, um novo wrapper callable gerenciado será criado para ela.

O cuidado deve ser exercido ao descartar Wrappers Callable Gerenciados se a instância puder ser inadvertidamente compartilhada entre threads, pois descartar a instância afetará as referências de qualquer outro thread. Para segurança máxima, somente Dispose() de instâncias que foram alocadas por meio newde ou de métodos que você sabe que sempre alocam novas instâncias e não instâncias armazenadas em cache, o que pode causar compartilhamento acidental de instância entre threads.

Subclasses de wrapper callable gerenciado

As subclasses de wrapper chamáveis gerenciadas são onde toda a lógica específica do aplicativo "interessante" pode residir. Elas incluem subclasses personalizadas do Android.App.Activity (como o tipo Activity1 no modelo de projeto padrão). (Especificamente, essas são quaisquer subclasses Java.Lang.Object que não contêm um atributo personalizado RegisterAttribute ou RegisterAttribute.DoNotGenerateAcw é false, que é o padrão.)

Assim como os wrappers chamáveis gerenciados, as subclasses de wrapper callable gerenciadas também contêm uma referência global, acessível por meio da propriedade Java.Lang.Object.Handle . Assim como acontece com wrappers callable gerenciados, as referências globais podem ser liberadas explicitamente chamando Java.Lang.Object.Dispose(). Ao contrário dos wrappers callable gerenciados, deve-se tomar muito cuidado antes de descartar essas instâncias, pois Dispose()-ing da instância interromperá o mapeamento entre a instância java (uma instância de um Wrapper Callable do Android) e a instância gerenciada.

Ativação do Java

Quando um ACW ( Android Callable Wrapper ) é criado a partir do Java, o construtor ACW fará com que o construtor C# correspondente seja invocado. Por exemplo, o ACW para MainActivity conterá um construtor padrão que invocará o construtor padrão do MainActivity. (Isso é feito por meio da chamada TypeManager.Activate() nos construtores ACW.)

Há uma outra assinatura de construtor de conseqüência: o construtor (IntPtr, JniHandleOwnership). O construtor (IntPtr, JniHandleOwnership) é invocado sempre que um objeto Java é exposto ao código gerenciado e um Wrapper Callable Gerenciado precisa ser construído para gerenciar o identificador JNI. Isso geralmente é feito automaticamente.

Há dois cenários em que o construtor (IntPtr, JniHandleOwnership) deve ser fornecido manualmente em uma subclasse Managed Callable Wrapper:

  1. Android.App.Application é subclasse. O aplicativo é especial; O construtor Applicaton padrão nunca será invocado e o construtor (IntPtr, JniHandleOwnership) deve ser fornecido.

  2. Invocação de método virtual de um construtor de classe base.

Observe que (2) é uma abstração vazada. Em Java, como em C#, chamadas para métodos virtuais de um construtor sempre invocam a implementação de método mais derivada. Por exemplo, o construtor TextView(Context, AttributeSet, int) invoca o método virtual TextView.getDefaultMovementMethod(), que é associado como a propriedade TextView.DefaultMovementMethod. Portanto, se um tipo LogTextBox fosse (1) textView de subclasse, (2) substituir TextView.DefaultMovementMethod e (3) ativar uma instância dessa classe via XML, a propriedade DefaultMovementMethod substituída seria invocada antes que o construtor ACW tivesse a chance de ser executado e ocorresse antes que o construtor C# tivesse a chance de executar.

Isso é compatível com a instanciação de uma instância logTextBox por meio do construtor LogTextView(IntPtr, JniHandleOwnership) quando a instância de LogTextBox acW insere primeiro o código gerenciado e, em seguida, invocando o construtor LogTextBox(Context, IAttributeSet, int)na mesma instância quando o construtor ACW é executado.

Ordem dos eventos:

  1. O XML de layout é carregado em um ContentView.

  2. O Android cria uma instância do grafo do objeto Layout e cria uma instância de monodroid.apidemo.LogTextBox , o ACW para LogTextBox .

  3. O construtor monodroid.apidemo.LogTextBox executa o construtor android.widget.TextView .

  4. O construtor TextView invoca monodroid.apidemo.LogTextBox.getDefaultMovementMethod() .

  5. monodroid.apidemo.LogTextBox.getDefaultMovementMethod() invoca LogTextBox.n_getDefaultMovementMethod(), que invoca TextView.n_GetDefaultMovementMethod(), que invoca Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer).

  6. Java.Lang.Object.GetObject<TextView>() verifica se já existe uma instância C# correspondente para o identificador . Se houver, ele será retornado. Nesse cenário, não há, portanto , Object.GetObject<T>() deve criar um.

  7. Object.GetObject<T>() procura o construtor LogTextBox(IntPtr, JniHandleOwneship), o invoca, cria um mapeamento entre o identificador e a instância criada e retorna a instância criada.

  8. TextView.n_GetDefaultMovementMethod() invoca o getter da propriedade LogTextBox.DefaultMovementMethod .

  9. O controle retorna ao construtor android.widget.TextView , que conclui a execução.

  10. O construtor monodroid.apidemo.LogTextBox é executado, invocando TypeManager.Activate() .

  11. O construtor LogTextBox(Context, IAttributeSet, int) é executado na mesma instância criada em (7) .

  12. Se o construtor (IntPtr, JniHandleOwnership) não puder ser encontrado, um System.MissingMethodException](xref:System.MissingMethodException) será gerado.

Chamadas de Descarte Prematuro()

Há um mapeamento entre um identificador JNI e a instância do C# correspondente. Java.Lang.Object.Dispose() interrompe esse mapeamento. Se um identificador JNI entrar no código gerenciado depois que o mapeamento tiver sido interrompido, ele se parecerá com a Ativação java e o construtor (IntPtr, JniHandleOwnership) será verificado e invocado. Se o construtor não existir, uma exceção será gerada.

Por exemplo, dada a seguinte subclasse de Encapsulador Callable Gerenciado:

class ManagedValue : Java.Lang.Object {

    public string Value {get; private set;}

    public ManagedValue (string value)
    {
        Value = value;
    }

    public override string ToString ()
    {
        return string.Format ("[Managed: Value={0}]", Value);
    }
}

Se criarmos uma instância, Descarte() dela e faça com que o Wrapper Callable Gerenciado seja recriado:

var list = new JavaList<IJavaObject>();
list.Add (new ManagedValue ("value"));
list [0].Dispose ();
Console.WriteLine (list [0].ToString ());

O programa morrerá:

E/mono    ( 2906): Unhandled Exception: System.NotSupportedException: Unable to activate instance of type Scratch.PrematureDispose.ManagedValue from native handle 4051c8c8 --->
System.MissingMethodException: No constructor found for Scratch.PrematureDispose.ManagedValue::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateProxy (System.Type type, IntPtr handle, JniHandleOwnership transfer) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   --- End of inner exception stack trace ---
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object.GetObject (IntPtr handle, JniHandleOwnership transfer, System.Type type) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object._GetObject[IJavaObject] (IntPtr handle, JniHandleOwnership transfer) [0x00000

Se a subclasse contiver um construtor (IntPtr, JniHandleOwnership), uma nova instância do tipo será criada. Como resultado, a instância parecerá "perder" todos os dados de instância, pois é uma nova instância. (Observe que o Valor é nulo.)

I/mono-stdout( 2993): [Managed: Value=]

Somente Dispose() de subclasses wrapper callable gerenciadas quando você souber que o objeto Java não será mais usado ou a subclasse não contém dados de instância e um construtor (IntPtr, JniHandleOwnership) foi fornecido.

Inicialização do aplicativo

Quando uma atividade, serviço etc. é iniciado, o Android primeiro marcar para ver se já há um processo em execução para hospedar a atividade/serviço/etc. Se esse processo não existir, um novo processo será criado, o AndroidManifest.xml será lido e o tipo especificado no atributo /manifest/application/@android:name será carregado e instanciado. Em seguida, todos os tipos especificados pelos valores de atributo /manifest/application/provider/@android:name são instanciados e têm seu método ContentProvider.attachInfo%28) invocado. O Xamarin.Android se conecta a isso adicionando um mono. ContentProvider MonoRuntimeProvider para AndroidManifest.xml durante o processo de build. O mono. O método MonoRuntimeProvider.attachInfo() é responsável por carregar o runtime mono no processo. Qualquer tentativa de usar Mono antes desse ponto falhará. ( Observação: é por isso que os tipos que subclasse Android.App.Application precisam fornecer um construtor (IntPtr, JniHandleOwnership), pois a instância do Aplicativo é criada antes que o Mono possa ser inicializado.)

Depois que a inicialização do processo for concluída, AndroidManifest.xml será consultado para localizar o nome da classe da atividade/serviço/etc. para iniciar. Por exemplo, o atributo /manifest/application/activity/@android:name é usado para determinar o nome de uma Atividade a ser carregada. Para Atividades, esse tipo deve herdar android.app.Activity. O tipo especificado é carregado por meio de Class.forName() (que requer que o tipo seja um tipo Java, daí os Wrappers Callable do Android) e, em seguida, instanciado. A criação de uma instância do Wrapper Callable do Android disparará a criação de uma instância do tipo C# correspondente. Em seguida, o Android invocará Activity.onCreate(Bundle), o que fará com que a Activity.OnCreate(Bundle) correspondente seja invocada e você esteja fora das corridas.