Start Debugging

Migre um app .NET MAUI para Android para o nível de API 36

O Google Play passou a exigir o nível de API alvo 36 desde 2026-08-31, com prorrogações até 2026-11-01. Este é o caminho completo no .NET MAUI de net9.0-android até a API 36: a mudança de target framework, o uses-sdk fixo que silenciosamente te prende no nível antigo, o edge-to-edge sem opção de exclusão, o gesto de voltar preditivo e as regras de telas grandes.

A mudança de build é uma linha. As mudanças de comportamento são a migração. O Google Play começou a exigir o nível de API alvo 36 para apps novos e atualizações em 2026-08-31, com uma prorrogação por app disponível no Play Console até 2026-11-01, então se sua atualização foi rejeitada esta semana, é por isso. Em um app .NET MAUI o nível de API alvo não é uma configuração do manifesto que você edita: ele deriva da versão da plataforma Android no seu TargetFramework, e o .NET 9 vai no máximo até a API 35. Ou seja, isto é uma atualização do SDK do .NET para o .NET 10 (ou .NET 11), não um ajuste de manifesto. Reserve um dia para um app pequeno e um sprint para qualquer um com orientação travada, botão de voltar personalizado ou insets ajustados na mão. Este guia mira o .NET 10 com .NET MAUI 10.0.100 (lançado em 2026-08-20) como destino, e aponta onde o .NET 11 difere.

Por que o Play verifica justamente o nível alvo

O que quebra

ÁreaMudança com alvo API 36Severidade
Edge-to-edgewindowOptOutEdgeToEdgeEnforcement está obsoleto e é ignorado em aparelhos com Android 16alta
Áreas seguras do .NET MAUIContentPage.SafeAreaEdges passa a valer None por padrão a partir do .NET 10, então as páginas vão de borda a bordaalta
Voltar preditivoAs animações de volta à tela inicial e entre atividades ficam ativas por padrão; OnBackPressed não é chamadoalta
Telas grandesandroid:screenOrientation, resizableActivity, minAspectRatio e maxAspectRatio são ignorados a partir de sw600dpalta (tablets, dobráveis)
SDK do .NETA API 36 exige net10.0-android ou posterior; a workload do .NET 9 para na API 35alta
API mínimaO .NET 11 sobe o piso da API 21 para a API 24média (só .NET 11)
Renderização de textoandroid:elegantTextHeight está obsoleto e é ignoradobaixa
AgendamentoScheduledExecutorService.scheduleAtFixedRate repõe no máximo uma execução perdidabaixa
Sensores de saúdeBODY_SENSORS substituído por permissões granulares android.permissions.healthbaixa (a menos que você leia frequência cardíaca)

As duas primeiras linhas se somam. Atualizar para o .NET 10 para conseguir a API 36 também muda o padrão de áreas seguras do próprio .NET MAUI no mesmo commit, então um app que estava bem no .NET 9 com alvo 35 pode sair do processo com a barra de título embaixo da barra de status por dois motivos independentes.

Checklist de pré-voo

Passos da migração

  1. Descubra qual é o seu alvo hoje, de verdade. Não leia o csproj, leia o manifesto mesclado que o build produz:

    dotnet build -f net9.0-android -c Release
    grep -o 'targetSdkVersion="[0-9.]*"' obj/Release/net9.0-android/AndroidManifest.xml

    Verificação: você obtém um único número. Se ele for menor que a versão da plataforma Android no seu TargetFramework, alguma coisa está fixando o valor, e o passo 3 é o que mais importa no seu caso.

  2. Mova o target framework para o .NET 10. A versão da plataforma Android no TFM é o que vira targetSdkVersion, então esta única edição é a migração de fato:

    <!-- .csproj, .NET 10, .NET MAUI 10.0.100 -->
    <PropertyGroup>
      <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
      <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
    </PropertyGroup>

    net10.0-android puro resolve para a API 36, que é o padrão documentado do .NET 10. Fixe explicitamente como net10.0-android36.0 se preferir que o build falhe em vez de derivar quando você depois for para o .NET 11, porque o .NET for Android promoveu a API 37 a estável no .NET 11 Preview 5 e agora projetos .NET 11 miram net11.0-android37 por padrão. $(SupportedOSPlatformVersion) é outro eixo: ele vira minSdkVersion e não tem nada a ver com o requisito do Play.

    Verificação: recompile e repita o grep do passo 1 contra obj/Release/net10.0-android/AndroidManifest.xml. Ele precisa imprimir targetSdkVersion="36".

  3. Apague qualquer uses-sdk fixo do seu manifesto. Este é o motivo mais comum de o passo 2 parecer não fazer nada. O .NET for Android só escreve targetSdkVersion quando o manifesto de template ainda não tem um, e um valor explícito ganha sem discussão (ManifestDocument.cs):

    <!-- Platforms/Android/AndroidManifest.xml: delete the uses-sdk line entirely -->
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
      <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
      <application android:allowBackup="true" android:icon="@mipmap/appicon" android:supportsRtl="true" />
    </manifest>

    A própria orientação para XA5207 da Microsoft mandava adicionar exatamente este elemento para segurar um nível alvo durante uma atualização de SDK, então muitos projetos da era Xamarin.Forms ainda carregam isso. O template atual do .NET MAUI não traz nenhum elemento uses-sdk, que é o estado que você quer.

    Verificação: grep -c uses-sdk Platforms/Android/AndroidManifest.xml retorna 0, e o manifesto mesclado continua mostrando targetSdkVersion="36".

  4. Decida sua estratégia de edge-to-edge, porque você não tem mais voto. Com alvo 36 o atributo windowOptOutEdgeToEdgeEnforcement está obsoleto e desabilitado em aparelhos com Android 16. Se você o tinha em Platforms/Android/Resources/values/styles.xml, apague. Depois escolha um valor de SafeAreaEdges por página em vez de aceitar o padrão do .NET 10, que é None:

    <!-- .NET MAUI 10.0.100: ContentPage defaults to SafeAreaEdges="None" -->
    <ContentPage SafeAreaEdges="Container">
        <Grid SafeAreaEdges="Container" RowDefinitions="Auto,*">
            <Label Text="Not under the status bar" />
        </Grid>
    </ContentPage>

    Container reproduz o comportamento do .NET 9 de ficar longe das barras de sistema e dos recortes de tela. All também evita o teclado, que é o que você quer se dependia do platform-specific WindowSoftInputModeAdjust.Resize do Android. None é a opção imersiva, e é uma escolha deliberada, não um padrão que você deva herdar por acidente.

    Verificação: em um aparelho com Android 16, a barra de status e a barra de navegação por gestos não sobrepõem nenhum controle clicável nas suas três telas principais, nos temas claro e escuro.

  5. Conserte o tratamento personalizado de voltar antes que o voltar preditivo o engula. Com alvo 36 as animações de voltar preditivo ficam ativas por padrão, onBackPressed() não é chamado e KeyEvent.KEYCODE_BACK não é despachado. Qualquer sobrescrita de activity como esta para de rodar:

    // Broken at targetSdkVersion 36 on Android 16
    public override void OnBackPressed()
    {
        if (_hasUnsavedChanges) { ShowConfirmDialog(); return; }
        base.OnBackPressed();
    }

    Trate na própria superfície de navegação do .NET MAUI, que continua funcionando em todas as plataformas:

    // .NET MAUI 10.0.100, cross-platform
    protected override bool OnBackButtonPressed()
    {
        if (!_hasUnsavedChanges)
            return base.OnBackButtonPressed();
    
        Dispatcher.Dispatch(async () => await DisplayAlertAsync("Discard changes?", "...", "OK"));
        return true; // handled
    }

    A saída de emergência do Android é android:enableOnBackInvokedCallback="false" em <application> ou em uma <activity> específica, e é um paliativo, não uma correção.

    Verificação: deslize a partir da borda da tela e segure. Você deve ver a animação de prévia, e ao soltar deve acontecer o que seu handler pretende.

  6. Audite orientação travada e proporções fixas. Em telas de sw600dp para cima, o alvo 36 faz o Android ignorar android:screenOrientation, android:resizableActivity, android:minAspectRatio e android:maxAspectRatio, além de SetRequestedOrientation em tempo de execução. No .NET MAUI isso normalmente significa um atributo em MainActivity:

    // Ignored on sw600dp+ displays at targetSdkVersion 36
    [Activity(ScreenOrientation = ScreenOrientation.Portrait, /* ... */)]
    public class MainActivity : MauiAppCompatActivity { }

    A exclusão temporária é uma propriedade do manifesto, e o Google já disse que ela para de valer no nível de API 37:

    <application>
      <property android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"
                android:value="true" />
    </application>

    Verificação: rode em um emulador de tablet ou dobrável e gire. Se o layout ficar inutilizável na horizontal, conserte o layout, porque a exclusão te compra um ano.

  7. Atualize o CI para que ele não compile contra uma plataforma que não tem. A falta da API 36 em um agente aparece como XA5207, e a correção é um target, não um download em portal:

    dotnet build -t:InstallAndroidDependencies -f net10.0-android \
      -p:AndroidSdkDirectory="$ANDROID_HOME" \
      -p:AcceptAndroidSDKLicenses=true

    O argumento -f é obrigatório; sem ele o MSBuild reporta MSB4057: The target "InstallAndroidDependencies" does not exist in the project.

    Verificação: um build limpo de CI a partir de um cache vazio do SDK produz um AAB assinado sem XA5207.

Checklist de verificação

Plano de rollback

Reverter o TargetFramework para net9.0-android restaura o nível alvo antigo e o comportamento antigo de áreas seguras do .NET MAUI, e é uma reversão limpa desde que você não tenha adotado também APIs do .NET 10. O que você não consegue reverter é o lado do Play: depois de publicar um AAB com alvo 36, você não pode publicar um nível alvo menor na mesma faixa, porque o Play aplica o piso em cada upload. Trate a faixa interna como sua janela de rollback e a promoção para produção como caminho sem volta.

Detalhes que custam tempo de verdade

Relacionado

Fontes

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Voltar