什麼是Chrome Custom Tabs(CCT)?

weixin_34365417發表於2017-07-06
本文是 Chrome Custom Tabs 官方文件的按照自己的理解的整理譯文

什麼是Chrome Custom Tabs(CCT)?

   當開發者是去開啟一個URL時候往往有兩種方式:WebView和開啟瀏覽器。目前這兩種方式皆有不足,WebView不與瀏覽器共享狀態,維護開銷大;開啟瀏覽器是一個重量級的操作並且瀏覽器不可定製化。
   此時谷歌爸爸給了我們新的選擇,CCT讓我們在網路體驗上有了很多的控制,使本地和Web內容更加無縫之間的轉換,而無需採取WebView的方式。

CCT允許應用定製瀏覽器的外觀和樣式,如下:

  • Toolbar顏色
  • 開啟關閉時的切換動畫
  • 新增Toolbar的Actions,新增Overflow Menu 和底部Toolbar

CCT還允許開發人員預啟動Chrome和更快的內容預抓取載入。


585714-447ac6bff438b052
這裡寫圖片描述

現在可以在Github上測試這些 sample.


怎麼選擇Chrome Custom Tabs vs WebView?

WebView是對於展示自己域名下或本地的網頁內容很好的一種解決方案,因為你可能會去網頁內容做很多自定義的內容。而如果引導使用者去第三方的網址時,建議您使用CCT,理由如下:

  • **易於實現。無需建程式碼來管理請求,許可權授予和Cookie **

  • UI上的自定義上文已提到

  • 導航:瀏覽器提供了一個可回撥的外部導航模組

  • 效能優化:
    - 在後臺瀏覽器的預加熱,避免從應用程式竊取資源。
    - 提前提供一個合適的URL到瀏覽器,它可以進行投機性的工作,加快頁面載入時間

  • 生命週期管理:通過提高“foreground”的重要級來防止CCT防止被系統被消滅

  • 共享Cookie和許可權模組,使用者不必再次登入到他們已經連線或者重新授權他們已經授權的網站

  • 使用CCT使用者依舊可以從Data Saver(Chrome外掛:幫助使用者節省瀏覽時的資料使用量)獲益

  • 更好的完成裝置自動同步

  • 簡單的定製模式

  • 更快速的返回到本地應用

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


使用條件?

需要安裝Chrome45或以上版本,支援的Android版本(Jellybean(4.1)以上)。


使用嚮導

一個完整的例子可在https://github.com/GoogleChrome/custom-tabs-client 找到。它包含了使用可重複的類來定製UI,連線到後臺服務,並處理應用程式和CCT的生命週期。

首先你需要新增CCT的依賴 Custom Tabs Support Library 在你的build.gradle檔案裡

dependencies {
...
compile<font color='#080'>'com.android.support:customtabs:24.2.0'</font>
}

當你完成新增依賴你可能有兩個配置需要自定義:

  • 自定義選項卡的UI和互動
  • 使頁面載入速度更快,並使應用程式保活

UI的自定義你可能會使用到 CustomTabsIntentCustomTabsIntent.Builder; 效能的改進通過使用CustomTabsClient 來連線Custom Tabs service,預加熱Chrome以及讓它知道那個URL將被開啟.

快速使用

String url = ¨https://paul.kinlan.me/¨;
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(this, Uri.parse(url));

設定Toolbar顏色

builder.setToolbarColor(colorInt);

自定義Action Buttons

作為開發者你將擁有呈現給使用者的Chrome標籤內的操作按鈕的所有控制。

在大多數情況下,這將是一個主要的作用,如分享,或者你的使用者將執行另一種常見的活動。

使用者點選操作按鈕呼叫的PendingIntent通過Bundle由Chrome傳遞過來。該圖示是高度最好是24dp和24-48dp的寬度。

// Adds an Action Button to the Toolbar.
// 'icon' is a Bitmap to be used as the image source for the
// action button.
 

// 'description' is a String be used as an accessible description for the button.

// 'pendingIntent is a 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.

// 'tint' is a boolean that defines if the Action Button should be tinted.

builder.setActionButton(icon, description, pendingIntent, tint);

自定義選單

Chrome瀏覽器CCT將始終會有前進,頁面資訊,重新整理三個圖示,選單上會有“查詢頁面”和“在瀏覽器中開啟”兩項。作為開發者你還可以新增多達5個選單項。

選單項是通過呼叫 CustomTabsIntent.Builder#addMenuItem 和帶有標題的PendingIntent,然後Chrome會將使用者的行為作為引數傳遞過來。

builder.addMenuItem(menuItemTitle, menuItemPendingIntent);

設定切換動畫

很多Android應用程式中使用自定義動畫來過渡Android上的Activities切換。 CCT並沒有什麼不同,你可以改變的開啟和關閉(當使用者按下後退)動畫,讓他們與你的應用程式的其他部分相一致。

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瀏覽器你設定的使用者將會訪問的網址,然後Chrome將會執行一下步驟:

  • DNS預解析主域
  • DNS預解析最有可能的子資源
  • 預連線到目的地,包括HTTPS / TLS。

預加熱Chrome的過程如下:

連線到Chrome Service

CustomTabsClient#bindCustomTabsService 簡化了連線到Custom Tabs service的流程,建立 CustomTabsServiceConnection,並使用 onCustomTabsServiceConnected 得到 CustomTabsClient的一個例項。操作如下

// 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.android.chrome";  // Change when in stable

CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
    @Override
    public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
        mCustomTabsClient = client;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};
boolean ok = CustomTabsClient.bindCustomTabsService(this, mPackageNameToBind, connection);

Warm up the Browser Process
boolean warmup(long flags)
返回 true 代表成功.

預加熱瀏覽器程式和載入庫檔案。此操作是非同步的,返回值表示請求是否被接受。多次呼叫成功亦返回true

建立Tab session
boolean newSession(CustomTabsCallback callback)

會話被用於隨後與CustomTabsCallback相連線的回撥,併產生相互的標籤。這裡提供的回撥與建立會話相關聯。所建立的會話的任何更新(見下面的自定義選項卡回撥)通過該回撥接收。返回值表示會話是否已成功建立。多個呼叫相同CustomTabsCallback或者空值將返回false。

設定可能開啟的URL
boolean mayLaunchUrl(Uri url, Bundle extras, List otherLikelyBundles)
該方法告訴瀏覽器一個未來可能載入的URL。warmup() 首先會被呼叫,最有可能的URL必須首先指定。(可選)可以提供其它可能的URL列表。它們被視為不太可能相比第一個,並且以降序進行排序。這些額外的URL可能會被忽略。這種方法以前所有的請求都將被deprioritized。返回值表示操作是否成功完成。

自定義選項卡Connection Callback
void onNavigationEvent(int navigationEvent, Bundle extras)
自定義選項卡有使用者行為發生時將被呼叫。該 navigationEvent int是定義的6種頁面狀態。請參閱下面的詳細資訊。

/**
* 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;

如果使用者沒有安裝了最新版本的Chrome會發生什麼?

自定義選項卡使用帶有附加功能鍵的ACTION_VIEW意圖定製UI。這意味著,在預設情況下頁面將會在系統瀏覽器,或使用者的預設瀏覽器。

如果使用者安裝了Chrome瀏覽器並且設定預設的瀏覽器,它會自動拿起額外裝置和呈現的自定義UI。另外,也可以為其他瀏覽器使用意圖額外提供一個類似的定製介面。

如何檢查Chrome是否支援CCT?

所有版本的Chrome都支援CCT暴露出來的一個服務。要檢查是否支援自定義選項卡,嘗試繫結到該服務。如果成功,則可以安全使用自定義選項卡。

最佳實踐

Connect to the Custom Tabs service and call warmup()

通過這樣的方式你最多可以節省700ms,700ms差不多是界定卡與不卡的時間。在Activity的 onStart() 去連線Custom Tab service, 連線後呼叫 warmup(). 這一操作將作為低優先順序的程式,這意味著它不會對你的應用程式效能的負面影響,但載入連結時會給出一個大的效能提升。

Pre-render content

預渲染將使外部內容瞬間開啟。所以,如果你的使用者至少點選連結的50%的可能性,呼叫mayLaunchUrl()這個方法會提前下載並渲染網頁內容,但不可避免的會有一點流量和電量的消耗。如果使用者正在使用收費的資料流量,或者手機電量不足,那麼這個方法不會生效。所以我們完全不用自己考慮效能優化

備選方案

如果使用者的手機上沒有安裝Chrome,那麼開啟預設瀏覽器可能並不是最好的使用者體驗。所以如果在bindService那一步失敗了,無論是開啟預設瀏覽器還是WebView,選擇一個你認為最好的備選方案。

Add your app as the referrer

很多網站都會統計自己的流量是從哪兒來的,所以最好告訴他們是你的帥氣APP給他們帶來了流量:

intent.putExtra(Intent.EXTRA_REFERRER, 
        Uri.parse(Intent.URI_ANDROID_APP_SCHEME + "//" + context.getPackageName()));

自定義動畫

自定義動畫將讓你的應用程式將網站內容更平滑的過渡。確保完成動畫和動畫開始是相對應的,這會幫助使用者瞭解他們已經回到了應用程式。

 //Setting custom enter/exit animations
    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);

//Open the Custom Tab        
    intentBuilder.build().launchUrl(context, Uri.parse("https://developer.chrome.com/"));        

為ActionButton新增一個Icon

新增ActionButton可以讓使用者更多的瞭解應用有那些功能。你可以建立描述該操作的文字的點陣圖,如果沒有一個很好的圖示來表示你的操作按鈕執行的操作,記住點陣圖的最大尺寸為24dp高x寬48dp。

    String shareLabel = getString(R.string.label_action_share);
    Bitmap icon = BitmapFactory.decodeResource(getResources(),
            android.R.drawable.ic_menu_share);

    //Create a PendingIntent to your BroadCastReceiver implementation
    Intent actionIntent = new Intent(
            this.getApplicationContext(), ShareBroadcastReceiver.class);
    PendingIntent pendingIntent = 
            PendingIntent.getBroadcast(getApplicationContext(), 0, actionIntent, 0);            

    //Set the pendingIntent as the action to be performed when the button is clicked.            
    intentBuilder.setActionButton(icon, shareLabel, pendingIntent);

多個瀏覽器的選擇

使用者可以安裝多個支援CCT的瀏覽器。如果有一個以上的瀏覽器支援自定義選項卡或者其中沒有一個是首選瀏覽器,那麼可以詢問使用者想如何開啟連結.另外新增一個可以選擇預設瀏覽器的選項,這樣可以讓使用者自由的去選擇是否使用CCT.

    /**
     * Returns a list of packages that support 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"));

        // Get all apps that can handle 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;
    }

使用本地應用處理連結

一些URL可以通過本地應用程式進行處理。如果使用者安裝了Twitter的應用程式,那麼點選連結後更期望在Twitter中開啟。所以在應用程式開啟一個URL之前,最好先檢查本機是否有方法可以代替。

定義Toolbar顏色

如果你希望使用者覺得內容是應用程式的一部分,那麼可以設定Toolbar顏色為Primary color。如果你考慮讓使用者知道該頁面已經離開了你的App那麼千萬不要自定義Toolbar顏色。

新增分享

大多數情況下使用者都會想要分享這個連結但是CCT預設不新增,所以最好自己加上

    //Sharing content from CustomTabs with on a BroadcastReceiver
    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);
        }
    }

定義關閉按鈕

如果你希望使用者感覺像CCT是一個對話方塊可以使用預設的“X”按鈕。如果你希望使用者感覺到CCT是App的一部分可以使用後退箭頭。

    //Setting a custom back button
    CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder();
    intentBuilder.setCloseButtonIcon(BitmapFactory.decodeResource(
        getResources(), R.drawable.ic_arrow_back));

處理內部連結

當攔截到由android:autoLink或者WebView產生的內部連結點選時,最好讓應用程式自己去處理,CCT只處理外部連結。

WebView webView = (WebView)findViewById(R.id.webview);
webView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        return true;
    }

    @Override
    public void onLoadResource(WebView view, String url) {
        if (url.startsWith("http://www.example.com")) {
            //Handle Internal Link...
        } else {
            //Open Link in a Custom Tab
            Uri uri = Uri.parse(url);
            CustomTabsIntent.Builder intentBuilder =
                    new CustomTabsIntent.Builder(mCustomTabActivityHelper.getSession());
           //Open the Custom Tab        
            intentBuilder.build().launchUrl(context, url));                            
        }
    }
});

連擊處理

確保使用者點選連結後到開啟CCT之間不要超過100ms,否則使用者會覺得反應遲鈍,並嘗試多次點選。當然卡頓是無法避免的,所以在使用者多次點選後確保只開啟CCT一次。


參考 http://qq157755587.github.io/2016/08/12/custom-tabs-best-practices/

相關文章