A finales del verano de 2015, Google publicó dentro de las librerías de compatibilidad un nuevo módulo para permitir realizar cierta integración entre nuestras apps y el navegador Chrome 45+. El objetivo consiste básicamente en poder solicitar a Chrome mostrar un contenido web y que el navegador «parezca» que forma parte de nuestra app y no de la sensación al usuario que hemos salido de la misma.
Algunas de las funcionalidades que ofrece esta integración:
- Botón cerrar en la toolbar para retornar a la Activity de nuestra app
- Color de la Toolbar
- Entradas personalizadas en el menú.
- «Precarga» en segundo plano de Chrome al iniciarse la Activity de tal modo que al abrirse Chrome desde la misma la carga sea más rápida con respecto a abrir un navegador o incluso un WebView.
- Capturar eventos de navegación o pulsación de enlaces como si fuera un WebView.
La limitación que tenemos es que el dispositivo ha de tener instalado Chrome y, si no implementamos la funcionalidad de precarga que veremos al final de este tutorial, además debe estar configurado como navegador por defecto. Si no se verifican estas restricciones la url se mostrará en el navegador por defecto (mismo comportamiento que si no utilizamos custom tabs).
La especificación de la integración con Chrome es abierta por lo que puede ser implementada por otros navegadores en un futuro.
Para poder abrir una instancia de Chrome desde nuestra app y beneficiarnos de esta integración seguiremos los siguientes pasos.
- Incluir la correspondiente librería de compatibilidad.
compile 'com.android.support:customtabs:23.2.1'
- Solicitar abrir una url con la clase CustomTabsIntent. Utilizaremos un builder.
public void openTab(View view) { String url = "https://www.danielme.com"; CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); CustomTabsIntent customTabsIntent = builder.build(); customTabsIntent.launchUrl(this, Uri.parse(url)); }
La ejecución del método anterior muestra la url en Chrome siempre y cuando sea el navegador por defecto.
Las personalizaciones más fáciles que podemos hacer son las siguientes:
- Color de la ActionBar
builder.setToolbarColor(ContextCompat.getColor(this, R.color.primary));
- Cambiar el icono del botón de cerrar por la flecha de volver. Este icono lo debemos tener en nuestros drawables.
builder.setCloseButtonIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_arrow_back));
- Mostrar el title de la página además de la url.
builder.setShowTitle(true);
- Crear un broadcast receiver para atender la pulsación del elemento del menú. En este ejemplo recibimos un texto y lo mostramos en un Toast.
package com.danielme.android.customtabs; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.widget.Toast; public class CustomTabsBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, intent.getStringExtra("text"), Toast.LENGTH_SHORT).show(); } }
- Registrar el brodcast en el AndroidManifest.xml.
<receiver android:name=".CustomTabsBroadcastReceiver" android:enabled="true"> </receiver>
- Añadir la entrada del menú en el builder. En el ejemplo se han añadido dos, no se ha abstraído el código para que se vea más claro. Se envía el texto que queremos recibir en el broadcast receiver.
Intent intent1 = new Intent(this, CustomTabsBroadcastReceiver.class); intent1.putExtra("text", getString(R.string.menu1)); PendingIntent pendingIntent1 = PendingIntent.getBroadcast(this, 1, intent1, 0); builder.addMenuItem(getString(R.string.menu1), pendingIntent1); Intent intent2 = new Intent(this, CustomTabsBroadcastReceiver.class); intent2.putExtra("text", getString(R.string.menu2)); PendingIntent pendingIntent2 = PendingIntent.getBroadcast(this, 2, intent2, 0); builder.addMenuItem(getString(R.string.menu2), pendingIntent2);
Añadir elementos al menú
Se pueden añadir hasta cinco entradas personalizadas en el menú de Chrome. Los pasos para añadir un elemento son los siguientes:
Añadir botón
También podemos añadir un botón a la ActionBar de Chrome siguiendo los mismos pasos que acabamos de utilizar para el menú. La única diferencia es que hay que llamar al método setActionButton en lugar de addMenuItem pasándole el icono a utilizar.
Bitmap icon = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_menu_add); builder.setActionButton(icon, getString(R.string.action), pendingIntent, true);
Precargar Chrome
Chrome proporciona un servicio que permite realizar una especie de «precarga» en segundo plano para que al solicitarse la visualización de una url la carga del navegador sea más rápida.
Para registrar el servicio llamaremos al método CustomTabsClient#bindCustomTabsService proporcionándole una implementación de CustomTabsServiceConnection. En esta implementación cuando recibamos la conexión con el servicio realizaremos la «precarga» o warmup de Chrome y si conocemos de antemano la url que probablemente va a solicitar el usuario también podemos indicarla. Asimismo hay que crear una sesión (CustomTabsSession) que tendremos que utilizar al mostrar la url en Chrome. Reutilizaremos siempre la misma sesión para mayor eficiencia.
Nota 1: es conveniente «desregistrar» el servicio en el onDestroy para evitar memory leaks.
Nota 2: el método onNavigationEvent se invocará cada vez que se solicite una url desde Chrome.
private static final String URL = "https://www.danielme.com"; private static final String CHROME_PACKAGE = "com.android.chrome"; private CustomTabsServiceConnection ctConnection; private CustomTabsSession customTabsSession; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); ctConnection = new CustomTabsServiceConnection() { @Override public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabsClient customTabsClient) { customTabsClient.warmup(0); customTabsSession = getSession(customTabsClient); customTabsSession.mayLaunchUrl(Uri.parse(URL), null, null); } @Override public void onServiceDisconnected(ComponentName componentName) { //nothing here } }; CustomTabsClient.bindCustomTabsService(this, CHROME_PACKAGE, ctConnection); } @Override protected void onDestroy() { super.onDestroy(); if (ctConnection != null) { unbindService(ctConnection); } } private CustomTabsSession getSession(CustomTabsClient customTabsClient) { if (customTabsClient != null) { return customTabsClient.newSession(new CustomTabsCallback() { /*@Override public void onNavigationEvent(int navigationEvent, Bundle extras) { super.onNavigationEvent(navigationEvent, extras); }*/ }); } return null; } public void openTab(View view) { CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(customTabsSession);
Proyecto de ejemplo
El proyecto completo se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.