# 안드로이드의 부동 창 2개: 프론트 데스크 서비스

Facebook Heads와 다른 앱에서 사용하는 부동 창을 어떻게 만드는지 생각해 보셨나요?너는 너의 응용 프로그램에서 같은 기술을 사용할 생각을 했니?이것은 매우 간단하다. 나는 너에게 전체 과정을 완성하도록 지도할 것이다.
나는 Floating Apps의 저자이다.구글플레이에서 동종 앱 최초로 다운로드 800만 뷰를 넘긴 가장 인기 있는 앱이다.6년간의 응용 개발을 거쳐 나는 그것에 대해 어느 정도 알고 있다.때때로 나는 몇 달 동안 문서와 안드로이드 소스 코드를 읽고 실험을 했다.나는 수만 명의 사용자의 피드백을 받았고, 서로 다른 버전의 안드로이드 휴대전화에서 각양각색의 문제를 보았다.
다음은 내가 가는 길에 배운 것이다.
본문을 읽기 전에 먼저 읽는 것을 권장합니다Floating Windows on Android 1: Jetpack Compose & Room.
본고에서 부동 창에 필요한 장기적인 프론트 데스크톱 서비스를 구축하는 방법과 한계를 가르쳐 드리겠습니다.

서비스
부동 기술에 대해서는 Service가 아니라 Activity가 있어야 한다.안드로이드 프론트 데스크톱은 하나Activity만 있을 수 있기 때문에 Activity를 사용하면 다른 프로그램이 중단되거나 다시 시작됩니다.이것은 우리가 원하는 행동이 아니다. 우리는 어떤 방식으로도 현재의 임무를 중단하고 싶지 않다.
표준 안드로이드 서비스는 장시간 운행을 위해 설계된 것이 아니다.그것들의 설계 목적은 백그라운드에서 임무를 완성하는 것이다.
우리Service가 안드로이드 시스템에 의해 죽는 것을 피하기 위해서는 프론트 데스크 서비스를 사용하는 것이 가장 좋다.
저희의 특정한 간단한 응용 프로그램에 대해 저희는 시종일관 실행하고 영구적인 알림을 보여주는 서비스를 사용합니다.응용 프로그램에 있어서, 일부 부동 창이 활성 상태일 때만 서비스를 실행하는 것이 더 좋은 방법일 수 있습니다.
마법은 덮어쓰기onStartCommand 방법에 숨겨져 있고 정확하게 되돌아오기START_STICKYSTART_NOT_STICKY.본문의 원본 코드는 다음과 같다.

제한성

알림 표시
프론트 데스크톱 서비스는 서비스가 시작된 후 즉시 영구/프론트 데스크톱 알림을 표시해야 한다.만약 우리가 이 점을 하지 못한다면 프로그램은 종료될 것이다.
일부 장치에서, 이것은 간혹 붕괴를 초래할 수 있다. 왜냐하면, 이 과정은 하드코딩 간격보다 조금 긴 시간이 필요할 수 있기 때문이다.
먼저 프론트 데스크 알림을 표시해야 합니다.본문의 원본 코드는 다음과 같다.
그 밖에 일부 사용자들은 영구적인 알림을 표시하는 것을 좋아하지 않을 뿐이지만, 우리는 몇 가지 일을 할 수 있다.일부 장치에서는 휴대전화 설정에 알림을 숨길 수 있다.

몇몇 설비에서 살해되다
일부 휴대전화와 태블릿PC에서는 공급업체가 적극적인 메모리와 프로세스 관리를 통합했기 때문에 서비스가 억제되는 것을 피할 수 없다.
이 화제에 관한 좋은 사이트가 하나 있다. Don't kill my app!

허가하다
Android API 레벨 28부터 프런트 데스크 서비스에 추가 권한이 필요합니다.AndroidManifest.xml에 다음 행을 추가합니다.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

통지하다
안드로이드 O에서는 서비스가 안드로이드에 의해 억제되지 않도록 모든 경험에 따라 영구적인 알림이 필요합니다.
프론트 데스크톱에서 알려주는 전체 소스 코드, 서비스 정지 코드 포함:
/**  
 * Remove the foreground notification and stop the service. 
 */
private fun stopService() {  
  stopForeground(true)  
  stopSelf()  
}

/**
 * Create and show the foreground notification.
 */
private fun showNotification() {

  val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

  val exitIntent = Intent(this, FloatingService::class.java).apply {
    putExtra(INTENT_COMMAND, INTENT_COMMAND_EXIT)
  }

  val noteIntent = Intent(this, FloatingService::class.java).apply {
    putExtra(INTENT_COMMAND, INTENT_COMMAND_NOTE)
  }

  val exitPendingIntent = PendingIntent.getService(
    this, CODE_EXIT_INTENT, exitIntent, 0
  )

  val notePendingIntent = PendingIntent.getService(
    this, CODE_NOTE_INTENT, noteIntent, 0
  )

  // From Android O, it's necessary to create a notification channel first.
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    try {
      with(
        NotificationChannel(
          NOTIFICATION_CHANNEL_GENERAL,
          getString(R.string.notification_channel_general),
          NotificationManager.IMPORTANCE_DEFAULT
        )
      ) {
        enableLights(false)
        setShowBadge(false)
        enableVibration(false)
        setSound(null, null)
        lockscreenVisibility = Notification.VISIBILITY_PUBLIC
        manager.createNotificationChannel(this)
      }
    } catch (ignored: Exception) {
      // Ignore exception.
    }
  }

  with(
    NotificationCompat.Builder(
      this,
      NOTIFICATION_CHANNEL_GENERAL
    )
  ) {
    setTicker(null)
    setContentTitle(getString(R.string.app_name))
    setContentText(getString(R.string.notification_text))
    setAutoCancel(false)
    setOngoing(true)
    setWhen(System.currentTimeMillis())
    setSmallIcon(R.drawable.ic_launcher_foreground)
    priority = Notification.PRIORITY_DEFAULT
    setContentIntent(notePendingIntent)
    addAction(
      NotificationCompat.Action(
        0,
        getString(R.string.notification_exit),
        exitPendingIntent
      )
    )
    startForeground(CODE_FOREGROUND_SERVICE, build())
  }

}
우리의 통지는 영구적이어서 취소할 수 없다.클릭하면 INTENT COMMAND NOTE 명령이 실행됩니다.또한 INTENT 명령 exit를 호출하기 위한 종료 작업도 있습니다.

OnStartCommand
앞에서 말한 바와 같이 서비스의 신기한 행위는 내부에 숨겨져 있다onStartCommand.간단합니다.
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {

  val command = intent.getStringExtra(INTENT_COMMAND) ?: ""

  // Exit the service if we receive the EXIT command.
  // START_NOT_STICKY is important here, we don't want 
  // the service to be relaunched. 
  if (command == INTENT_COMMAND_EXIT) {
    stopService()
    return START_NOT_STICKY
  }

  // Be sure to show the notification first for all commands.
  // Don't worry, repeated calls have no effects.
  showNotification()

  // Show the floating window for adding a new note.
  if (command == INTENT_COMMAND_NOTE) {
    Toast.makeText(
      this, 
      "Floating window to be added in the next lessons.", 
      Toast.LENGTH_SHORT
    ).show()
  }

  return START_STICKY
}

서비스 및 AndroidManifest
Google 서비스에 대해서는 AndroidManifest.xml 파일의 기록이 필요합니다.
<service  
  android:name=".FloatingService"  
  android:excludeFromRecents="true"  
  android:exported="false"  
  android:label="@string/app_name"  
  android:roundIcon="@mipmap/ic_launcher_round"
  android:stopWithTask="false" />
위의 매개변수 설명:
  • android:excludeFromRecents - 최근 프로젝트 화면에 서비스가 표시되지 않습니다.
  • android:exported - 어플리케이션 외부에서 서비스에 액세스할 이유가 없습니다.
  • android:stopWithTask - 응용 프로그램이 종료될 때 서비스를 중지하지 마십시오. 예를 들어 "최근 사용한 프로젝트"화면에서 삭제됩니다.

  • 서비스 시작
    서비스를 시작하기 위해서 작은 조수 방법을 만듭니다.우리는 안드로이드 O 및 이상의 버전의 요구 사항인 사용startForegroundService을 처리해야 한다startService.
    fun Context.startFloatingService(command: String = "") {  
    
      val intent = Intent(this, FloatingService::class.java)  
      if (command.isNotBlank()) {
        intent.putExtra(INTENT_COMMAND, command)  
      }
    
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {  
        this.startForegroundService(intent)  
      } else {  
        this.startService(intent)  
      }  
    
    }
    
    다음 글은 장치가 시작될 때 서비스를 시작하는 방법을 배울 것입니다. 그러나 현재는 메인 응용 프로그램이 시작될 때 서비스를 시작하는 것을 고수합니다.이를 위해 우리는 기존MainActivityonCreate 방법에 한 줄만 추가할 수 있다.
    override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
    
      // Start the foreground service.
      startFloatingService()
    
      // ... the rest of the code ...
    
    }
    

    다중 프로세스 방법
    만약 부동 서비스가 매우 무겁다면, 간혹 붕괴를 초래할 수도 있고, 응용 프로그램과 분리해야 할 수도 있다.android:multiprocess에서 AndroidManifest.xml를 사용할 수 있으며, 서로 다른 절차에서 활동을 실행하여 서비스와 분리할 수 있습니다.
    그러나 더 많은 프로세스를 사용하려면 상태를 활동으로 동기화하는 추가 작업이 필요하고 서비스는 메모리를 공유하지 않는다는 것을 기억하십시오.

    현지화
    위에서 우리는 새로운 문자열notification_channel_general, notification_textnotification_exit를 추가했기 때문에Gradle 작업uploadStrings을 실행해서 번역을 Localazy platform에 업로드해야 합니다.
    내가 11개의 문자열을 올린 지 1분 만에 6개가 80개 언어로 준비되었다!
    애플리케이션 현지화의 중요성에 대한 자세한 내용은 Floating Windows on Android 1: Jetpack Compose & Room를 참조하십시오.

    결실
    다음 애니메이션에서는 주 애플리케이션이 Recent items 화면에서 제거된 경우에도 영구 알림을 표시하는 방법을 보여 줍니다.

    소스 코드
    본문의 모든 소스 코드는 available on Github입니다.

    기대 많이 해주세요.
    안드로이드 개발에 대한 더 많은 정보를 원하십니까?트위터에서 나()와 로컬레이지(Localazy), 혹은 유사한Localazy on Facebook을 팔로우한다.

    시리즈
    이 문서는 Android 시리즈 부동 창의 일부입니다.
  • Floating Windows on Android 1: Jetpack Compose & Room
  • Floating Windows on Android 2: Foreground Service
  • Floating Windows on Android 3: Permissions
  • Floating Windows on Android 4: Floating Window
  • Floating Windows on Android 5: Moving Window
  • Floating Windows on Android 6: Keyboard Input
  • Floating Windows on Android 7: Boot Receiver
  • Floating Windows on Android 8: The Final App
  • Floating Windows on Android 9: Shortcomings
  • Floating Windows on Android 10: Tips & Tricks
  • 좋은 웹페이지 즐겨찾기