꾸쀀함이 진리닀!!

μ–΄μ œλ³΄λ‹€ λ°œμ „ν•œ 였늘이 λ˜κ³ ν”ˆ πŸ§‘πŸ»β€πŸ’» 의 λΈ”λ‘œκ·Έ

Android/Kotlin

[Kotlin][Android] μ•ˆλ“œλ‘œμ΄λ“œ Service(μ„œλΉ„μŠ€)에 λŒ€ν•˜μ—¬

λŽμš” 2022. 10. 14. 16:16

μ•ˆλ…•ν•˜μ„Έμš”

μ˜€λŠ˜μ€ μ•ˆλ“œλ‘œμ΄λ“œμ˜ Service에 λŒ€ν•˜μ—¬ κ³΅λΆ€ν•œ λ‚΄μš©μ„

ν¬μŠ€νŒ…ν•΄λ³΄λ € ν•©λ‹ˆλ‹€.


Service(μ„œλΉ„μŠ€)λž€?

μΌμ •ν•œ κ°„κ²©μœΌλ‘œ λ°±κ·ΈλΌμš΄λ“œμ—μ„œ μ‹€ν–‰λ˜λŠ” ν”„λ‘œμ„ΈμŠ€ 즉, ν™”λ©΄μ—μ„œλŠ” 보이지 μ•Šμ§€λ§Œ

λ’€μ—μ„œ μ§€μ†μ μœΌλ‘œ ν•΄μ•Ό ν•˜λŠ” μž‘μ—…μ„ μœ„ν•΄μ„œ μ‚¬μš©ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈμž…λ‹ˆλ‹€.

 

βœ”οΈ μ„œλΉ„μŠ€λŠ” λ‘œμ»¬μ—μ„œ μˆ˜ν–‰λ˜λŠ” μ„œλΉ„μŠ€( startService( ) )와 μ›κ²©μ—μ„œ μ‹€ν–‰λ˜λŠ” μ„œλΉ„μŠ€( bindService( ) )κ°€ 쑴재 


Service(μ„œλΉ„μŠ€)의 생λͺ…μ£ΌκΈ°

service의 생λͺ…μ£ΌκΈ°

 

μœ„ μ΄λ―Έμ§€λŠ” μ„œλΉ„μŠ€μ˜ 생λͺ…μ£ΌκΈ°λ₯Ό λ‚˜νƒ€λ‚˜λŠ” μ΄λ―Έμ§€μž…λ‹ˆλ‹€.

μ„œλΉ„μŠ€λ„ Activity와 λ§ˆμ°¬κ°€μ§€λ‘œ 생λͺ…μ£ΌκΈ°κ°€ μ‘΄μž¬ν•˜λ©° 둜컬 μ„œλΉ„μŠ€μ™€ 원격 μ„œλΉ„μŠ€μ˜ 생λͺ…μ£ΌκΈ°λŠ” μ„œλ‘œ λ‹€λ₯Έ 것을 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

 

Localμ—μ„œ λ™μž‘ν•˜λŠ” μ„œλΉ„μŠ€μ˜ 경우

startService()둜 μ„œλΉ„μŠ€λ₯Ό μ‹œμž‘ν•˜λŠ” 경우 

onCreate() -> onStartCommnad() -> μ„œλΉ„μŠ€ stop -> onDestroy()둜 λ™μž‘ν•˜λ©°

이미 μ„œλΉ„μŠ€κ°€ μ‹€ν–‰ 쀑인 κ²½μš°μ—λŠ” onCreate()λ₯Ό μ‹€ν–‰ν•˜μ§€ μ•Šκ³  λ°”λ‘œ onStartCommnad()λ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€.

 

Remote( 원격 )μ—μ„œ λ™μž‘ν•˜λŠ” μ„œλΉ„μŠ€μ˜ 경우

bindService()λ₯Ό ν˜ΈμΆœν•˜λŠ” κ²½μš°μ—λŠ”

onCreate -> onBind() -> μ„œλΉ„μŠ€ stop -> onUnbind() -> (μ—°κ²°λ˜μ–΄ μžˆλŠ” ν”„λ‘œμ„ΈμŠ€κ°€ μ—†λ‹€λ©΄) onDestroy()

둜 λ™μž‘ν•˜λ©° onStartCommand()λŠ” ν˜ΈμΆœν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

 


ForegroundServiceλž€?

λ°±κ·ΈλΌμš΄λ“œμ— μ„œλΉ„μŠ€κ°€ μ‚¬μš©μžμ—κ²Œ 톡지(Notification)ν•˜μ—¬

μ‹œμŠ€ν…œμ΄ μ„œλΉ„μŠ€λ₯Ό 쀑단할 ν›„λ³΄λ‘œ κ³ λ €ν•˜μ§€ μ•ŠλŠ” μ„œλΉ„μŠ€(μ‚¬μš©μžμ—κ²Œ μ„œλΉ„μŠ€λ₯Ό 쀑지할 것인지 선택)

βœ”οΈAndroid Oreo : 8λΆ€ν„°λŠ” 의무적으둜 κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€.

startForegroundService()둜 μ‹€ν–‰

 


λ§ˆμ§€λ§‰μœΌλ‘œ κ°„λ‹¨ν•œ 예제λ₯Ό λ³΄λ©΄μ„œ μ„œλΉ„μŠ€μ˜ μ‚¬μš©λ²•μ„ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€. 😎

 

< activity_main.xml μ½”λ“œ >

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <Button
        android:id="@+id/startBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="μ„œλΉ„μŠ€μ‹œμž‘"/>
    <Button
        android:id="@+id/stopBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/startBtn"
        android:layout_centerHorizontal="true"
        android:text="μ„œλΉ„μŠ€μ’…λ£Œ"/>
</RelativeLayout>

 

< MainActivity.kt μ½”λ“œ >

import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.pyo.service.lifecycle.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private val TAG = "MainActivity"
    private lateinit var serviceIntent : Intent
    private lateinit var binding : ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "onCreate()")
        binding = ActivityMainBinding.inflate(layoutInflater).also {
            setContentView(it.root)
        }
        serviceIntent = Intent(this@MainActivity , NewsService::class.java)
        binding.run {
            startBtn.setOnClickListener { executeButtonEvent(it) }
            stopBtn.setOnClickListener {  executeButtonEvent(it) }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy()")
        stopService(serviceIntent) // μ•± μ’…λ£Œμ‹œμ— μ„œλΉ„μŠ€λ„ ν•¨κ»˜ μ’…λ£Œ
    }

    private fun executeButtonEvent(view : View) { // Button 이벀트 μ„€μ • ν•¨μˆ˜
        when(view.id){
            R.id.startBtn -> {
                serviceIntent.putExtra("newSubhect" , 3)
                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ 
                // Android 8μ΄μƒμ—μ„œλŠ” ForegroundService둜 μ‹€ν–‰
                    startForegroundService(serviceIntent)
                }
                else {
                    startService(serviceIntent)
                }
            }
            R.id.stopBtn -> {
                stopService(serviceIntent)
            }
            else -> stopService(serviceIntent)
        }
    }
}

 

< NewsService.kt >

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.core.app.NotificationCompat
import kotlin.random.Random

const val CHANNEL_ID = "news_channel_id"
const val CHANNEL_NAME = "news_desk"

class NewsService : Service() {
  private val TAG = "NewService"
  private var extraValue = 0
  private var mHandler = Handler(Looper.getMainLooper())
  private lateinit var mSubThread : BackgroundSubThread
  
  override fun onBind(p0: Intent?): IBinder? {
    Log.d(TAG, "onBind() ")
    return null
  }

  override fun onCreate() {
    super.onCreate()
    Log.d(TAG, "onCreate()")
    Toast.makeText(applicationContext , "μ„œλΉ„μŠ€λ₯Ό μ‹œμž‘ν•©λ‹ˆλ‹€." , Toast.LENGTH_SHORT).show()
    displayNotification()
    mSubThread = BackgroundSubThread("subThread")
  }

  override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    super.onStartCommand(intent, flags, startId)
    Log.d(TAG, "onStartCommand()")
    extraValue = intent!!.getIntExtra("newSubject" , 0)
    if(!::mSubThread.isInitialized){
      mSubThread = BackgroundSubThread("subThread")
    }
    if(!mSubThread.isAlive){
      mSubThread.start()
    }
    return START_STICKY
  }

  override fun onDestroy() {
    super.onDestroy()
    Log.d(TAG, "onDestroy()")
    if(mSubThread.isAlive){
      mSubThread.interrupt()
    }
    stopSelf()
  }

  inner class BackgroundSubThread(threadName : String) : Thread(threadName){
    private val newsMap : java.util.HashMap<Int , String> = java.util.HashMap()
    private val random : Random = Random(System.currentTimeMillis())
    private var newsMessage : String? = null

    init {
        newsMap[0] = "first news"
        newsMap[1] = "second news"
        newsMap[2] = "third news"
        newsMap[3] = "fourth news"
    }

    override fun run() {
      super.run()
      while (!isInterrupted){
        newsMessage = newsMap[extraValue]
        try {
            mHandler.post {
              Toast.makeText(applicationContext, newsMessage, Toast.LENGTH_SHORT).show()
            }
          extraValue = random.nextInt(4)
          sleep(3000)
        } catch (e : InterruptedException){
          currentThread().interrupt()
        }
      }
    }
  }

  private fun displayNotification() { // Status bar에 μ„œλΉ„μŠ€ 싀행쀑을 notify ν•˜κΈ° μœ„ν•œ ν•¨μˆ˜
    val intent = Intent(this , MainActivity::class.java)
    val pIntent = PendingIntent.getActivity(this , 1004 , intent , PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
      val channel = NotificationChannel(CHANNEL_ID , CHANNEL_NAME , NotificationManager.IMPORTANCE_DEFAULT)
      (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)
      val builder = NotificationCompat.Builder(this , CHANNEL_ID)
      builder.setSmallIcon(R.mipmap.ic_launcher_round).setContentTitle("NewService").setContentText("μ„œλΉ„μŠ€κ°€ μ‹€ν–‰μ€‘μž…λ‹ˆλ‹€!").setContentIntent(pIntent)
      startForeground(12345 , builder.build())
    }
  }
}

 

μœ„ μ˜ˆμ œλŠ” NewsServiceλΌλŠ” μ„œλΉ„μŠ€λ₯Ό ν•˜λ‚˜ μƒμ„±ν•˜μ—¬

μ„œλΉ„μŠ€ λ‚΄λΆ€μ—μ„œ SubThreadλ₯Ό 톡해 3초 κ°„κ²©μœΌλ‘œ λ‰΄μŠ€λ₯Ό Toast ν•˜λŠ” κ°„λ‹¨ν•œ μ•±μž…λ‹ˆλ‹€.

 

Android 8 μ΄μƒλΆ€ν„°λŠ” ForegroundService둜 μ‹€ν–‰ν•˜μ—¬μ•Ό ν•˜κΈ°μ—

SDK 버전을 μ²΄ν¬ν•˜μ—¬ μ„œλΉ„μŠ€ μ‹€ν–‰ν•˜λŠ” ν•¨μˆ˜λ₯Ό λ‹€λ₯΄κ²Œ ν˜ΈμΆœν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

MainActivityμ—μ„œλŠ” λ²„νŠΌμ„ 톡해 μ„œλΉ„μŠ€λ₯Ό μ‹€ν–‰ 및 μ’…λ£Œμ‹œν‚¬ 수 μžˆλ„λ‘ κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

 

이와 같이 μ„œλΉ„μŠ€λŠ” λ°±κ·ΈλΌμš΄λ“œμ—μ„œ μ§€μ†μ μœΌλ‘œ μˆ˜ν–‰ν•˜κ³  싢은 μž‘μ—…μ΄ μžˆμ„ 경우 μœ μš©ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

μ‹€ν–‰ ν™”λ©΄