ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TECHIT 앱 스쿨 2기: Android 47일차 (23.07.05)
    [THEC!T] 앱 스쿨2기 : Android 2023. 7. 5. 23:27
    728x90

    자료 출처 : 안드로이드 앱스쿨 2기 윤재성 강사님 수업 내용

     

    오늘은 raw에 대한 설명과 Assets에 대한 설명으로 시작해주셨습니다.

     

    raw 데이터는 가공되지 않은 원천 데이터를 의미하며, 사운드나 동영상, 사진 등을 용량을 줄이기 위해 압축을 하는데 이러한 가공을 거치지 않은 순수 데이터들을 raw 데이터라고 부릅니다. 안드로이드에서는 각종 데이터 파일이나 동영상, 사운드 등의 데이터를 사용할 때 주로 사용합니다.

    실행 중 다운받거나 생성된 데이터 파일은 내부 저장소나 외부저장소에 저장해 두었다가 필요할 때 읽어오면 됩니다.
    만약 데이터가 저장된 파일을 애플리케이션 내부에 포함 시키겠다면 raw 폴더에 저장하고, raw 폴더에 저장된 파일은 스트림으로 손쉽게 추출할 수 있습니다.

     

    사용예시

    activityMainBinding.run{
        button.setOnClickListener{
            // raw 폴더에 있는 파일과 연결된 스트림을 추출한다.
            val inputStream = resources.openRawResource(R.raw.data)
            val inputStreamReader = InputStreamReader(inputStream, "UTF-8")
            val bufferedReader = BufferedReader(inputStreamReader)
    
            var str:String? = null
            val stringBuffer = StringBuffer()
    
            do {
                str = bufferedReader.readLine()
    
                if(str != null){
                    stringBuffer.append("${str}\n")
                }
            }while(str != null)
    
            bufferedReader.close()
    
            textView.text = stringBuffer.toString()
        }
    
        button2.setOnClickListener{
            if(mediaPlayer == null){
                // 사운드 재생 관리 객체를 생성한다.
                mediaPlayer = MediaPlayer.create(this@MainActivity, R.raw.song)
                // 재생한다.
                mediaPlayer?.start()
            }
        }
    
        button3.setOnClickListener {
            if(mediaPlayer != null){
                // 사운드를 중지시킨다.
                mediaPlayer?.stop()
                mediaPlayer = null
            }
        }
    
        button4.setOnClickListener {
            // ViewView가 재생중이 아니라면
            if(videoView.isPlaying == false){
                // 영상 파일의 경로를 가져온다.
                val uri = Uri.parse("android.resource://${packageName}/raw/video")
                // 영상 주소를 설정한다.
                videoView.setVideoURI(uri)
                // 재생한다.
                videoView.start()
            }
        }
    
        button5.setOnClickListener {
            // ViewView가 재생 중이라면
            if(videoView.isPlaying == true){
                // 영상 재생을 중지한다.
                videoView.stopPlayback()
            }
        }

     

    assets

     

    raw 데이터 파일은 raw 폴더에 담으면 스트림을 손쉽게 추출할 수 있다는 장점이 있으나, raw 폴더는 하위 폴더를 만드는 등 계층적으로 관리할 수 없습니다. 
    만약 파일들을 계층적인 폴더 구조를 만들어 관리하겠다면 assets 폴더를 사용하며, assets 폴더는 res 폴더 내부가 아니므로 리소스(R 클래스)로 관리할 수 없습니다.

    assets 폴더에는 다양한 종류의 파일들을 담고 사용할 수 있고, 특히 폰트 파일을 손쉽게 사용할 수 있도록 클래스를 제공하고 있습니다.

     

    package com.test.android68_assets
    
    import android.graphics.Typeface
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import com.test.android68_assets.databinding.ActivityMainBinding
    import java.io.BufferedReader
    import java.io.InputStreamReader
    
    class MainActivity : AppCompatActivity() {
    
        lateinit var activityMainBinding: ActivityMainBinding
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(activityMainBinding.root)
    
            activityMainBinding.run{
                button.setOnClickListener {
                    // Assets 폴더에 있는 파일과 연결된 스트림을 추출한다.
                    val inputStream = assets.open("text/data.txt")
                    val inputStreamReader = InputStreamReader(inputStream, "UTF-8")
                    val bufferedReader = BufferedReader(inputStreamReader)
    
                    var str:String? = null
                    val stringBinding = StringBuffer()
    
                    do{
                        str = bufferedReader.readLine()
                        if(str != null){
                            stringBinding.append("${str}\n")
                        }
                    }while(str != null)
    
                    bufferedReader.close()
    
                    textView.text = stringBinding.toString()
                }
    
                button2.setOnClickListener {
                    // Assets 폴더에 있는 폰트 파일을 이용해 폰트 객체를 생성한다.
                    val typeFace = Typeface.createFromAsset(assets, "fonts/NanumPen.ttf")
                    // TextView에 적용한다.
                    textView.typeface = typeFace
                    textView.textSize = 50.0f
                }
            }
        }
    }

     

     

    이후로는 SQLite 에 대해서 알려주셨습니다.

    SQLite 는 안드로이드에서 데이터를 관리하기 효율적으로 관리하기 위해 사용하는 데이터베이스이며, 문법은 MySQL 과 거의 동일합니다.

     

    DB 생성

    package com.test.android69_sqlitedatabase1
    
    import android.content.Context
    import android.database.sqlite.SQLiteDatabase
    import android.database.sqlite.SQLiteOpenHelper
    
    // Test.db : 사용할 데이터 베이스 파일의 이름
    // null : NullFactory, Null에 대한 처리를 어떻게 할 것인가.. Null 셋팅하시면 됩니다.
    // 1 : 버전
    class DBHelper(context:Context) : SQLiteOpenHelper(context, "Test.db", null, 1) {
        // 데이터 베이스 파일이 없으면 만들고 이 메서드를 호출해준다.
        // 테이블을 생성하는 작업을 수행하면 된다.
        override fun onCreate(sqliteDatabase: SQLiteDatabase?) {
            // 테이블의 구조를 정의한다.
    
            // create table 테이블이름
            // (컬럼이름 자료형 제약조건 ... )
            // 자료형 : 정수 - integer, 문자열 - text, 실수 - real, 날짜 - date
            // 제약조건 : 저장할 수 있는 값에 대한 조건
            // primary key : null을 허용하지 않고 중복된 값을 허용하지 않는다.
            // 각 행들을 개별적으로 구분할 수 있는 값을 저장하기 위해 사용한다.
            // autoincrement : 컬럼에 저장할 값을 지정하지 않으면 1부터 1씩 증가되는 값이 자동으로 저장한다.
            // not null : null 값을 허용하지 않는다. 즉, 개발자가 무조건 값을 정해줘야 한다.
    
            val sql = """create table TestTable
                (idx integer primary key autoincrement,
                textData text not null,
                intData integer not null,
                doubleData real not null,
                dateData date not null)
            """.trimIndent()
    
            // 쿼리문을 수행한다.
            sqliteDatabase?.execSQL(sql)
        }
    
        // 사용하는 데이터 베이스 파일의 버전이 변경되어 있을 때 호출되는 메서드
        // 부모의 생성자의 마지막에 넣어준 버전 번호가 데이터 베이스 파일에 기록 버전 보다 높을 때 호출된다.
        // 과거에 만들어진 테이블을 현재의 구조가 될 수 있도록 테이블을 수정하는 작업을 하면 된다.\
        override fun onUpgrade(sqliteDatabase: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
            TODO("Not yet implemented")
        }
    }

    DB 조회 

    package com.test.android69_sqlitedatabase1
    
    import android.content.Context
    
    class DAO {
    
        companion object {
            // Create : 저장
            fun insertData(context: Context, data:TestClass ){
                // autoincrement가 있는 컬럼은 제외하고 나머지만 지정한다.
                val sql = """insert into TestTable
                    | (textData, intData, doubleData, dateData)
                    | values (?, ?, ?, ?)
                """.trimMargin()
    
                // ? 에 설정할 값을 배열에 담아준다.
                val arg1 = arrayOf(
                    data.textData, data.intData, data.doubleData, data.dateData
                )
                // 데이터베이스 오픈
                val sqliteDatabase = DBHelper(context)
                // 쿼리 실행 (쿼리문, ?에 셋팅할 값 배열)
                sqliteDatabase.writableDatabase.execSQL(sql, arg1)
                // 데이터 베이스를 닫아준다.
                sqliteDatabase.close()
            }
    
            // Read Condition : 조건에 맞는 행 하나를 가져온다.
            fun selectData(context: Context, idx:Int):TestClass{
                // 쿼리문
                val sql = "select * from TestTable where idx=?"
                // ?에 들어갈 값 (문열 배열)
                val arg1 = arrayOf("$idx")
    
                // 데이터베이스 오픈
                val dbHelper = DBHelper(context)
                // 쿼리실행
                val cursor = dbHelper.writableDatabase.rawQuery(sql, arg1)
                cursor.moveToNext()
    
                // 컬럼의 이름을 지정하여 컬럼의 순서값을 가져온다.
                val idx1 = cursor.getColumnIndex("idx")
                val idx2 = cursor.getColumnIndex("textData")
                val idx3 = cursor.getColumnIndex("intData")
                val idx4 = cursor.getColumnIndex("doubleData")
                val idx5 = cursor.getColumnIndex("dateData")
    
                // 데이터를 가져온다.
                val idx = cursor.getInt(idx1)
                val textData = cursor.getString(idx2)
                val intData = cursor.getInt(idx3)
                val doubleData = cursor.getDouble(idx4)
                val dateData = cursor.getString(idx5)
    
                val testClass = TestClass(idx, textData, intData, doubleData, dateData)
    
                dbHelper.close()
                return testClass
            }
    
            // Read All : 모든 행을 가져온다
            fun selectAllData(context: Context):MutableList<TestClass>{
                // 모든 행을 가져오는 쿼리문을 작성한다.
                val sql = "select * from TestTable"
    
                // 데이터베이스 오픈
                val dbHelper = DBHelper(context)
                // 쿼리 실행
                val cursor = dbHelper.writableDatabase.rawQuery(sql, null)
                // cursor 객체는 쿼리문에 맞는 행에 접근할 수 있는 객체가 된다.
                // 처음에는 아무 행도 가르치고 있지 않는다.
                // moveToNext 메서드를 호출하면 다음 행에 접근할 수 있다.
                // 이때 접근할 행이 있으면 true를 반환하고 없으면  false를 반환한다.
    
                val dataList = mutableListOf<TestClass>()
    
                while(cursor.moveToNext()){
                    // 컬럼의 이름을 지정하여 컬럼의 순서값을 가져온다.
                    val idx1 = cursor.getColumnIndex("idx")
                    val idx2 = cursor.getColumnIndex("textData")
                    val idx3 = cursor.getColumnIndex("intData")
                    val idx4 = cursor.getColumnIndex("doubleData")
                    val idx5 = cursor.getColumnIndex("dateData")
    
                    // 데이터를 가져온다.
                    val idx = cursor.getInt(idx1)
                    val textData = cursor.getString(idx2)
                    val intData = cursor.getInt(idx3)
                    val doubleData = cursor.getDouble(idx4)
                    val dateData = cursor.getString(idx5)
    
                    val testClass = TestClass(idx, textData, intData, doubleData, dateData)
                    dataList.add(testClass)
                }
    
                dbHelper.close()
    
                return dataList
            }
            // Update : 조건에 맞는 행의 컬럼의 값을 수정한다.
            fun updateData(context:Context, obj:TestClass){
                // 쿼리문
                // idx가 ? 인 행의 textData, intData, doubleData, dateData 컬럼의 값을 변경한다
                val sql = """update TestTable
                    | set textData=?, intData=?, doubleData=?, dateData=?
                    | where idx=?
                """.trimMargin()
    
                // ? 에 들어갈 값
                val args = arrayOf(obj.textData, obj.intData, obj.doubleData, obj.dateData, obj.idx)
                // 쿼리 실행
                val dbHelper = DBHelper(context)
                dbHelper.writableDatabase.execSQL(sql, args)
                dbHelper.close()
            }
    
    
            // Delete : 조건 맞는 행을 삭제한다.
            fun deleteData(context:Context, idx:Int){
                // 쿼리문
                // TestTable에서 idx가 ? 인 행을 삭제한다.
                val sql = "delete from TestTable where idx = ?"
                // ?에 들어갈 값
                val args = arrayOf(idx)
                // 쿼리 실행
                val dbHelper = DBHelper(context)
                dbHelper.writableDatabase.execSQL(sql, args)
                dbHelper.close()
            }
        }
    
    }

     

    이 외에도 다른 방법으로 SQLite를 사용하는 방법도 알려주셨습니다.

     

    DB 생성 방법은 동일합니다.

     

    DB조회

    package com.test.android70_sqlitedatabase2
    
    
    import android.content.ContentValues
    import android.content.Context
    
    class DAO {
    
        companion object {
            // Create : 저장
            fun insertData(context: Context, data:TestClass ){
                // 컬럼이름과 데이터를 설정하는 객체
                val contentValues = ContentValues()
                // 컬럼 이름, 값을 지정한다.
                contentValues.put("textData", data.textData)
                contentValues.put("intData", data.intData)
                contentValues.put("doubleData", data.doubleData)
                contentValues.put("dateData", data.dateData)
    
                val dbHelper = DBHelper(context)
                // 데이터를 저장할 테이블의 이름, null값을 어떻게 처리할 것인가(그냥 null 넣어주세요),
                // 저장할 데이터를 가지고 있는 객체
                dbHelper.writableDatabase.insert("TestTable", null, contentValues)
                dbHelper.close()
            }
    
            // Read Condition : 조건에 맞는 행 하나를 가져온다.
            fun selectData(context: Context, idx:Int):TestClass{
    
                val dbHelper = DBHelper(context)
                // 첫 번째 : 테이블명
                // 두 번째 : 가지고 오고자 하는 컬럼 이름 목록. null을 넣어주면 모두 가져온다.
                // 세 번째 : 특정 행을 선택하기 위한 조건절
                // 네 번째 : 세 번째에 들어가는 조건절의 ? 에 셋팅될 값 배열
                // 다섯 번째 : Group by의 기준 컬럼
                // 여섯 번째 : Having절에 들어갈 조건절
                // 일곱 번째 : Having절의 ?에 셋팅될 값 배열
                val selection = "idx = ?"
                val args = arrayOf("$idx")
                val cursor = dbHelper.writableDatabase.query("TestTable", null, selection, args, null, null, null)
    
                cursor.moveToNext()
    
                // 컬럼의 이름을 지정하여 컬럼의 순서값을 가져온다.
                val idx1 = cursor.getColumnIndex("idx")
                val idx2 = cursor.getColumnIndex("textData")
                val idx3 = cursor.getColumnIndex("intData")
                val idx4 = cursor.getColumnIndex("doubleData")
                val idx5 = cursor.getColumnIndex("dateData")
    
                // 데이터를 가져온다.
                val idx = cursor.getInt(idx1)
                val textData = cursor.getString(idx2)
                val intData = cursor.getInt(idx3)
                val doubleData = cursor.getDouble(idx4)
                val dateData = cursor.getString(idx5)
    
                val testClass = TestClass(idx, textData, intData, doubleData, dateData)
    
                dbHelper.close()
                return testClass
            }
    
            // Read All : 모든 행을 가져온다
            fun selectAllData(context: Context):MutableList<TestClass>{
    
                val dbHelper = DBHelper(context)
                // 첫 번째 : 테이블명
                // 두 번째 : 가지고 오고자 하는 컬럼 이름 목록. null을 넣어주면 모두 가져온다.
                // 세 번째 : 특정 행을 선택하기 위한 조건절
                // 네 번째 : 세 번째에 들어가는 조건절의 ? 에 셋팅될 값 배열
                // 다섯 번째 : Group by의 기준 컬럼
                // 여섯 번째 : Having절에 들어갈 조건절
                // 일곱 번째 : Having절의 ?에 셋팅될 값 배열
                val cursor = dbHelper.writableDatabase.query("TestTable", null, null, null, null, null, null)
    
    
                // cursor 객체는 쿼리문에 맞는 행에 접근할 수 있는 객체가 된다.
                // 처음에는 아무 행도 가르치고 있지 않는다.
                // moveToNext 메서드를 호출하면 다음 행에 접근할 수 있다.
                // 이때 접근할 행이 있으면 true를 반환하고 없으면  false를 반환한다.
    
                val dataList = mutableListOf<TestClass>()
    
                while(cursor.moveToNext()){
                    // 컬럼의 이름을 지정하여 컬럼의 순서값을 가져온다.
                    val idx1 = cursor.getColumnIndex("idx")
                    val idx2 = cursor.getColumnIndex("textData")
                    val idx3 = cursor.getColumnIndex("intData")
                    val idx4 = cursor.getColumnIndex("doubleData")
                    val idx5 = cursor.getColumnIndex("dateData")
    
                    // 데이터를 가져온다.
                    val idx = cursor.getInt(idx1)
                    val textData = cursor.getString(idx2)
                    val intData = cursor.getInt(idx3)
                    val doubleData = cursor.getDouble(idx4)
                    val dateData = cursor.getString(idx5)
    
                    val testClass = TestClass(idx, textData, intData, doubleData, dateData)
                    dataList.add(testClass)
                }
    
                dbHelper.close()
    
                return dataList
            }
            // Update : 조건에 맞는 행의 컬럼의 값을 수정한다.
            fun updateData(context:Context, obj:TestClass){
                // 컬럼과 값을 지정하는 ContentValues 생성한다.
                val cv = ContentValues()
                cv.put("textData", obj.textData)
                cv.put("intData", obj.intData)
                cv.put("doubleData", obj.doubleData)
                cv.put("dateData", obj.dateData)
                // 조건절
                val condition = "idx = ?"
                // ?에 들어갈 값
                val args = arrayOf("${obj.idx}")
                // 수정한다.
                val dbHelper = DBHelper(context)
                // 테이블명, content values, 조건절, ?에 들어갈 값
                dbHelper.writableDatabase.update("TestTable", cv, condition, args)
                dbHelper.close()
            }
    
    
            // Delete : 조건 맞는 행을 삭제한다.
            fun deleteData(context:Context, idx:Int){
                // 조건절
                val condition = "idx = ?"
                // ?에 들어갈 값
                val args = arrayOf("$idx")
    
                val dbHelper = DBHelper(context)
                dbHelper.writableDatabase.delete("TestTable", condition, args)
                dbHelper.close()
            }
        }
    
    }

     

     

    이후 SQLite를 사용하여 작성하는 문제를 내주셨습니다. 

     

    이 문제에서 데이터를 SQLiteDataBase로 저장해 사용하는 것으로 바꾼다.

     

    상기와 같은 문제를 내주셨고, 저는 추가적으로 데이터를 삭제하는 기능까지 작성해보았습니다.

     

     

    강사님께서 알려주신 코드에서 약간 변형을 하면 순서에 꼬이지 않게 삭제처리가 가능하였습니다.

    바로 기본키를 변경하는 것입니다. 저의 경우 저장하기의 버튼을 눌렀을 때의 시간값을 기본키로 지정하여 db를 생성하였습니다.

     

    만약 많은 사람들이 사용한다고 할 경우 시간값도 중복이 안된다는 보장이 없기에 시간값+사용자의 사번 혹은 작업의 종류 등을 추가로 지정하여 중복을 피하는 방법도 있습니다.

     

    마무리

    오늘은 안풀리던 알고리즘 문제에 대해서도 같이 스터디를 하는 분께 설명을 들어 풀 수 있었고, 제가 평소에 생각해보지 못한 방법이라 뭔가 뚫린듯한 느낌을 받아 좋았습니다.

     

    오늘의 마음가짐

    안되는 날이 있다면 잘되는 날도 있으니 꾸준히 하자

Designed by Tistory.