코딩하는 개굴이

CustomTab 의 모든 것을 알아보자. 본문

안드로이드

CustomTab 의 모든 것을 알아보자.

개굴이모자 2023. 6. 1. 00:16
반응형

해당 내용은 “Android CustomTab” 공식 문서를 기반으로 작성되었습니다.
직접 번역하였기 때문에 오역이 있을 수 있습니다. 발견 시, 댓글 등으로 공유 부탁드립니다.

 

CustomTab 이란?

안드로이드의 웹 콘텐츠

안드로이드에서 웹 콘텐츠를 로딩하는 방법은 이전부터 사용되어왔지만, 예전 방법은 개발자에게 어려움을 줄 수 있다. 외부 브라우저를 실행하면 사용자에게 부담스러운 컨텍스트 전환을 하게 만들고, 웹뷰의 경우 웹 플랫폼의 모든 기능을 지원하지 않으며 브라우저와의 상태를 공유할 수도 없기에 유지 관리 과정에서 오버헤드가 발생하게 된다.

 

CustomTab 은 무엇을 할 수 있을까?

CustomTab을 이용하면 렌더링 엔진이 사용자가 선호하는 브라우저에서 웹 콘텐츠를 로드시킨다. CustomTab 은 모든 API 혹은 웹 플랫폼 기능(사용자의 세션, 브라우저에 저장된 비밀번호, 결제 방법 및 주소 등)을 사용할 수 있다.

 

어떤 것을 CustomTab 에서 Custom 할 수 있는가?

CustomTab을 사용하면 크롬과 다른 브라우저들 그리고 사용자 경험 측면에서 많은 부분을 세세하게 제어할 수 있다. 앱 내에서 Intent를 통해 호출하면 CustomTabIntent 에 여러 속성을 부여해서 아래와 같이 다양한 커스터마이징을 할 수 있다.

  • 앱과 동일하게 시작/종료 애니메이션을 설정
  • 앱과 동일하게 툴바 색상을 변경
  • 앱과 동일하게 테마 변경. 또한 기기의 테마 변환(밝은 테마/어두운 테마)이 중간에 이루어지더라도 대응
  • 브라우저의 툴바, 그리고 메뉴에 Custom Action 추가 가능
  • CustomTab이 실행되는 높이를 설정하여 앱의 콘텐츠와 동시 노출 가능

해당 기능을 앱에서 사용하기 위해서는 물론 각 브라우저들의 기능 구현이 필요하다.

 

 💡브라우저들의 CustomTab 지원
거의 대부분의 브라우저들이 어느정도 지원을 하고 있지만,
사용자들이 주로 사용하는 브라우저들에 해당 기능들이 제공되고 있는지 파악하는 것은 중요하다.
따라서, 안드로이드의 대표적인 브라우저들에서 각 지원하는 커스텀 기능들에 대한 정리는
“Browser Support” 문서를 참고할 수 있다.

 

CustomTab 의 장점

  • 보안: CustomTab은 Google’s Safe Browsing 을 사용하기 때문에, 위험한 사이트들로부터 사용자를 보호할 수 있다.
  • 성능의 최적화: 앱의 리소스를 크게 잡지 않으면서 사용할 브라우저를 백그라운드에서 Pre-warming (예열) 하고 있기 때문에, URL 을 미리 로드하여 페이지 로딩 시간을 단축한다.
  • Lifecycle 관리: CustomTab을 실행하는 앱은 탭을 사용하는 동안 System 으로부터 강제 종료되지 않으며, 중요성은 “forground” 레벨로 높게 변경된다.
  • 쿠키/권한 공유: 쿠키 저장소와 권한 모델을 공유하기 때문에, 사용자가 이미 연결했던 이력이 있는 사이트에 로그인하거나, 이미 부여한 권한이 있을 경우 다시 로그인부여하지 않아도 된다.
  • Browser 의 Data Saving 기능 공유: 브라우저가 만일 데이터 세이버 등의 기능을 제공한다면 콘텐츠를 더욱 빠르고 저렴하게 로드할 수 있다. (브라우저의 기능 공유)
  • 자동 완성 기능 공유: 브라우저의 자동 완성 기능을 동기화하여 여러기기에서 form 완성도를 높일 수 있다.
  • 앱으로 돌아가기: 사용자는 백버튼으로 쉽게 앱으로 복귀할 수 있다.

 

지원 버전

CustomTab 은 안드로이드 플랫폼의 브라우저에서 지원하는 기능으로, Chome 의 경우 45.ver 이상 에서 사용할 수 있다.

 


 

CustomTab 적용하기

dependency 추가

CustomTab 적용을 위해서는 AndroidX Browser Library 가 필요하다.

아래와 같이 app/build.gradle 파일에 dependency 를 추가하자.

dependencies {
   …
   implementation 'androidx.browser:browser:1.5.0'
}

CustomTab 으로 웹을 열자

androidx.browser/browser 라이브러리를 설치했다면, CustomTabsIntent 를 생성하기 위해CustomTabsIntent.Builder 를 사용할 수 있다. 그리고 CustomTabsIntent 로 launchUrl() 을 Uri 를 넘기며 호출하여 CustomTab 을 부를 수 있다.

 

💡만일, 사용자의 기본 브라우저가 CustomTab 을 지원하지 않을 경우의 동작은 어찌되는가?
CustomTab은 대부분의 안드로이드 브라우저에서 지원되지만, 만일 지원하는 브라우저가 없을 경우
CustomTabIntent 가 사용자의 기본 브라우저를 대신 열어 외부 브라우저로 작동한다.
이는 CustomTabItnet 가 ACTION_VIEW Intent 를 Extras 키와 함께 사용해 UI 를 커스터마이징하기 때문이다.

 

 


 

 

CustomTab 의 UI를 Customize 하기

CustomTab 의 장점은 자연스럽게 앱과 동화할 수 있도록 커스터마이징을 가능하게 한다는 점이다. 여러 커스터마이징 기능들을 적용해보자.

Toolbar 색상 지정하기

우선 setDefaultColorSchemeParams() 를 이용해 default toolbar 색상을 바꿀 수 있다. 만약 앱이 다크모드를 지원한다면, setColorSchemeParams(CustomTabsIntent.COLOR_SCHEME_DARK, …) 을 사용할 수 있다.

// get the current toolbar background color (this might work differently in your app)
@ColorInt int colorPrimaryLight = ContextCompat.getColor(MainActivity.this, R.color.md_theme_light_primary);
@ColorInt int colorPrimaryDark = ContextCompat.getColor(MainActivity.this, R.color.md_theme_dark_primary);

CustomTabsIntent intent = new CustomTabsIntent.Builder()
        // set the default color scheme
        .setDefaultColorSchemeParams(new CustomTabColorSchemeParams.Builder()
                .setToolbarColor(colorPrimaryLight)
                .build())
        // set the alternative dark color scheme
        .setColorSchemeParams(CustomTabsIntent.COLOR_SCHEME_DARK, new CustomTabColorSchemeParams.Builder()
                .setToolbarColor(colorPrimaryDark)
                .build())
        .build();

 

시작/종료 애니메이션 설정하기

setStartAnimation 과 setExitAnimation 으로 animation을 지정할 수 있다.

CustomTabsIntent intent = new CustomTabsIntent.Builder()
…
.setStartAnimations(MainActivity.this, R.anim.slide_in_right, R.anim.slide_out_left)
.setExitAnimations(MainActivity.this, android.R.anim.slide_in_left, android.R.anim.slide_out_right)
.build();

 

추가적인 Customization : Title, Autohide AppBar, custom close icon, and Referrer

  • setUrlBarHidingEnabled(true)로 URL 바를 스크롤 시 숨겨 사용자에에게 웹을 볼 수 있는 공간을 더 확보시켜준다.
  • setShowTitle()로 URL 대신 문서의 타이틀을 제공한다.
  • close button 을 커스터마이즈하여 앱과 사용성이 유사하게 사용할 수 있다. 예를들어 ← 대신 X 로 변경하는 등 setCloseButtonIcon() 을 이용해 변경 가능하다.
Bitmap myCustomCloseIcon = getDrawable(R.drawable.ic_baseline_arrow_back_24));
CustomTabsIntent intent = new CustomTabsIntent.Builder()
  …
  .setUrlBarHidingEnabled(true)
  .setShowTitle(true)
  .setCloseButtonIcon(toBitmap(myCustomCloseIcon))
  .build();

 

custom referrer 세팅하기

CustomTab 을 실행할 때, 앱을 referrer 로 세팅한다면, 웹사이트에서 어떤 트래픽으로 들어오는지를 알게 할 수 있다.

CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build()
customTabsIntent.intent.putExtra(Intent.EXTRA_REFERRER,
       Uri.parse("android-app://" + context.getPackageName()));

 


 

Custom 상호작용 추가하기

기본 공유 버튼 활성화하기

    CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder();
    intentBuilder.setShareState(CustomTabsIntent.SHARE_STATE_ON);

Custom Action button 추가하기

CustomTab 의 툴바는 custom tab 에 액션 버튼을 추가할 수 있도록 제공한다. 24dp 높이에 24-48dp 너비의 Text Label 이나 Custom Icon 을 사용할 수 있다.

커스텀 버튼을 추가했다면 BroadcastReceiver 를 생성해 사용자가 커스텀탭의 액션을 클릭했는지를 받아와야한다.

AndroidManifest.xml 파일에 등록하고, 새로운 SharedBroadCastReceiver 를 등록한다.

<application …>
  <receiver android:name=".ShareBroadcastReceiver" />
</application>

onReceive() 메서드에서는 현재 display 된 URL을 intent 에서 추출하고 send intent 를 트리거한다.

public class ShareBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String url = intent.getDataString();
        Intent sendIntent = new Intent();
        sendIntent.setAction(Intent.ACTION_SEND);
        sendIntent.putExtra(Intent.EXTRA_TEXT, url);
        sendIntent.setType("text/plain");
        Intent shareIntent = Intent.createChooser(sendIntent, null);
        context.startActivity(shareIntent);
    }
}

이제 PendingIntent를 생성하고, ShareBroadcast 에 setActionButton() 으로 아이콘, 설명과 함께 등록한다.

String shareDescription = getString(R.string.label_action_share);
Bitmap shareIcon = 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 /* request code */, actionIntent, PendingIntent.FLAG_MUTABLE);

//Set the pendingIntent as the action to be performed when the button is clicked.
CustomTabsIntent intentBuilder = new CustomTabsIntent.Builder()
    …
    .setActionButton(shareIcon, shareDescription, pendingIntent)
    .build();

 


 

Custom 메뉴 아이템

커스텀 아이콘과 유사하게 pendingIntent 를 setMenuItem 지정 시 넘겨준다.

CustomTabsIntent intent = new CustomTabsIntent.Builder()
        ...
        .addMenuItem("Menu item 1", pendingIntent)
        .addMenuItem("Menu item 2", pendingIntent)
        .addMenuItem("Menu item 3", pendingIntent)
        .addMenuItem("Menu item 4", pendingIntent)
        .addMenuItem("Menu item 5", pendingIntent)
        .build();

하단 툴바 추가하기

RemoteViews 객체를 CustomTabIntent.Builder.setSecondaryToolbarViews() 에 넘긴다. 전부 커스터마이징이 가능하고 동적으로 업데이트가 가능하다.

layout 파일을 생성하고, broadcastReceiver 를 등록한다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:gravity="center">

    <Button xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/ct_toolbar_next"
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_margin="8dp"
        android:padding="8dp"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"
        android:text="Next" />

    <Button xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/ct_toolbar_previous"
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_margin="8dp"
        android:padding="8dp"
        android:text="Previous" />
</LinearLayout>
<application …>
  <receiver android:name=".CustomTabBottomToolbarBroadcastReceiver" />
</application>

바텀 툴바의 상호작용을 정의하는 리시버를 생성하고, 툴바에 등록한다.

public class BottomToolbarBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String url = intent.getDataString();
        int remoteViewId = intent.getIntExtra(EXTRA_REMOTEVIEWS_CLICKED_ID, -1);
        if (remoteViewId == R.id.ct_toolbar_previous) {
          // handle previous
        } else if (remoteViewId == R.id.ct_toolbar_next) {
          // handle next
        }
    }
}
// Create the pending intent
Intent actionIntent = new Intent(
       this.getApplicationContext(), BottomToolbarBroadcastReceiver.class);

PendingIntent pendingIntent =
       PendingIntent.getBroadcast(getApplicationContext(), 0 /* request code */, actionIntent, PendingIntent.FLAG_MUTABLE);

// Pass the toolbar layout to the RemoteViews instance
RemoteViews secondaryToolbarViews = new RemoteViews(getPackageName(), R.layout.custom_tab_toolbar);
// All toolbar buttons
int[] clickableIds = {R.id.ct_toolbar_next, R.id.ct_toolbar_previous};

// Register the bottom toolbar when creating a new custom tab intent
CustomTabsIntent intent = new CustomTabsIntent.Builder()
    .setSecondaryToolbarViews(secondaryToolbarViews, clickableIds, toolbarPendingIntent)
    .build();

 


 

CustomTabs Service 를 이용한 Warm-up 그리고 Pre-fetch

브라우저를 warmup() 하여 스피드를 높이고 웹페이지를 mayLaunchUrl() 을 이용해 prefetch 하는 방법을 알아보자. 브라우저의 프로세스를 워밍업하면 링크를 열 때, 약 700ms 의 사용자 시간을 절약해줄 수 있다. mayLaunchUrl 을 통해 콘텐츠를 미리 렌더링하면 외부 콘텐츠가 즉시 열린다. 두 API 를 함께 사용하여 CustomTab 의 사용자 경험을 크게 개선할 수 있다.

순서는 아래와 같다.

  • CustomTabsClient.getPackageName 으로 기본 브라우저가 customtab 을 지원하는지 확인한다. 지원한다면 CustomTabsClient.bindCustomTabsService() 를 으로 CustomTabsService 에 바인딩한다.
    • API level 30 이상을 타겟팅할 경우, CustomTabsClient.getPackageName()을 사용하려면 AndroidManifest 에 쿼리 섹션을 추가하여 CustomTab을 지원하는 브라우저와 일치하는 인텐트 필터를 선언해야한다.
  • CustomTabsService 에 연결되면 CustomTabsServiceConnection.onCustomTabsServiceConnected() 콜백에서 아래 내용을 수행한다.
    • CustomTabsClient.warmup() 을 이용해 브라우저를 워밍업한다.
    • CustomTabsClient.newSession() 을 통해 CustomTabSession 을 생성한다.
  • 추가 선택 사항으로, 사용자가 방문할 가능성이 높은 웹페이지 등의 경우 CustomTabsSession.mayLaunchUrl() 을 사용해 미리 가져올 수 있다.
  • CustomTab 을 새로 실행할 때 생성자 CustomTabsIntent.Builder(session) 으로 세션을 넘긴다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
  …
   <queries>
        <intent>
            <action android:name="android.support.customtabs.action.CustomTabsService" />
        </intent>
    </queries>
</manifest>
private CustomTabsClient mClient;
private CustomTabsSession mSession;

private CustomTabsServiceConnection mConnection = new CustomTabsServiceConnection() {
    @Override
    public void onCustomTabsServiceConnected(
            @NonNull ComponentName name,
            @NonNull CustomTabsClient client
    ) {
        mClient = client;
        // Warm up the browser process
        mClient.warmup(0 /* placeholder for future use */);
        // Create a new browser session
        mSession = mClient.newSession(new CustomTabsCallback());
        // Pre-render pages the user is likely to visit
        // you can do this any time while the service is connected
        mSession.mayLaunchUrl("<https://developers.android.com>", null, null);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mClient = null;
        mSesssion = null;
    }
};

private void bindCustomTabService(Context context) {
    // Check for an existing connection
    if (mClient != null) {
        // Do nothing if there is an existing service connection
        return;
    }

    // Get the default browser package name, this will be null if
    // the default browser does not provide a CustomTabsService
    String packageName = CustomTabsClient.getPackageName(context, null);
    if (packageName == null) {
        // Do nothing as service connection is not supported
        return;
    }
    CustomTabsClient.bindCustomTabsService(context, packageName, mConnection);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
…
    bindCustomTabService(this);
    findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            String url = "<https://developers.android.com>";
            CustomTabsIntent intent = new CustomTabsIntent.Builder(session)
                    .build();
            intent.launchUrl(MainActivity.this, Uri.parse(url));
        }
    });
}
💡 Activity 가 돌고 있는 동안 CustomTabsService 의 Connection fail이 발생 할 경우의 처리
첫번째 전략은, onCreate 에서 CustomTabsService 를 연결할 때 만일 activity 가 disconnected 되고,
onServiceDisconnected 콜백이 호출되는 경우 서비스에 다시 연결하게 할 수 있다.

두번째 전략은, 사용자가 CustomTab 을 사용하고자 할 때까지 기다렸다가, 사용하는 시점에 서비스를 연결해 주는 것이다.

 


 

Partial CustomTabs 사용하기 (부분 커스텀 탭)

기본 동작으로 커스텀탭은 full-window activity 로 실행된다. 그러나, 크롬 107 버전 이상부터, Paritial CustomTab 을 사용해 portrait 일 때 실행 높이를 지정할 수 있다. 따라서, 사용자는 웹 콘텐츠를 보면서 앱과 상호작용하며 멀티태스킹이 가능하다. 사용자는 툴바 핸들을 위로 드래그하여 CustomTab을 전체 화면으로 확장하거나 아래로 드래그하여 초기 실행 높이로 복원할 수도 있다.

CustomTab을 Partial CustomTab 으로 사용하고자 한다면, CustomTabBuilder 클래스의 setInitialActivityHeightPx() 메서드를 호출해 초기 실행 높이를 픽셀 단위로 넘겨야한다.

기본적으로 커스텀탭은 크기를 조정할 수 있게 되어있지만, 해당 동작을 비활성화 하고자 한다면, ACTIVITY_HEIGHT_FIXED 를 넘길 수도 있다.

new CustomTabsBuilder().setInitialActivityHeightPx(
    400,
    ACTIVITY_HEIGHT_FIXED
);
💡 커스텀탭의 최소 크기는? 
커스텀탭의 최소 높이는 스크린 높이의 50% 이다. 만일 스크린 높이가 50% 이하로 세팅된다면,
크롬은 자동으로 커스텀탭의 50% 에 해당하는 높이로 실행된다.

 

또한, 아래 두가지 중 하나 이상을 해야한다.

  • CustomTabsServiceConnection을 통해 새 브라우저 세션을 시작하고 이를 사용자 지정 탭 인텐트로 전달
  • startActivityForResult()를 통해 커스텀탭 activity 를 시작
 💡 만일 기본 브라우저가 부분 커스텀탭을 지원하지 않는다면? 
intent extra 는 받은 높이를 무시하고 전체 높이에 해당하는 커스텀탭이 노출되게 된다.
💡 부분 커스텀탭의 custom 속성? 
부분 커스텀탭은 CustomTabColorScheme.navigationBarColor 및 CustomTabColorScheme.navigationBarDividerColor 속성을
사용할 수 없다. 이는 부분 커스텀탭이 navigation 의 색상을 상속하기 때문이다.

 

기존 세션으로 부분 커스텀탭을 실행하기

CustomTabsSession customTabsSession;

// ...

CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder(customTabsSession)
   .setInitialActivityHeightPx(500)
   .setCloseButtonPosition(CustomTabsIntent.CLOSE_BUTTON_POSITION_END)
   // ...
   .build();

customTabsIntent.launchUrl(context, Uri.parse(url))

부분 커스텀탭을 startActivityForResult 로 실행하기

private ActivityResultLauncher<String> mCustomTabLauncher = registerForActivityResult(new ActivityResultContract<String, Integer>() {
    @Override
    public Integer parseResult(int statusCode, @Nullable Intent intent) {
        return statusCode;
    }

    @NonNull
    @Override
    public Intent createIntent(@NonNull Context context, String url) {
        CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(customTabsSession)
                .setInitialActivityHeightPx(500)
                .setCloseButtonPosition(CustomTabsIntent.CLOSE_BUTTON_POSITION_END)
                .setToolbarCornerRadiusDp(10);
        Intent customTabsIntent = builder.build().intent;
        customTabsIntent.setData(Uri.parse(url));
        return customTabsIntent;
    }
}, new ActivityResultCallback<Integer>() {
    @Override
    public void onActivityResult(Integer statusCode) {
       // ...
    }
});

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    Button selectButton = findViewById(R.id.select_button);
    selectButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            mCustomTabLauncher.launch(customTabsIntent.intent);
        }
    });
}

 


 

User Engagement 측정하기

앱에서 정기적으로 사용자에게 웹 콘텐츠 링크를 표시하는 경우(예: 뉴스피드), 사용자가 어떤 링크를 가치 있다고 생각하는지, 어떤 링크를 가치 없다고 생각하는지 파악하는 것이 중요할 수 있다. 커스텀탭에서는 탐색 횟수, 스크롤 방향 변경 및 스크롤 깊이를 통해 세션별 사용자 참여도를 측정할 수 있다.

해당 기능은 androidx.browser:browser:1.6.0-alpha01 이상의 버전에서 지원된다.

CustomTab은 User Engagement 를 측정하기 위해 2가지의 콜백을 제공한다.

  • CustomTabsCallback: 기본적인 탐색 이벤트를 추적하기 위한 콜백이다.
    • NAVIGATION_STARTED
    • NAVIGATION_FINISHED
  • EngagementSignalsCallback: 스크롤방향과 스크롤 퍼센트 등의 특정 user engagement 를 측정하는데 사용된다.

둘다 활성화된 CustomTabsServiceConnection 을 사용해야하며, 측정을 위해서는 CustomTabsCallback 과 EngagementSignalsCallback 인스턴스를 생성해야한다.

CustomTabsCallback 의 사용

CustomTabsCallback 은 어떤 네이게이션이 발생했는지를 포함하는 naviationEvent 를 받는다.

private CustomTabsCallback mCustomTabsCallback = new CustomTabsCallback() {
    @Override
    public void onNavigationEvent(int navigationEvent, @Nullable Bundle extras) {
        String event;
        switch (navigationEvent) {
            case CustomTabsCallback.NAVIGATION_ABORTED:
                event = "NAVIGATION_ABORTED";
                break;
            case CustomTabsCallback.NAVIGATION_FAILED:
                event = "NAVIGATION_FAILED";
                break;
            case CustomTabsCallback.NAVIGATION_FINISHED:
                event = "NAVIGATION_FINISHED";
                break;
            case CustomTabsCallback.NAVIGATION_STARTED:
                event = "NAVIGATION_STARTED";
                break;
            case CustomTabsCallback.TAB_SHOWN:
                event = "TAB_SHOWN";
                break;
            case CustomTabsCallback.TAB_HIDDEN:
                event = "TAB_HIDDEN";
                break;
            default:
                event = String.valueOf(navigationEvent);
        }
        Log.d(TAG, "onNavigationEvent (navigationEvent=" + event + ')');
        mTextNavigation.setText("onNavigationEvent " + event);
    }
};

EngagementSignalsCallback 의 사용

EngagementSignalsCallback 는 세가지 콜백을 지원한다.

  • onVerticalScrollEvent()
    • 사용자가 스크롤 방향을 변경할 때마다 호출되며, isDirectionUp 의 값은 방향 값을 나타낸다.
  • onGreatestScrollPercentageIncreased()
    • 사용자의 스크롤에 따라, 최대 100%까지 5% 의 간격으로 스크롤의 깊이를 알린다. 사용자가 스크롤을 멈추었을 때만 실행되며, 값은 매번 새로운 탐색(새로운 페이지로 이동)마다 0% 로 재설정된다.
  • onSessionEnded()
    • 커스텀탭이 engagement signal 을 보내는 것을 중지할 경우 (사용자가 커스텀탭을 닫은 후) 발생한다. didUserInteract 로 사용자의 닫기 버튼 등의 액션에 의해 닫혔는지 확인할 수도 있다.
private EngagementSignalsCallback mEngagementSignalsCallback = new EngagementSignalsCallback() {
    @Override
    public void onVerticalScrollEvent(boolean isDirectionUp, @NonNull Bundle extras) {
        Log.d(TAG, "onVerticalScrollEvent (isDirectionUp=" + isDirectionUp + ')');
        mTextVerticalScroll.setText("vertical scroll " + (isDirectionUp ? "UP️" : "DOWN"));
    }

    @Override
    public void onGreatestScrollPercentageIncreased(int scrollPercentage, @NonNull Bundle extras) {
        Log.d(TAG, "scroll percentage: " + scrollPercentage + "%");
        mTextGreatestPercentage.setText("scroll percentage: " + scrollPercentage + "%");
    }

    @Override
    public void onSessionEnded(boolean didUserInteract, @NonNull Bundle extras) {
        Log.d(TAG, "onSessionEnded (didUserInteract=" + didUserInteract + ')');
        mTextSessionEnd.setText(didUserInteract ? "session ended with user interaction" : "session ended without user interaction");
    }
};

CustomTabsCallback과 EngagementSignalsCallback 모두 활성화 된 CustomTabServiceConnection이 필요하다. 서비스가 연결되면 CustomTabsClient.newSession()을 호출하고 CustomTabsCallback을 전달해 새 CustomTabsSession을 만들 수 있다.

 

그런 다음 isEngagementSignalsApiAvailable()을 호출해 현재 브라우저에서 engagement signal 이 지원되는지 확인한다. 지원되는 경우 CustomTabsSession.setEngagementSignalsCallback()을 통해 EngagmentSignalsCallback을 등록할 수 있다.

private CustomTabsClient mCustomTabsClient;
private CustomTabsSession mCustomTabsSession;

private final CustomTabsServiceConnection mServiceConnectionCallback = new CustomTabsServiceConnection() {

    @Override
    public void onCustomTabsServiceConnected(@NonNull ComponentName name, @NonNull CustomTabsClient client) {
        mCustomTabsClient = client;
        mCustomTabsSession = mCustomTabsClient.newSession(mCustomTabsCallback);
        try {
            boolean engagementSignalsApiAvailable = mCustomTabsSession.isEngagementSignalsApiAvailable(Bundle.EMPTY);
            if (!engagementSignalsApiAvailable) {
                Log.d(TAG, "CustomTab Engagement signals not available, make sure to use the " +
                        "latest Chrome version and enable via chrome://flags/#cct-real-time-engagement-signals");
                return;
            }
            mCustomTabsSession.setEngagementSignalsCallback(mEngagementSignalsCallback, Bundle.EMPTY);
        } catch (RemoteException e) {
            Log.w(TAG, "The Service died while responding to the request.", e);
        } catch (UnsupportedOperationException e) {
            Log.w(TAG, "Engagement Signals API isn't supported by the browser.", e);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mCustomTabsClient = null;
        mConnection = null;
        mCustomTabsSession = null;
    }
};
@Override
protected void onStart() {
    super.onStart();
    bindCustomTabsService();
}

private void bindCustomTabsService() {
    String packageName = CustomTabsHelper.getPackageNameToUse(this);
    if (packageName == null) return;
    CustomTabsClient.bindCustomTabsService(this, packageName, mConnection);
}
반응형
Comments