2024. 4. 9. 19:50, ๐ฑAndroid Study
๋ฐ์ํ
1. ์๋ฆผ (Notification)
- ์ฑ์ UI์ ๋ณ๋๋ก ์ฌ์ฉ์์๊ฒ ์ฑ๊ณผ ๊ด๋ จํ ์ ๋ณด๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ธฐ๋ฅ
- ์๋ฆผ์ ํฐ์นํ์ฌ ํด๋น ์ฑ์ ์ด ์ ์๊ณ , ๋ฐ๋ก ๊ฐ๋จํ ์์ (์ : ๋ฌธ์ ๋ตํ๊ธฐ)์ ํ ์ ์์ (Android 7.0๋ถํฐ)
- ๋ณดํต ๋จ๋ง๊ธฐ ์๋จ ๋ถ๋ถ์ ํ์๋๊ณ , ์ฑ ์์ด์ฝ์ ๋ฐฐ์ง๋ก๋ ํ์ (Android 8.0๋ถํฐ)
2. ์๋ฆผ ์ฑ๋ ๋ง๋ค๊ธฐ
- Android 8.0์ด์์ ๊ฒฝ์ฐ๋ ์๋ฆผ์ ๋ง๋ค๊ธฐ ์ ์ ์๋ฆผ ์ฑ๋์ ๋จผ์ ๋ง๋ค์ด์ผํจ
- ์๋ฆผ ์ฑ๋์ ์๋ฆผ์ ๊ทธ๋ฃนํ์ฌ ์๋ฆผ ํ์ฑํ๋ ๋ฐฉ์์ ๋ณ๊ฒฝ ํ ์ ์์
- ํ์ฌ ์ฑ์ด ์คํ ์ค์ธ ์๋๋ก์ด๋ ๋ฒ์ ์ ํ์ธํ์ฌ 8.0์ด์์ธ ๊ฒฝ์ฐ๋ง ์ฑ๋ ์์ฑ
private val myNotificationID = 1
private val channelID = "default"
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Android 8.0
val channel = NotificationChannel(channelID, "default channel",
NotificationManager.IMPORTANCE_DEFAULT)
channel.description = "description text of this channel."
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
- ๊ณ ์ ํ ์ฑ๋ ID, ์ฌ์ฉ์์๊ฒ ํ์๋๋ ์ด๋ฆ, ์ค์๋ ์์ค์ ์ฌ์ฉํ์ฌ NotificationChannel ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑ
- ํ์ํ ๊ฒฝ์ฐ setDescription()๋ฅผ ์ฌ์ฉํ์ฌ ์์คํ ์ค์ ์์ ์ฌ์ฉ์์๊ฒ ํ์๋๋ ์ค๋ช ์ ์ง์
- ์๋ฆผ ์ฑ๋์ createNotificationChannel()์ ์ ๋ฌํ์ฌ ๋ฑ๋ก
3. ์๋ฆผ ์์ฑ
private val myNotificationID = 1
private fun showNotification() {
val builder = NotificationCompat.Builder(this, channelID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("title")
.setContentText("notification text")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
NotificationManagerCompat.from(this).notify(myNotificationID, builder.build())
}
- 1) NotificationCompat.Builder ๊ฐ์ฒด์์ ์๋ฆผ์ ๋ํ UI์ ๋ณด์ ์์
์ ์ง์
- setSmallIcon() : ์์ ์์ด์ฝ
- setContentTitle() : ์ ๋ชฉ
- setContentText() : ์ธ๋ถ ํ ์คํธ - 2) NotificationCompat.Builder.build()ํธ์ถ
- Notification๊ฐ์ฒด๋ฅผ ๋ฐํ - 3) NotificationManagerCompat.notify()๋ฅผ ํธ์ถํด์ ์์คํ ์ Notification๊ฐ์ฒด๋ฅผ ์ ๋ฌ
4. ์๋ฆผ ์ค์๋
- Android 7.1 (API ์์ค 25) ์ดํ์์๋ ์๋ฆผ์ ์ค์๋๊ฐ ์๋ฆผ์ priority์ ์ํด ๊ฒฐ์
NotificationCompat.Builder(this,channelID).setPriority(NotificationCompat.PRIORITY_DEFAULT) - Android 8.0 (API ์์ค 26) ์ด์์์ ์๋ฆผ์ ์ค์๋๋ ์๋ฆผ์ด ๊ฒ์๋ ์ฑ๋์ importance์ ์ํด ๊ฒฐ์
NotificationChannel - (channelID, "defaultchannel", NotificationManager.IMPORTANCE_DEFAULT) - ์ฌ์ฉ์๋ ์์คํ ์ค์ ์์ ์๋ฆผ ์ฑ๋์ ์ค์๋๋ฅผ ๋ณ๊ฒฝํ ์ ์์
5. ์๋ฆผ ํ์ฅ๋ทฐ
5-1. ๊ธด ํ ์คํธ
์๋ ์ฝ๋๋ฅผ ์ถ๊ฐ
builder.setStyle(NotificationCompat.BigTextStyle()
.bigText("Much longer text that cannot fit one line..."))
์์
var builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("My notification")
.setContentText("Much longer text that cannot fit one line...")
.setStyle(NotificationCompat.BigTextStyle()
.bigText("Much longer text that cannot fit one line..."))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
5-2. ์ด๋ฏธ์ง
์๋ ์ฝ๋๋ฅผ ์ถ๊ฐ
.setStyle(NotificationCompat.BigPictureStyle()
.bigPicture(myBitmap))
์์
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.new_post)
.setContentTitle(imageTitle)
.setContentText(imageDescription)
.setLargeIcon(myBitmap)
.setStyle(NotificationCompat.BigPictureStyle()
.bigPicture(myBitmap)
.bigLargeIcon(null)) // ์๋ฆผ์ ํ์ฅํ ๋ ์์ด์ฝ์ ์จ๊น
.build()
5-3. ๋ฒํผ ์ถ๊ฐ
- ์๋ฆผ์ ์ฌ์ฉ์๊ฐ ์ ์ํ๊ฒ ์๋ตํ ์ ์๋ ์์ ๋ฒํผ์ ์ต๋ 3๊ฐ๊น์ง ์ ๊ณต ๊ฐ๋ฅ
- ์ด๋ฌํ ์์ ๋ฒํผ์ ์ฌ์ฉ์๊ฐ ์๋ฆผ์ ํญํ ๋ ์คํ๋๋ ์์ ๊ณผ ์ค๋ณต๋์ง ์์์ผ ํจ
- ์์
๋ฒํผ์ ์ถ๊ฐํ๋ ค๋ฉด PendingIntent๋ฅผ addAction() ๋ฉ์๋์ ์ ๋ฌ
์ด๋ ํ๋์ ์คํํ๋ ๋์ ์๋ฆผ์ ๊ธฐ๋ณธ ํญ ์์ ์ ์ค์ ํ๋ ๊ฒ๊ณผ ๊ฐ์ - ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์์ ์ ์คํํ๋ BroadcastReceiver๋ฅผ ์์ํด ์์ ์ด ์ด๋ฏธ ์ด๋ ค ์๋ ์ฑ์ ๋ฐฉํดํ์ง ์๋๋ก ํ๋ ๋ฑ ๋ค๋ฅธ ์์ ์ ํ ์ ์์
val ACTION_SNOOZE = "snooze"
val snoozeIntent = Intent(this, MyBroadcastReceiver::class.java).apply {
action = ACTION_SNOOZE
putExtra(EXTRA_NOTIFICATION_ID, 0)
}
val snoozePendingIntent: PendingIntent =
PendingIntent.getBroadcast(this, 0, snoozeIntent, 0)
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("My notification")
.setContentText("Hello World!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.addAction(R.drawable.ic_snooze, getString(R.string.snooze),
snoozePendingIntent)
ํน์ intent๋ก Activity๋ฅผ ์์ํจ (Snooze ๋ฒํผ์ ๋๋ฅด๋ฉด TestActivity ์คํ ์์)
val intent = Intent(this, TestActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
val builder = NotificationCompat.Builder(this, channelID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Notification Title")
.setContentText("Notification body")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.addAction(R.drawable.android_hsu, "Snooze", pendingIntent)
NotificationManagerCompat.from(this).notify(myNotificationID, builder.build())
5-4. ํ๋ก๊ทธ๋ ์ค๋ฐ ์ถ๊ฐ
- ์ธ์ ๋ ์ง ์๋ฃ๋ ์์ ์ ์์ ์ถ์ ํ ์ ์๋ ๊ฒฝ์ฐ setProgress(max, progress, false)๋ฅผ ํธ์ถ
- ์ฒซ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ '์๋ฃ' ๊ฐ(์: 100) / ๋ ๋ฒ์งธ๋ ์ผ๋ง๋ ์๋ฃ๋์๋์ง ์งํ์ ๋ / ๋ง์ง๋ง์ ํ์ ๋ ์งํ๋ฅ ํ์์ค
- ์์ ์ด ์งํ๋จ์ ๋ฐ๋ผ ์ ๋ฐ์ดํธ๋ progress ๊ฐ์ผ๋ก setProgress(max, progress, false)๋ฅผ ๊ณ์ ํธ์ถํ๊ณ ์๋ฆผ์ ๋ค์ ๋ฐํ. ์์ ์ด ๋๋๋ฉด progress๋ max์ ๊ฐ์์ผ ํจ
- ์งํ๋ฅ ํ์์ค์ ์ญ์ ํ๋ ค๋ฉด setProgress(0, 0, false)๋ฅผ ํธ์ถ
- ์ฐธ๊ณ ๋ก ํ๋ก๊ทธ๋ ์ค๋ฐ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ์ฑ์ด ์ง์์ ์ผ๋ก ์๋ฆผ์ ์ ๋ฐ์ดํธํด์ผ ํ๋ฏ๋ก, ์ด ์ฝ๋๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋ ์๋น์ค์์ ์คํ๋๋๋ก ํด์ผํจ
- ์ฐธ๊ณ ๋ก ๋์ค์ ํ์ผ์ ๋ค์ด๋ก๋ํ๋ ์ดํ์ ๊ตฌ์ํ๋ค๋ฉด ๋ค์ด๋ก๋ ์งํ๋ฅ ์ ์ถ์ ํ๋ ์์ฒด ์๋ฆผ์ ์ ๊ณตํ๋ DownloadManager๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ข์
val builder = NotificationCompat.Builder(this, CHANNEL_ID).apply {
setContentTitle("Picture Download")
setContentText("Download in progress")
setSmallIcon(R.drawable.ic_notification)
setPriority(NotificationCompat.PRIORITY_LOW)
}
val PROGRESS_MAX = 100
val PROGRESS_CURRENT = 0
NotificationManagerCompat.from(this).apply {
// Issue the initial notification with zero progress.
builder.setProgress(PROGRESS_MAX, PROGRESS_CURRENT, false)
notify(notificationId, builder.build())
// Do the job that tracks the progress here.
// Usually, this is in a worker thread.
// To show progress, update PROGRESS_CURRENT and update the notification with:
// builder.setProgress(PROGRESS_MAX, PROGRESS_CURRENT, false);
// notificationManager.notify(notificationId, builder.build());
// When done, update the notification once more to remove the progress bar.
builder.setContentText("Download complete")
.setProgress(0, 0, false)
notify(notificationId, builder.build())
}
6. ์๋ฆผ์ ์กํฐ๋นํฐ ์ฐ๊ฒฐ
- ์๋ฆผ์ ํด๋ฆญํ ๋ ์๋ฆผ์ ์ฌ๋ผ์ง๊ณ , SecondActivity๊ฐ ์คํ๋๋๋ก ๊ตฌํ
- SecondActivity๊ฐ ์คํ๋ ์ํ์์ Back์ด๋ Up์ ๋๋ฅด๋ฉด MainActivity๊ฐ ๋์ค๋๋ก ๊ตฌํ
- ์๋ฆผ์ ํฐ์นํ๋ฉด ์ผ๋ฐ ์กํฐ๋นํฐ์ธ SecondActivity๊ฐ ์์, ์ด ๋ MainActivity์์ SecondActivity๊ฐ ์๋ ๋ฐฑ์คํ ์์ฑ
- (์ฌ์ค MainAcitivity๊ฐ ์ด๋ฏธ ๋ฐฑ์คํ์ ์๊ธฐ ๋๋ฌธ์ TaskStackBuilder๋ก ๋ฐฑ์คํ์ ์กฐ์ํ์ง ์์๋ ๋์ผํ๊ฒ ๋์)
- ๋ฐฑ์คํ์ ์๋ ๋ค๋ฅธ ์กํฐ๋นํฐ๋ฅผ SecondActivity์ parentActivity๋ก ํ๋ฉด ๋ฌ๋ผ์ง
AndroidManifest.xml์ SecondActivity ์ ์ ๋ถ๋ถ
<activity android:name=".SecondAcitivty" android:parentActivityName=".MainActivity" />
PendingIntent ์์ฑํ๊ณ ์๋ฆผ ๋ฑ๋ก
val intent = Intent(this, SecondActivity::class.java)
val pendingIntent = with (TaskStackBuilder.create(this)) {
addNextIntentWithParentStack(intent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
val builder = NotificationCompat.Builder(this, channelID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Notification Title")
.setContentText("Notification body")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true) // ์ ์ ๊ฐ ํฐ์งํ๋ฉด ์๋ฆผ ์๋ ์ญ์
NotificationManagerCompat.from(this).notify(myNotificationID, builder.build())
๐ก Android API 33์ด์์์ ๊ถํ ์์คํ ๋ณ๊ฒฝ
- ์๋๋ก์ด๋ 12(API ๋ ๋ฒจ 33)๋ถํฐ, POST_NOTIFICATIONS ๊ถํ์ ์ฑ์ ๋งค๋ํ์คํธ ํ์ผ์ ๋ช ์์ ์ผ๋ก ์ถ๊ฐ ํ์
- ์ฌ์ฉ์์ ํ๋ผ์ด๋ฒ์๋ฅผ ๊ฐํํ๊ณ , ์ฑ์ด ์ฌ์ฉ์์ ๋์ ์์ด ์๋ฆผ์ ๋ณด๋ด๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํจ
- ์ฌ์ฉ์๋ ์ฑ ์ค์น ํ ์ฒ์ ์๋ฆผ์ ์์ ํ ๋ ์ด ๊ถํ์ ๋ถ์ฌํ ์ง ๊ฒฐ์ ํ๋ ๋ํ์์๋ฅผ ๋ณผ ์ ์์
AndroidManifest.xml ์ ๊ถํ ์ถ๊ฐ
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<!-- API 33 ์ด์์ ์ํ ์๋ฆผ ๊ถํ ์ถ๊ฐ -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
...
</manifest>
์ฌ์ฉ์ ๊ถํ์ ํ์ธํ๊ณ ๊ถํ ์์ฒญ
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) {
// ์๋ฆผ ๊ถํ์ด ์๋ค๋ฉด, ์ฌ์ฉ์์๊ฒ ๊ถํ ์์ฒญ
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
}
startActivity(intent)
}
}
๐ก ์๋ฆผ ๊ตฌํ ์์
๋๋ณด๊ธฐ
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/notificationButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="์๋ฆผ ๋ณด๋ด๊ธฐ"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.notificationButton.setOnClickListener{
notification()
}
}
fun notification(){
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val builder: NotificationCompat.Builder
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
// 26 ๋ฒ์ ์ด์
val channelId="one-channel"
val channelName="My Channel One"
val channel = NotificationChannel(
channelId,
channelName,
NotificationManager.IMPORTANCE_DEFAULT
).apply {
// ์ฑ๋์ ๋ค์ํ ์ ๋ณด ์ค์
description = "My Channel One Description"
setShowBadge(true)
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
.build()
setSound(uri, audioAttributes)
enableVibration(true)
}
// ์ฑ๋์ NotificationManager์ ๋ฑ๋ก
manager.createNotificationChannel(channel)
// ์ฑ๋์ ์ด์ฉํ์ฌ builder ์์ฑ
builder = NotificationCompat.Builder(this, channelId)
}else {
// 26 ๋ฒ์ ์ดํ
builder = NotificationCompat.Builder(this)
}
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.flower)
val intent = Intent(this, SecondActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
// ์๋ฆผ์ ๊ธฐ๋ณธ ์ ๋ณด
builder.run {
setSmallIcon(R.mipmap.ic_launcher)
setWhen(System.currentTimeMillis())
setContentTitle("์๋ก์ด ์๋ฆผ์
๋๋ค.")
setContentText("์๋ฆผ์ด ์ ๋ณด์ด์๋์.")
setStyle(NotificationCompat.BigTextStyle()
.bigText("์ด๊ฒ์ ๊ธดํ
์คํธ ์ํ์
๋๋ค. ์์ฃผ ๊ธด ํ
์คํธ๋ฅผ ์ธ๋๋ ์ฌ๊ธฐ๋ค ํ๋ฉด ๋ฉ๋๋ค.์ด๊ฒ์ ๊ธดํ
์คํธ ์ํ์
๋๋ค.
์์ฃผ ๊ธด ํ
์คํธ๋ฅผ ์ธ๋๋ ์ฌ๊ธฐ๋ค ํ๋ฉด ๋ฉ๋๋ค.์ด๊ฒ์ ๊ธดํ
์คํธ ์ํ์
๋๋ค. ์์ฃผ ๊ธด ํ
์คํธ๋ฅผ ์ธ๋๋ ์ฌ๊ธฐ๋ค ํ๋ฉด ๋ฉ๋๋ค."))
setLargeIcon(bitmap)
// setStyle(NotificationCompat.BigPictureStyle()
// .bigPicture(bitmap)
// .bigLargeIcon(null)) // hide largeIcon while expanding
addAction(R.mipmap.ic_launcher, "Action", pendingIntent)
}
manager.notify(11, builder.build())
}
}
๋ฐ์ํ
๐ฌ C O M M E N T