O Android 11 introduziu mudanças na forma como os apps podem interagir com outros apps que o usuário instalou no dispositivo. Leia mais sobre essas mudanças na documentação do Android.
Quando um app Android que usa as guias personalizadas é direcionado ao nível 30 do SDK ou mais recente, algumas mudanças podem ser necessárias. Este artigo aborda as mudanças que podem ser necessárias para esses apps.
No caso mais simples, as guias personalizadas podem ser iniciadas com uma linha, como esta:
new CustomTabsIntent.Builder().build()
.launchUrl(this, Uri.parse("https://www.example.com"));
Os aplicativos que iniciam outros usando essa abordagem ou que adicionam personalizações de interface, como mudar a cor da barra de ferramentas ou adicionar um botão de ação, não precisam fazer nenhuma mudança no aplicativo.
Preferir apps nativos
No entanto, se você seguiu as práticas recomendadas, algumas mudanças podem ser necessárias.
A primeira prática recomendada relevante é que os aplicativos precisam preferir um app nativo para processar a intent em vez de uma guia personalizada se um app capaz de processá-la estiver instalado.
No Android 11 e versões mais recentes
O Android 11 apresenta uma nova flag de intent, FLAG_ACTIVITY_REQUIRE_NON_BROWSER
, que é a
maneira recomendada de tentar abrir um app nativo, já que não exige que o app declare nenhuma consulta
do gerenciador de pacotes.
static boolean launchNativeApi30(Context context, Uri uri) {
Intent nativeAppIntent = new Intent(Intent.ACTION_VIEW, uri)
.addCategory(Intent.CATEGORY_BROWSABLE)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
try {
context.startActivity(nativeAppIntent);
return true;
} catch (ActivityNotFoundException ex) {
return false;
}
}
A solução é tentar iniciar a intent e usar FLAG_ACTIVITY_REQUIRE_NON_BROWSER
para pedir que o Android
evite navegadores ao iniciar.
Se um app nativo que pode processar essa intent não for encontrado, uma
ActivityNotFoundException
será gerada.
Antes do Android 11
Mesmo que o aplicativo seja direcionado ao Android 11 ou ao nível 30 da API, as versões anteriores do Android não
vão entender a flag FLAG_ACTIVITY_REQUIRE_NON_BROWSER
. Portanto, precisamos consultar o
gerenciador de pacotes nesses casos:
private static boolean launchNativeBeforeApi30(Context context, Uri uri) {
PackageManager pm = context.getPackageManager();
// Get all Apps that resolve a generic url
Intent browserActivityIntent = new Intent()
.setAction(Intent.ACTION_VIEW)
.addCategory(Intent.CATEGORY_BROWSABLE)
.setData(Uri.fromParts("http", "", null));
Set<String> genericResolvedList = extractPackageNames(
pm.queryIntentActivities(browserActivityIntent, 0));
// Get all apps that resolve the specific Url
Intent specializedActivityIntent = new Intent(Intent.ACTION_VIEW, uri)
.addCategory(Intent.CATEGORY_BROWSABLE);
Set<String> resolvedSpecializedList = extractPackageNames(
pm.queryIntentActivities(specializedActivityIntent, 0));
// Keep only the Urls that resolve the specific, but not the generic
// urls.
resolvedSpecializedList.removeAll(genericResolvedList);
// If the list is empty, no native app handlers were found.
if (resolvedSpecializedList.isEmpty()) {
return false;
}
// We found native handlers. Launch the Intent.
specializedActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(specializedActivityIntent);
return true;
}
A abordagem usada aqui é consultar o Gerenciador de pacotes para aplicativos que oferecem suporte a uma intent
http
genérica. Esses aplicativos provavelmente são navegadores.
Em seguida, consulte os aplicativos que processam itents para o URL específico que queremos iniciar. Isso vai retornar a configuração de navegadores e aplicativos nativos para processar esse URL.
Agora, remova todos os navegadores encontrados na primeira lista da segunda e só vamos ter apps nativos.
Se a lista estiver vazia, saberemos que não há processadores nativos e retornaremos "false". Caso contrário, vamos iniciar a intent para o gerenciador nativo.
Como tudo funciona em conjunto
Precisamos garantir o uso do método certo para cada ocasião:
static void launchUri(Context context, Uri uri) {
boolean launched = Build.VERSION.SDK_INT >= 30 ?
launchNativeApi30(context, uri) :
launchNativeBeforeApi30(context, uri);
if (!launched) {
new CustomTabsIntent.Builder()
.build()
.launchUrl(context, uri);
}
}
Build.VERSION.SDK_INT
fornece as informações necessárias. Se for igual ou maior que 30, o Android
vai conhecer o FLAG_ACTIVITY_REQUIRE_NON_BROWSER
, e podemos tentar iniciar um app nativo com a nova
abordagem. Caso contrário, tentamos iniciar com a abordagem antiga.
Se a inicialização de um app nativo falhar, vamos iniciar uma guia personalizada.
Há um modelo envolvido nesta prática recomendada. Estamos trabalhando para simplificar isso encapsulando a complexidade em uma biblioteca. Fique por dentro das atualizações da biblioteca de suporte android-browser-helper.
Como detectar navegadores que oferecem suporte a guias personalizadas
Outro padrão comum é usar o PackageManager para detectar quais navegadores oferecem suporte a guias personalizadas no dispositivo. Os casos de uso comuns para isso são definir o pacote na intent para evitar a caixa de diálogo de desambiguação do app ou escolher qual navegador conectar ao conectar ao serviço de guias personalizadas.
Ao segmentar o nível 30 da API, os desenvolvedores precisam adicionar uma seção de consultas ao manifesto do Android, declarando um filtro de intent que corresponda aos navegadores com suporte a guias personalizadas.
<queries>
<intent>
<action android:name=
"android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
Com a marcação em vigor, o código atual usado para consultar navegadores com suporte a guias personalizadas vai funcionar como esperado.
Perguntas frequentes
P: O código que procura provedores de Custom Tabs consulta aplicativos que podem processar
intents https://
, mas o filtro de consulta declara apenas uma
consulta android.support.customtabs.action.CustomTabsService
. Não seria necessário declarar uma consulta para intents https://
?
A: Ao declarar um filtro de consulta, ele filtra as respostas de uma consulta para o PackageManager, e não a consulta em si. Como os navegadores que oferecem suporte a Custom Tabs declaram o processamento do CustomTabsService, eles não são filtrados. Os navegadores que não oferecem suporte a guias personalizadas serão filtrados.
Conclusão
Essas são todas as mudanças necessárias para adaptar uma integração de guias personalizadas para funcionar com o Android 11. Para saber mais sobre a integração das guias personalizadas em um app Android, comece com o guia de implementação e confira as práticas recomendadas para saber como criar uma integração de primeira classe.
Entre em contato se tiver dúvidas ou feedback.