본문 바로가기
개발/Android

[Kotlin] Android Push 알림(Notification) 받아서 저장하기 with NotificationListenerService, Room

by jeomn 2022. 2. 28.

0. 알림을 받아오는 App 만들기

  • 알림을 보내는 어플과 알림을 받아 저장하는 어플 분리
  • 실제 앱 개발 시 분리해서 작성될 거니까...분리해서 실습해봤다..!

 

1. NotificationListener

  • Notification Listener Service를 상속받아 Notification Listener 만들기
  • 만들어서 Toast로 잘 받아와지는 지 확인
class MyNotificationListener : NotificationListenerService(){

    override fun onNotificationPosted(sbn: StatusBarNotification?) {
        super.onNotificationPosted(sbn)

        val notification = sbn?.notification

        val title = notification?.extras?.getString(Notification.EXTRA_TITLE)
        val content = notification?.extras?.getString(Notification.EXTRA_TEXT)
        
        val text = title + " " + content
        val duration = Toast.LENGTH_LONG
        val toast = Toast.makeText(applicationContext, text, duration)
        toast.show()
    }

    override fun onNotificationRemoved(sbn: StatusBarNotification?) {
        super.onNotificationRemoved(sbn)
    }
}

 

2. Listener Service 등록

  • 1에서 만든 Listener Service를 AndroidManifest.xml에 등록
  • <application> 안에 넣어야한다.
<!--<service android:name=".내가만든NotificationListenerService클래스이름"-->
<service android:name=".MyNotificationListener"
    android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
    android:exported="true">
    <intent-filter>
        <action android:name="android.service.notification.NotificationListenerService" />
    </intent-filter>
</service>

 

3. 권한 받기

  • 사용자에게 Notification 권한 받기
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        
        if(!permissionGranted()){
            val intent = Intent(
                "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")
            startActivity(intent)
        }
    }

    private fun permissionGranted() : Boolean {
        val sets = NotificationManagerCompat.getEnabledListenerPackages(this)
        return sets != null && sets.contains(packageName)
    }
}

 

4. Room - 라이브러리

  • 불러오는 게 잘 되길래 Room으로 SQLite 저장해서 불러오는 것까지 해보려고 함
  • SQLite를 직접 쓰려고 했는데..? 아래 문구를 읽고, Room 라이브러리를 써보기로 함...
Room 지속성 라이브러리는 SQLite를 완벽히 활용하면서 원활한 데이터베이스 액세스가 가능하도록 SQLite에 추상화 계층을 제공
plugins{
    ...
    id 'kotlin-kapt'
}

dependencies {
    ...
    
    //Room for SQLite
    def roomVersion = "2.4.1"
    implementation("androidx.room:room-runtime:$roomVersion")
    annotationProcessor("androidx.room:room-compiler:$roomVersion")
    kapt("androidx.room:room-compiler:$roomVersion")
}

 

5. Room - Entity, Dao, Database

  • 임시 테스트이기 때문에... 한 개의 파일에 전부 작성
  • 실제로 개발할 때는 각 파일 다 나눠서 작성할 것...!!!
  • Room Database는 리소스를 많이 소비하기 때문에, 싱글톤 디자인 패턴을 따르라는 Tip이 있다.
  • Entity
@Entity
data class NotiData(
    @PrimaryKey(autoGenerate = true) val nid:Int?,
    @ColumnInfo(name = "title") val title : String?,
    @ColumnInfo(name = "content") val content : String?
){
    constructor(title: String?, content: String?): this( null, title, content)
}
  • Dao
@Dao
interface NotiDataDao{
    @Query("SELECT * FROM NotiData")
    fun getAll() : List<NotiData>

    @Insert
    fun insert(noti : NotiData)
}
  • Database
@Database(entities = [NotiData::class], version = 1)
abstract class NotiDatabase : RoomDatabase(){
    abstract fun notiDataDao() : NotiDataDao
	
    //Singletone
    companion object{
        private var notiInstance: NotiDatabase? = null

        @Synchronized
        fun getNotiInstance(context: Context): NotiDatabase?{
            if(notiInstance == null){
                synchronized(NotiDatabase::class){
                    notiInstance = Room.databaseBuilder(context.applicationContext, NotiDatabase::class.java, "noti.db")
                        .allowMainThreadQueries()
                        .build()
                }
            }
            return notiInstance
        }

        fun destroyNotiInstance(){
            notiInstance = null
        }
    }
}

 

6. Room - 사용

  • Notification Listener가 새 알림을 잡으면 notiDB에 저장
class MyNotificationListener : NotificationListenerService(){

    private lateinit var notiDb : NotiDatabase

    override fun onNotificationPosted(sbn: StatusBarNotification?) {
        
        ...
        
        notiDb = NotiDatabase.getNotiInstance(applicationContext)!!
        notiDb.notiDataDao().insert(NotiData(title, content))
    }
    ...
}
  • Notification App에서 불러오기 버튼을 누르면 notiDB에 저장된 값을 출력

class MainActivity : AppCompatActivity() {

    private lateinit var notiDb : NotiDatabase

    override fun onCreate(savedInstanceState: Bundle?) {
        
        ...
        
        notiDb = NotiDatabase.getNotiInstance(applicationContext)!!

        btn_refresh.setOnClickListener{
            all_noti.setText(notiDb.notiDataDao().getAll().toString())
        }
    }
    ...
}

 

7. 전체 코드

  • MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var notiDb : NotiDatabase

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

        if(!permissionGranted()){
            val intent = Intent(
                "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")
            startActivity(intent)
        }

        notiDb = NotiDatabase.getNotiInstance(applicationContext)!!

        btn_refresh.setOnClickListener{
            all_noti.setText(notiDb.notiDataDao().getAll().toString())
        }
    }

    private fun permissionGranted() : Boolean {
        val sets = NotificationManagerCompat.getEnabledListenerPackages(this)
        return sets != null && sets.contains(packageName)
    }
}
  • MyNotificationListener.kt
class MyNotificationListener : NotificationListenerService(){

    private lateinit var notiDb : NotiDatabase

    override fun onNotificationPosted(sbn: StatusBarNotification?) {
        super.onNotificationPosted(sbn)

        val notification = sbn?.notification

        val title = notification?.extras?.getString(Notification.EXTRA_TITLE)
        val content = notification?.extras?.getString(Notification.EXTRA_TEXT)
        val text = title + " " + content
        val duration = Toast.LENGTH_LONG
        val toast = Toast.makeText(applicationContext, text, duration)
        toast.show()

        notiDb = NotiDatabase.getNotiInstance(applicationContext)!!
        notiDb.notiDataDao().insert(NotiData(title, content))
    }

    override fun onNotificationRemoved(sbn: StatusBarNotification?) {
        super.onNotificationRemoved(sbn)
    }
}
  • NotiData.kt
//NotiEntity.kt, NotiDao.kt, NotiDatabase.kt 등 파일 나눠서 쓰기...!

@Entity
data class NotiData(
    @PrimaryKey(autoGenerate = true) val nid:Int?,
    @ColumnInfo(name = "title") val title : String?,
    @ColumnInfo(name = "content") val content : String?
){
    constructor(title: String?, content: String?): this( null, title, content)
}

@Dao
interface NotiDataDao{
    @Query("SELECT * FROM NotiData")
    fun getAll() : List<NotiData>

    @Insert
    fun insert(noti : NotiData)
}

@Database(entities = [NotiData::class], version = 1)
abstract class NotiDatabase : RoomDatabase(){
    abstract fun notiDataDao() : NotiDataDao

    companion object{
        private var notiInstance: NotiDatabase? = null

        @Synchronized
        fun getNotiInstance(context: Context): NotiDatabase?{
            if(notiInstance == null){
                synchronized(NotiDatabase::class){
                    notiInstance = Room.databaseBuilder(context.applicationContext, NotiDatabase::class.java, "noti.db")
                        .allowMainThreadQueries()   //main에서 써주려고 추가
                        .build()
                }
            }
            return notiInstance
        }

        fun destroyNotiInstance(){
            notiInstance = null
        }
    }
}

 

8. 결과

 

9. 참조

 

댓글