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 Android Runtime (ART). Ambos os ambientes de tempo de execução 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 System, o System.IO, o System.Net e o restante das bibliotecas de classes do .NET para acessar os recursos subjacentes do sistema operacional Linux.

No Android, a maioria dos recursos do sistema, como áudio, gráficos, OpenGL e telefonia, não está disponível 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 Android.*. A arquitetura é mais ou menos assim:

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

Os desenvolvedores do Xamarin.Android acessam os vários recursos no sistema operacional chamando 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 do Android se comunicam com as classes do Android Runtime, consulte o documento Design da 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 na pasta assemblies . Durante a inicialização do processo em compilações de versão, o .apk é mmap() ed no processo e os assemblies são carregados da memória. Isso permite uma inicialização mais rápida do aplicativo, pois os assemblies não precisam ser extraídos antes da execução.

  • Observação: as informações de local do assembly, como Assembly.Location e Assembly.CodeBase,não podem ser confiáveis em builds de versão. Eles não existem como entradas distintas do sistema de arquivos e não têm local utilizável.

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

Os aplicativos Xamarin.Android também contêm wrappers que podem ser chamados do Android para permitir que o Android chame o código gerenciado.

Callable Wrappers do Android

  • Os wrappers que podem ser chamados do Android são uma ponte JNI que é usada sempre que o tempo de execução do Android precisa invocar código gerenciado. Os wrappers que podem ser chamados 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 que podem ser chamados gerenciados

Os wrappers callable gerenciados são uma ponte JNI que é usada sempre que o código gerenciado precisa invocar o código do Android e fornecer suporte para substituir métodos virtuais e implementar interfaces Java. Todo o Android.* e namespaces relacionados são wrappers que podem ser chamados gerenciados gerados por meio de .jar vinculação. Os wrappers callable gerenciados são responsáveis por converter entre tipos gerenciados e Android e invocar os métodos subjacentes da plataforma Android por meio do JNI.

Cada wrapper callable gerenciado criado contém uma referência global Java, que pode ser acessada por meio da propriedade Android.Runtime.IJavaObject.Handle . As 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 existam apenas 2000 referências globais por vez, enquanto a maioria dos hardwares permite que existam mais de 52.000 referências globais por vez.

Para rastrear quando as referências globais são criadas e destruídas, você pode definir a propriedade debug.mono.log system 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 a partir do código gerenciado, um novo wrapper callable gerenciado será criado para ela.

Deve-se ter cuidado ao descartar Managed Callable Wrappers se a instância puder ser compartilhada inadvertidamente entre threads, pois descartar a instância afetará as referências de quaisquer outros threads. Para máxima segurança, somente Dispose() as instâncias que foram alocadas por meio new de ou de métodos que você sabe sempre alocam novas instâncias e não instâncias armazenadas em cache que podem causar compartilhamento acidental de instâncias entre threads.

Subclasses de wrapper que podem ser chamadas gerenciadas

As subclasses de wrapper que podem ser chamadas gerenciadas são onde toda a lógica específica do aplicativo "interessante" pode residir. Isso inclui subclasses Android.App.Activity personalizadas (como o tipo Activity1 no modelo de projeto padrão). (Especificamente, estes são quaisquer Java.Lang.Object que não contêm um atributo customizado RegisterAttribute ou RegisterAttribute.DoNotGenerateAcw é false, que é o padrão.)

Assim como os wrappers que podem ser chamados gerenciados, as subclasses de wrapper que podem ser chamadas gerenciadas também contêm uma referência global, acessível por meio da propriedade Java.Lang.Object.Handle . Assim como acontece com wrappers que podem ser chamados 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() da instância interromperá o mapeamento entre a instância Java (uma instância de um Android Callable Wrapper) e a instância gerenciada.

Ativação Java

Quando um Android Callable Wrapper (ACW) é 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 de MainActivity. (Isso é feito através do TypeManager.Activate() nos construtores ACW.)

Há uma outra assinatura de construtor de consequência: o construtor (IntPtr, JniHandleOwnership). O construtor (IntPtr, JniHandleOwnership) é chamado 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 é subclassificada. 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 com vazamento. Em Java, como em C#, as 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 (1) subclasse TextView, (2) substituir TextView.DefaultMovementMethod e (3) ativar uma instância dessa classe por meio de XML, a propriedade DefaultMovementMethod substituída seria invocada antes que o construtor ACW tivesse a chance de executar e ocorreria antes que o construtor C# tivesse a chance de ser executado.

Isso tem suporte instanciando uma instância LogTextBox por meio do construtor LogTextView(IntPtr, JniHandleOwnership) quando a instância ACW LogTextBox insere o código gerenciado pela primeira vez 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 instancia o grafo do objeto Layout e instancia 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 chama 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> (identificador, JniHandleOwnership.DoNotTransfer) .

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

  7. Object.GetObject<T>() procura o construtor LogTextBox(IntPtr, JniHandleOwneship), invoca-o, 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, chamando 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 prematuras de Dispose()

Há um mapeamento entre um identificador JNI e a instância C# correspondente. Java.Lang.Object.Dispose() interrompe esse mapeamento. Se um identificador JNI inserir 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á lançada.

Por exemplo, dada a seguinte subclasse Managed Callable Wraper:

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, Dispose() 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 da instância, pois é uma nova instância. (Observe que o valor é nulo.)

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

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

Inicialização de aplicativos

Quando uma atividade, serviço, etc. é iniciado, o Android primeiro verifica se já existe 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. MonoRuntimeProvider ContentProvider para AndroidManifest.xml durante o processo de build. O mono. O método MonoRuntimeProvider.attachInfo() é responsável por carregar o tempo de execução Mono no processo. Qualquer tentativa de usar o Mono antes desse ponto falhará. (Observação: é por isso que os tipos de subclasse Android.App.Application precisam fornecer um construtor (IntPtr, JniHandleOwnership), pois a instância Application é criada antes que o Mono possa ser inicializado.)

Após a conclusão da inicialização do processo, AndroidManifest.xml o é consultado para encontrar o nome da classe da atividade/serviço/etc. a ser iniciado. 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, portanto, os Android Callable Wrappers) e, em seguida, instanciado. A criação de uma instância do Android Callable Wrapper acionará a criação de uma instância do tipo C# correspondente. O Android invocará Activity.onCreate(Bundle) , o que fará com que o Activity.OnCreate(Bundle) correspondente seja invocado e você partirá para as corridas.