[Android] Notification ์ •๋ฆฌ

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

Android

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

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


โ“ Notification ์ด๋ž€

  • ์ƒํƒœ๋ฐฉ์— ์•ฑ์˜ ์ •๋ณด๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ๊ฒƒ์„ ์•Œ๋ฆผ(Notifiaction) ์ด๋ผ๊ณ  ํ•œ๋‹ค.

  • ์ƒํƒœ๋ฐ”๋Š” ์‹œ์Šคํ…œ์— ์˜ํ•ด ๊ด€๋ฆฌ๋˜๋Š” ๊ณณ

  • ์•ฑ์ด ์ง์ ‘ ์ œ์–ด ๋ถˆ๊ฐ€

  • ์•ฑ์˜ ์ƒํƒœ๋ฅผ ์ƒํƒœ๋ฐ”์— ์ถœ๋ ฅํ•ด ์œ ์ €์—๊ฒŒ ๋ฌด์–ธ๊ฐ€์˜ ์ƒํ™ฉ์„ ์•Œ๋ ค์ฃผ๋Š” ๊ธฐ๋Šฅ

์•ฑ์ด ์ž์‹ ์˜ ์•กํ‹ฐ๋น„ํ‹ฐ๋ฅผ ์ถœ๋ ฅํ•˜๊ณ  ์žˆ๋Š” ์ƒํ™ฉ์ด๋ฉด ๊ตณ์ด ์•Œ๋ฆผ์„ ๋ฐœ์ƒํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
๊ทธ๋ž˜์„œ ์ฃผ๋ ฅ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด Receiver ํ˜น์€ Service ์ด๋‹ค.


๐Ÿ’ฌ ์•Œ๋ฆผ ๋ฐœ์ƒ ๊ณผ์ •

  • ์•Œ๋ฆผ์€ NotificationManager ์˜ notify() ํ•จ์ˆ˜๋กœ ๋ฐœ์ƒ

  • Notification ๊ฐ์ฒด๋Š” NotificationCompat.Builder ์— ์˜ํ•ด ์ƒ์„ฑ

  • NotificationChannel ๋กœ NotificationCompat.Builder ์ƒ์„ฑ

  • NotificationCompat.Builder ๊ฐ€ ํ•„์š”ํ•œ๋ฐ Builder ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ด API Level 26(Android 8) ๋ฒ„์ „๋ถ€ํ„ฐ ๋ณ€๊ฒฝ

API 26๋ถ€ํ„ฐ Channel ๊ฐœ๋…์ด ๋„์ž…๋˜๋ฉด์„œ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

1. `Mnanger` ๊ฐ€ `notify()` ๋กœ ์•Œ๋ฆผ์„ ๋ฐœ์ƒ 

2. ์ด ์•Œ๋ฆผ ๋‚ด์šฉ์„ `Notification ๊ฐ์ฒด` ๊ฐ€ ๊ฐ–์Œ

3. ์ด ๊ฐ์ฒด๋ฅผ `Builder` ๊ฐ€ ์ƒ์„ฑ

4. ์ด Builder ๋ฅผ ๋งŒ๋“ค ๋•Œ `Channel` ๊ฐœ๋…์ด ๋„์ž…๋จ

๐Ÿ’ฌ NotificationManager

โ“ NotificationManager ๋ž€

  • Builder(context: Context!) - API Level 26(Android 8) ์ด์ „ ๋ฒ„์ „

  • Builder(context: Context!, channelId: String!) - API Level 26(Android 8) ๋ถ€ํ„ฐ

API 26 ์ด์ „์—๋Š” ๊ทธ๋ƒฅ Context ๋งŒ ์ฃผ๋ฉด ์•Œ๋ฆผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์—ˆ๋‹ค.
๊ทธ๋Ÿฐ๋ฐ ์ฑ„๋„ ๊ฐœ๋…์ด์ด ๋„์ž…๋˜๋ฉด์„œ ์ฑ„๋„ ์„ ๊ฐ™์ด ๋˜์ ธ ์ฃผ์–ด์•ผ ํ•œ๋‹ค.


โ“ NotificationChannel ์ด๋ž€

ํ•ธ๋“œํฐ ํ™˜๊ฒฝ ์„ค์ •์—์„œ ์•ฑ์— ๋Œ€ํ•œ ์•Œ๋ฆผ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ฑ„๋„ ๋„์ž… ์ด์ „์—๋Š” ๊ทธ๋ƒฅ ์•ฑ ์ž์ฒด ๊ฐ€ ๋Œ€์ƒ์ด์—ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์•ฑ์—์„œ ๋ณด๋‚ด๋Š” ์•Œ๋ฆผ์€ ์ข…๋ฅ˜๊ฐ€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๋‹ค. ๊ทธ๋ž˜์„œ ์•ฑ์—์„œ ๋ณด๋‚ด๋Š” ๊ฐ๊ฐ์˜ ์•Œ๋ฆผ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ฒŒ๋” ๋„์ž…๋œ ๊ฐœ๋…์ด ์ฑ„๋„ ์ด๋‹ค.

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

NotificationChannel(id: String!, name: CharSequence!, importance: Int)
์œ„ํ—˜๋„๋‚ด์šฉ
NotificationManager.IMPORTANCE_HIGH๊ธด๊ธ‰ ์ƒํ™ฉ์ด๋ฉฐ ์•Œ๋ฆผ์Œ์ด ์šธ๋ฆฌ๋ฉฐ ํ—ค๋“œ์—…์œผ๋กœ ํ‘œ์‹œ
NotificationManager.IMPORTANCE_DEFAULT๋†’์€ ์ค‘์š”๋„์ด๋ฉฐ ์•Œ๋ฆผ์Œ์ด ์šธ๋ฆผ
NotificationManager.IMPORTANCE_LOW์ค‘๊ฐ„ ์ค‘์š”๋„์ด๋ฉฐ ์•Œ๋ฆผ์Œ์ด ์šธ๋ฆฌ์ง€ ์•Š์Œ
NotificationManager.IMPORTANCE_MIN๋‚ฎ์€ ์ค‘์š”๋„์ด๋ฉฐ ์•Œ๋ฆผ์Œ์ด ์—†๊ณ  ์ƒํƒœํ‘œ์‹œ์ค„์— ํ‘œ์‹œ๋˜์ง€ ์•Š์Œ

๐Ÿ”Ž Notification ๊ตฌ์„ฑ

  • Notification ์€ ์•Œ๋ฆผ์— ์ถœ๋ ฅ๋  ์ด๋ฏธ์ง€, ๋ฌธ์ž์—ด ๋“ฑ์˜ ์ •๋ณด๋ฅผ ๋‹ด๋Š” ๊ฐ์ฒด

๐Ÿ“ ๊ตฌ์„ฑ ๋ฐฉ๋ฒ•

builder.setSmallIcon(android.R.drawable.ic_notification_overlay)
builder.setWhen(System.currentTimeMillis())
builder.setContentTitle("Content Title")
builder.setContentText("Content Massage")

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

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

  • AndroidManifest.kt
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

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

  • 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="notification"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

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

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

class MainActivity : AppCompatActivity() {

	private lateinit var btn: Button

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

		btn = findViewById(R.id.button)
		btn.setOnClickListener {
			initNotification()
		}
	}

	private fun initNotification() {
		val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
		val builder: NotificationCompat.Builder

		// channel ์ •๋ณด ์ž…๋ ฅ
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
			val channelId = "one-channel"
			val channelName = "My One Channel"
			val channel = NotificationChannel(
				channelId,
				channelName,
				NotificationManager.IMPORTANCE_HIGH
			)

			// channel ์— ์ฃผ๋Š” ์ •๋ณด
			channel.description = "My Channel One Description"	// ํ™˜๊ฒฝ์„ค์ •์— ์ถœ๋ ฅ๋˜๋Š” ์„ค๋ช…
			channel.setShowBadge(true)
			val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
			val audio = AudioAttributes.Builder()
				.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
				.setUsage(AudioAttributes.USAGE_ALARM)
				.build()
			channel.setSound(uri, audio)
			channel.enableLights(true)			// ์•Œ๋ฆผ ์‹œ์— ๋น›๋‚จ (์ œ๊ณต ์—ฌ๋ถ€๋Š” ๊ธฐ์ข…์— ๋”ฐ๋ผ ๋‹ค๋ฆ„)
			channel.lightColor = Color.RED		// ๋ถˆ๋น› ์ƒ‰์ƒ
			channel.enableVibration(true)
			channel.vibrationPattern = longArrayOf(100, 200, 100, 200)

			manager.createNotificationChannel(channel)

			builder = NotificationCompat.Builder(this, channelId)
		} else {
			builder = NotificationCompat.Builder(this)
		}
		builder.setSmallIcon(android.R.drawable.ic_notification_overlay)
		builder.setWhen(System.currentTimeMillis())
		builder.setContentTitle("Title")
		builder.setContentText("message")

		manager.notify(1, builder.build())
	}
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ


๐Ÿ’ฌ Notification ๊ตฌ์„ฑ

๐Ÿ“š ํ„ฐ์น˜ ์ด๋ฒคํŠธ

static fun getActivity(context: Context!, requestCode: Int, intent: Intent!, flags: Int)
: PendingIntent!


static fun getBroadcast(context: Context!, requestCode: Int, intent: Intent!, flags: Int)
: PendingIntent!


static fun getService(context: Context!, requestCode: Int, intent: Intent!, flags: Int)
: PendingIntent!

โ“ PendingIntent ๋ž€

์ธํ…ํŠธ ์ •๋ณด๋ฅผ ๊ฐ–์ง€๊ณ  ์žˆ๋Š”๋ฐ ์•„์ง ๋ฐœ์ƒํ•˜์ง€ ์•Š์€ ์ธํ…ํŠธ.
PendingIntent ๋กœ ์‹œ์Šคํ…œ์— ๋“ฑ๋กํ•˜๋ฉด, ์‹œ์Šคํ…œ์—์„œ ์‹ค์ œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ PendingIntent ๋‚ด์šฉ ๋Œ€๋กœ ์ธํ…ํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

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

val intent = Intent(this, DetailActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 10, intent, PendingIntent.FLAG_UPDATE_CURRENT)
builder.setContentIntent(pendingIntent)

๐Ÿ““ ์•ก์…˜

  • ์•Œ๋ฆผ์— ์ตœ๋Œ€ 3๊ฐœ๊นŒ์ง€์˜ ์œ ์ € ์ด๋ฒคํŠธ๋ฅผ ์œ„ํ•œ ์•ก์…˜์„ ์ถ”๊ฐ€

  • ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ธํ…ํŠธ ๋‚ด์šฉ์€ ์šฐ๋ฆฌ๊ฐ€ ์ค€๋น„ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ธํ…ํŠธ ๋ฐœ์ƒ์€ ์‹œ์Šคํ…œ์—์„œ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— PendingIntent ๋กœ ์˜๋ขฐํ•œ๋‹ค.

val actionIntent = Intent(this, DetailActivity::class.java)
val actionPendingIntent 
	= PendingIntent.getActivity(this, 20, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT)
    
builder.addAction(
	NotificationCompat.Action.Builder(
    	android.R.drawable.stat_notify_more,
        "Action",
        actionPendingIntent
	).build()
)

๐Ÿ““ Progress

  • ์•Œ๋ฆผ์„ ์ด์šฉํ•ด ์ผ์˜ ์ง„ํ–‰ ์ƒํ™ฉ์„ ํ‘œ์‹œ

builder.setProgress(100, 0, false)		// (max, min, Boolean)
manager.notify(11, builder.build())

thread {
	for (i in 1..100) {
    builder.setProgress(100, i, false)
    manager.notify(11, builder.build())
    SystemClock.sleep(100)
	}
}

๐Ÿ““ Style

  • BigPictureStyle, BigTextStyle, InboxStyle, Message Style

// BigPicture Style ์˜ˆ์‹œ

val bigPicture = BitmapFactory.decodeResource(resources.R.drawable.logo_1)
val bigStyle = NotificationCompat.BigPictureStyle()
bigStyle.bigPicture(bigPicture)
builder.setStyle(bigStyle)

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

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

  • AndroidManifest.kt
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

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

  • 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="notification"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

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

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

class MainActivity : AppCompatActivity() {

	private lateinit var btn: Button

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

		btn = findViewById(R.id.button)
		btn.setOnClickListener {
			initNotification()
		}
	}

	// Notification ๊ตฌํ˜„
	private fun initNotification() {
		val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
		val builder: NotificationCompat.Builder

		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
			val channelId = "one"
			val channelName = "channel name"
			val channel = NotificationChannel(
				channelId,
				channelName,
				NotificationManager.IMPORTANCE_HIGH
			)

			channel.description = "one description"
			manager.createNotificationChannel(channel)

			builder = NotificationCompat.Builder(this, channelId)
		} else {
			builder = NotificationCompat.Builder(this)
		}

		builder.setSmallIcon(android.R.drawable.ic_notification_overlay)
		builder.setWhen(System.currentTimeMillis())
		builder.setContentTitle("Title")
		builder.setContentText("Text")

		pendingIntent(builder)
		manager.notify(1, builder.build())
	}

	// PendingIntent ๊ตฌํ˜„
	private fun pendingIntent(builder: NotificationCompat.Builder) {

		// ์•Œ๋ฆผ PendingIntent
		val actionIntent = Intent(this, DetailActivity::class.java)
		val actionPending =
			PendingIntent.getActivity(
				this, 20, actionIntent,
				PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
			)
		builder.setContentIntent(actionPending)

		// Action PendingIntent
		val actionIntent2 = Intent(this, DetailActivity::class.java)
		val actionPending2 =
			PendingIntent.getActivity(
				this, 20, actionIntent2,
				PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
			)
		builder.addAction(
			NotificationCompat.Action.Builder(
				android.R.drawable.stat_notify_more,
				"Action",
				actionPending2
			).build()
		)

		// BigPicture Style
		val bigPicture = BitmapFactory.decodeResource(resources, R.drawable.logo_1)
		val style = NotificationCompat.BigPictureStyle()
		style.bigPicture(bigPicture)
		builder.setStyle(style)
	}
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ

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

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