진행 중인 활동

Wear OS 기기는 운동 추적과 같은 장기 실행 환경에 자주 사용됩니다. 이로 인해 사용자 환경 문제가 발생합니다. 사용자가 작업을 시작한 후 시계 화면으로 이동하면 어떻게 다시 돌아갈 수 있을까요? 특히 이동 중에 런처를 사용하여 앱으로 돌아가는 것이 어려워 불필요한 불편함이 발생할 수 있습니다.

진행 중인 알림을 OngoingActivity와 페어링하는 것이 해결책입니다. 이를 통해 기기는 사용자 인터페이스 전반에 걸쳐 장기 실행 활동에 관한 정보를 표시할 수 있으므로 워치 화면 하단의 탭할 수 있는 아이콘과 같은 기능을 사용할 수 있습니다. 이렇게 하면 사용자가 백그라운드 작업을 인식하고 탭 한 번으로 앱으로 돌아갈 수 있습니다.

예를 들어, 이 운동 앱에서는 정보를 사용자의 시계 화면에서 탭할 수 있는 달리기 아이콘으로 표시할 수 있습니다.

달리기 아이콘

그림 1. 활동 표시기

진행 중인 알림은 전역 앱 런처의 최근 섹션에도 정보를 표시합니다. 이렇게 하면 사용자가 작업 상태를 확인하고 앱을 다시 이용할 수 있는 또 다른 편리한 위치가 제공됩니다.

런처

그림 2. 전역 런처

다음은 진행 중인 활동과 연결된 진행 중인 알림을 사용하기에 좋은 상황입니다.

타이머

그림 3. 타이머: 자동으로 시간을 카운트다운하며 타이머가 일시중지 또는 중지되면 종료됩니다.

지도

그림 4. 내비게이션 세부 경로 안내: 목적지까지의 경로를 알려줍니다. 사용자가 목적지에 도착하거나 내비게이션을 중지하면 종료됩니다.

음악

그림 5. 미디어: 세션 내내 음악을 재생합니다. 사용자가 세션을 일시중지하면 즉시 종료됩니다.

Wear는 미디어 앱에 관해 진행 중인 활동을 자동으로 생성합니다.

다른 종류의 앱에 관해 진행 중인 활동을 만드는 자세한 예는 진행 중인 활동 Codelab을 참고하세요.

설정

앱에서 Ongoing Activity API를 사용하려면 앱의 build.gradle 파일에 다음 종속 항목을 추가합니다.

dependencies {
  implementation "androidx.wear:wear-ongoing:1.1.0"
  implementation "androidx.core:core:1.17.0"
}

진행 중인 활동 만들기

이 프로세스는 다음 세 단계로 진행됩니다.

  1. 표준 NotificationCompat.Builder를 만들고 이를 진행 중으로 구성합니다.
  2. 알림 빌더를 전달하여 OngoingActivity 객체를 만들고 구성합니다.
  3. 진행 중인 활동을 알림 빌더에 적용하고 결과 알림을 게시합니다.

알림 만들기 및 구성

먼저 NotificationCompat.Builder를 만듭니다. 중요한 단계는 setOngoing(true)를 호출하여 진행 중인 알림으로 표시하는 것입니다. 이 단계에서 작은 아이콘 및 카테고리와 같은 다른 알림 속성을 설정할 수도 있습니다.

// Create a PendingIntent to pass to the notification builder
val pendingIntent =
    PendingIntent.getActivity(
        this,
        0,
        Intent(this, AlwaysOnActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
        },
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
    )

val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
    .setContentTitle("Always On Service")
    .setContentText("Service is running in background")
    .setSmallIcon(R.drawable.animated_walk)
    // Category helps the system prioritize the ongoing activity
    .setCategory(NotificationCompat.CATEGORY_WORKOUT)
    .setContentIntent(pendingIntent)
    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
    .setOngoing(true) // Important!

OngoingActivity 만들기

그런 다음 빌더를 사용하여 OngoingActivity 인스턴스를 만듭니다. OngoingActivity.Builder에는 Context, 알림 ID, 이전 단계에서 만든 NotificationCompat.Builder이 필요합니다.

새 UI 표시 경로에 표시될 주요 속성을 구성합니다.

  • 애니메이션 및 정적 아이콘: 활성 모드와 대기 모드에서 시계 화면에 표시되는 아이콘을 제공합니다.
  • 터치 인텐트: 사용자가 진행 중인 활동 아이콘을 탭할 때 사용자를 앱으로 다시 가져오는 PendingIntent입니다. 이전 단계에서 만든 pendingIndent를 재사용할 수 있습니다.

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // Sets the icon that appears on the watch face in active mode.
        .setAnimatedIcon(R.drawable.animated_walk)
        // Sets the icon that appears on the watch face in ambient mode.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap target to bring the user back to the app.
        .setTouchIntent(pendingIntent)
        .build()

알림에 적용하고 게시

마지막 단계는 OngoingActivity를 알림과 연결한 다음 게시하는 것입니다. ongoingActivity.apply() 메서드는 시스템이 추가 표시 경로에 표시할 수 있도록 필요한 데이터를 추가하여 원래 알림 빌더를 수정합니다. 적용한 후에는 평소와 같이 알림을 빌드하고 게시할 수 있습니다.

// This call modifies notificationBuilder to include the ongoing activity data.
ongoingActivity.apply(applicationContext)

// Post the notification.
startForeground(NOTIFICATION_ID, notificationBuilder.build())

런처에 동적 상태 텍스트 추가

위 코드는 탭할 수 있는 아이콘을 시계 화면에 추가합니다. 런처의 최근 섹션에서 더욱 풍부한 실시간 업데이트를 제공하려면 Status 객체를 만들어 OngoingActivity에 연결하세요 . 맞춤 Status를 제공하지 않으면 시스템은 기본적으로 알림의 콘텐츠 텍스트 (setContentText()를 사용하여 설정)를 사용합니다.

동적 텍스트를 표시하려면 Status.Builder를 사용합니다. 자리표시자가 있는 템플릿 문자열을 정의하고 Status.Part 객체를 제공하여 자리표시자를 채울 수 있습니다. Status.Part는 스톱워치나 타이머와 같이 동적일 수 있습니다 .

다음 예에서는 '[스톱워치 타이머] 동안 실행'을 표시하는 상태를 만드는 방법을 보여줍니다.

// Define a template with placeholders for the activity type and the timer.
val statusTemplate = "#type# for #time#"

// Set the start time for a stopwatch.
// Use SystemClock.elapsedRealtime() for time-based parts.
val runStartTime = SystemClock.elapsedRealtime()

val ongoingActivityStatus = Status.Builder()
    // Sets the template string.
    .addTemplate(statusTemplate)
    // Fills the #type# placeholder with a static text part.
    .addPart("type", Status.TextPart("Run"))
    // Fills the #time# placeholder with a stopwatch part.
    .addPart("time", Status.StopwatchPart(runStartTime))
    .build()

마지막으로 OngoingActivity.Builder에서 setStatus()를 호출하여 이 StatusOngoingActivity에 연결합니다.

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // ...
        // Add the status to the OngoingActivity.
        .setStatus(ongoingActivityStatus)
        .build()

추가 맞춤설정

Status 외에도 다음과 같은 방법으로 진행 중인 활동이나 알림을 맞춤설정할 수 있습니다. 그러나 이러한 맞춤설정은 OEM 구현에 따라 사용되지 않을 수도 있습니다.

진행 중인 알림

  • 카테고리 집합에 따라 진행 중인 활동의 우선순위가 결정됩니다.
    • CATEGORY_CALL: 수신 음성 통화나 영상 통화 또는 유사한 동기 통신 요청
    • CATEGORY_NAVIGATION: 지도 또는 내비게이션 세부 경로 안내
    • CATEGORY_TRANSPORT: 재생을 위한 미디어 전송 제어
    • CATEGORY_ALARM: 알람 또는 타이머
    • CATEGORY_WORKOUT: 운동
    • CATEGORY_LOCATION_SHARING: 임시 위치 공유 카테고리)
    • CATEGORY_STOPWATCH: 스톱워치

진행 중인 활동

  • 애니메이션 아이콘: 흑백 벡터로, 배경이 투명한 것이 좋습니다. 활성 모드에서 시계 화면에 표시됩니다. 애니메이션 아이콘이 제공되지 않으면 기본 알림 아이콘이 사용됩니다. 기본 알림 아이콘은 애플리케이션마다 다릅니다.

  • 정적 아이콘: 배경이 투명한 벡터 아이콘입니다. 대기 모드에서 시계 화면에 표시됩니다. 애니메이션 아이콘이 설정되지 않으면 활성 모드에서 정적 아이콘이 시계 화면에 사용됩니다. 정적 아이콘이 제공되지 않으면 알림 아이콘이 사용됩니다. 둘 다 설정되어 있지 않으면 예외가 발생합니다. (앱 런처는 여전히 앱 아이콘을 사용합니다.)

  • OngoingActivityStatus: 일반 텍스트 또는 Chronometer입니다. 앱 런처의 최근 섹션에 표시됩니다. 제공되지 않으면 '컨텍스트 텍스트' 알림이 사용됩니다.

  • 터치 인텐트: 사용자가 진행 중인 활동 아이콘을 탭할 경우 관련 앱으로 다시 전환하는 데 사용되는 PendingIntent입니다. 시계 화면이나 런처 항목에 표시됩니다. 앱을 실행하는 데 사용된 원래 인텐트와 다를 수 있습니다. 제공되지 않으면 알림의 콘텐츠 인텐트가 사용됩니다. 둘 다 설정되어 있지 않으면 예외가 발생합니다.

  • LocusId: 진행 중인 활동에 해당하는 런처 바로가기를 할당하는 ID입니다. 활동이 진행되는 동안 런처의 최근 섹션에 표시됩니다. 제공되지 않으면 런처는 최근 섹션의 모든 앱 항목을 같은 패키지에서 숨기고 진행 중인 활동만 표시합니다.

  • 진행 중인 활동 ID: 애플리케이션에 진행 중인 활동이 2개 이상 있는 경우 fromExistingOngoingActivity() 호출을 명확하게 구분하는 데 사용되는 ID입니다.

진행 중인 활동 업데이트

대부분의 경우 개발자는 화면의 데이터를 업데이트해야 할 경우 진행 중인 알림과 진행 중인 활동을 새로 만듭니다. 그러나 인스턴스를 다시 만드는 대신 유지하고자 하는 경우 Ongoing Activity API에서는 OngoingActivity를 업데이트하기 위한 도우미 메서드도 제공합니다.

앱이 백그라운드에서 실행 중이면 Ongoing Activity API에 업데이트를 보낼 수 있습니다. 그러나 업데이트 메서드는 서로 너무 가까운 호출을 무시하므로 이 작업을 너무 자주 실행하지는 마세요. 분당 몇 번의 업데이트가 적절합니다.

진행 중인 활동과 게시된 알림을 업데이트하려면 다음 예에서와 같이 이전에 만든 객체를 사용하고 update()를 호출합니다.

ongoingActivity.update(context, newStatus)

편의상, 진행 중인 활동을 만드는 정적 메서드가 있습니다.

OngoingActivity.recoverOngoingActivity(context)
               .update(context, newStatus)

진행 중인 활동 중지

앱이 진행 중인 활동으로서 실행 종료되면 진행 중인 알림만 취소하면 됩니다.

포그라운드로 전환될 때 알림 또는 진행 중인 활동을 취소하고 백그라운드로 다시 돌아갈 때 이를 다시 만들 수도 있지만 필수는 아닙니다.

진행 중인 활동 일시중지

앱에 명시적인 중지 작업이 있는 경우 일시중지가 해제되면 진행 중인 활동을 계속 진행합니다. 명시적인 중지 작업이 없는 앱의 경우 일시중지되면 활동을 종료합니다.

권장사항

Ongoing Activity API로 작업할 때는 다음 사항에 유의하세요.

  • 진행 중인 활동의 정적 아이콘을 명시적으로 설정하거나 알림을 사용하여 대체 방안으로 설정합니다. 그러지 않으면 IllegalArgumentException이 발생합니다.

  • 배경이 투명한 흑백 벡터 아이콘을 사용합니다.

  • 진행 중인 활동의 터치 인텐트를 명시적으로 설정하거나 알림을 사용하여 대체 방안으로 설정합니다. 그러지 않으면 IllegalArgumentException이 발생합니다.

  • 매니페스트에 선언된 MAIN LAUNCHER 활동이 앱에 두 개 이상 있는 경우 동적 바로가기를 게시하고 이를 LocusId를 사용하여 진행 중인 활동과 연결합니다.

Wear OS 기기에서 미디어를 재생할 때 미디어 알림 게시

미디어 콘텐츠가 Wear OS 기기에서 재생되고 있다면 미디어 알림을 게시하세요. 이렇게 하면 시스템이 여기에 상응하는 진행 중인 활동을 생성할 수 있습니다.

Media3을 사용하는 경우 알림이 자동으로 게시됩니다. 알림을 수동으로 만드는 경우 MediaStyleNotificationHelper.MediaStyle을 사용해야 하고 상응하는 MediaSession에는 세션 활동이 채워져 있어야 합니다.