Android SDK μ‹œμž‘ν•˜κΈ°

Requirements

🚧

Minimum requirements on AsleepTrack SDK for Android

  • Android 7.0 (API level 24) ν˜Ήμ€ 더 높은 버전
  • Java 1.8 ν˜Ήμ€ 더 높은 버전
  • Android Gradle plugin 8.0 ν˜Ήμ€ 더 높은 버전

API Key

  • AsleepTrack SDKλ₯Ό μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„œλŠ” API keyκ°€ ν•„μš”ν•©λ‹ˆλ‹€.
  • API keyλ₯Ό λ°œκΈ‰ν•˜λŠ” 방법에 λŒ€ν•΄μ„œλŠ” 이 링크 API Key μƒμ„±ν•˜κΈ°λ₯Ό μ°Έκ³ ν•˜μ„Έμš”.

Getting Ready

Install AsleepTrack SDK and Settings

  1. Android Studioλ₯Ό μ΄μš©ν•˜μ—¬ κΈ°λ³Έ ν”„λ‘œμ νŠΈλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
  2. AndroidManifest.xml νŒŒμΌμ„ μ—΄μ–΄ νΌλ―Έμ…˜μ„ μΆ”κ°€ν•©λ‹ˆλ‹€.
<manifest ...>
  	
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
  <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

  <application ...>
    ...
  </application>
</manifest>
    
  1. μ•± μˆ˜μ€€μ˜ build.gradle νŒŒμΌμ„ μ—΄μ–΄ lifecycle-service, okhttp, gson 및 asleepsdkλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
dependencies {
  ...
  implementation("androidx.lifecycle:lifecycle-service:2.8.7")
  implementation("com.squareup.okhttp3:okhttp:4.11.0")
  implementation("com.google.code.gson:gson:2.10")
  implementation("ai.asleep:asleepsdk:3.1.0")
}
dependencies {
		...
    implementation 'androidx.lifecycle:lifecycle-service:2.8.7'
    implementation 'com.squareup.okhttp3:okhttp:4.11.0'
    implementation 'com.google.code.gson:gson:2.10'
    implementation 'ai.asleep:asleepsdk:3.1.0'
}

Sleep Tracking with AsleepTrack SDK

κΆŒν•œ νšλ“

  • ν•„μˆ˜ κΆŒν•œμΈ RECORD_AUDIO와 POST_NOTIFICATIONS(Android 13 이상)을 νšλ“ν•©λ‹ˆλ‹€.
class MainActivity : AppCompatActivity() {
  
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...

    ActivityCompat.requestPermissions(
      this@MainActivity,
      arrayOf(android.Manifest.permission.RECORD_AUDIO,
              android.Manifest.permission.POST_NOTIFICATIONS),
      0)
      ...
  }

UI ꡬ성

  • 화면에 λ³΄μ—¬μ§ˆ init, begin, end, report의 λ²„νŠΌμ„ λ§Œλ“­λ‹ˆλ‹€.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_init"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="200dp"
        android:text="init"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_begin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="32dp"
        android:text="begin"
        app:layout_constraintEnd_toStartOf="@+id/btn_end"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_init" />

    <Button
        android:id="@+id/btn_end"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:text="end"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/btn_begin"
        app:layout_constraintTop_toBottomOf="@+id/btn_init" />

    <Button
        android:id="@+id/btn_report"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:text="report"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_init" />
          
</androidx.constraintlayout.widget.ConstraintLayout>
  • λ²„νŠΌμ„ λ³€μˆ˜λ‘œ μ„ μ–Έν•©λ‹ˆλ‹€.
    ...
    private lateinit var btnInit: Button
    private lateinit var btnBegin: Button
    private lateinit var btnEnd: Button
    private lateinit var btnReport: Button

    ...
    btnInit = findViewById(R.id.btn_init)
    btnBegin = findViewById(R.id.btn_begin)
    btnEnd = findViewById(R.id.btn_end)
    btnReport = findViewById(R.id.btn_report)

AsleepTrack SDK μ΄ˆκΈ°ν™”

  • apiKey의 [YOUR API KEY]μ—λŠ” μ‹€μ œ λ°œκΈ‰λ°›μ€ apiKeyλ₯Ό μž…λ ₯ν•©λ‹ˆλ‹€.
  • userIdκ°€ null인 경우, SDKμ—μ„œ μƒˆλ‘œμš΄ userIdλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
    μ‹€μ œ μ•± κ΅¬ν˜„ μ‹œ, μƒμ„±λœ userIdλŠ” μ €μž₯ ν›„ ν•„μš” μ‹œ κ°€μ Έμ™€μ„œ μž…λ ₯ν•˜λŠ” λ°©μ‹μœΌλ‘œ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • serviceλŠ” κ°œλ°œν•  μ•±μ˜ μ„œλΉ„μŠ€λͺ…이 μžˆλ‹€λ©΄ μž…λ ₯ν•˜μ„Έμš”.
  • asleepConfigListenerλŠ” μ΄ˆκΈ°ν™”μ— 성곡, μ‹€νŒ¨ μ—¬λΆ€λ₯Ό μ½œλ°±ν•©λ‹ˆλ‹€.
    • μ„±κ³΅μ‹œ userId와 asleepConfigκ°€ λ°˜ν™˜λ©λ‹ˆλ‹€.
val TAG = "[AsleepSDK]"
private var createdUserId: String? = null
private var createdAsleepConfig: AsleepConfig? = null
private var createdSessionId: String? = null

...
btnInit.setOnClickListener {
  Asleep.initAsleepConfig(
    context = this,
    apiKey = "[YOUR API KEY]",
    userId = null,
    service = "Test App",
    asleepConfigListener = object: Asleep.AsleepConfigListener {
      override fun onFail(errorCode: Int, detail: String) {
        Log.d(TAG, "initAsleepConfig onFail $errorCode $detail")
      }

      override fun onSuccess(userId: String?, asleepConfig: AsleepConfig?) {
        Log.d(TAG, "initAsleepConfig onSuccess $userId")
        createdUserId = userId
        createdAsleepConfig = asleepConfig
      }
    })
}

Begin SleepTracking

  • 수면 츑정을 μ‹œμž‘ν•©λ‹ˆλ‹€.
    1. initAsleepConfigμ—μ„œ μƒμ„±λœ asleepConfig와 수면 μΈ‘μ • μƒνƒœλ₯Ό 확인할 수 μžˆλŠ” asleepTrackingListenerλ₯Ό μž…λ ₯ν•©λ‹ˆλ‹€.
    2. 츑정이 μ‹œμž‘λ˜λ©΄ onStart() 콜백이 ν˜ΈμΆœλ©λ‹ˆλ‹€.
    3. λ§€ 30μ΄ˆλ§ˆλ‹€ onPerform() 콜백이 ν˜ΈμΆœλ˜μ–΄ μ§„ν–‰ μƒνƒœλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.
    4. 츑정이 μ’…λ£Œλ˜λ©΄ onFinish() 콜백이 ν˜ΈμΆœλ©λ‹ˆλ‹€.
btnBegin.setOnClickListener {
  createdAsleepConfig?.let { asleepConfig ->

    Asleep.beginSleepTracking(
      asleepConfig = asleepConfig,
      asleepTrackingListener = object : Asleep.AsleepTrackingListener {
        override fun onFail(errorCode: Int, detail: String) {
          Log.d(TAG, "beginSleepTracking onFail $errorCode $detail")
        }

        override fun onFinish(sessionId: String?) {
          Log.d(TAG, "beginSleepTracking onFinish $sessionId")
        }

        override fun onPerform(sequence: Int) {
          Log.d(TAG, "beginSleepTracking onPerform $sequence")
        }

        override fun onStart(sessionId: String) {
          Log.d(TAG, "beginSleepTracking onStart $sessionId")
          createdSessionId = sessionId
        }
      })
  }
}

End SleepTracking

  • 수면 츑정을 μ’…λ£Œν•©λ‹ˆλ‹€.
btnEnd.setOnClickListener {
  Asleep.endSleepTracking()
}

Get Report

  • μΈ‘μ •λœ 수면 데이터λ₯Ό λ°›μ•„μ˜΅λ‹ˆλ‹€.
    1. initAsleepConfigμ—μ„œ μƒμ„±λœ asleepConfigλ₯Ό νŒŒλΌλ©”ν„°λ‘œ μž…λ ₯ν•˜μ—¬ Reports 객체λ₯Ό λ§Œλ“­λ‹ˆλ‹€.
    2. 수면 μΈ‘μ • μ‹œ μƒμ„±λœ sessionIdλ₯Ό μž…λ ₯ν•©λ‹ˆλ‹€.
btnReport.setOnClickListener {
  createdSessionId?.let { sessionId ->

    val reports = Asleep.createReports(createdAsleepConfig)

    reports?.getReport(
      sessionId = sessionId,
      reportListener = object : Reports.ReportListener {
        override fun onFail(errorCode: Int, detail: String) {
          Log.d(TAG, "getReport onFail $errorCode $detail")
        }
        override fun onSuccess(report: Report?) {
          Log.d(TAG, "getReport onSuccess $report")
        }
      })
  }
}

Logcat 확인

  • logcat λ‘œκ·Έμ— μ•„λž˜μ™€ 같이 userId, sessionId, reportκ°€ μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜μ—ˆλ‹€λ©΄, SDKκ°€ μ •μƒμ μœΌλ‘œ λ™μž‘ν•˜κ³  μžˆλŠ” κ²ƒμž…λ‹ˆλ‹€.

πŸ“˜

μ•± κ°œλ°œμ‹œ 주의점

  • ν¬κ·ΈλΌμš΄λ“œ μ„œλΉ„μŠ€ μ‚¬μš©
    수면 츑정을 μœ„ν•΄, 앱은 λ°€ λ™μ•ˆ 지속적인 λ ˆμ½”λ”©, 데이터 처리, λ„€νŠΈμ›Œν¬ μž‘μ—… 등을 μˆ˜ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ μž‘μ—…μ΄ μ€‘λ‹¨λ˜μ§€ μ•Šλ„λ‘ ν•˜κΈ° μœ„ν•΄, ν¬κ·ΈλΌμš΄λ“œ μ„œλΉ„μŠ€(Foreground Service)λ₯Ό ν™œμš©ν•˜μ—¬ 앱을 κ°œλ°œν•˜λŠ” 것이 ν•„μˆ˜μ μž…λ‹ˆλ‹€.
    더 μžμ„Έν•œ λ‚΄μš©μ€ Android ν¬κ·ΈλΌμš΄λ“œ μ„œλΉ„μŠ€ κ°€μ΄λ“œλ₯Ό μ°Έμ‘°ν•˜μ„Έμš”.
    저희 SDKλŠ” 두 κ°€μ§€ 개발 방식을 μ§€μ›ν•©λ‹ˆλ‹€
    1. Begin-End 방식
      ν¬κ·ΈλΌμš΄λ“œ μ„œλΉ„μŠ€λ₯Ό SDK 내뢀에 ν¬ν•¨ν•˜μ—¬, 수면 μΈ‘μ •κ³Ό κ΄€λ ¨λœ λͺ¨λ“  μž‘μ—…μ„ μΆ”μƒν™”ν–ˆμŠ΅λ‹ˆλ‹€.
      κ°œλ°œμžλŠ” ν¬κ·ΈλΌμš΄λ“œ μ„œλΉ„μŠ€λ₯Ό μ•±μ—μ„œ κ΅¬ν˜„ν•  ν•„μš”κ°€ μ—†κ³  beginSleepTracking() 및 endSleepTracking() ν•¨μˆ˜λ§Œ ν˜ΈμΆœν•˜μ—¬ κ°„νŽΈν•˜κ²Œ 수면 μΈ‘μ • κΈ°λŠ₯을 κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€. Begin-End 방식 μƒ˜ν”Œ 앱을 μ°Έκ³ ν•˜μ„Έμš”.
    2. SleepTrackingManager 방식
      ν¬κ·ΈλΌμš΄λ“œ μ„œλΉ„μŠ€λ₯Ό 직접 κ΅¬ν˜„ν•˜μ—¬ 더 λ§Žμ€ μ œμ–΄λ₯Ό μ œκ³΅ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€.
      이 방식은 λ‹€μ†Œ λ³΅μž‘ν•  수 μžˆμœΌλ‚˜, 슬립 μŠ€ν…Œμ΄μ§€(Sleep Stage)에 따라 ν•˜λ“œμ›¨μ–΄λ₯Ό μ œμ–΄ν•˜κ±°λ‚˜ νŠΉμ • μ»€μŠ€ν…€ μž‘μ—…μ΄ ν•„μš”ν•œ 경우 μ ν•©ν•©λ‹ˆλ‹€. FGS ν”„λ‘œμ„ΈμŠ€ 뢄리 μƒ˜ν”Œ 앱을 μ°Έκ³ ν•˜μ„Έμš”.
      κ°œλ°œν•˜λ €λŠ” μ•±μ˜ μš”κ΅¬ 사항에 따라 μ ν•©ν•œ 방식을 μ„ νƒν•˜μ„Έμš”.
  • 인앱 μ—…λ°μ΄νŠΈ κΈ°λŠ₯ ꢌμž₯ 인앱 μ—…λ°μ΄νŠΈ κ°€μ΄λ“œ
    1. μ•ˆλ“œλ‘œμ΄λ“œ μ‚¬μš©μž 섀정에 따라 μˆ˜λ©΄μΈ‘μ • μ‹œμž‘ ν›„ μ•± μ—…λ°μ΄νŠΈκ°€ μžλ™μœΌλ‘œ μ΄λ€„μ§€κ²Œ 되면 μ˜λ„μΉ˜ μ•Šμ€ μ•± μ’…λ£Œλ‘œ 수면 츑정쀑 비정상 μ’…λ£Œκ°€ λ°œμƒν•˜κ²Œ λ©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 상황을 λ°©μ§€ν•˜κ³ μž 미리 μ—…λ°μ΄νŠΈλ₯Ό 받을 수 μžˆλŠ” 인앱 μ—…λ°μ΄νŠΈλΌλŠ” κΈ°λŠ₯을 μ‚¬μš©ν•˜κ²Œ 되면 μ˜λ„μΉ˜ μ•Šμ€ 상황을 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€.
  • 배터리 μ΅œμ ν™” μ˜ˆμ™Έ κΆŒν•œ μ•ˆλ‚΄ doze-standby
    1. 수면 μΈ‘μ •μ˜ μ•ˆμ •μ„±μ„ 보μž₯ν•˜κΈ° μœ„ν•΄ 배터리 μ΅œμ ν™” μ˜ˆμ™Έ κΆŒν•œμ΄ ν•„μš”ν•©λ‹ˆλ‹€.
      이 κΆŒν•œμ€ 수면 μΈ‘μ • 쀑 Doze λͺ¨λ“œλ‘œ μ „ν™˜λ˜λŠ” 것을 λ°©μ§€ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λ©°, REQUEST_IGNORE_BATTERY_OPTIMIZATIONS κΆŒν•œμ„ 톡해 μ„€μ •λ©λ‹ˆλ‹€.