본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android] Chrome Custom Tabs

by 돼지왕 왕돼지 2019. 1. 26.
반응형



chrome custom tabs


-

기존에 특정 web page 를 보여주기 위해서는 external browser 에 의존하거나 internal WebView 를 활용하는 방법만 있었다.

external browser 는 많은 기능을 support 하며 state 를 share 한다는 장점이 있지만, context switch 적 성격이 강하며 customize 하기 어렵다.

internal WebView 의 경우 반대로 context switch 적 성격이 약하고, customize 하기는 쉽지만, state 를 share 하지 못하고, standard 를 맞춰 support 하기 어렵워 많은 공수가 든다는 단점이 있다.


external browser (chrome) 만큼의 호환성을 갖춘 “Custom Tabs” 라는 것이 support library 에 추가되었고, 이것을  internal 처럼 쓸 수 있게 되었다.



-

자신이 제공하는 컨텐츠를 보여줄 때는 WebView 를 쓰는 것도 좋은 방법이다.

그러나 자신이 관리하는 내용이 아닌 외부 link 로 연결하는 경우에는 Chrome Custom Tab 이 추천된다.



-

Chrome custom tabs 를 통해 web page 를 로드하는 것은 기존의 external browser 나 WebView 에 비해 약 2배정도 빠르다.


loading speed chrome vs customtab vs webview


-

Chrome custom tabs 은 다음과 같은 기능을 제공한다. (장점)

    Context switch 의 느낌을 적게 준다. Launch 때도 Activity level 의 화면 전환 느낌이 아니며, 닫을 때도 그렇다.


    Toolbar 영역을 customize 할 수 있다.

        색상을 지정할 수 있다. ( 글자색을 지정할 수 없는 것은 안타깝다. )

        Action Button 1개를 추가할 수 있다.

        Menu Item 을 여러 개 추가할 수 있다.

        Close Icon (Toolbar 최좌측) 을 지정할 수 있다.


    Bottom toolbar 를 customize 할 수 있다.


    Enter, Exit animation 을 지정할 수 있다.


    pre-warming 이라고 부르는 일종의 pre-loading 을 통해 훨씬 빠르게 web page 를 유저에게 보여줄 수 있다.


    Chrome browser 와 cookie 와 permission 을 공유하기 때문에 이미 연결된 사이트(예를 들어 자동 로그인으로 연결된 사이트)에 재연결(재로그인) 할 필요가 없다.


    Activity 에서는 Chrome custom tab 에서 전달하는 Callback 을 받을 수 있다.


    Google 의 Safe Browsing 을 사용하여 이상한 사이트로의 접근을 막아준다.


    (당연하지만) foreground level 로 실행되어 memory kill 에도 대응해준다. 그래서 Data Saver 가 켜져 있어도 이 녀석을 이용하는 데 문제 없다.


    Shared cookie, jar, permission 으로 인해 유연한 브라우징이 가능하다. AutoComplete 연동도 물론!



-

Chrome 45 부터 & JellyBean 부터 사용가능하다.



-

우선 이 녀석을 활용하기 위해서는 dependency 정의가 필요하다.

implementation "androidx.browser:browser:1.2.0"



-

Chrome tab 을 지원하는 package 가 존재하는지 query 가 필요하다

이 때 ACTION_VIEW 도 처리할 수 있으면서 CustomTabService.ACTION_CUSTOM_TABS_CONNECTION 도 처리할 수 있는 녀석이 custom tab 을 지원하는 브라우저 앱이라고 볼 수 있다.

Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));
List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);
List<String> packagesSupportingCustomTabs = new ArrayList<>(); 
for (ResolveInfo info : resolvedActivityList) { 
    Intent serviceIntent = new Intent(); 
    serviceIntent.setAction(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION); 
    serviceIntent.setPackage(info.activityInfo.packageName); 
    if (pm.resolveService(serviceIntent, 0) != null) { 
        packagesSupportingCustomTabs.add(info.activityInfo.packageName); 
    } 
}


query 후에 지원하는 activity intent 가 여러개일 경우 선별이 필요한데, 참고 글의 필자는 다음과 같은 선별규칙을 거친다.

String packageNameToUser = null
if (packagesSupportingCustomTabs.isEmpty()) {
    packageNameToUser = null; 
} else if (packagesSupportingCustomTabs.size() == 1) { 
    packageNameToUser = packagesSupportingCustomTabs.get(0); 
} else if (!TextUtils.isEmpty(defaultViewHandlerPackageName) // defaultViewHandlerPackageName 는 pm.resolveActivity 로 query 한 기본 activity
    && !hasSpecializedHandlerIntents(context, activityIntent) // authority 나 path scheme 이 정의되었는지 확인
    && packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) { 
    packageNameToUser = defaultViewHandlerPackageName; 
} else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) { // com.android.chrome
    packageNameToUser = STABLE_PACKAGE; 
} else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) { // com.chrome.beta
    packageNameToUser = BETA_PACKAGE; 
} else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) { // com.chrome.dev
    packageNameToUser = DEV_PACKAGE; 
} else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) { // com.google.android.apps.chrome
    packageNameToUser = LOCAL_PACKAGE; 
}



-

Custom tab 을 지원하는 녀석은 bind 할 수 있는 service 를 갖는다.

보통 Activity 의 onStart 에서 bind 를 하고, onStop 에서 unbind 를 한다.

Service 에 연결되서 보통 하는 일은 warmup 이며, 이 때 DNS pre-resolution, pre-connection 등을 한다. 이 과정은 low priority process 에서 진행하여 performance 에 영향을 크게 주지 않는다.

public void bindCustomTabsService(Activity activity) { if (mClient != null) return; // service binding 시 전달되는 binder 역할의 연결체, 이미 연결되었다는 의미 String packageName = CustomTabsHelper.getPackageNameToUse(activity); // 위에서 설명한 custom tab 지원하는 packageName 을 얻어오는 과정 if (packageName == null) return; mConnection = new CustomTabsServiceConnection() { @Override public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) { mClient = client; mClient.warmup(0L); // pre-load web page, async, return value 는 async 요청이 수용되었는가 여부 if (mConnectionCallback != null){ mConnectionCallback.onCustomTabsConnected(); } // Initialize a session as soon as possible. getSession(); } @Override public void onServiceDisconnected(ComponentName name) { mClient = null; if (mConnectionCallback != null){ mConnectionCallback.onCustomTabsDisconnected(); } } }; CustomTabsClient.bindCustomTabsService(activity, packageName, mConnection); } public void unbindCustomTabsService(Activity activity) { if (mConnection == null) return; activity.unbindService(mConnection); mClient = null; mCustomTabsSession = null; } public CustomTabsSession getSession() { if (mClient == null) { mCustomTabsSession = null; } else if (mCustomTabsSession == null) { mCustomTabsSession = mClient.newSession(null); // param 은 callback 으로 page load callback 을 받을 수 있다. } return mCustomTabsSession; }


CustomTabSession#mayLaunchUrl(Uri url, Bundle extras, List otherLikelyBundles) 을 통해 chrome 에 유저가 로딩할 페이지에 대한 힌트를 미리 줄 수 있다. 이것이 warmup 을 돕는다. (Bundle 은 future use 를 위한 reserve)

이것을 설정하면 Custom Tab 은 해당 페이지를 pre-fetch 해놓는다. 성능상 이점은 있지만 네트워크나 베터리 코스트의 단점이 있다. (metered network 등의 경우나 저사양 단말 등에서는 pre-rendering 을 하지 않는 등의 고려도 되어 있다.)

참고로 해당 API 는 warmup 이 먼저 실행된 후에 불려야 한다.



-

Service 가 연결이 된 후에는 Custom tab 을 열 수 있다.

UI Customize 는 CustomTabsIntent 를 통해 한다.

private void openCustomTab() {

    // warmup 기능을 제대로 사용하려면, builder 의 constructor 에 CustomTabsSession 을 전달한다.     // CustomTabsIntent 는 supportLib class CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder(); // titlebar show intentBuilder.setShowTitle(true); // titlebar color - color, 글자색을 바꿀 수 없는 것은 조금 아쉽다 int color = getResources().getColor(R.color.primary); intentBuilder.setToolbarColor(color); // menu items 추가 - label, pendingIntent String menuItemTitle = getString(R.string.menu_title_share); PendingIntent menuItemPendingIntent = createPendingShareIntent(); intentBuilder.addMenuItem(menuItemTitle, menuItemPendingIntent); String menuItemEmailTitle = getString(R.string.menu_title_email); PendingIntent menuItemPendingIntentTwo = createPendingEmailIntent(); intentBuilder.addMenuItem(menuItemEmailTitle, menuItemPendingIntentTwo); // close btn - icon intentBuilder.setCloseButtonIcon(mCloseButtonBitmap); // action btn - icon, label, pendingIntent intentBuilder.setActionButton(mActionButtonBitmap, getString(R.string.menu_title_share), createPendingShareIntent()); // enter & exit animation - 기본은 bottom to top animation 이다 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); // WebViewFallback 은 custom tab 을 지원하는 app 이 없을 때 처리하는 내용을 담고 있다 CustomTabActivityHelper.openCustomTab( this, intentBuilder.build(), Uri.parse(URL_ARGOS), new WebviewFallback()); }

// CustomTabActivityHelper public static void openCustomTab(Activity activity, CustomTabsIntent customTabsIntent, Uri uri, CustomTabFallback fallback) { String packageName = CustomTabsHelper.getPackageNameToUse(activity); //If we cant find a package name, it means there's no browser that supports //Chrome Custom Tabs installed. So, we fallback to the webview if (packageName == null) { if (fallback != null) { fallback.openUri(activity, uri); } } else { customTabsIntent.intent.setPackage(packageName); customTabsIntent.launchUrl(activity, uri); } }



-

Custom Tabs 을 띄울 때는 ACTION_VIEW 를 이용하며, UI customize 를 위해 해당 Intent 의 extras 를 이용한다.

이 말은 Chrome 이 최신 버전이 아닌 경우 시스템 브라우저 또는 유저의 기본 브라우저가 뜰 수 있다는 말. 해당 browser 들이 extra 를 이용해서 기능들을 지원해주길 기대하는 수밖에 없을 듯 하다.



-

현재 설치되어 있는 Chrome 이 Chrome Custom Tabs 를 지원하는지 보려면, bind service 를 해보는 것이 가장 명확하다.

성공하면 지원한다는 것이고, 아니면 지원하지 않는다는 이야기일 가능성이 높다.



-

참고 자료

https://labs.ribot.co.uk/exploring-chrome-customs-tabs-on-android-ef427effe2f4

https://developer.chrome.com/multidevice/android/customtabs

Github Repo : https://github.com/GoogleChrome/custom-tabs-client




끝!





반응형

댓글