提升体验-支持Chrome Custom Tabs
前言
文章比較長,先放項目地址:PaperPlane
俗話說,沒圖說個那啥,先看實際效果。
什么是Custom Tabs?
所有的APP開發者都面臨這樣一個選擇,當用戶點擊一個URL時,是應該用瀏覽器打開還是應該用應用內置的WebView打開呢?
兩個選項都面臨著一些問題。通過瀏覽器打開是一個非常重的上下文切換,并且是無法定制的。而WebView不能和瀏覽器共享數據并且需要需要手動去處理更多的場景。
Chrome Custom Tabs讓APP在進行網頁瀏覽時更多的控制權限,在不采用WebView的情況下,這既能保證Native APP和網頁之間流暢的切換,又允許APP定制Chrome的外觀和操作。可定義的內容如下:
toolbar的顏色
進場和退場動畫
給Chrome的toolbar、overflow menu和bottom toolbar添加自定義操作
并且,Chrome Custom Tabs允許開發者對Chrome進行預啟動和網頁內容的預加載,以此提升加載的速度。
Chrome Custom Tabs VS WebView, 我應該什么時候用?
如果頁面的內容是由我們自己控制的,可以和Android組件進行交互,那么,WebView是一個好的選擇,如果我們的應用需要打開外部的網站,那么推薦使用Chrome Custom Tabs,原因如下:
導入非常簡單。不需要編寫額外的代碼來管理請求,授予權限或者存儲cookie
-
定制UI:
Toolbar 顏色
動作按鈕 (Action Button)
定制菜單項
定制進場退場動畫
Bottom Toolbar
導航感知:瀏覽器通知回調接口通知應用網頁的導航情況
安全性:瀏覽器使用了Google's Safe Browsing,用于保護用戶和設備免受危險網站的侵害
-
性能優化:
瀏覽器會在后臺進行預熱,避免了應用占用大量資源的情況
提前向瀏覽器發送可能的URL,提高了頁面加載速度
生命周期管理:在用戶與Custom Tabs進行交互時,瀏覽器會將應用標示為前臺應用,避免了應用被系統所回收
共享cookie數據和權限,這樣,用戶在已經授權過的網站,就不需要重新登錄或者授權權限了
如果用戶開啟了數據節省功能,在這里仍然可以從中受益
同步的自動補全功能
僅僅需要點擊一下左上角按鈕就可以直接返回原應用
想要在Lollipop之前的設備上最新引入的瀏覽器(Auto updating WebView),而不是舊版本的WebView
什么時候可用?
從Chrome 45版本開始,所有的Chrome用戶都可以使用這項功能,目前僅支持Android系統。
開發指南
完整的示例可以查看https://github.com/GoogleChrome/custom-tabs-client。包含了定制UI、連接后臺服務、處理應用和Custom Tab Activity生命周期的可復用的類。
第一步當然是將 Custom Tabs Support Library 添加到工程中來。打開build.gradle文件,添加support library的依賴。
dependencies {...compile 'com.android.support:customtabs:23.3.0' }一旦Support Library添加項目成功了,我們就有兩種可能的定制操作了:
定制UI和與Chrome Custom Tabs的交互
使頁面加載更快速,保持應用激活
UI的定制是通過使用 CustomTabsIntent 和 CustomTabsIntent.Builder類完成的;而速度的提升則是通過使用 CustomTabsClient 鏈接Custom Tabs服務,預熱Chrome和讓Chrome知曉將要打開的URL實現的。
打開一個Chrome Custom Tab
// 使用CustomTabsIntent.Builder配置CustomTabsIntent // 準備完成后,調用CustomTabsIntent.Builder.build()方法創建一個CustomTabsIntent // 并通過CustomTabsIntent.launchUrl()方法加載希望加載的urlString url = ¨https://github.com/marktony¨; CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); CustomTabsIntent customTabsIntent = builder.build(); customTabsIntent.launchUrl(this, Uri.parse(url));配置地址欄的顏色
Chrome Custom Tabs一個很重要的功能就是我們能夠改變地址欄的顏色,使之和我們應用的顏色協調。
// 改變toolbar的背景色。colorInt就是想要指定的int值builder.setToolbarColor(colorInt);配置定制化的action button
作為應用的開發者,我們對呈現在用戶眼前的Chrome Custom Tab內的Action Button擁有完全的控制權。
在大部分的情況下,用戶會執行最基礎的操作像分享,或者是其他公共的Activity。
Action Button被表示為一個action button的圖標和用戶點擊action button之后Chrome將要執行的pendingIntent。圖標的高度一般為24dp,寬度一般為24-48dp。
// 向toolbar添加一個Action Button // ‘icon’是一張位圖(Bitmap),作為action button的圖片資源使用// 'description'是一個字符串,作為按鈕的無障礙描述所使用// 'pendingIntent' 是一個PendingIntent,當action button或者菜單項被點擊時調用。 // 在url作為data被添加之后,Chrome 會調用PendingIntent#send()方法。 // 客戶端應用會通過調用Intent#getDataString()獲取到URL// 'tint'是一個布爾值,定義了Action Button是否應該被著色builder.setActionButton(icon, description, pendingIntent, tint);
配置定制化菜單
Chrome瀏覽器擁有非常全面的action菜單,用戶在瀏覽器內操作非常順暢。然而,對于我們自己的應用,可能就不適合了。
Chrome Custom Tabs頂部有三個橫向排列的圖標,分別是“前進”、"頁面信息"和”刷新“。在菜單的底部分別是"查找頁面"和“在瀏覽器中打開”。
作為開發者,我們最多可以在頂部橫向圖標和底部菜單之間添加5個自定義菜單選項。
菜單項通過調用CustomTabsIntent.Builder#addMenuItem)添加,title和用戶點擊菜單選項后Chrome調用的pendingIntent需要作為參數被傳入。
builder.addMenuItem(menuItemTitle, menuItemPendingIntent);
配置進場和退場動畫
許多的Android都會在Activity之間切換時使用自定義的視圖進入和退出動畫。Chrome Custom Tabs也一樣,我們可以改變進入和退出動畫,以此保持Chrome Custom Tabs和應用其他內容的協調性和一致性。
builder.setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left); builder.setExitAnimations(this, R.anim.slide_in_left, R.anim.slide_out_right);預熱Chrome,提高頁面加載速度
默認情況下,當 CustomTabsIntent#launchUrl )被調用時會激活Chrome,加載URL。這會花費我們寶貴的時間并且影響流暢度。
Chrome團隊了解用戶對于流暢體驗的渴望,所以他們在Chrome中提供了一個Service使我們的APP能夠連接并且預熱瀏覽器和原生組件。他們也把這種能力分享給了我們普通開發者,開發者能夠告知Chrome用戶訪問頁面的可能性。然后,Chrome就能完成如下的操作:
主域名的DNS預解析
最有可能加載的資源的DNS預解析
包括HTTPS/TLS驗證在內的預連接
預熱Chrome的步驟如下:
使用CustomTabsClient#bindCustomTabsService)連接service
一旦service連接成功,后臺調用 CustomTabsClient#warmup)啟動Chrome
調用 CustomTabsClient#newSession )創建一個新的session.這個session被用作所有的API請求
我們可以在創建session時選擇性的添加一個 CustomTabsCallback作為參數,這樣我們就能知道頁面是否被加載完成
通過 CustomTabsSession#mayLaunchUrl)告知Chrome用戶最有可能加載的頁面
調用 CustomTabsIntent.Builder 構造方法,并傳入已經創建好的CustomTabsSession作為參數傳入
連接Chrome服務
CustomTabsClient#bindCustomTabsService http://developer.android.com/... java.lang.String, android.support.customtabs.CustomTabsServiceConnectio)) 方法簡化了連接Custom Tabs服務的過程。
創建一個繼承自CustomTabsServiceConnection的類并使用onCustomTabsServiceConnected )方法獲取 CustomTabsClient的實例。在下一步中會用到此實例:
// 官方示例 // 客戶端需要連接的Chrome的包名,取決于channel的名稱 // Stable(發行版) = com.android.chrome // Beta(測試版) = com.chrome.beta // Dev(開發版) = com.chrome.dev public static final String CUSTOM_TAB_PACKAGE_NAME = "com.android.chrome"; // Change when in stableCustomTabsServiceConnection connection = new CustomTabsServiceConnection() {@Overridepublic void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {mCustomTabsClient = client;}@Overridepublic void onServiceDisconnected(ComponentName name) {} }; boolean ok = CustomTabsClient.bindCustomTabsService(this, mPackageNameToBind, connection); // 我的示例 package com.marktony.zhihudaily.customtabs;import android.support.customtabs.CustomTabsServiceConnection; import android.content.ComponentName; import android.support.customtabs.CustomTabsClient;import java.lang.ref.WeakReference;/*** Created by Lizhaotailang on 2016/9/4.* Implementation for the CustomTabsServiceConnection that avoids leaking the* ServiceConnectionCallback*/public class ServiceConnection extends CustomTabsServiceConnection {// A weak reference to the ServiceConnectionCallback to avoid leaking it.private WeakReference<ServiceConnectionCallback> mConnectionCallback;public ServiceConnection(ServiceConnectionCallback connectionCallback) {mConnectionCallback = new WeakReference<>(connectionCallback);}@Overridepublic void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {ServiceConnectionCallback connectionCallback = mConnectionCallback.get();if (connectionCallback != null) connectionCallback.onServiceConnected(client);}@Overridepublic void onServiceDisconnected(ComponentName name) {ServiceConnectionCallback connectionCallback = mConnectionCallback.get();if (connectionCallback != null) connectionCallback.onServiceDisconnected();}}預熱瀏覽器
boolean warmup(long flags))
預熱瀏覽器進程并加重原生庫文件。預熱是異步進行的,返回值表示請求是否被接收。多個成功的請求都會返回true。
true代表著成功。
創建新的tab sessioin
boolean newSession(CustomTabsCallback callback))
session用于在連續請求中鏈接mayLaunchUrl方法。CustomTabsIntent和tab互相聯系。這里所提供的回調和已經創建成功的session相關。通過這個回調,任何關于已經成功創建的session的更新都會被接收到。返回session是否被成功創建。多個具有相同CustomTabsCallback或者null值的請求都會返回false。
告知Chrome用戶可能打開的鏈接
boolean mayLaunchUrl(Uri url, Bundle extras, List otherLikelyBundles))
CustomTabsSession方法告知瀏覽器未來可能導航到的url。warmup())方法應該先被調用。最有可能的url應該最先被指出。也可以選擇性的提供可能加載的url的列表。列表中的數據被認為被加載的可能性小于最初的那一個,而且必須按照優先級降序排列。這些額外的url可能被忽略掉。所有之前對于這個方法的調用都會被去優先化。返回操作是否成功完成。
Custom Tabs連接回調
void onNavigationEvent(int navigationEvent, Bundle extras))
在custom tab中,導航事件發生時被調用。‘navigationEvent int’是關于頁面內的6個狀態值之一。6個狀態值定義如下:
/** * 頁面開始加載時被發送 */ public static final int NAVIGATION_STARTED = 1;/** * 頁面完成加載時被發送 */ public static final int NAVIGATION_FINISHED = 2;/** * 由于錯誤tab不能完成加載時被發送 */ public static final int NAVIGATION_FAILED = 3;/** * 在加載完成之前,加載因為用戶動作例如點擊了另外一個鏈接或者刷新頁面 * 加載被中止時被發送 */ public static final int NAVIGATION_ABORTED = 4;/** * tab狀態變為可見時發送 */ public static final int TAB_SHOWN = 5;/** * tab狀態變為不可見時發送 */ public static final int TAB_HIDDEN = 6;如果用戶沒有安裝最新版本的Chrome,會發生什么呢?
Custom Tabs通過帶有key Extras的 ACTION_VIEW Intent來定制UI。這就意味著將要打開的頁面會通過系統瀏覽器或者用戶默認瀏覽器打開。
如果用戶已經安裝了Chrome并且是默認瀏覽器,它會自動的獲取EXTRAS的值并提供一個定制化的UI。其他的瀏覽器使用Intent extras提供相同的定制UI也是有可能的。
怎樣檢測Chrome是否支持Chrome Custom Tabs?
所有支持Chrome Custom Tabs的Chrome瀏覽器都暴露了一個service。為了檢測是否支持Chrome Custom Tabs,可以嘗試著綁定service,如果成功的話,那么Customs Tabs可以成功的使用。
最佳實踐
啟用Chrome Custom Tabs后,我們看到了各種不同質量界別的實現效果。這里介紹一組實現優秀集成的最佳實踐。
連接Custome Tabs Service并發起預熱
連接到Custom Tabs Service并預加載Chrome之后,通過Custom Tabs打開鏈接 最多可以節省700ms 。
在我們打算啟用Custom Tabs的Activity的 onStart()) 方法中連接 Custom Tabs service。連接成功后,調用warmup()方法。
Custom Tabs作為一個非常低優先級的進程,這也就意味著 它不會對我們的應用不會有任何的負面的影響,但是當加載鏈接時,會獲得非常好的啟動性能。
內容預渲染
預渲染讓內容打開非常迅速。所以,如果用戶 至少有50%的可能性 打開某個鏈接,調用mayLaunchUrl()
方法。
調用mayLaunchUrl()
方法方法能使Custom Tabs預獲取主頁面所支持的內容并預渲染。這會最大程度的加快頁面的加載速度。但是會不可避免的有 一點流量和電量的消耗。
Custom Tabs非常的智能,能夠感知用戶是否在使用收費的網絡或者設備電量不足,預渲染對設備的整體性能有負面的影響,在這樣的場景下,Custom Tabs就不會進行預獲取或者預渲染。所以,不用擔心應用的性能問題。
當Custom Tabs沒有安裝時,提供備選方案
盡管Custom Tabs對于大多數用戶都是適用的,仍然有一些場景不適用,例如設備上沒有安裝支持Custom Tabs的瀏覽器或者是設備上的瀏覽器版本不支持Custom Tabs。
確保提供了備選方案以提供好的應用體驗。打開默認瀏覽器或者引入WebView都是不錯的選擇。
將我們的應用作為referrer(引薦來源)
通常,對于網站而言,追用訪問的來源非常地重要。當加載了Custom Tabs時,通過設置referrer,讓他們知曉我們正在給他們提高訪問量。
intent.putExtra(Intent.EXTRA_REFERRER, Uri.parse(Intent.URI_ANDROID_APP_SCHEME + "//" + context.getPackageName()));添加定制動畫
定制的動畫能夠使我們的應用切換到網頁內容時更加地順暢。 確保進場動畫和出廠動畫是反向的,這樣能夠幫助用戶理解跳轉的關系。
//設置定制的進入/退出動畫CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder();intentBuilder.setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left);intentBuilder.setExitAnimations(this, android.R.anim.slide_in_left,android.R.anim.slide_out_right);//打開Custom Tab intentBuilder.build().launchUrl(context, Uri.parse("https://github.com/marktony"));為Action Button選擇一個icon
添加一個Action Button能夠使用戶更加理解APP的功能。但是,如果沒有好的icon代表Action Button將要執行的操作,有必要創建一個帶操作文字描述的位圖。
牢記位圖的最大尺寸為高度24dp,寬度48dp。
String shareLabel = getString(R.string.label_action_share);Bitmap icon = BitmapFactory.decodeResource(getResources(),android.R.drawable.ic_menu_share);// 為我們的BroadCastReceiver創建一個PendingIntentIntent actionIntent = new Intent(this.getApplicationContext(), ShareBroadcastReceiver.class);PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, actionIntent, 0); // 設置pendingIntent作為按鈕被點擊后將要執行的操作 intentBuilder.setActionButton(icon, shareLabel, pendingIntent);為其他瀏覽器做準備
牢記用戶安裝的瀏覽器中,支持Custom Tabs的數量可能不止一個。如果有不止一個瀏覽器支持Custom Tabs,并且沒有任何一個瀏覽器被設置為偏好瀏覽器,需要詢問用戶如何打開鏈接
/*** 返回支持Custom Tabs的應用的包名*/ public static ArrayList getCustomTabsPackages(Context context) {PackageManager pm = context.getPackageManager();// Get default VIEW intent handler.Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));// 獲取所有能夠處理VIEW intents的應用List resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);ArrayList packagesSupportingCustomTabs = new ArrayList<>();for (ResolveInfo info : resolvedActivityList) {Intent serviceIntent = new Intent();serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION);serviceIntent.setPackage(info.activityInfo.packageName);// Check if this package also resolves the Custom Tabs service.if (pm.resolveService(serviceIntent, 0) != null) {packagesSupportingCustomTabs.add(info);}}return packagesSupportingCustomTabs;}允許用戶不使用Custom Tabs
為應用添加一個設置選項,允許用戶通過默認瀏覽器而不是Custom Tab打開鏈接。如果我們的應用在添加Custom Tabs之前,都是通過默認瀏覽器打開鏈接顯得尤為重要。
盡量讓Native應用處理URL
Native應用可以處理一些url。如果用戶安裝了Twitter APP,在點擊tweet內的鏈接時,她更加希望Twitter應用能夠處理這些鏈接。
在應用內打開鏈接之前,檢查手機里有沒有其他APP能夠處理這些url。
定制Toolbar的顏色
如果想要讓用戶感覺網頁內容是我們應用的一部分,將toolbar的顏色設置為primaryColor。
如果想要讓用戶清楚的了解到已經離開了我們的應用,那就完全不要定義toolbar的顏色。
// 設置自定義的toolbar的顏色CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder();intentBuilder.setToolbarColor(Color.BLUE);增加分享按鈕
確保在overflow菜單中添加了一個分享的操作,在大多數的情況下,用戶希望能夠分享當前所見網頁內容的鏈接,Custom Tabs默認沒有添加分享的按鈕。
// 在BroadCastReceiver中分享來自CustomTabs的內容public void onReceive(Context context, Intent intent) {String url = intent.getDataString();if (url != null) {Intent shareIntent = new Intent(Intent.ACTION_SEND);shareIntent.setType("text/plain");shareIntent.putExtra(Intent.EXTRA_TEXT, url);Intent chooserIntent = Intent.createChooser(shareIntent, "Share url");chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(chooserIntent);}}定制關閉按鈕
自定義關閉按鈕使CustomTabs看起來像應用的一部分。
如果希望CustomTabs在用戶看來像一個Dialog, 使用'x'(叉叉)按鈕。如果希望Custom Tab是用戶的一部分,使用返回箭頭。
//設置自定義的關閉按鈕CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder();intentBuilder.setCloseButtonIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_arrow_back));處理內部鏈接
當監聽到鏈接是由android:autoLink生成的或者在WebView中復寫了click方法,確保我們的應用處理了這些內容的鏈接,讓CustomTabs處理外部鏈接。
WebView webView = (WebView)findViewById(R.id.webview); webView.setWebViewClient(new WebViewClient() {@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) {return true;}@Overridepublic void onLoadResource(WebView view, String url) {if (url.startsWith("http://www.example.com")) {//Handle Internal Link...} else {//Open Link in a Custom TabUri uri = Uri.parse(url);CustomTabsIntent.Builder intentBuilder =new CustomTabsIntent.Builder(mCustomTabActivityHelper.getSession());//Open the Custom Tab intentBuilder.build().launchUrl(context, url)); }} });處理連擊
如果希望在用戶點擊鏈接和打開CustomTabs之間做一些準備工作,確保所花費的時間不超過100ms。否則用戶會認為APP沒有響應,可能是試著點擊鏈接多次。
如果不能避免延遲,確保我們的應用對可能的情況做好準備,當用戶點擊相同的鏈接多次時,不要多次打開CustomTab。
低版本API
盡管整合Custom Tabs的推薦方式是使用Custom Tabs Support Library,低API版本的系統也是可以使用的。
完整的Support Library的導入方法可以參見GitHub,并可以做為一個起點。連接service的AIDL文件也被包含在其中,Chromium倉庫中也包含了這些文件,而這些文件在Android Studio中是不能直接被使用的。
在低版本API中使用Custom Tabs的基本方法
String url = ¨https://github.com/marktony¨; Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 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() /* 不需要session時設置為null */); intent.putExtras(extras);定制UI
UI定制是通過向ACTION_VIEW Intent添加Extras實現的。用于定制UI的完整的extras keys的列表可以在 CustomTabsIntent docs 找到。下面是添加自定義的toolbar的顏色的示例:
private static final String EXTRA_CUSTOM_TABS_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR"; intent.putExtra(EXTRA_CUSTOM_TABS_TOOLBAR_COLOR, colorInt);連接Custom Tabs Service
Custom Tabs service和其他Android Service的使用方法相同。接口通過AIDL創建并且代理service類也會自動創建。
// 客戶端需要連接的Chrome的包名,取決于channel的名稱 // 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 stablepublic 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);一些有用的網址
Github Demo
Chrome Custom Tabs on StackOverflow
我的項目地址:PaperPlane
總結
如果我們的應用是面向國外用戶的,那理所當然的,應該加入Chrome Custom Tabs的支持,這在很大程度上能夠提升用戶的體驗。如果我們的應用只是面向國內用戶,我的建議還是應該加上這項功能,畢竟,還是有部分用戶安裝了Chrome瀏覽器,當用戶瀏覽到Custom Tab頁面,應該也會像我一樣,感覺到眼前一亮吧。
文章比較長,感謝閱讀。
本文章由簡書用戶TonnyL原創,轉載請注明作者、出處以及鏈接。
總結
以上是生活随笔為你收集整理的提升体验-支持Chrome Custom Tabs的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于NFS实现lamp的负载均衡之二:
- 下一篇: Java Web之BaseServlet