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 guias personalizadas é direcionado ao SDK de nível 30 ou mais recente, algumas mudanças podem ser necessárias. Este artigo aborda as mudanças necessárias para esses apps.
No caso mais simples, guias personalizadas podem ser iniciadas com uma linha, como:
new CustomTabsIntent.Builder().build()
.launchUrl(this, Uri.parse("https://www.example.com"));
Os apps que iniciam apps usando essa abordagem ou até mesmo adicionam personalizações na interface, como mudar a cor da barra de ferramentas e adicionar um botão de ação, não precisam fazer mudanças no aplicativo.
Preferência por aplicativos nativos
No entanto, se você seguiu as práticas recomendadas, algumas mudanças podem ser necessárias.
A primeira prática recomendada relevante é que, se um app capaz de processar a intent estiver instalado, os aplicativos precisam preferir um app nativo para processar a intent em vez de uma guia personalizada.
No Android 11 e versões mais recentes
O Android 11 introduz uma nova flag de intent, FLAG_ACTIVITY_REQUIRE_NON_BROWSER
, que é a
maneira recomendada de tentar abrir um app nativo, já que o app não exige que ele 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 solicitar que o Android
evite navegadores durante a inicialização.
Se um app nativo capaz de processar essa intent não for encontrado, uma
ActivityNotFoundException
será gerada.
Antes do Android 11
Mesmo que o aplicativo possa ser direcionado ao Android 11 ou ao nível 30 da API, as versões anteriores do Android não
entenderão a flag FLAG_ACTIVITY_REQUIRE_NON_BROWSER
. Portanto, precisamos recorrer à consulta do
Gerenciador de pacotes nos casos a seguir:
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 encontrar aplicativos com suporte a uma intent
http
genérica. Esses aplicativos provavelmente são navegadores.
Em seguida, consulte os aplicativos que processam isso para o URL específico que queremos iniciar. Isso faz com que os navegadores e os aplicativos nativos estejam configurados para processar o URL.
Agora, remova todos os navegadores encontrados na primeira lista da segunda lista e ficaremos apenas com apps nativos.
Se a lista estiver vazia, saberemos que não há gerenciadores nativos e retornaremos "false". Caso contrário, iniciaremos 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 ele for igual ou maior que 30, o Android
conhecerá a FLAG_ACTIVITY_REQUIRE_NON_BROWSER
e podemos tentar lançar um app nativo com a nova
abordagem. Caso contrário, tentaremos iniciar com a abordagem antiga.
Se a inicialização de um aplicativo nativo falhar, iniciamos uma guia personalizada.
Essa prática recomendada envolve um pouco de código boilerplate. Estamos trabalhando para simplificar isso encapsulando a complexidade em uma biblioteca. Fique de olho nas atualizações da Biblioteca de Suporte android-browser-helper.
Detecção de navegadores compatíveis com guias personalizadas
Outro padrão comum é usar o PackageManager para detectar quais navegadores têm suporte a guias personalizadas no dispositivo. Casos de uso comuns para isso são configurar o pacote na intent para evitar a caixa de diálogo de desambiguação do app ou escolher a qual navegador se conectar ao se conectar ao serviço de guias personalizadas.
Ao segmentar o nível 30 da API, os desenvolvedores vão precisar 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 implementada, o código atual usado para consultar navegadores com suporte a guias personalizadas vai funcionar conforme o esperado.
Perguntas frequentes
P: O código que procura por consultas de provedores de guias personalizadas para aplicativos que podem processar
intents https://
, mas o filtro de consulta declara apenas uma
consulta android.support.customtabs.action.CustomTabsService
. Uma consulta para intents https://
não deve ser declarada?
R: Ao declarar um filtro de consulta, ele filtra as respostas para uma consulta para o PackageManager, não a consulta em si. Como os navegadores compatíveis com guias personalizadas declaram que processa o 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 de guias personalizadas em um app Android, comece com o guia de implementação e confira as práticas recomendadas para aprender a criar uma integração de primeira classe.
Entre em contato se tiver dúvidas ou feedback.