[Android] Background Limit ์ •๋ฆฌ

Minjun Kimยท2023๋…„ 10์›” 13์ผ
0

Android

๋ชฉ๋ก ๋ณด๊ธฐ
46/47
post-thumbnail

๐Ÿ“ SeSAC์˜ 'JetPack๊ณผ Kotlin์„ ํ™œ์šฉํ•œ Android App ๊ฐœ๋ฐœ' ๊ฐ•์ขŒ๋ฅผ ์ •๋ฆฌํ•œ ๊ธ€ ์ž…๋‹ˆ๋‹ค.


๐Ÿ’ฌ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ œ์•ฝ ๊ฐœ์š”

์•ˆ๋“œ๋กœ์ด๋“œ ๋ฒ„์ „์ด ์˜ฌ๋ผ๊ฐ€๋ฉด์„œ ์›๋ž˜ ๋™์ž‘ํ–ˆ๋˜ ๊ฒƒ๋“ค์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ œ์•ฝ ์ด๋ผ๋Š” ์ด์œ ๋กœ ๋ถˆ๊ฐ€๋Šฅํ•ด์ง„ ๋ถ€๋ถ„์ด ์žˆ๋‹ค. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ œ์•ฝ์ด ์–ด๋””์— ๊ฐ€ํ•ด์ง„ ๊ฒƒ์ด๊ณ , ์ด๋ฅผ ํ•ด๊ฒฐํ•  ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด์ž.

  • ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ์€ ์•กํ‹ฐ๋น„ํ‹ฐ, ์„œ๋น„์Šค, ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„, ์ปจํ…์ธ  ํ”„๋Ÿฌ๋ฐ”์ด๋” ๋กœ ๊ตฌ์„ฑ

  • ์•กํ‹ฐ๋น„ํ‹ฐ๋ฅผ ์ œ์™ธํ•˜๊ณ  ํ™”๋ฉด๊ณผ ์ƒ๊ด€์—†๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ๋ชฉ์ 

์•ˆ๋“œ๋กœ์ด๋“œ๋Š” 4๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์„œ๋น„์Šค ์™€ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„ ๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์ด๋ผ๊ณ  ๋ถ€๋ฅด๋Š”๋ฐ, ์ด ๋‘ ๋ถ€๋ถ„์— ์ œ์•ฝ์ด ๊ฐ€ํ•ด์กŒ๋‹ค.

์˜ˆ์ „์—๋Š” ์„œ๋น„์Šค์™€ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์žฅ์‹œ๊ฐ„(ํ•˜๋ฃจ์ข…์ผ) ๋Œ๋ฆด ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๋˜ํ•œ ๋ถ€ํŒ…์ด ์™„๋ฃŒ๋˜๋ฉด ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„๋ฅผ ํ†ตํ•ด ์„œ๋น„์Šค๋ฅผ ๋Œ๋ฆด ์ˆ˜๋„ ์žˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ œ์•ฝ ์— ์˜ํ•ด ์ด๊ฒƒ๋“ค์ด ๋ถˆ๊ฐ€๋Šฅ ํ•ด์กŒ๋‹ค.

  • ์„œ๋น„์Šค, ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„๋ฅผ ์ด์šฉํ•˜์—ฌ ์•ฑ์˜ ํ™”๋ฉด์ด ์ถœ๋ ฅ๋˜์ง€ ์•Š์€ ์ƒํ™ฉ์—์„œ๋„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์—…๋ฌด๋ฅผ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅ

  • ์•ˆ๋“œ๋กœ์ด๋“œ 8 ๋ฒ„์ „(API Level 26) ๋ถ€ํ„ฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ œ์•ฝ

ํ™”๋ฉด์— ์ถœ๋ ฅ๋˜์ง€ ์•Š๋Š” ์ž‘์—…(๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…)์— API 26 ๋ถ€ํ„ฐ ์ œ์•ฝ์ด ๊ฑธ๋ ธ๋‹ค.

  • ์ด์ „์— ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ–ˆ๋˜ ๊ฒƒ๋“ค์ด ๋ถˆ๊ฐ€๋Šฅํ•ด์ง€๊ธฐ ์‹œ์ž‘

  • ์‹œ์Šคํ…œ์ด ๋ถ€ํŒ…์ด ์™„๋ฃŒ๋˜์ž ๋งˆ์ž ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„๋ฅผ ์‹คํ–‰ ๊ฐ€๋Šฅ

  • ์ด ์ƒํ™ฉ์—์„œ ์„œ๋น„์Šค๋ฅผ ์‹คํ–‰์‹œ์ผœ ์–ด๋–ค ์—…๋ฌด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์•ˆ๋“œ๋กœ์ด๋“œ 8 ๋ฒ„์ „๋ถ€ํ„ฐ ๋ถˆ๊ฐ€๋Šฅ

๋Œ€ํ‘œ์ ์œผ๋กœ ๋ถ€ํŒ… ์™„๋ฃŒ ์‹œ ์„œ๋น„์Šค ๊ตฌ๋™ ์— ์ œ์•ฝ์ด ๊ฑธ๋ ธ๋‹ค. ์—ฌ์ „ํžˆ ๋ถ€ํŒ… ์™„๋ฃŒ ์‹œ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„ ๋Š” ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด ๋ฆฌ์‹œ๋ฒ„๊ฐ€ ์„œ๋น„์Šค ๋ฅผ ๊ตฌ๋™์‹œํ‚ฌ ์ˆ˜ ์—†๊ฒŒ ๋˜์—ˆ๋‹ค. ๋ฌผ๋ก  ์•กํ‹ฐ๋น„ํ‹ฐ ๋Š” ์ž˜ ์‹คํ–‰๋œ๋‹ค.

์ด๋Ÿฌํ•œ ์ œ์•ฝ์ด ์„œ๋น„์Šค ์™€ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„ ์— ๊ฐ€ํ•ด์กŒ๋‹ค.


๐Ÿ’ฌ Broadcast Limit

  • ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ธํ…ํŠธ๋ฅผ sendBroadcast() ํ•จ์ˆ˜๋กœ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

  • ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„๋ฅผ ์•”์‹œ์  ์ธํ…ํŠธ ์— ์˜ํ•ด ์‹คํ–‰ ์‹œํ‚ค๋Š” ๊ฒƒ ๊ธˆ์ง€

๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„ ์— ์ œ์•ฝ์ด ๊ฐ€ํ•ด์ง€๊ธด ํ–ˆ๋Š”๋ฐ, ๊ฒฐ๊ณผ์ ์œผ๋กœ ๊ฐœ๋ฐœ ์ฝ”๋“œ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค. ์–ผ๋งˆ๋“ ์ง€ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ํ•œ๋‹ค.

๐Ÿ““ ์•”์‹œ์  ์ธํ…ํŠธ ์ œ์•ฝ

<receiver
	android:name=".MyReceiver"
    android:enabled="true"
    android:exported="true>
    <intent-filter>
    	<action android:name="ACTION_RECEIVER"/>
    </intent-filter>
</receiver>
val intent = Intent("ACTION_RECEIVER")
sendBroadcast(intent)

Manifest ํŒŒ์ผ์— ๋ฆฌ์‹œ๋ฒ„ + ์ธํ…ํŠธ ํ•„ํ„ฐ ๋ฅผ ๋“ฑ๋กํ•˜์—ฌ ์•”์‹œ์  ์ธํ…ํŠธ๋ฅผ ๋ฐ›๋Š” ์ƒํ™ฉ์ด๋‹ค. ํ•ด๋‹น ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ณ  ์‹ถ์€ ๊ณณ์—์„œ ์ธํ…ํŠธ ํ•„ํ„ฐ์˜ ์•ก์…˜ ๋ฌธ์ž์—ด๋กœ ์ธํ…ํŠธ๋ฅผ ๋‚ ๋ฆฌ๋ฉด ์‹คํ–‰๋œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ œ์•ฝ ์œผ๋กœ ์ธํ•ด ์•”์‹œ์ ์œผ๋กœ ์‹คํ–‰์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. ์–ด๋””์—์„œ? ๋‚ด๋ถ€์—์„œ.

A์•ฑ๊ณผ B์•ฑ์ด ์žˆ๋‹ค. B์•ฑ์—์„œ ์ธํ…ํŠธ๋ฅผ ๋‚ ๋ ค A์•ฑ์˜ ๋ฆฌ์‹œ๋ฒ„๋ฅผ ์•”์‹œ์ ์œผ๋กœ ์‹คํ–‰์‹œํ‚ค๋Š” ๊ฑด ์ž˜ ๋œ๋‹ค. ํ•˜์ง€๋งŒ A์•ฑ์—์„œ ์ž์‹  ๋‚ด๋ถ€์˜ ๋ฆฌ์‹œ๋ฒ„๋ฅผ ์•”์‹œ์ ์œผ๋กœ ์‹คํ–‰์‹œํ‚ค๋Š” ๊ฒƒ์€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

์ฆ‰, ๋ณธ์ธ์˜ ๋ฆฌ์‹œ๋ฒ„๋Š” ํด๋ž˜์Šค ๋ช…์„ ์ž‘์„ฑํ•ด์„œ ๋ช…์‹œ์  ์ธํ…ํŠธ๋กœ ์‹คํ–‰์‹œํ‚ค๋ผ๋Š” ์ œ์•ฝ์ด๋‹ค.
๋‚ด๋ถ€ ๋ฆฌ์‹œ๋ฒ„์— ์•”์‹œ์  ์ธํ…ํŠธ๋ฅผ ๋‚ ๋ฆฌ๋ฉฐ ๋กœ๊ทธ์ฐฝ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์—๋Ÿฌ๊ฐ€ ๋œฌ๋‹ค.

  • ์•”์‹œ์  ์ธํ…ํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ฉด ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๊ณ  ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ

Background execution not allowed: receiving Intent { act=ACTION_RECEIVER flg=0x10 } to
com.example.test


๐Ÿ“Œ ๋™์  ๋ฆฌ์‹œ๋ฒ„ ๋“ฑ๋ก

  • AndroidManifest.xml ํŒŒ์ผ์— ๋“ฑ๋ก๋œ ๋ฆฌ์‹œ๋ฒ„๋ฅผ ์•”์‹œ์  ์ธํ…ํŠธ๋กœ ์‹คํ–‰์‹œํ‚ค๋Š” ๊ฒฝ์šฐ ๊ธˆ์ง€

  • ๋ช…์‹œ์ ์œผ๋กœ ์‹คํ–‰์‹œํ‚ค๋Š” ๊ฒƒ์€ ๊ฐ€๋Šฅ

  • ์ฝ”๋“œ์—์„œ registerReceiver() ๋กœ ๋“ฑ๋ก์‹œํ‚จ ๊ฒฝ์šฐ์—๋Š” ์•”์‹œ์  ์ธํ…ํŠธ์— ์‹คํ–‰ ๊ฐ€๋Šฅ

// ์ฝ”๋“œ๋กœ ๋™์  ๋“ฑ๋ก
receiver = object : BroadcastReceiver() {
	override fun onReceiver(context: Context?, intent: Intent?) {
    	Log.d("kotdev", "outer app dynamic receiver......")
    }
}

// ์ธํ…ํŠธ ํ•„ํ„ฐ
registerReceiver(receiver, IntentFilter("ACTION_OUTER_DYNAMIC_RECEIVER"))

๊ทธ๋Ÿฐ๋ฐ ์ฝ”๋“œ์—์„œ ๋™์  ๋“ฑ๋กํ•˜๋Š” ์ƒํ™ฉ์ด๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ? ๋™์  ๋“ฑ๋ก ์‹œ์—๋Š” ํด๋ž˜์Šค๋ช…์ด ์•„๋‹Œ ๊ฐ์ฒด๋ฅผ ๋“ฑ๋กํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹คํ–‰ ์กฐ๊ฑด์œผ๋กœ ์ธํ…ํŠธ ํ•„ํ„ฐ๋ฅผ ์ค„ ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

๊ทผ๋ฐ ์•„๋ฌด๋Ÿฐ ๊ฑฑ์ •ํ•  ํ•„์š” ์—†๋‹ค. registerReceiver() ํ•จ์ˆ˜๋กœ ๋™์  ๋“ฑ๋ก๋œ ๋ฆฌ์‹œ๋ฒ„๋Š” ์•”์‹œ์  ์ธํ…ํŠธ๋กœ ์‹คํ–‰์ด ๋œ๋‹ค.

์ฆ‰, ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„ ๊ด€๋ จํ•ด์„œ ๊ฐ€ํ•ด์ง„ ์ œ์•ฝ์€ Manifest ์— ์ •์  ๋“ฑ๋ก๋œ ๊ฒฝ์šฐ ๋งŒ ํ•ด๋‹น๋œ๋‹ค.
๊ทธ๋ž˜์„œ ๊ทธ๋ƒฅ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.


๐Ÿ’ฌ Service Limit

  • ์•ฑ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒํƒœ์— ์žˆ์„ ๋•Œ ์„œ๋น„์Šค๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ ์ธํ…ํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ

  • ์•ฑ์ด ํฌ๊ทธ๋ผ์šด๋“œ ์ƒํ™ฉ์ด๋ผ๋ฉด ์ •์ƒ ์‹คํ–‰

Not allowed to start service Intent { act=ACTION_OUTER_SERVICE pkg=com.example.test }: app is in background uid null

์•ฑ์˜ ํ™”๋ฉด์ด ์ถœ๋ ฅ๋˜์ง€ ์•Š๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒํƒœ์ผ ๋•Œ ์„œ๋น„์Šค ๊ตฌ๋™ ์‹œ ์œ„์™€ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋–จ์–ด์ง„๋‹ค.

โ“ ๊ทธ๋Ÿผ ์•ฑ์˜ ํ™”๋ฉด์ด ์ถœ๋ ฅ๋˜์ง€ ์•Š์œผ๋ฉด ๋ฌด์กฐ๊ฑด ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ ๋ณด๋Š”๊ฐ€?

์•„๋‹ˆ๋‹ค. ํฌ๊ทธ๋ผ์šด๋“œ๋ผ๊ณ  ๋ณด๋Š” ์ƒํ™ฉ์ด ๋ช‡ ๊ฐ€์ง€ ์žˆ๋‹ค. ๋•Œ๋ฌธ์— ํ™”๋ฉด์ด ์•ˆ ๋‚˜์˜จ๋‹ค๊ณ  ๋ฌด์กฐ๊ฑด ๋ฐฑ๊ทธ๋ผ์šด๋“œ๊ฐ€ ์•„๋‹ˆ๋‹ค.

1. Activity ๊ฐ€ ์‹œ์ž‘๋˜๊ฑฐ๋‚˜ ์ผ์‹œ ์ค‘์ง€๋˜๊ฑฐ๋‚˜ ์ƒ๊ด€์—†์ด ๋ณด์ด๋Š” Activity ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ

2. ํฌ๊ทธ๋ผ์šด๋“œ ์„œ๋น„์Šค๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ

3. ์•ฑ์˜ ์„œ๋น„์Šค ์ค‘ ํ•˜๋‚˜์— ๋ฐ”์ธ๋“œํ•˜๊ฑฐ๋‚˜ ์•ฑ์˜ ์ฝ˜ํ…์ธ  ํ”„๋Ÿฌ๋ฐ”์ด๋” ์ค‘ 
   ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•ฑ์— ๋˜ ๋‹ค๋ฅธ ํฌ๊ทธ๋ผ์šด๋“œ ์•ฑ์ด ์—ฐ๊ฒฐ๋œ ๊ฒฝ์šฐ

๐Ÿ“Œ ๋˜ํ•œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒํƒœ์ผ์ง€๋ผ๋„ ์•„๋ž˜์˜ ๊ฒฝ์šฐ์—๋Š” ์„œ๋น„์Šค๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋œ๋‹ค!

1. ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ Firebase ํด๋ผ์šฐ๋“œ ๋ฉ”์‹œ์ง•(FCM) ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ

2. SMS/MMS ๋ฉ”์‹œ์ง€์™€ ๊ฐ™์€ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ์ˆ˜์‹ 

3. ์•Œ๋ฆผ์—์„œ PendingIntent ์‹คํ–‰

4. VPN ์•ฑ์ด ํฌ๊ทธ๋ผ์šด๋“œ๋กœ ์Šน๊ฒฉ๋˜๊ธฐ ์ „์— VpnService ์‹œ์ž‘

์ฆ‰, ์•ฑ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—ฌ๋„ ์ž์‹ ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ค๋ฅธ ๊ณณ์— ๋ฟŒ๋ ค์ง€๋Š” ์ƒํ™ฉ์ด๋ผ๋ฉด ํฌ๊ทธ๋ผ์šด๋“œ ์ทจ๊ธ‰ํ•ด ์ค€๋‹ค.

๐Ÿ““ ์„œ๋น„์Šค ๊ฐ•์ œ ์‹คํ–‰

  • ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒํ™ฉ์—์„œ ์„œ๋น„์Šค๊ฐ€ ์ •์ƒ ์‹คํ–‰๋˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•

  • startForegroundService() ํ•จ์ˆ˜์— ์˜ํ•ด ์ธํ…ํŠธ๋ฅผ ๋ฐœ์ƒ ์‹œํ‚ค๋ฉด ์•ฑ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒํ™ฉ์ด๋ผ๊ณ  ํ•˜๋”๋ผ๋„ ์ •์ƒ์ ์œผ๋กœ ์„œ๋น„์Šค๊ฐ€ ์‹คํ–‰

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
	startForegroundService(intent)
} else {
	startService(intent)
}

๋ถ€ํŒ…์ด ์™„๋ฃŒ๋œ ์ˆœ๊ฐ„์€ ์•ฑ์ด ๋ช…๋ฐฑํžˆ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒํƒœ์ธ ์ˆœ๊ฐ„์ด๋‹ค. ๋ถ€ํŒ… ์™„๋ฃŒ ์‹œ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„๋Š” ์ž˜๋งŒ ์‹คํ–‰๋œ๋‹ค. ํ•˜์ง€๋งŒ ์ด ๋ฆฌ์‹œ๋ฒ„๊ฐ€ ์•ฑ์˜ ์„œ๋น„์Šค๋ฅผ ๊ตฌ๋™ํ•˜๋Š” ์ˆœ๊ฐ„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

โ— ํ•˜์ง€๋งŒ startForegroundService() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„œ๋น„์Šค๋ฅผ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜๊ฐ€ ์žˆ๋‹ค. (์กฐ๊ฑด์ด ์žˆ์ง€๋งŒ)

  • startForegroundService() ํ•จ์ˆ˜์— ์˜ํ•ด ์„œ๋น„์Šค๊ฐ€ ์ •์ƒ ์‹คํ–‰๋˜๊ธฐ๋Š” ํ•˜์ง€๋งŒ ์–ผ๋งˆ ํ›„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ

Context.startForegroundService() did not then call Service.startForeground()

startForegroundService() ๋กœ ๊ตฌ๋™๋œ ์„œ๋น„์Šค๋Š” ์•„์ฃผ ๋น ๋ฅธ ์‹œ๊ฐ„ ๋‚ด์— ๋ฌด์–ธ๊ฐ€๋ฅผ ๋„์›Œ ์ฃผ์–ด์•ผ ํ•œ๋‹ค. ๊ทธ๋Ÿฌ์ง€ ์•Š์œผ๋ฉด ์œ„์™€ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„๊ฐ€ ์ธํ…ํŠธ๋กœ ์„œ๋น„์Šค๋ฅผ ์ •์ƒ ๊ตฌ๋™์‹œํ‚ฌ ์ˆ˜๋Š” ์žˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์„œ๋น„์Šค๊ฐ€ ๊ตฌ๋™๋˜์ž ๋งˆ์ž startForeground() ํ•จ์ˆ˜๋ฅผ ์ฝœ ํ•ด์ฃผ์–ด ํ•œ๋‹ค.

โ“ ๊ทธ๋Ÿผ ๊ทธ๋ƒฅ ์ด ํ•จ์ˆ˜๋ฅผ ๋นจ๋ฆฌ ์‹คํ–‰์‹œํ‚ค๋ฉด ๋˜๋Š” ๊ฒƒ ์•„๋‹ˆ๋ƒ?

๊ทธ๋Ÿฐ๋ฐ ์ด ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ Notification ์ด๋‹ค. ๊ฒฐ๊ตญ ์„œ๋น„์Šค๋ฅผ ๊ตฌ๋™์‹œ์ผœ ์ค„ ํ…Œ๋‹ˆ ๋นจ๋ฆฌ Notification ์„ ๋„์šฐ๋ผ๋Š” ์†Œ๋ฆฌ๋‹ค. ๊ทธ๋Ÿผ ํฌ๊ทธ๋ผ์šด๋“œ ์ทจ๊ธ‰ ํ•ด์ฃผ๊ฒ ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.๊ทผ๋ฐ ์ด๊ฑธ ์œ„ํ•ด ์˜๋ฏธ ์—†๋Š” ์•Œ๋ฆผ์„ ๋„์šฐ๋ฉด ์œ ์ €๊ฐ€ ๊ณผ์—ฐ ์ข‹์•„ํ• ๊นŒ?

  • startForegroundService() ์— ์˜ํ•ด ์‹คํ–‰๋œ ์„œ๋น„์Šค ๋‚ด์—์„œ ๋น ๋ฅธ ์‹œ๊ฐ„ ๋‚ด์— startForeground() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด ์ค˜์•ผ ํ•œ๋‹ค.

  • startForeground() ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์•Œ๋ฆผ ๊ฐ์ฒด

val notification = builder.build()
startForeground(1, notification)
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

์„œ๋น„์Šค๊ฐ€ ๊ตฌ๋™๋œ ์ˆœ๊ฐ„ ๋น ๋ฅด๊ฒŒ startForeground ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ Notification ์„ ์ค˜์„œ ์ฝœ ํ•ด์•ผ ํ•œ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ํผ๋ฏธ์…˜์ด ์š”๊ตฌ๋˜๋Š” ๊ธฐ๋Šฅ์ด๊ธฐ ๋•Œ๋ฌธ์— Manifest ์— ๋“ฑ๋ก ํ•ด์ฃผ์ž.


๐Ÿ’ฌ JobScheduler

์•ฑ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์ด๋ผ๋„ ์กฐ๊ฑด์— ์•Œ๋งž๊ฒŒ JobScheduler ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—…๋ฌด ๋กœ์ง์„ ๋Œ๋ฆด ์ˆ˜ ์žˆ๋‹ค.
JobScheduler ๋„ ์„œ๋น„์Šค์ด๋‹ค. ๋•Œ๋ฌธ์— Manifest ์— ๋“ฑ๋กํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ผ๋ฐ˜์ ์ธ ์„œ๋น„์Šค์™€๋Š” ์กฐ๊ธˆ ๋‹ค๋ฅด๊ฒŒ ๊ตฌ๋™๋œ๋‹ค. ์„œ๋น„์Šค๋Š” ์ธํ…ํŠธ์— ์˜ํ•ด ๊ตฌ๋™๋˜์ง€๋งŒ, JobScheduler ๋Š” ์‹œ์Šคํ…œ์— ๋“ฑ๋ก๋˜๋ฉด ํŠน์ • ์กฐ๊ฑด์ด ๋งŒ์กฑํ•˜๋Š” ์ˆœ๊ฐ„์— ์•Œ์•„์„œ ์‹คํ–‰์ด ๋œ๋‹ค.

  • JobScheduler ์„ ์ด์šฉํ•˜๋ฉด ์•ฑ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒํ™ฉ์—์„œ๋„ ์—…๋ฌด์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

  • ์กฐ๊ฑด์„ ๋ช…์‹œํ•˜๊ณ  ๊ทธ ์กฐ๊ฑด์— ๋งž๋Š” ๊ฒฝ์šฐ์—๋งŒ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์—…๋ฌด ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

1. ๋„คํŠธ์›Œํฌ ํƒ€์ž…

2. ๋ฐฐํ„ฐ๋ฆฌ ์ถฉ์ „ ์ƒํƒœ

3. ํŠน์ • ์•ฑ์˜ ์ฝ˜ํ…์ธ  ํ”„๋Ÿฌ๋ฐ”์ด๋”์˜ ๊ฐฑ์‹ 

4. ์‹คํ–‰ ์ฃผ๊ธฐ (1์‹œ๊ฐ„์— ํ•œ ๋ฒˆ ๋“ฑ)

5. ์ตœ์†Œ ์ง€์—ฐ ์‹œ๊ฐ„

๐Ÿ“ ์ž‘์„ฑ ๋ฐฉ๋ฒ•

  • ๊ฐœ๋ฐœ์ž ์„œ๋น„์Šค

  • android.permission.BIND_JOB_SERVICE ํผ๋ฏธ์…˜์ด ๋“ฑ๋ก๋˜์–ด์•ผ ํ•จ

<service
	android:name=".MyService"
    android:enabled="true"
    android:exported="true"
    android:permission="android.permission.BIND_JOB_SERVICE"></service>
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class MyService : JobService() {	์ผ๋ฐ˜ ์„œ๋น„์Šค๊ฐ€ ์•„๋‹Œ JobService ๋ฅผ ์ƒ์†
	
    override fun onCreate() {
    	super.onCreate()
    }
    
    override fun onDestroy() {
    	super.onDestroy()
    }
    
    override fun onStartJob(params: JobParameters?): Boolean {
    	// ์—…๋ฌด๋กœ์ง
    	return false
    }
    
    override fun onStopJob(params: JobParameters?): Boolean {
    	// ์—…๋ฌด๋กœ์ง
    	return false
    }
}

์‹œ์Šคํ…œ์— ์˜ํ•ด์„œ JobService ๊ฐ€ ๊ตฌ๋™๋  ๋•Œ onStartJob() ์™€ onStopJob() ํ•จ์ˆ˜๊ฐ€ ์ž๋™ ์ฝœ ๋œ๋‹ค.
์—…๋ฌด๋กœ์ง์€ onStartJob() ์— ๋‹ด์•„์ฃผ๋ฉด ๋œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด ํ•จ์ˆ˜๋“ค์€ ๋ฆฌํ„ด๊ฐ’์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.

๐Ÿ““ onStartJob

  • onStartJob() ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•˜๋Š” ์—…๋ฌด๋ฅผ ๊ฐ€์ง€๋Š” ํ•จ์ˆ˜

  • ๋ฆฌํ„ด๊ฐ’์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘

- false : job ์ด ์™„๋ฒฝํ•˜๊ฒŒ ์ข…๋ฃŒ๋˜์—ˆ๋‹ค๋Š” ์˜๋ฏธ (๋” ์ด์ƒ ์ฒ˜๋ฆฌํ•  ์ž‘์—…์ด ์—†์Œ์„ ์‹œ์Šคํ…œ์—๊ฒŒ ์•Œ๋ฆผ)
		-> onStopJob() ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์Œ
        

- true : job ์ด ์•„์ง ๋๋‚˜์ง€ ์•Š์•˜์Œ์„ ์˜๋ฏธ (์ž์‹ ์€ ๋๋‚ฌ์ง€๋งŒ ์•„์ง ์ž‘์—…์ด ๋‚จ์•˜๋‹ค๋Š” ๊ฒƒ์„ ์‹œ์Šคํ…œ์—๊ฒŒ ์•Œ๋ฆผ)
		-> onStopJob() ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋จ

๐Ÿ““ onStopJob

  • onStopJob() ์˜ ๋ฆฌํ„ด๊ฐ’๋„ true, false ์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘
- false : JobScheduler ๋“ฑ๋ก์ด ์ทจ์†Œ -> ๋‹ค์‹œ ์‹คํ–‰์กฐ๊ฑด์ด ๋˜๋„ ์‹คํ–‰ x

- true : ๋‹ค์‹œ JobScheduler ๋“ฑ๋ก -> ๋‹ค์‹œ ์‹คํ–‰์กฐ๊ฑด์ด ๋˜๋ฉด ์‹คํ–‰

๐Ÿ“š JobInfo

JobService ๋กœ ์„œ๋น„์Šค๋ฅผ ์‹œ์Šคํ…œ์— ๋“ฑ๋กํ•˜๊ณ , JobInfo ๋กœ ์กฐ๊ฑด์„ ๋‹ด์•„ ๋“ฑ๋กํ•œ๋‹ค.

๊ทธ๋ž˜์„œ ๋นŒ๋” ๊ฐ์ฒด ๋กœ ๋งŒ๋“ค์–ด ์ค€๋‹ค.

  • ์‹คํ–‰๋˜๋Š” ์กฐ๊ฑด์„ JobInfo ๊ฐ์ฒด์— ๋‹ด์•„ ์‹œ์Šคํ…œ์— ๋“ฑ๋ก
JobInfo.Builder(1, ComponentName(this, MyService::class.java)).run {
	setRequireNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
    jobScheduler?.schedule(build())
}

๋นŒ๋”๋กœ JobService ๋ช… ์„ ์ฃผ๊ณ  setter ํ•จ์ˆ˜ ๋กœ ์กฐ๊ฑด์„ ์ค€ ํ›„์— schedule() ํ•จ์ˆ˜๋กœ ์‹œ์Šคํ…œ์— ๋“ฑ๋กํ•œ๋‹ค.

๐Ÿ“Œ ์‹คํ–‰ ์กฐ๊ฑด

์กฐ๊ฑด๋‚ด์šฉ
setPersisted(true)๋‹จ๋ง์„ ์žฌ๋ถ€ํŒ… ํ•ด๋„ Job ๋“ฑ๋ก ์œ ์ง€ํ•ด์•ผ ํ•˜๋Š”์ง€ ์„ค์ •
setPeriodic(long intervalMillis)Job ์˜ ์‹คํ–‰ ์ฃผ๊ธฐ ์„ค์ •
setMinimumLatency(long minLatencyMillis)Job ์‹คํ–‰์˜ ์ง€์—ฐ ์‹œ๊ฐ„ ์„ค์ •
setOverrideDeadline(long maxExecutionDelayMillis)๋‹ค๋ฅธ ์กฐ๊ฑด์ด ๋งŒ์กฑํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ํ•˜๋”๋ผ๋„ ์ด ์‹œ๊ฐ„ ์•ˆ์— Job ์ด ์‹คํ–‰๋˜์–ด์•ผ ํ•จ์„ ์„ค์ •
setRequiredNetworkType(int networkType)๋„คํŠธ์› ํƒ€์ž… ์„ค์ •
setRequiresBatteryNotLow(boolean batteryNotLow)๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ๋‚ฎ์€ ์ƒํƒœ๊ฐ€ ์•„๋‹˜์„ ์„ค์ •
setRequiresCharging(boolean requiresCharging)๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ์ถฉ์ „ ์ƒํƒœ์ธ์ง€๋ฅผ ์„ค์ •

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

โ• ํผ๋ฏธ์…˜ ์ถ”๊ฐ€

  • AndroidManifest.kt
<service
	android:name=".MyService"
	android:enabled="true"
	android:exported="true"
	android:permission="android.permission.BIND_JOB_SERVICE"></service>

๐ŸŽจ ์•กํ‹ฐ๋น„ํ‹ฐ ๋ ˆ์ด์•„์›ƒ

  • activity_main.xml
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="add job"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

๐Ÿ›  JobService

  • MainActivity.kt
package com.kotdev99.android.c79

class MyService : JobService() {

	override fun onCreate() {
		super.onCreate()
		Log.d("JobService", "MyService...onCreate...")      // ์„œ๋น„์Šค ๊ตฌ๋™ ํ™•์ธ
	}

	override fun onStartJob(p0: JobParameters?): Boolean {
		Log.d("JobService", "MyService...onStartJob...")
		return false
	}

	override fun onStopJob(p0: JobParameters?): Boolean {
		Log.d("JobService", "MyService...onStopJob...")
		return false
	}

	override fun onDestroy() {
		super.onDestroy()
		Log.d("JobService", "MyService...onDestroy...")     // ์„œ๋น„์Šค ์ข…๋ฃŒ ํ™•์ธ
	}
}

๐Ÿ“‚ ๋ฉ”์ธ ์†Œ์Šค ์ฝ”๋“œ

  • MainActivity.kt
package com.kotdev99.android.c79

class MainActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)

		val btn = findViewById<Button>(R.id.button)
		btn.setOnClickListener {

			// ์‹œ์Šคํ…œ ์„œ๋น„์Šค ํš๋“
			var scheduler = getSystemService(JOB_SCHEDULER_SERVICE) as JobScheduler

			// JobInfo ๋“ฑ๋ก
			JobInfo.Builder(1, ComponentName(this, MyService::class.java)).run {
				setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)  // ์™€์ดํŒŒ์ด์— ๋ถ™๋Š” ์ˆœ๊ฐ„ ์‹คํ–‰
				scheduler.schedule(build())
			}
		}
	}
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ

์™€์ดํŒŒ์ด๋ฅผ ๋ˆ ์ƒํƒœ์—์„œ ์•ฑ ์‹คํ–‰ ํ›„ ๋ฒ„ํŠผ ํด๋ฆญ -> ์•ฑ ์™„์ „ํžˆ ์ข…๋ฃŒ ํ›„ ์™€์ดํŒŒ์ด ์—ฐ๊ฒฐ -> Logcat ํ™•์ธ

profile
์‘์•  ๋‚˜ ์•„๊ธฐ ๋‰ด๋น„

0๊ฐœ์˜ ๋Œ“๊ธ€