1. 데이터베이스에 파일 저장 (SQLite)
| 항목 | 내용 |
|---|---|
execSQL(sql, args): Unit | 원시 C-UD |
rawQuery(sql, args): Cursor | 원시 R |
insert(), query(), update(), delete() | 고급 CRUD |
onCreate() | 최초 1회 |
onUpgrade() | DB 버전 변경 |
readableDatabase | 읽기 모드 DB 접근 |
writableDatabase | 쓰기 모드 DB 접근 |
- 커서 초기 위치는 Before First (인덱스 -1);
moveToNext()(없으면 false 반환)
1.1. 안드로이드 데이터베이스 개요
- 안드로이드는 오픈소스 SQLite를 DBMS로 탑재
- 모바일용 경량화 데이터베이스
- 앱의 저장소에 파일 형태로 저장 → 외부 앱 접근 불가
- 데이터의 CRUD 작업: Create(insert), Read(select), Update, Delete
1.2. SQLiteDatabase 객체 생성
val db = openOrCreateDatabase("testdb", Context.MODE_PRIVATE, null)| 인자 | 설명 |
|---|---|
| 첫 번째 | DB 이름 (없으면 새로 생성) |
| 두 번째 | Context.MODE_PRIVATE → 현재 앱에서만 접근 가능 |
| 세 번째 | 커서 팩토리 (null = 기본 커서 사용) |
커서(Cursor): 쿼리 결과를 저장하는 객체로, 데이터베이스 조회 시 결과값을 담음
1.3. SQL 실행 함수
| 함수 | 용도 |
|---|---|
execSQL(sql, args): Unit | 테이블 생성, 삽입, 삭제, 수정 |
rawQuery(sql, args): Cursor | 데이터 조회 |
// 테이블 생성
db.execSQL("
create table USER_TB(
_id INTEGER primary key autoincrement,
name TEXT not null,
phone TEXT
)
")
// 데이터 삽입
db.execSQL(
"insert into USER_TB(name, phone) values (?,?)",
arrayOf<String>("kkang","0101111")
)
// 데이터 조회
val cursor = db.rawQuery("select * from USER_TB", null)1.4. Cursor 사용법
(1) 행(Row) 이동 함수
| 함수 | 설명 |
|---|---|
moveToFirst() | 첫 번째 행 선택 |
moveToLast() | 마지막 행 선택 |
moveToNext() | 다음 행 선택 (없으면 false 반환) |
moveToPrevious() | 이전 행 선택 |
moveToPosition(n) | n번째 위치의 행 선택 |
- 커서 초기 위치
- Before First (인덱스 -1; 데이터 시작 바로 직전)
(2) 열(Column) 데이터 가져오기
while (cursor.moveToNext()) {
val name = cursor.getString(0) // 첫 번째 열
val phone = cursor.getString(1) // 두 번째 열
}| 함수 | 타입 |
|---|---|
getString(index) | 문자열 |
getInt(index) | 정수 |
getDouble(index) | 실수 |
1.5. 데이터 다루는 함수 (CRUD)
- 원시 SQL문 대신 안드로이드에서 제공하는 메서드를 사용할 수도 있다.
(1) INSERT
insert(): 테이블에 데이터 추가- (ContentValues 객체에 컬럼-값 쌍으로 저장하여 전달)`
val values = ContentValues()
values.put("name", "홍길동")
values.put("phone", "010-1234-5678")
val newRowId = db.insert("USER_TB", null, values) // 값이 없는 컬럼에는 null을 저장
// 성공 시 고유 ID 반환, 실패 시 -1 반환(2) UPDATE
update(): 조건에 맞는 데이터 수정
val values = ContentValues()
values.put("phone", "010-9999-8888")
val updatedCount = db.update("USER_TB", values, "name = ?", arrayOf("홍길동"))
// 수정된 ROW 개수 반환(3) DELETE
delete(): 조건에 맞는 데이터 삭제
val deletedCount = db.delete("USER_TB", "name = ? AND phone = ?", arrayOf("홍길동", "010-1234-5678"))
// 삭제된 ROW 개수 반환(4) SELECT
query(): 구조화된 SELECT 쿼리 실행`
val cursor = db.query(
"USER_TB", // table
arrayOf("name","phone"), // columns
"phone=?", // selection (where절)
arrayOf("0101112"), // selectionArgs (?에 삽입)
null, // groupBy
null, // having
null // orderBy
)| 매개변수 | 의미 |
|---|---|
table | 조회할 테이블명 |
columns | 가져올 열 이름 배열 |
selection | WHERE 조건식 |
selectionArgs | ?에 들어갈 값 배열 |
groupBy | GROUP BY 절 |
having | HAVING 조건 |
orderBy | ORDER BY 정렬 기준 |
1.6. SQLiteOpenHelper
SQLiteOpenHelper- 테이블 생성, 변경, 제거를 관리하는 추상 클래스
- 추상 클래스이므로 반드시 상속받아 하위 클래스 작성 필요
| 메서드 | 호출 시점 | 용도 |
|---|---|---|
onCreate() | 앱 설치 후 최초 1회 | 테이블 생성 |
onUpgrade() | DB 버전 변경 시마다 | 테이블 스키마 변경 |
class DBHelper(context: Context) : SQLiteOpenHelper(context, "testdb", null, 1) {
override fun onCreate(db: SQLiteDatabase?) {
db!!.execSQL("CREATE TABLE groupTBL (gName CHAR(20) PRIMARY KEY, gNumber INTEGER);")
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
db!!.execSQL("DROP TABLE IF EXISTS groupTBL")
onCreate(db)
}
}-
onCreate(db)- 앱 설치 후 DB가 처음 생성될 때 1회 호출됨
- 주로 테이블 생성(
CREATE TABLE) 코드를 작성
-
onUpgrade(db, old, new)- DB 버전 번호가 변경될 때 호출됨
- 주로 기존 테이블 삭제(
DROP) 후 재생성 등 스키마 변경 코드를 작성
// 쓰기 모드로 DB 객체 생성
val db: SQLiteDatabase = DBHelper(this).writableDatabase
// 읽기 모드로 DB 객체 생성
val db: SQLiteDatabase = DBHelper(this).readableDatabase2. 파일 저장의 기본 개념
-
안드로이드 앱에서 파일을 다룰 때는
- 자바와 동일하게
java.io.*패키지를 사용한다.
- 자바와 동일하게
-
주요 클래스는 다음과 같다.
- File: 파일 및 디렉터리를 다루는 클래스
- FileReader / FileWriter: 파일을 문자열 스트림으로 읽고 쓰는 클래스
- FileInputStream / FileOutputStream: 파일을 바이트 스트림으로 읽고 쓰는 클래스
-
앱별 저장소에 저장된 데이터는
- 외부에서 접근할 수 없으며,
- 경로는
/data/data/앱이름이다.
| 구분 | 내장 메모리 | 외장 - 앱별 저장소 | 외장 - 공용 저장소 |
|---|---|---|---|
| 저장 위치 | /data/data/패키지명 | /storage/emulated/0/Android/data/패키지명/files | 시스템 지정 폴더 |
| 접근 방법 | filesDir, openFileOutput() | getExternalFilesDir() | 시스템 API |
| 특징 | 앱 전용, 외부 접근 불가, 용량 작음 | 앱 삭제 시 함께 삭제 | 모든 앱 공유, 앱 삭제 후에도 유지, 권한 필요 |
3. 내부(내장) 메모리에 파일 저장
- 앱을 설치하면
- 패키지명으로 디렉터리가 생성되고, 이것이 내장 메모리 공간이다.
- 용량이 작기 때문에 큰 데이터는 외장 메모리에 저장한다.
저장 방법 1: File 클래스 직접 사용
File(filesDir, "파일명")으로 객체를 만들고writer()로 쓰고flush()로 저장,reader().buffered()로 읽는다.
// 파일 객체 생성 후 데이터 쓰기
val file = File(filesDir, "test.txt") // 경로, 파일명
val writeStream: OutputStreamWriter = file.writer()
writeStream.write("hello world")
writeStream.flush() // 파일 저장
// 파일의 데이터 읽기
val readStream: BufferedReader = file.reader().buffered()
readStream.forEachLine {
Log.d("tukorea", "$it")
}저장 방법 2: Context 함수 사용
openFileOutput("파일명", 모드)과openFileInput("파일명")을 사용한다.use { }블록: 자원을 자동 해제하여 메모리 누수를 막는다.bufferedReader(): 데이터를 문자 단위로 버퍼에 읽어온다.forEachLine { }블록: 한 줄씩 읽은 뒤 자동으로 파일을 닫아준다.
openFileOutput("test.txt", Context.MODE_PRIVATE).use {
it.write("hello world!!".toByteArray())
}
openFileInput("test.txt").bufferedReader().forEachLine {
Log.d("tukorea", "$it")
}4. 외장 메모리에 파일 저장
- 기기마다 외장 메모리 탑재 여부가 다르므로,
Environment.getExternalStorageState()함수로MEDIA_MOUNTED(외장 메모리 사용 가능)인지 먼저 확인한 후 사용해야 한다.
// 외장 메모리 사용 가능 여부 판단
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
Log.d("tukorea", "ExternalStorageState MOUNTED")
} else {
Log.d("tukorea", "ExternalStorageState UNMOUNTED")
}외장 메모리는 앱별 저장소와 공용 저장소로 구분된다.
4.1. 앱별 저장소
- 개별 앱에 할당된 공간으로,
getExternalFilesDir()로 위치를 확인한다.
// 앱별 저장소에 접근
val file: File? = getExternalFilesDir(null)
Log.d("tukorea", "${file?.absolutePath}")
// /storage/emulated/0/Android/data/com.example.file_project/files-
인자로 다음 상수를 넘기면 해당 종류별 폴더를 가리킨다.
null: 기본 디렉터리Environment.DIRECTORY_PICTURES: 사진Environment.DIRECTORY_DOCUMENTS: 문서Environment.DIRECTORY_MUSIC: 음악Environment.DIRECTORY_MOVIES: 동영상
-
외부 앱에서 접근하게 하려면 파일 프로바이더로 공개해야 한다.
4.2. 공용 저장소
- 모든 앱이 이용 가능하며(주로 카메라 사진 등), 앱을 삭제해도 파일이 남는다.
- 파일 종류에 따라 폴더가 지정되어 있어 경로로 직접 접근하지 않고 시스템 API를 사용해야 한다.
(1) 공용 저장소의 권한 설정
권한 설정이 안드로이드 버전에 따라 달라지는 점이 중요히다.
-
API 32 (안드로이드 12) 이전
READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE권한 사용
-
API 33 (안드로이드 13)
- 공용 공간의 미디어 파일 타입을 구분
- →
READ_MEDIA_IMAGES,READ_MEDIA_VIDEO
-
API 34 (안드로이드 14)
- 사용자가 앱의 미디어 파일 접근을 다시 선택할 수 있는
READ_MEDIA_VISUAL_USER_SELECTED권한 추가
- 사용자가 앱의 미디어 파일 접근을 다시 선택할 수 있는
<!-- API Level 32 이전 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- API Level 33 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<!-- API Level 34 이후 -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"/>5. 실습: 파일 쓰고 읽기
내부 저장소의
file디렉터리에file.txt를 생성하는 예제이다.
- 쓰기: 문자열을
toByteArray()로 바이트 배열로 변환해write()에 전달 - 읽기: 바이트 배열로 읽은 뒤
toString(Charsets.UTF_8)로 변환 (UTF-8을 써야 한글이 깨지지 않음) - 파일을 읽지 못할 때 앱이 비정상 종료되지 않도록
try-catch로IOException예외 처리