안드로이드 SMS와 MMS 읽어 저장하기 (1)

2018.03.29 추가사항

기존에 메세지를 읽어오는 getList 함수는 SMS와 MMS의 전체 크기를 읽는다. 때문에 SMS를 읽고 저장한 후 MMS를 읽어 저장하려고 할 경우에 문제가 있다. 메세지 리스트는 이미 SMS 리스트가 저장된 상태이기에 MMS의 저장 크기 또한 SMS 리스트의 크기로 인식되는 버그가 발생한다.

기존 코드는 메세지 리스트에 저장된 메세지의 크기를 바탕으로 SMS와 MMS를 저장할 것인지의 여부를 결정한다. 즉, 메세지는 읽되 리스트의 크기로 인해 해당 메세지들이 이미 저장되어 있다고 판단하여 문제가 발생한다. 따라서 SMS와 MMS의 리스트를 따로 불러오는 함수가 필요하다.

따라서 메세지 타입을 저장하는 변수를 MessageVO 클래스에, 메세지 타입에 따라 해당 타입의 메세지를 반환하는 fun getListBy(messageType: Boolean): RealmResults 함수를 MessageList 클래스에 추가했다. (두 종류의 메세지를 합칠 때 발생하는 문제이므로 SMS, 혹은 MMS만 읽어 저장할 경우에는 해당 함수를 추가할 필요가 없다.)



이번 졸업작품을 준비하면서 휴대폰 내부의 메세지 정보를 읽고 저장하는 작업을 진행했다. 이번 게시글에서는 SMS, 다음 게시글에서는 MMS 데이터를 읽어 Realm 데이터베이스에 저장하는 방법에 대해 나누려고 한다. Realm 데이터베이스를 사용하는 법을 알고 싶다면 안드로이드 DB로 Realm 사용하기, MMS를 읽고 저장하는 법을 알고 싶다면 안드로이드 SMS와 MMS 읽어 저장하기 (2)를 참조하시면 된다. 아래의 코드는 모두 코틀린 으로 작성되었다.

1. 메세지 정보 저장을 위한 모델 클래스를 생성한다.

1
2
3
4
5
6
7
8
9
10
// 데이터 클래스를 활용하면 편리하겠지만
// Realm이 해당 클래스를 지원하지 않기 때문에 일반 클래스를 사용했다.
open class MessageVO : RealmObject() {
    @PrimaryKey
    var id: Long = 0 // 메세지를 식별하기 위한 id 값
    lateinit var phoneNumber: String
    lateinit var date: Date // 메세지 수신 날짜
    lateinit var body: String
    var messageType: Boolean = true // 메세지 타입
}

2. Realm과 상호작용하는 클래스를 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 멀티 스레딩을 위해 Realm.getDefaultInstance 값을 넘겨받는다.
// 멀티 스레딩을 사용하지 않을 경우 멤버 변수에 값을 선언한다.
class MessageList(var realm: Realm) {
    // getList 메소드를 통해 불러온 메세지 데이터베이스 리스트를 저장한다.
    private val messageList: RealmResults<MessageVO> = getList()

    // 데이터베이스에서 저장된 메세지 데이터 객체들을 가져온다.
    fun getList(): RealmResults<MessageVO> {
        return realm.where(MessageVO::class.java).findAll()
    }

    // 데이터베이스에서 메세지 타입(SMS or MMS)에 해당하는 메세지 데이터 객체들을 가져온다.
    fun getListBy(messageType: Boolean): RealmResults<MessageVO> {
        return realm.where(MessageVO::class.java)
                .equalTo("messageType", messageType).findAll()
    }

    // 데이터베이스에 새 메세지 데이터 객체를 추가한다.
    fun addToList(phoneNumber: String, date: Date, body: String) {
        realm.beginTransaction()
        val message: MessageVO = realm.createObject(MessageVO::class.java, messageList.size.toLong())
        message.apply {
            this.phoneNumber = phoneNumber
            this.date = date
            this.body = body
        }
        realm.commitTransaction()
    }

    // 파라미터로 넘겨준 값과 같은 값에 해당하는 body의 갯수를 리턴한다.
    // 데이터의 중복 저장을 방지하기 위한 메소드이다.
    fun getBodyNumbersSameWith(body: String): Int {
        val selectedBody = realm.where(MessageVO::class.java).contains("body", body).findAll()
        return selectedBody.size
    }
}

3. SMS와 MMS에서 공통으로 사용되는 변수 및 함수를 모은 인터페이스를 만든다.(한 가지 메세지 타입만 사용할 시 생략가능)

1
2
3
4
5
6
7
interface MessageReader {
    var messageList: MessageList

    fun gatherMessages(context: Context)

    fun setDbFieldsFromMessageInbox(cursor: Cursor)
}

4. 디바이스에서 SMS를 읽고 2의 클래스를 활용해 데이터베이스에 저장하는 클래스를 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class SmsReader(realm: Realm) : MessageReader {
    override var messageList: MessageList = MessageList(realm)

    // 컨텐츠제공자에 접근해 디바이스 저장소에 있는 문자 메세지를 읽어온다.
    override fun gatherMessages(context: Context) {
        val uri: Uri = Uri.parse("content://sms/inbox")
        val contentResolver = context.contentResolver
        val cursor: Cursor
                = contentResolver.query(uri, null, null, null, null)

        if (cursor.moveToFirst()) {
            for (i: Int in messageList.getListBy(MessageVO.TYPE_SMS).size until cursor.count) {
                setDbFieldsFromMessageInbox(cursor)
                cursor.moveToNext()
            }
        }
        cursor.close()
    }

    // 커서가 위치하는 메세지에서 필요한 값을 뽑아 데이터베이스에 저장한다.
    override fun setDbFieldsFromMessageInbox(cursor: Cursor) {
        val phoneNumber: String = cursor.getString(cursor.getColumnIndexOrThrow("address"))
        val date: String = cursor.getString(cursor.getColumnIndexOrThrow("date"))
        val smsDayTime = Date(java.lang.Long.valueOf(date))
        val body: String = cursor.getString(cursor.getColumnIndexOrThrow("body"))

        if (messageList.getBodyNumbersSameWith(body) == 0) {
            messageList.addToList(phoneNumber, smsDayTime, body, MessageVO.TYPE_SMS)
        }
    }
}

SMS 데이터를 읽어오는 코드는 단순하고, 자료가 많기 때문에 어려움을 겪을 일이 그다지 없을 것으로 예상된다. 하지만 MMS 데이터를 읽어오는 일은 비교적 어려운 편이다. MMS의 데이터를 읽어오는 방법은 안드로이드 SMS와 MMS 읽어 저장하기 (2)를 참고하시기 바란다.