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 ๊ถŒํ•œ์„ ํ†ตํ•ด ์„ค์ •๋ฉ๋‹ˆ๋‹ค.