Compartilhar via


Princípios de design da API do Xamarin.Android

Além das principais Bibliotecas de Classes Base que fazem parte do Mono, o Xamarin.Android é fornecido com associações para várias APIs do Android para permitir que os desenvolvedores criem aplicativos Android nativos com Mono.

No núcleo do Xamarin.Android, há um mecanismo de interoperabilidade que une o mundo C# com o mundo Java e fornece aos desenvolvedores acesso às APIs Java do C# ou de outras linguagens .NET.

Princípios de design

Estes são alguns dos nossos princípios de design para a associação Xamarin.Android

  • Em conformidade com as diretrizes de design do .NET Framework.

  • Permitir que os desenvolvedores subclassem classes Java.

  • A subclasse deve funcionar com constructos padrão C#.

  • Derivar de uma classe existente.

  • Chame o construtor base para encadear.

  • Os métodos de substituição devem ser feitos com o sistema de substituição do C#.

  • Torne as tarefas comuns do Java fáceis e difíceis de serem possíveis.

  • Exponha as propriedades do JavaBean como propriedades do C#.

  • Expor uma API fortemente tipada:

    • Aumente a segurança de tipo.

    • Minimizar erros de runtime.

    • Obtenha o IntelliSense do IDE em tipos de retorno.

    • Permite a documentação pop-up do IDE.

  • Incentive a exploração no IDE das APIs:

    • Utilize alternativas de estrutura para minimizar a exposição de Classlib do Java.

    • Exponha delegados C# (lambdas, métodos anônimos e System.Delegate) em vez de interfaces de método único quando apropriado e aplicável.

    • Forneça um mecanismo para chamar bibliotecas Java arbitrárias ( Android.Runtime.JNIEnv).

Assemblies

O Xamarin.Android inclui vários assemblies que constituem o Perfil MonoMobile. A página Assemblies tem mais informações.

As associações à plataforma Android estão contidas no Mono.Android.dll assembly. Esse assembly contém toda a associação para consumir APIs do Android e se comunicar com a VM de runtime do Android.

Design de associação

Coleções

As APIs do Android utilizam amplamente as coleções java.util para fornecer listas, conjuntos e mapas. Expomos esses elementos usando as interfaces System.Collections.Generic em nossa associação. Os mapeamentos fundamentais são:

Fornecemos classes auxiliares para facilitar o marshaling sem cópia mais rápido desses tipos. Quando possível, recomendamos usar essas coleções fornecidas em vez da implementação fornecida pela estrutura, como List<T> ou Dictionary<TKey, TValue>. As implementações do Android.Runtime utilizam uma coleção Java nativa internamente e, portanto, não exigem a cópia de e para uma coleção nativa ao passar para um membro da API do Android.

Você pode passar qualquer implementação de interface para um método Android aceitando essa interface, por exemplo, passar um List<int> para o construtor arrayAdapter<int>(Context, int, IList<int>). No entanto, para todas as implementações , exceto para as implementações do Android.Runtime, isso envolve copiar a lista da VM Mono para a VM de runtime do Android. Se a lista for alterada posteriormente no runtime do Android (por exemplo, invocando o ArrayAdapter<T>. Método Add(T ), essas alterações não ficarão visíveis no código gerenciado. Se um JavaList<int> fosse usado, essas alterações seriam visíveis.

Implementações de interface de coleções reformuladas que não são uma das classes auxiliareslistadas acima es apenas marshal [In]:

// This fails:
var badSource  = new List<int> { 1, 2, 3 };
var badAdapter = new ArrayAdapter<int>(context, textViewResourceId, badSource);
badAdapter.Add (4);
if (badSource.Count != 4) // true
    throw new InvalidOperationException ("this is thrown");

// this works:
var goodSource  = new JavaList<int> { 1, 2, 3 };
var goodAdapter = new ArrayAdapter<int> (context, textViewResourceId, goodSource);
goodAdapter.Add (4);
if (goodSource.Count != 4) // false
    throw new InvalidOperationException ("should not be reached.");

Propriedades

Os métodos Java são transformados em propriedades, quando apropriado:

  • O par T getFoo() de métodos Java e void setFoo(T) são transformados na Foo propriedade . Exemplo: Activity.Intent.

  • O método getFoo() Java é transformado na propriedade Foo somente leitura. Exemplo: Context.PackageName.

  • As propriedades somente de conjunto não são geradas.

  • As propriedades não serão geradas se o tipo de propriedade for uma matriz.

Eventos e ouvintes

As APIs do Android são criadas com base no Java e seus componentes seguem o padrão Java para conectar ouvintes de eventos. Esse padrão tende a ser complicado, pois exige que o usuário crie uma classe anônima e declare os métodos para substituir, por exemplo, é assim que as coisas seriam feitas no Android com Java:

final android.widget.Button button = new android.widget.Button(context);

button.setText(this.count + " clicks!");
button.setOnClickListener (new View.OnClickListener() {
    public void onClick (View v) {
        button.setText(++this.count + " clicks!");
    }
});

O código equivalente em C# usando eventos seria:

var button = new Android.Widget.Button (context) {
    Text = string.Format ("{0} clicks!", this.count),
};
button.Click += (sender, e) => {
    button.Text = string.Format ("{0} clicks!", ++this.count);
};

Observe que ambos os mecanismos acima estão disponíveis com o Xamarin.Android. Você pode implementar uma interface do ouvinte e anexá-la com View.SetOnClickListener ou anexar um delegado criado por meio de qualquer um dos paradigmas C# usuais ao evento Click.

Quando o método de retorno de chamada do ouvinte tem um retorno nulo, criamos elementos de API com base em um delegado EventHandler<TEventArgs> . Geramos um evento como o exemplo acima para esses tipos de ouvinte. No entanto, se o retorno de chamada do ouvinte retornar um valor não nulo e não booliano , eventos e EventHandlers não serão usados. Em vez disso, geramos um delegado específico para a assinatura do retorno de chamada e adicionamos propriedades em vez de eventos. O motivo é lidar com a ordem de invocação delegada e a manipulação de retorno. Essa abordagem espelha o que é feito com a API do Xamarin.iOS.

Eventos ou propriedades do C# só serão gerados automaticamente se o método de registro de eventos do Android:

  1. Tem um set prefixo, por exemplo, definaOnClickListener.

  2. Tem um void tipo de retorno.

  3. Aceita apenas um parâmetro, o tipo de parâmetro é uma interface, a interface tem apenas um método e o nome da interface termina em Listener , por exemplo, View.OnClick Listener.

Além disso, se o método de interface Listener tiver um tipo de retorno de booliano em vez de void, a subclasse EventArgs gerada conterá uma propriedade Handled . O valor da propriedade Handled é usado como o valor retornado para o método Listener e usa como padrão true.

Por exemplo, o método Android View.setOnKeyListener() aceita a interface View.OnKeyListener e o método View.OnKeyListener.onKey(View, int, KeyEvent) tem um tipo de retorno booliano. O Xamarin.Android gera um evento View.KeyPress correspondente, que é um EventHandler<View.KeyEventArgs>. A classe KeyEventArgs, por sua vez, tem uma propriedade View.KeyEventArgs.Handled, que é usada como o valor retornado para o método View.OnKeyListener.onKey().

Pretendemos adicionar sobrecargas para outros métodos e ctors para expor a conexão baseada em delegado. Além disso, ouvintes com vários retornos de chamada exigem alguma inspeção adicional para determinar se a implementação de retornos de chamada individuais é razoável, portanto, estamos convertendo-os conforme eles são identificados. Se não houver nenhum evento correspondente, os ouvintes deverão ser usados em C#, mas traga qualquer um que você ache que possa ter o uso delegado para nossa atenção. Também fizemos algumas conversões de interfaces sem o sufixo "Ouvinte" quando ficou claro que elas se beneficiariam de uma alternativa delegada.

Todas as interfaces de ouvintes implementam oAndroid.Runtime.IJavaObject devido aos detalhes de implementação da associação, portanto, as classes de ouvinte devem implementar essa interface. Isso pode ser feito implementando a interface do ouvinte em uma subclasse de Java.Lang.Object ou qualquer outro objeto Java encapsulado, como uma atividade do Android.

Executáveis

O Java utiliza a interface java.lang.Runnable para fornecer um mecanismo de delegação. A classe java.lang.Thread é um consumidor notável dessa interface. O Android também empregou a interface na API. Activity.runOnUiThread() e View.post() são exemplos notáveis.

A Runnable interface contém um único método void, run(). Portanto, ele se presta à associação em C# como um delegado System.Action . Fornecemos sobrecargas na associação que aceitam um Action parâmetro para todos os membros da API que consomem um Runnable na API nativa, por exemplo, Activity.RunOnUiThread() e View.Post().

Deixamos as sobrecargas IRunnable em vez de substituí-las, pois vários tipos implementam a interface e, portanto, podem ser passadas como executáveis diretamente.

Classes internas

O Java tem dois tipos diferentes de classes aninhadas: classes aninhadas estáticas e classes não estáticas.

As classes aninhadas estáticas do Java são idênticas aos tipos aninhados em C#.

Classes aninhadas não estáticas, também chamadas de classes internas, são significativamente diferentes. Eles contêm uma referência implícita a uma instância de seu tipo delimitador e não podem conter membros estáticos (entre outras diferenças fora do escopo dessa visão geral).

Quando se trata de associação e uso de C#, classes aninhadas estáticas são tratadas como tipos aninhados normais. As classes internas, entretanto, têm duas diferenças significativas:

  1. A referência implícita ao tipo recipiente deve ser fornecida explicitamente como um parâmetro de construtor.

  2. Ao herdar de uma classe interna, a classe interna deve ser aninhada dentro de um tipo que herda do tipo recipiente da classe interna base, e o tipo derivado deve fornecer um construtor do mesmo tipo que o tipo que contém C#.

Por exemplo, considere a classe interna Android.Service.Wallpaper.WallpaperService.Engine . Como é uma classe interna, o construtor WallpaperService.Engine() faz referência a uma instância de WallpaperService (comparar e contrastar com o construtor Java WallpaperService.Engine(), que não usa parâmetros.

Um exemplo de derivação de uma classe interna é CubeWallpaper.CubeEngine:

class CubeWallpaper : WallpaperService {
    public override WallpaperService.Engine OnCreateEngine ()
    {
        return new CubeEngine (this);
    }

    class CubeEngine : WallpaperService.Engine {
        public CubeEngine (CubeWallpaper s)
                : base (s)
        {
        }
    }
}

Observe como CubeWallpaper.CubeEngine é aninhado dentro CubeWallpaperde , CubeWallpaper herda da classe que contém de WallpaperService.Enginee CubeWallpaper.CubeEngine tem um construtor que usa o tipo de declaração , CubeWallpaper nesse caso , tudo conforme especificado acima.

Interfaces

As interfaces Java podem conter três conjuntos de membros, dois dos quais causam problemas de C#:

  1. Métodos

  2. Tipos

  3. Campos

As interfaces Java são convertidas em dois tipos:

  1. Uma interface (opcional) que contém declarações de método. Essa interface tem o mesmo nome que a interface Java, exceto que também tem um prefixo ' I '.

  2. Uma classe estática (opcional) que contém todos os campos declarados na interface Java.

Os tipos aninhados são "realocados" para serem irmãos da interface delimitante em vez de tipos aninhados, com o nome da interface delimitado como um prefixo.

Por exemplo, considere a interface android.os.Parcelable . A interface Parcelable contém métodos, tipos aninhados e constantes. Os métodos de interface Parcelable são colocados na interface Android.OS.IParcelable . As constantes de interface Parcelable são colocadas no tipo Android.OS.ParcelableConsts . Os tipos android.os.Parcelable.ClassLoaderCreator<T> e android.os.Parcelable.Creator<T> aninhados atualmente não estão associados devido a limitações em nosso suporte a genéricos; se eles tivessem suporte, eles estariam presentes como as interfaces Android.OS.IParcelableClassLoaderCreator e Android.OS.IParcelableCreator . Por exemplo, a interface aninhada android.os.IBinder.DeathRecipient está associada como a interface Android.OS.IBinderDeathRecipient .

Observação

A partir do Xamarin.Android 1.9, as constantes de interface Java são duplicadas em um esforço para simplificar a portabilidade do código Java. Isso ajuda a melhorar a portabilidade do código Java que depende de constantes de interface do provedor android .

Além dos tipos acima, há quatro outras alterações:

  1. Um tipo com o mesmo nome que a interface Java é gerado para conter constantes.

  2. Os tipos que contêm constantes de interface também contêm todas as constantes provenientes de interfaces Java implementadas.

  3. Todas as classes que implementam uma interface Java que contém constantes recebem um novo tipo de InterfaceConsts aninhado que contém constantes de todas as interfaces implementadas.

  4. O tipo Consts agora está obsoleto.

Para a interface android.os.Parcelable , isso significa que agora haverá um tipo Android.OS.Parcelable para conter as constantes. Por exemplo, a constante Parcelable.CONTENTS_FILE_DESCRIPTOR será associada como a constante Parcelable.ContentsFileDescriptor , em vez de como a constante ParcelableConsts.ContentsFileDescriptor .

Para interfaces que contêm constantes que implementam outras interfaces que contêm ainda mais constantes, a união de todas as constantes agora é gerada. Por exemplo, a interface android.provider.MediaStore.Video.VideoColumns implementa a interface android.provider.MediaStore.MediaColumns . No entanto, antes da 1.9, o tipo Android.Provider.MediaStore.Video.VideoColumnsConsts não tem como acessar as constantes declaradas em Android.Provider.MediaStore.MediaColumnsConsts. Como resultado, a expressão Java MediaStore.Video.VideoColumns.TITLE precisa ser associada à expressão C# MediaStore.Video.MediaColumnsConsts.Title , que é difícil de descobrir sem ler muitas documentações do Java. Na versão 1.9, a expressão C# equivalente será MediaStore.Video.VideoColumns.Title.

Além disso, considere o tipo android.os.Bundle , que implementa a interface Parcelable java. Como ela implementa a interface, todas as constantes nessa interface são acessíveis "por meio" do tipo Bundle, por exemplo, Bundle.CONTENTS_FILE_DESCRIPTOR é uma expressão Java perfeitamente válida. Anteriormente, para portar essa expressão para C#, você precisaria examinar todas as interfaces implementadas para ver de qual tipo o CONTENTS_FILE_DESCRIPTOR veio. A partir do Xamarin.Android 1.9, as classes que implementam interfaces Java que contêm constantes terão um tipo InterfaceConsts aninhado, que conterá todas as constantes de interface herdadas. Isso permitirá a tradução de Bundle.CONTENTS_FILE_DESCRIPTOR para Bundle.InterfaceConsts.ContentsFileDescriptor.

Por fim, os tipos com um sufixo Consts , como Android.OS.ParcelableConsts , agora estão Obsoletos, além dos tipos aninhados InterfaceConsts recém-introduzidos. Eles serão removidos no Xamarin.Android 3.0.

Recursos

Imagens, descrições de layout, blobs binários e dicionários de cadeia de caracteres podem ser incluídos em seu aplicativo como arquivos de recurso. Várias APIs do Android foram projetadas para operar nas IDs de recurso em vez de lidar diretamente com imagens, cadeias de caracteres ou blobs binários.

Por exemplo, um aplicativo Android de exemplo que contém um layout de interface do usuário ( main.axml), uma cadeia de caracteres de tabela de internacionalização ( strings.xml) e alguns ícones ( drawable-*/icon.png) manteria seus recursos no diretório "Recursos" do aplicativo:

Resources/
    drawable-hdpi/
        icon.png

    drawable-ldpi/
        icon.png

    drawable-mdpi/
        icon.png

    layout/
        main.axml

    values/
        strings.xml

As APIs nativas do Android não operam diretamente com nomes de arquivo, mas operam em IDs de recurso. Quando você compila um aplicativo Android que usa recursos, o sistema de build empacotará os recursos para distribuição e gerará uma classe chamada Resource que contém os tokens para cada um dos recursos incluídos. Por exemplo, para o layout de recursos acima, isso é o que a classe R exporia:

public class Resource {
    public class Drawable {
        public const int icon = 0x123;
    }

    public class Layout {
        public const int main = 0x456;
    }

    public class String {
        public const int first_string = 0xabc;
        public const int second_string = 0xbcd;
    }
}

Em seguida, você usaria para fazer Resource.Drawable.icon referência ao drawable/icon.png arquivo ou Resource.Layout.main para fazer referência ao layout/main.xml arquivo ou Resource.String.first_string para fazer referência à primeira cadeia de caracteres no arquivo values/strings.xmlde dicionário .

Constantes e enumerações

As APIs nativas do Android têm muitos métodos que pegam ou retornam um int que deve ser mapeado para um campo constante para determinar o que o int significa. Para usar esses métodos, o usuário precisa consultar a documentação para ver quais constantes são valores apropriados, o que é menor que o ideal.

Por exemplo, considere Activity.requestWindowFeature(int featureID).

Nesses casos, nos esforçamos para agrupar constantes relacionadas em uma enumeração .NET e remapear o método para usar a enumeração. Ao fazer isso, podemos oferecer a seleção do IntelliSense dos valores potenciais.

O exemplo acima se torna: Activity.RequestWindowFeature(WindowFeatures featureId).

Observe que esse é um processo muito manual para descobrir quais constantes pertencem juntas e quais APIs consomem essas constantes. Registre bugs para quaisquer constantes usadas na API que seriam melhor expressas como uma enumeração.