Chrome custom tabs:在浏览器与本地应用之间流畅的切换

原文:Chrome Custom Tabs 

什么是Chrome custom tabs?

app开发在用户点击url的时候面临一个选择:启动浏览器还是在自己app内部使用WebView来浏览。

两种选择都有挑战 - 启动浏览器是严重打断上下文环境的行为,并且不可自定义,而WebView不能和浏览器共享状态且维护起来比较头疼。

Chrome custom tabs给了app更多控制web体验的机会,在不借助于WebView的情况下,让本地内容与web内容之间的切换更平滑流畅。

Chrome custom tabs允许一个app自定义Chrome 的外观,app可以改变类似如下的东西:

  • Toolbar 颜色

  • 进入与退出动画

  • 在Chrome 工具栏(Toolbar)(与溢出菜单上添加自定义操作

Chrome custom tabs 还允许开发者预启动Chrome 与预加载内容,达到更快的加载效果。

performance.gif

你现在可以使用我们在Github上的 Chrome custom tab sample 来测试它。

When should I use Chrome custom tabs vs WebView?

如果你自己在app内部掌控着自己的内容,那么WebView 是不错的解决办法。如果你的app把用户导向你域名之外的url,我们推荐你使用Chrome custom tabs,基于以下原因:

  • 实现简单。无需编写代码管理请求,权限授予或者cookie 存储。

  • UI 自定义:

  • Toolbar 颜色

  • 操作按钮

  • 自定义菜单项

  • 自定义进入退出动画

  • Navigation awareness: the browser delivers a callback to the application upon an external navigation.浏览器

  • Performance optimization:

  • Pre-warming of the Browser in the background, while avoiding stealing resources from the application.

  • Providing a likely URL in advance to the browser, which may perform speculative work, speeding up page load time.

  • Lifecycle management: the browser prevents the application from being evicted by the system while on top of it, by raising its importance to the "foreground" level.

Shared Cookie Jar and permissions model so users don't have to log in to sites they are already connected to, or re-grant permissions they have already granted.

If the user has turned on Data Saver, they will still benefit from it.

Synchronized AutoComplete across devices for better form completion.

Simple customization model.

Quickly return to app with a single tap.

You want to use the latest browser implementations on devices pre-Lollipop (auto updating WebView) instead of older WebViews.

When will this be available?

As of Chrome 45, Chrome custom tabs are available in Chrome Beta Channel, on all of Chrome's supported Android versions (Jellybean onwards). Please note that the API will change slightly over the coming weeks.

The below spec only applies to Chrome 45.

We are looking for feedback, questions and suggestions on this project, so we encourage you to file issues oncrbug.com and ask questions to our Twitter account @ChromiumDev.

Implementation guide

完整的例子在 https://github.com/GoogleChrome/custom-tabs-client。它包含了可复用的类来自定义UI,连接后台服务,以及处理application 与  custom tab activity的生命周期。它还包含了连接到服务所需的 AIDL 文件 ,因为Chromium仓库中的那个无法在Android Studio里直接使用。

如果你遵循本文的指导,你将可以创建一个

  • 使用custom tab自定义UI与交互。

  • 让页面加载更快,让应用不间断。

第一个是通过向发送给Chrome的ACTION_VIEW Intent 追加extra数据来完成;第二个通过连接Chrome公开的服务来完成。

注意:这些笔记对于Chrome 45来说是准确的。

启用Chrome Custom Tabs

// Using a VIEW intent for compatibility with any other browsers on device.
// Caller should not be setting FLAG_ACTIVITY_NEW_TASK or 
// FLAG_ACTIVITY_NEW_DOCUMENT. 
String url = ¨https://paul.kinlan.me/¨;
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
//  Must have. Extra used to match the session. Its value is an IBinder passed
//  whilst creating a news session. See newSession() below. Even if the service is not 
//  used and there is no valid session id to be provided, this extra has to be present 
//  with a null value to launch a custom tab.
private static final String EXTRA_CUSTOM_TABS_SESSION = "android.support.customtabs.extra.SESSION";
Bundle extras = new Bundle;
extras.putBinder(EXTRA_CUSTOM_TABS_SESSION, 
   sessionICustomTabsCallback.asBinder() /* Set to null for no session */);
intent.putExtras(extras);

如果用户没有安装最新版本的Chrome该怎么办?

我们使用的是ACTION_VIEW  Intent,这意味着页面默认会被系统浏览器或则用户的默认浏览器打开。

如果用户已经安装了同时也是默认的浏览器,它将自动获得EXTRAS 然后呈现一个自定义的UI。当然也可能是另外的浏览器使用了Intent extra来提供了一个类似的自定义界面。

我该如何检查Chrome是否支持Chrome custom tabs?

所有支持Chrome custom tabs的Chrome 版本都会暴露一个service (见后面的"Connect to the Service”一节)。检查Chrome 是否支持custom tabs,尝试绑定到这个service。如果成功,则可以安全的使用custom tabs。

配置地址栏的颜色

Chrome Custom Tabs 最重要(也是实现起来最简单)的方面之一就是可以改变地址栏的颜色去适应app的主题。

// Extra that changes the background color for the omnibox. colorInt is an int
// that specifies a Color.
private static final String EXTRA_CUSTOM_TABS_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR";
intent.putExtra(EXTRA_CUSTOM_TABS_TOOLBAR_COLOR, colorInt);

配置自定义操作按钮

作为开发者,你可以完全掌控Chrome tab里面展现给用户的操作按钮。

绝大多数情况下,这都是些基本的操作比如分享,或者其它用户常触发的活动。

操作按钮是被一个带有图标的按钮以及pendingIntent 的Bundle 来代表的。当用户点击了这个操作按钮的时候,pendingIntent 将被Chrome 调用。图标目前的24dp 高,24-48 dp宽。

6178EDC8-D60E-4C44-A1DA-99D474D469F1.png

// Key that specifies the Bitmap to be used as the image source for the
// action button.
private static final String KEY_CUSTOM_TABS_ICON = "android.support.customtabs.customaction.ICON";
// Key that specifies the PendingIntent to launch when the action button
// or menu item was tapped. Chrome will be calling PendingIntent#send() on
// taps after adding the url as data. The client app can call
// Intent#getDataString() to get the url.
public static final String KEY_CUSTOM_TABS_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT";
// Optional. Use a bundle for parameters if an the action button is specified.
public static final String EXTRA_CUSTOM_TABS_ACTION_BUTTON_BUNDLE = 
"android.support.customtabs.extra.ACTION_BUNDLE_BUTTON";
Bundle actionButtonBundle = new Bundle();
actionButtonBundle.putParcelable(KEY_CUSTOM_TABS_ICON, icon);
actionButtonBundle.putParcelable(KEY_CUSTOM_TABS_PENDING_INTENT, pendingIntent);
intent.putExtra(EXTRA_CUSTOM_TABS_ACTION_BUTTON_BUNDLE, actionButtonBundle);

配置自定义的菜单

Chrome 浏览器有许多菜单可以用,但是这些菜单可能跟你的应用场景好无关联。

Chrome custom tabs有三个总是在上面的箭头图标,分别是 "向前",”页面信息" 和"刷新" 。以及在下面的”在页面中查找”和”在浏览器中打开”的菜单。

作为一个开发者,你可以在上面的箭头图标与脚部item之间添加与自定义菜单。

menu 是由Bundle (目前没有图标),menu 文字,以及一个在用户点击item时Chrome 会调用的pendingIntent 组成的数组来代表的。

B5132307-1D1C-4114-AD92-ACE53201F548.png

// Key for the title string for a given custom menu item
public static final String KEY_CUSTOM_TABS_MENU_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE";
// Optional. Use an ArrayList for specifying menu related params. There 
// should be a separate Bundle for each custom menu item.
public static final String EXTRA_CUSTOM_TABS_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
ArrayList menuItemBundleList = new ArrayList<>();
// For each menu item do:
Bundle menuItem = new Bundle();
menuItem.putString(KEY_CUSTOM_TABS_MENU_TITLE, menuItemTitle);
menuItem.putParcelable(KEY_CUSTOM_TABS_PENDING_INTENT, pendingIntent);
menuItemBundleList.add(menuItem);
intent.putParcelableArrayList(EXTRA_CUSTOM_TABS_MENU_ITEMS, menuItemBundleList);

配置自定义的进入和退出动画

许多安卓应用在Activity之间切换的时候都使用自定义的进入与退出动画。Chrome custom tabs也一样,你可以改变进入和退出(当用户按back键)的动画以适应app整体风格。

// Optional. Bundle constructed out of
ActivityOptions that Chrome will be running when
// it finishes CustomTabActivity. If you start the Custom Tab with 
// a customized animation, you can specify a matching animation when Custom Tab 
// returns to your app.
public static final String EXTRA_CUSTOM_TABS_EXIT_ANIMATION_BUNDLE =
"android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
Bundle finishBundle = ActivityOptions.makeCustomAnimation(context, R.anim.clientEnterAnimResId, R.anim.CustomTabExitAnimResId).toBundle; 
intent.putExtra(EXTRA_CUSTOM_TABS_EXIT_ANIMATION_BUNDLE, finishBundle);
Bundle startBundle = ActivityOptions.makeCustomAnimation(context, R.anim.CustomTabEnterAnimResId, R.anim.clientExitAnimResId).toBundle; 
startActivity(intent, startBundle);

预热Chrome让页面加载速度更快

默认,当startActivity 与配置了正确ACTION_VIEW Intent 被调用之后,会打开Chrome 然后开始url的加载。这可能会花费宝贵的时间并对流畅程度造成较大影响。

我们相信用户需要更快的体验,因此我们在Chrome 中提供了一个Service ,让你的app可以连接并告诉Chrome 预热浏览器与本地组件。我们也将这个功能对你开放,开发者可以告诉Chrome用户将访问页面的可能设置。然后Chrome 就能:

  • DNS pre-resolution of the main domain

  • DNS pre-resolution of the most likely sub-resources

  • Pre-connection to the destination including HTTPS/TLS negotiation.

The process for warming up Chrome is as follows:

而Chrome 的预热过程如下:

  • 连接service。

  • 用finishSetup附加一个navigation callback ,这样你就知道一个页面加载完成。

  • 在service中,调用warmup在后台开始Chrome 。

  • 创建一个newSession,这个session 用于所有对API的请求。

  • 通过mayLaunchUrl告诉Chrome 用户肯能加载哪个页面。

  • 使用session ID来启动 VIEW Intent 。

连接到Chrome Service

假设你对连接到 Android Service不熟悉,这里的接口就是用 AIDL 来创建,自动为你创建一个 proxy service类。

定义这个service 的AIDL 接口可以在我们的 Sample on Github上找到。

// Package name for the Chrome channel the client wants to connect to. This
// depends on the channel name.
// Stable = com.android.chrome
// Beta = com.chrome.beta
// Dev = com.chrome.dev
public static final String CUSTOM_TAB_PACKAGE_NAME = "com.chrome.dev";  // Change when in stable
// Action to add to the service intent. This action can be used as a way 
// generically pick apps that handle custom tabs for both activity and service 
// side implementations.
public static final String ACTION_CUSTOM_TABS_CONNECTION =
       "android.support.customtabs.action.CustomTabsService";
Intent serviceIntent = new Intent(ACTION_CUSTOM_TABS_CONNECTION);
serviceIntent.setPackage(CUSTOM_TAB_PACKAGE_NAME);
context.bindService(serviceIntent, mServiceConnection,
                    Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);

预热Browser Process

boolean warmup(long flags)

预热Browser Process与加载本地库。Warmup 方法是异步的,返回值表明请求是否被允许。多个成功的调用也会返回true。

成功返回true 。

ps:头都看大了。

创建一个新的tab session

boolean newSession(ICustomTabsCallback callback)

Session is used in subsequent calls to link mayLaunchUrl call, the VIEW intent and the tab generated to each other. The callback IInterface provided here is associated with the created session and should be passed for any consecutive mayLaunchUrl calls. Any updates for the created session (see Custom Tabs Callback below) is also received through this IInterface. Returns whether a session was created successfully. Multiple calls with the same ICustomTabsCallback or a null value will return false.

Tell Chrome what URL's the user is likely to open

boolean mayLaunchUrl(ICustomTabsCallback sessionCallback, Uri url, Bundle extras,ListotherLikelyBundles)

Tells the browser of a likely future navigation to a URL. The method warmup() should be called first as a best practice. The most likely URL has to be specified first. Optionally, a list of other likely URLs can be provided. They are treated as less likely than the first one, and have to be sorted in decreasing priority order. These additional URLs may be ignored. All previous calls to this method will be deprioritized. Returns whether the operation completed successfully.

Custom Tabs Connection Callback

void onNavigationEvent(int navigationEvent, Bundle extras)

Will be called when a navigation event happens in the Custome tab. The navigationEvent int is one of 6 values that defines the state of the the page is in. See below for more information.

/**
* Sent when the tab has started loading a page.
*/
public static final int NAVIGATION_STARTED = 1;
/**
* Sent when the tab has finished loading a page.
*/
public static final int NAVIGATION_FINISHED = 2;
/**
* Sent when the tab couldn't finish loading due to a failure.
*/
public static final int NAVIGATION_FAILED = 3;
/**
* Sent when loading was aborted by a user action before it finishes like clicking on a link
* or refreshing the page.
*/
public static final int NAVIGATION_ABORTED = 4;
/**
* Sent when the tab becomes visible.
*/
public static final int TAB_SHOWN = 5;
/**
* Sent when the tab becomes hidden.
*/
public static final int TAB_HIDDEN = 6;

Using the support library on the client side

See the sample application here.

FAQ

  • Is this available on iOS?

  • No. There is a project that allows you to integrate Chrome into your app on iOS but in a completely separate way.

  • When will this be available on stable channel?

  • We don't have a fixed date or version of Chrome yet. The normal release cycle from being in the Dev Channel of Chrome is about 12 weeks, however we are looking for feedback from developers first.

  • Where can I ask questions?

  • Stackoverflow tag: chrome-custom-tabs

Content available under the CC-By 3.0 license