Post

[안드로이드] 03. Service / Notification

Android Service & Notification

Android Service

  • 서비스의 특징
    • 액티비티의 생명주기에 종속되어 있지 않음
    • 호스팅 프로세스의 메인 스레드에서 작동
    • 액티비티가 비활성화되면 액티비티보다 우선순위가 높음
  • startService vs bindService

    startServicebindService
    앱 내의 액티비티같은 컴포넌트가 호출됐을 때 실행됨자신을 호출했던 컴포넌트와 interaction을 주고 받을 수 있음
    백그라운드에서 한 가지 일을 하며, 결과를 호출했던 컴포넌트에게 보내지 않음처리한 결과를 주고 받을 수 있음
    startForegroundService()로도 시작 가능(API Level 26 이상)서로 다른 프로세스 상에 있어도 처리 가능
  • startService

    • 컴포넌트에서 호출

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
      val intent = Intent(this, MyService::class.java)
          
      binding.startServiceBtn.setOnClickListener {
          intent.putExtra("hello", "from Activity")
          startService(intent)
          Log.d(TAG, "onCreate: startService()")
      }
          
      binding.stopServiceBtn.setOnClickListener {
          stopService(intent);
          Log.d(TAG, "onCreate: StopService()")
      }
      
    • MyService::class.java

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      class MyService : Service() {
          
          override fun onCreate() {
              super.onCreate()
              Log.d(TAG, "onCreate: ")
          }
          
          override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
              Log.d(TAG, "onStartCommand: ")
      //        return super.onStartCommand(intent, flags, startId)
              return START_STICKY
          }
          
          override fun onBind(intent: Intent): IBinder {)
          }
          
          override fun onDestroy() {
              super.onDestroy()
              Log.d(TAG, "onDestroy: ")
          }
      }
      
    • return 값

      • START_NOT_STICKY: 서비스가 시스템에 의해 종료되었을 때, PendingIntent가 따로 있지 않으면 다시 생성 X
      • START_STICKY: 시스템에 의해 종료되어도 다시 서비스가 생성되지만, 마지막 인텐트를 전해주지는 않음(default)
      • START_REBUILDER_INTENT: 시스템에 의해 종료되어도 서비스 + 전달되었던 인텐트 값까지 전부 유지됨

  • bindService

    • 작동 원리
      • startService로 서비스 실행, bindService()로 이를 binding
      • 위 둘을 모두 제공하는 서비스는 onStartCommand(), onBind() 를 모두 구현해야함
      • stopSelf() 또는 stopService()도 호출해주어야 함
    • 구현 방식
      • Local
      • Local이 아닌 경우(다른 프로세스에서 실행): Messenger, Handler를 사용
      • Local이 아닌 경우(다른 프로세스에서 실행): AIDL을 사용

Foreground Service

  • AndroidManifest.xml 설정

    1
    2
    3
    4
    5
    6
    7
    8
    
    <!-- push전송 permission -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- foreground 서비스 권한 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
      
    <service
            android:name=".MyForegroundService"
            android:foregroundServiceType="mediaPlayback" />
    
    • 서비스 타입(foregroundServiceType)을 지정해야 함
  • 컴포넌트에서 서비스 호출

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    // 알림 허용 팝업권한 체크 & 팝업 띄우기.
    val permission = ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS)
    Log.d(TAG, "onCreate: $permission")
    if(permission != PackageManager.PERMISSION_GRANTED){
        ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.POST_NOTIFICATIONS), 100)
    }
      
    val intent = Intent(this, MyForeService::class.java)
      
    binding.startServiceBtn.setOnClickListener {
        intent.putExtra("hello", "from Activity")
        startService(intent)
        Log.d(TAG, "onCreate: startService()")
    }
      
    binding.stopServiceBtn.setOnClickListener {
        stopService(intent);
        Log.d(TAG, "onCreate: StopService()")
    }
    
  • MyForeService::class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    private fun startForegroundService() {
            val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
            val notificationIntent = Intent(this, StartServiceActivity::class.java)
            val pendingIntent =
                PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
            val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val CHANNEL_ID = "notification_channel"
                val channel = NotificationChannel(
                    CHANNEL_ID,
                    "Notification Channel",
                    NotificationManager.IMPORTANCE_LOW // 알람만 (소리 X)
                )
                manager.createNotificationChannel(channel)
                NotificationCompat.Builder(this, CHANNEL_ID)
            } else {
                NotificationCompat.Builder(this)
            }
      
            builder.setSmallIcon(R.drawable.ic_outline_play_arrow_24)
                .setContentTitle("MyMusic Foreground")
                .setContentText("music play.....")
                .setAutoCancel(true)
                .setContentIntent(pendingIntent)
            startForeground(101, builder.build())
    }
    

AIDL

  • 동작 과정

    • aidl 파일 생성

    • 인터페이스 구현

    • Activity에서 서비스 바인딩

      • onBind()를 오버라이딩하여 Stub 추상클래스를 상속받은 클래스의 객체를 생성하여 반환

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        
        val my= object: IMyAidlInterface.Stub(){
            override fun getCurrentTime(): String {
                return "현재 시간은: ${Date()}"
            }
        }
              
        override fun onBind(p0: Intent?): IBinder? {
            Log.d(TAG, "onBind: ")
            return my
        }
        
      • 액티비티에서 구현한 서비스 바인딩

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        
        override fun onStart() {
            super.onStart()
            if (!bound) {
                Log.d(TAG, "onStart: ")
                //Component Name 생성하고, bindService  호출.
                var target = ComponentName("com.android.service.aidl_binder", "com.android.service.aidl_binder.AIDLService")
                val intent= Intent()
                intent.setComponent(target)
                bindService(intent, connection, Context.BIND_AUTO_CREATE)
            }
        }
        

Notification

  • AndroidManifest.xml 에서 권한 설정

    1
    
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    
  • Notification 구현

    • Channel 생성

      1
      2
      3
      4
      5
      6
      
      fun createNotification(){
      		val channelId = "$packageName-$localClassName"
          val title = "Simple Notification"
          val content = "This is simple android notification"
          ...
      }
      
    • Notification 생성

      1
      2
      3
      
      val intent = Intent(applicationContext, SimpleNotification::class.java)
      intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
      val pendingIntent = PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_IMMUTABLE)
      
    • NotificationManager를 통해 전송

      1
      2
      
      val notificationManager = NotificationManagerCompat.from(this)
      notificationManager.notify( 100, builder.build())
      
This post is licensed under CC BY 4.0 by the author.