540 likes | 750 Views
10. 자료 관리. 학습목표 프로그램이 생성한 데이터를 영구적으로 저장하는 기술에 대하여 학습한다 . CP 를 통해 다른 응용 프로그램과 정보를 공유하는 방법에 대하여 학습한다 . 내용 파일 입출력 프레프런스 SQLite CP (Content Provider). 1. 파일 입출력. 파일 관리 메서드 안드로이드 자체에는 파일 관리 기능이 따로 포함되어 있지 않다 . 파일 시스템은 기반 운영체제인 리눅스의 것 그대로 사용하며 , 파일 입출력 라이브러리는 자바의 것을 이용한다 .
E N D
10 자료 관리
학습목표 • 프로그램이 생성한 데이터를 영구적으로 저장하는 기술에 대하여 학습한다. • CP를 통해 다른 응용 프로그램과 정보를 공유하는 방법에 대하여 학습한다. • 내용 • 파일 입출력 • 프레프런스 • SQLite • CP (Content Provider)
1. 파일 입출력 • 파일 관리 메서드 • 안드로이드 자체에는 파일 관리 기능이 따로 포함되어 있지 않다. • 파일 시스템은 기반 운영체제인 리눅스의 것 그대로 사용하며, 파일 입출력 라이브러리는 자바의 것을 이용한다. • 따라서 리눅스 파일 시스템의 이해가 선행되며, 자바 입출력 스트림을 자유자재로 사용할 수 있어야 한다. • 또한, 보안상의 제약으로 인해 Context 클래스에서 보안이 적용된 파일 관리 메서드를 별도로 제공하며, 이를 이용하여 파일을 Open한다. • Context클래스 파일 입출력 메서드 • FileOutputStreamopenFileOutput (String name, int mode) • FileInputStreamopenFileInput (String name) • 각각 쓰기용, 읽기용으로 파일을 열어 스트림을 리턴한다. • name • - 열고자 하는 파일의 이름. • - 파일의 위치는/data/data/패키지명/file디렉토리로지정되어 있으며, 반드시 파일명만 적어야 한다. (임의 경로의 파일을 마음대로 열 수 없으며, 자신의 디렉토리만 액세스 가능하다.) • - 파일명에 경로를 표시하는 ‘/’문자가 들어가면 에러 처리된다.
1. 파일 입출력 • 파일 Open 시 스트림이 리턴되며 이후부터 자바의 스트림 입출력 메서드를 이용한다. • 파일 삭제 및 파일 List 구하기 메서드 • mode • - 파일 생성시 아래의 mode중 하나를 지정한다. • booleandeleteFile (String name) • String[] fileList () • 둘 다 패키지 디렉토리 아래의 파일들만 관리할 수 있으며, 경로는 사용할 수 없다 .
1. 파일 입출력 • FileIO예제 • 레이아웃은 버튼 4개와 입출력 결과 확인을 위한 에디트를 하나 배치한다. • 각 버튼 클릭 시 파일 입출력 동작 수행한다.
1. 파일 입출력 • Save버튼 - 쓰기용, 공유 모드로 test.txt파일을 생성한다. - 리턴된 출력 스트림의 입출력 메서드로 데이터 출력 시 파일에 기록된다. -write메서드로 문자열 데이터를 출력, close메서드로 스트림을 닫는다. - 파일 입출력 중 예외가 발생할 확률이 높으므로 try, catch를 이용하여 예외 처리를 해야 한다. - 파일을 기록 후 에디트에 기록 완료 사실을 출력한다. ※ 스트림은 가비지 컬렉터의 정리 대상이 아니므로 다 사용한 후 반드시 닫아야 하며, 예외 발생 시 스트림을 닫기 위해 finally블록에서 close를 호출하는 것이 정석이다. [ FileIO 예제 실행 결과 ] • Load버튼 • - 파일 생성 여부를 확인할 수 있다. • - test.txt 파일의 문자열을 읽어 아래쪽의 에디트에 출력한다. • -openFileInput메서드로 스트림을 열고, read메서드를 이용하여 읽는다. • - 파일이 없으면 FileNotFoundException예외가 발생한다. ( 이 예외는 흔히 발생하므로 반드시 예외처리를 해야한다. ) • 문자열 외에 이미지나 복잡한 설정 정보 등 큰 이진 데이터도 저장 가능하며, 사용자가 삭제하지 않는 한은 영구적으로 보존되며 읽고 갱신 가능하다.
1. 파일 입출력 • LoadRes버튼 - 응용 프로그램 동작에 필요한 대용량의 읽기 전용 데이터 파일은 리소스에 포함시켜 두는 것이 좋다. ( ex: 게임의 지도 맵 데이터, 우편 번호부, 영한사전 데이터 등 ) - 분리된 파일로 배포 가능하지만 파일이 누락되면 이상 동작할 위험이 있고 불의의 사고로 파일이 변경될 가능성도 배제할 수 없으며, 리소스에 파일을 넣어두면 실행 파일에 포함되므로 편리하고 안전하다. - 포함시킬 파일은 res폴더 아래에 raw폴더를 생성하고 복사해 둔다. - 리소스의 파일을 읽을 때는 Resources객체의 메서드를 사용하며, id로는 확장자를 뺀 파일명을 부여한다. • InputStreamopenRawResource (int id) • - 입력 스트림이 리턴되며이후부터는 자바의 표준 스트림 메서드로 액세스한다. • - 예제에서는 텍스트파일을 넣어두었으므로 read메서드로 읽어 문자열로 바꾼 후 에디트에 출력한다. • - res/raw폴더에 저장되는 파일은 원래 모습 그대로 저장되며, 어떠한 변형도 가해지지 않는다. • - res/xml폴더에 저장되는 파일은 이진 형태로 컴파일되어 포함된다. • Delete버튼 • - 생성된 test.txt파일을 삭제하며, deleteFile메서드로 파일 이름만 전달하면 삭제된다. • - 패키지 폴더 아래의 파일만 삭제 가능하므로 결국 자신이 만든 파일만 삭제할 수 있다. • - 파일이 없으면 삭제 에러 처리된다.
1. 파일 입출력 • 파일 공유 • 모바일 사용자는 비전문가들이므로 임의의 파일을 자유롭게 건드릴 수 있도록 관리해서는 안된다. • 따라서, 안드로이드 폰 및 에뮬레이터에는 파일 탐색기가 제공되지 않으며, 이미지 뷰어, 동영상 재생기, MP3플레이어 등으로 데이터 파일만 볼 수 있도록 되어있다. • 안전상의 이유로 최종 사용자는 장비의 파일 시스템을 액세스하지 못하도록 금지되어 있으며, 개발자들은 이클립스의 DDMS를 사용하여 File Explorer탭에서 장비나 에뮬레이터의 파일을 관리한다. • FileIO예제 디렉토리 • /data/data/exam.Data/files아래에 test.txt파일이 생성되어 있다. • 탐색기 상단의 버튼을 사용하여 파일을 가져오거나 복사 또는삭제가 가능하다. • adb의 push, pull명령으로 파일 관리가 가능하지만 DDMS가 더 편리하다. [ eclipse의 DDMS- File Explorer탭 ]
1. 파일 입출력 • 파일은 기본적으로 생성한 프로그램만 액세스할 수 있으며, 다른 응용 프로그램이 파일을 공유하려면 여러 가지 조건을 만족해야 한다. • 대상 파일의 액세스 모드가 외부에 대해 허용되어 있어야 한다. • FileIO예제 test.txt파일의 퍼미션란을 보면 -rw-rw-r--로 되어 있으며, 이는 파일 생성 시 MODE_WORLD_READABLE플래그를 주었기 때문이다. • 외부의 파일을 읽으려면 해당 파일을 생성한 프로그램의 컨텍스트를 구해야 한다. • 파일 열기 메서드에 경로를 지정할 수 없으므로 해당 프로그램의 컨텍스트를 구해야 한다. • 외부 프로그램의 컨텍스트를 구할때는 아래의 메서드를 호출한다. • Context createPackagecontext (String packageName, int flags) • - packageName인수 : 패키지 경로 지정 • - flage : 모드를 지정하며, CONTEXT_IGNORE_SECURITY플래그 지정. (디폴트 모드로 열면 보안상의 이유로 예외가 발생한다.) • ※ 이 메서드를 호출하려면 대상 프로그램의 패키지 경로를 정확히 알고 있어야 하며, 해당 패키지가 설치되어 있어야 한다. • 컨텍스트를 구한 뒤 컨텍스트의 파일 열기 메서드로 원하는 파일을 열 수 있다.
1. 파일 입출력 • ShareFile예제 • 예제는 exam.Data패키지의 컨텍스트를 구하며, Load버튼을 누르면 외부 패키지의 파일을 열어 그 내용을 아래쪽 에디트에 출력한다. • 컨텍스트를 구한 뒤 컨텍스트의 openFile*메서드를 호출하여 해당 컨텍스트가 생성한 파일을 열 수 있다. • 스트림을 연 뒤에는 자바 문법으로 액세스할 수 있다. [ ShareFile 예제 실행 결과 ] • ※외부의 파일을 읽을 때는 여러 가지 예외가 발생할 수 있으므로 적절한 예외 처리가 필요하다. • ※이 방법을 이용한 응용 프로그램끼리의 파일 공유는 권장되지 않으며, 추후 좀 더 일반적인 방법들이 제공되므로 이 방법은 가급적 사용하지 않는 것이 좋다.
1. 파일 입출력 • SD카드 • 안드로이드의 파일 시스템은 운영체제에 의해 보호되어 있으나, MP3, 비디오 파일, 그림 파일 등 공동으로 사용될 필요가 있는 단순 데이터는 보안이 중요하지 않다. • SD카드는 보안과 무관한 단순 데이터 파일 저장을 위해 사용되며, 대용량 멀티미디어 파일 저장에 적합하다. • SD카드는 운영체제와 분리된 기억장치이며 FAT포맷으로 되어 있어 보안상의 제약 없이 누구나 사용 가능하다. • SD카드 액세스를 위해선 SD카드가 필요하며, 에뮬레이터를 이용한 실습을 위해 mksdcard로 생성하거나 AVD생성 시 SD카드 용량을 지정하여 생성할 수 있다. • AVD생성 시 SD카드 용량 지정 - SD Card의 Size로 지정한 용량만큼 AVD디렉토리에 sdcard.img파일이 생성되며 이 파일이 가상의 SD카드로 활용된다. [ AVD - SD Card 용량 지정]
1. 파일 입출력 • mksdcard.exe유틸리티를 이용한 가상 SD Card 생성 • mksdcard [ -l 레이블 ] 용량 파일명 • - AVD와 별도로 SD카드를 생성하며, 필요한 만큼의 SD카드 생성이 가능하다. • - 용량과 파일명을 지정하면 지정한 용량대로 하드 디스크에 파일이 생성된다. (ISO파일과 비슷한 개념) • - 임의로 이름 설정이 가능하며 생성 후 삭제, 복사, 이름 변경이 자유롭다. • - 용량은 자유롭되 최소 8M이상이어야 한다. • - 명령창의 tools디렉토리에서 아래의 명령을 입력한다. [ mksdcard 생성된 SD카드 경로 지정] • mksdcard 16M testcard.sd • - 16M크기의 파일이 포맷된 상태로 생성된다. • - 생성된 SD카드를 사용하려면 AVD생성 시 File에서 SD카드 파일의 경로를 지정한다. • - 에뮬레이터가 열릴 때마다 지정한 SD카드를 로드한다. • - 특정 프로젝트에 특정 SD카드를 사용하려면 프로젝트 구성의 Target페이지에서 명령행 옵션에 아래와 같이 지정하며, 이 옵션을 적용하려면 에뮬레이터를 재시작하여야 한다. • - sdcard c:\testcard.sd
1. 파일 입출력 • 아래의 Environment클래스 정적 메서드를 호출하여 현재장비에SD카드 장착 여부와장착되어 있을 때 그 경로를 확인한다. • Static String getExternalStorageState () • Static File getExternalStorageDirectory () • Static File getRootDirectory () • Static File getDataDirectory () • Static File getDownloadCacheDirectory () • getExternalStorageState • - 외부 저장 장치, 즉 SD카드의 현재 상태를 조사한다. • - 장착되어 있으며 읽고 쓰기가 가능한 상태이면 MEDIA_MOUNTED가 리턴되며 그렇지 않을 시엔 MEDIA_UNMOUNTED가 리턴된다. • getExternalStorageDirectory • - SD카드가 마운트된 경로를 조사한다. • - SD카드는 통상 리눅스 파일 시스템의 /sdcard 경로로 마운팅된다. • 나머지 메서드는 루트, 데이터, 캐쉬 경로를 조사한다. • SD 카드의 파일을 액세스하려면 매니페스트에 아래의 퍼미션을 지정해야 한다. • <uses-permission android:name=“android.permission.WRITE_EXTERNAL_STORAGE”/> • 퍼미션이 지정되어 있지 않으면 컴파일은 되지만 모든 액세스가 실패로 처리된다. • SD카드 마운트 및 경로 조사 후엔 표준 자바 입출력 클래스로 파일과 디렉토리들을 액세스 및 관리할 수 있으며, 별다른 제약은 없다.
1. 파일 입출력 • SDCard예제 • SD카드의 현재 상태를 조사하고 SD카드에 파일을 생성 및 읽는 예제이며, 레이아웃은 버튼 세 개와 결과 확인을 위한 에디트를 배치한다.
1. 파일 입출력 • 파일 입출력을 위해 경로를 알아야 하므로 onCreate에서 SD카드의 장착 여부와 경로를 미리 조사해 둔다. • Test버튼 - 그 외 나머지 디렉토리 정보를 조사하여 아래쪽 에디트에 출력한다. - 실습 환경에서 아래의 그림과 같이 출력되면 정상적으로 마운트되어 있는 것이다. • Load, Save버튼 • - 자유로운 파일 관리를 위해 디렉토리를 생성하고 그 안에파일을 생성한다. • - 생성된 파일은 DDMS의 파일 탐색기로 확인 가능하다. • - Load버튼을 누르면 아래쪽 에디트에 파일의 내용이 출력된다. • - 자바를 사용하여 예제의 File클래스와 입출력 스트림을 작성한다. [ SDCard 예제 실행 결과 ]
1. 파일 입출력 • TextLog • 이클립스에서 디버깅을 지원하며 안드로이도 시스템 차원에서 로그 기능을 제공한다. • 단계 실행은 멀티 스레드 디버깅이나 비순차적 실행에는 취약하여 이벤트 발생 순서를 정확하게 알기 어려우며, 시스템 로그는 호스트에 장비가 장착되어 있어야 사용 가능하지만 실무 개발 시엔 그렇지 못한 경우가 많다. • 하드웨어와 소프트웨어가 동시에 개발될 때는 디바이스 드라이버가 완비되지 못해 장비를 개발 컴퓨터에 붙이지 못하는 경우가 흔하며, 이때 전체 이미지를 ROM에 구워서 테스트해야 하므로 시스템 로그 기능을 사용하지 못한다. • 시스템 로그는 장비가 다운되었을 시 이전의 사건들은 확인이 불가능하다. ※개발툴의 지원이 불가능할 때 디버깅 방법은 텍스트로 로그를 남기는 방법밖에 없으며, 외부 지원이 없으므로 응용 프로그램이 스스로 로그를 남기고 이를 확인해야 한다.
1. 파일 입출력 • TextLog예제
1. 파일 입출력 • TextLog예제
1. 파일 입출력 • TextLog클래스 - 응용프로그램 폴더, SD카드, 시스템 로그에 3중으로 로그를 기록할 수 있다. - mDir멤버 : 로그를 남길 위치 지정. (디폴트는 자기 폴더 및 SD카드) - mFile : 로그 파일명. andlog.txt로 초기화되며 태그값은 mTag로 지정. - mFile, mTag mDir 멤버는 별도의 액세스 메서드가 없는 대신 공개되어 있으므로 필요 시 외부에서 값을 직접 수정할 수 있다. • init메서드 - 로그를남길 준비. 장비에 SD카드 장착 여부, 경로를 조사한다. - SD카드가 없으면 자신의 디렉토리에만 기록되며, 로그 기록 시작과 현재 시간으로 첫 번째 로그를 남긴다. - 로그를 남기는 주체인 Context를 받아놓는데 이 값이 있어야 파일 생성이 가능하다. • reset메서드 - 작성된로그를 모두 삭제하며 리셋되었다는 기록을 남긴다. • o메서드 - 로그를 기록하며 자주 호출되므로 메서드 이름을 짧게 지정하였다. - 인수로 문자열 하나만 전달받으며 +로 연결하면 변수값도 사용할 수 있다. - 로그 출력문 : mFile경로의 파일을 추가용으로 open, 로그 문자열과 개행 문자열을 뒤에 덧붙이고 파일 close. - SD카드에 대한 기록문도 로그 출력문과 거의 동일하며 시스템 로그 기록문은 Log.d호출로 되어있다.
1. 파일 입출력 • ViewLog메서드 - 작성된로그를 대화상자로 나타내며, 로그 파일 전체를 읽어 문자열로 만든 후 출력한다. - AlertDialog는 내용이 많을 경우 스크롤을 지원한다. • addMenu, execMenu메서드 - 로그 확인, 리셋을 위한 명령을 호스트의 메뉴에 덧붙이고 실행한다. ※외부에서 ViewLog, reset메서드를 직접 호출할 수 있지만 적당한 시점이 없는 경우엔 메뉴를 사용하는 것이 편리하며, 이 두 메서드는 호스트를 대신하여 메뉴를 관리한다. • lg클래스 - TextLog의 o메서드를 대신 호출한다. - public이 아니므로 같은 패키지 내부에서만 사용 가능하며 외부 패키지에서 호출 시 사용할 수 없다. - 외부 패키지에서 호출 시 TextLog클래스를 import하여 사용한다.
1. 파일 입출력 • TextLogTest예제 • onCreate에서 init메서드를 호출하여 초기화하고 메뉴 생성 및 실행시에 addMenu, execMenu메서드를 호출한다. • 기록을 남길 부분에서 lg.o메서드로 문자열 형태의 기록을 남긴다. • 프로그램이 실행 중에 다운되어도 reset 명령을 내래지 않는 한 로그는 그대로 남아있으며, 장비를 재부팅해도 로그 파일은 항상 유지된다. [ TextLogTest 예제 실행 결과 ]
2. 프레프런스 • 프레프런스 • 프레프런스는 응용 프로그램의 설정 정보를 영구적으로 저장하는 장치이며, 사용자의 옵션 선택 사항이나 프로그램 자체의 구성 정보를 주로 저장한다. • 윈도우 환경의 레지스트리나 리눅스 환경의 세팅 파일 정도에 대응되는 개념이되, XML포맷의 텍스트 파일에 정보를 저장하므로 세팅 파일이나 INI파일에 더 가깝다. • SharedPreferences클래스 • 프레프런스의 데이터를 관리하는 클래스이다. • 응용 프로그램내의 모든 액티비티가 공유하며, 한쪽 액티비티에서 수정 시 다른 액티비티에서도 수정된 값을 읽을 수 있다. • 응용 프로그램의 고유한 정보이므로 외부에서는 읽을 수 없다. • 컨텍스트의 아래 메서드로 생성한다. • SharedPreferencesgetSharedPreferences (String name, int mode) • - 첫번째 인수 : 프레프런스를 저장한 XML파일의 이름이다. • - 두 번째 인수 : 파일의 공유 모드로 0이면 읽기 쓰기가 가능하고, MODE_WORLD_READABLE은 읽기 공유, MODE_WORLD_WRITEABLE는 쓰기 공유이다. • 액티비티의 메서드로도 프레프런스를 열 수 있으나 생성한 액티비티 전용이므로 같은 패키지의 다른 액티비티는 읽을 수 없다.(파일명인수 생략 시 액티비티와 동일한 이름의 XML파일 생성) • public getSharedPreferences (int mode)
2. 프레프런스 • 프레프런스는 키와 값의 쌍으로 데이터를 저장한다. • 키는 정보의 이름이며 값은 정보의 실제값이다. • 여러 타입의 정보를 저장할 수 있으며 가장 자주 사용되는 타입은 정수, 문자열 논리형으로 아래의 메서드를 이용하여 읽을 수 있다. • int getInt (String key, intdefValue) • String getString (String key, String defValue) • boolean getBoolean (String key, booleandefValue) • - 읽는 데이터 타입이 다를 뿐이며 사용 방법은 동일하다. • - key인수 : 데이터의 이름 지정 • - defValue인수 : 값이 없을 때 적용할 디폴트 지정. • ※최초 실행될 때에는 프레프런스가 생성되기 전이므로 디폴트가 리턴되므로 여기서 지정한 디폴트가 곧 프로그램 설정의 초기값이 된다.
2. 프레프런스 • 프레프런스 클래스 자체에는 값을 읽는 메서드만 제공되며 값을 기록하는 메서드는 이너 클래스인 SharedPreferences.Editor가 제공한다. • 값을 읽는 것에 비해 변경할 때는 동기화를 해야 하며 Editor는 모든 변경을 모아 두었다가 한꺼번에 적용하는 기능을 제공한다. • 데이터 저장 시 프레프런스의 edit메서드를 호출하여 Editor객체를 먼저 얻어야 하며, Editor객체에는 값을 저장하고 관리하는 메서드가 제공된다. • SharedPreferences.EditorputInt(String key, int value) • SharedPreferences.EditorputBoolean(String key, int value) • SharedPreferences.EditorputString(String key, String value) • SharedPreferences.Editor remove(String key) • Boolean commit() • SharedPreferences.Editor clear() • - 저장하는 데이터 타입에 따라 각각의 기록 메서드가 제공된다. • - put*메서드로 값을 저장한 후에 반드시 commit메서드를 호출해야 실제 파일에 저장된다. • - 만약 두 개의 스레드가 동시에 프레프런스를 편집할 시 먼저 적용한 내용은 나중에 적용한 내용에 덮힌다. • - commit메서드는 변경 사항을 원자적으로 적용하므로 섞이지 않는다.
2. 프레프런스 • PrefTest예제 • 문자열과 정수열을 저장하는 예제 • 문자열, 정수를 입력 받기 위해 두 개의 에디트를 배치한다. • 에디트를 이용하여 사용자는 문자열 입력 및 편집을 실행할 수 있으나, 저장을 하지 않으므로 종료 후 재실행 시 초기화된다. • 세션간 정보 유지를 위해선 정보의 저장이 필요하며, 정보 저장을 위한 최적의 장소는 프레프런스이다. [ preftest 예제 실행 결과 ]
2. 프레프런스 • PrefTest예제 • onPause로프레프런스에 데이터를 저장하며, 액티비티가 종료되기 전에 반드시 거치게 되어있다. • 프레프런스 객체 얻기 - getSharedPreferences메서드를 이용하여, PrefTest프레프런스를 open 한다. - 파일명이 액티비티명과 같을땐 아래의 방법으로 호출 가능하다. • SharedPreferencespref = getPreferences(0); • Editor객체 얻기 • - 프레프런스에 정보를 기록하기 위해 edit메서드를 이용한다. • - 사용자가 에디트에 입력한 정보를 먼저 읽는다. • - 문자열 타입 : getText().toString() • - 정수 타입은 문자열을 정수로 변환하여 읽으며, 예외처리한다. • 프레프런스로 정보 출력 • - 이름(Name): putString() • - 학번(StNum): putInt() • - 저장 후 commit메서드 호출 • ※ 메모리가 부족하면 onStop, onDestroy 는 호출되지 않을수도 있으므로 반드시 onPause에서 저장해야 한다.
2. 프레프런스 • PreferenceActivity • 프로그램은동작이 고정적이지 않으므로 어느 정도의 옵션을 소유한다. • 옵션은 사용자의 취향에 따라 프로그램의 기능을 선택적으로 사용할 수 있도록 함으로써 다양한 사용자의 기호를 충족시키는 역할을 한다. • 프로그램이 옵션을 제공하기 위해 사용자가 실행 중 옵션을 편집할 수 있는 UI를 제공해야 하며 사용자의 선택을 프레프런스에 저장하고 다시 읽어오는 기능이 필요하다. • 안드로이드에서 옵션들을 입력 받고 관리하는 자동화된 방법이 제공된다. • PreferenceActivity로부터 상속 받고 입력 받고자 하는 옵션의 종류를 XML문서에 기록한다. • 액티비티의 레이아웃 루트는 반드시 PreferencesScreen이어야 하며, 옵션 타입에 따라 위젯을 배치하면 남은 처리는 자동으로 수행된다.
2. 프레프런스 • PrefActivity예제 • 나이와 성별을 입력 받기 위한 에디트와 체크 박스를 배치한다. • 위젯이 프레프런스 입출력 기능을 가지므로 key속성에 저장할 키의 이름과 defaultValue속성에 처음 적용할 값을 지정해야 한다. • summary속성에 옵션에 대한 간단한 안내 문자열을 작성한다. • PreferencesActivity로부터 상속받아 액티비티를 생성, 초기화 시에 onCreate메서드에서 addPreferencesFromResource메서드로 레이아웃을 지정한다. • 액티비티가 열릴 때 옵션값이 위젯으로 로드되고 닫히기 전에 옵션값을 다시 저장한다. [ PrefActivity 예제 실행 결과 ]
3. SQLite • 도우미 클래스 • 조직화된 대량의 데이터를 효율적으로 관리하기 위해 데이터베이스가 필요하다. • 안드로이드는 운영체제 차원에서 SQLite라이브러리를 포함하고 있으며, 별도의 설정 없이 DB를 사용할 수 있다. • SQLite • 2000년 리처드 힙(Richard Hipp) 박사에 의해 개발된 무료 DB엔진. • 안정적, 적은 용량, C 언어로 작성되어 속도가 빠르며 소규모의 데이터베이스에 적합. • 아이폰, 심비안 등의 모바일 환경, 휴대용 MP3등에서 주로 이용됨. • 데이터를 저장하는 장소는 단순한 파일이므로 별도의 서버 및 연결, 권한 설정 등이 필요 없다. • 복사, 삭제, 이동이 가능하며 설정 및 관리 정책은 불필요하다. • 복수 사용자는 지원되지 않는다. • 안드로이드의 일부로 포함되어 별도의 라이브러리가 필요하지 않다. • SQLite정보 : www.sqlite.ort ※이용을 위해 데이터베이스에 대한 기본 개념과 표준 SQL구문에 대한 선행 학습이 필요하다.( 쿼리를 대신하는 메서드를 제공해 주지만 SQL만큼의 융통성이 부족하다.)
3. SQLite • 정보를 저장할 DB를 생성하고 관리할 정보와 업무 규칙에 적합한 테이블을 디자인한다. • SQLite는 초기화와 테이블 디자인을 위한 별도의 툴을 제공하지 않으므로 초기화 역시 SQL스크립트로 처리해야 한다. • SQLiteOpenHelper클래스 • 안드로이드에서 제공하는 도우미 클래스로 DB생성 및 Open 처리를 담당한다. • 추상 클래스이므로 서브 클래스를 파생하여 사용하는 DB의 구조에 맞게 메서드를 재정의, 적절한 스크립트를 작성해야 한다. • 생성자 - context: DB를 생성하는 컨텍스트, 보통 메인 액티비티를 전달. - name, version : DB파일의 이름 및 버전, 이후 DB 생성 및 업데이트 시 이용됨. - factory : 커스텀 커서 사용을 위해 지정, 표준 커서 이용 시 null로 지정. • 객체를 생성해 놓으면 DB가 필요한 시점에 아래의 메서드가 호출된다. • - onCreate : CREATE TABLE 문을 실행하여 테이블을 생성. • - onUpgrade : DB의 버전이 변경될 때 호출됨.
3. SQLite • 생성 및 업그레이드를 위한 메서드 정의 후 DB필요 시 아래의 메서드를 호출한다. • - getReadable(Writable)Database메서드만 호출하면 이후는 자동 처리된다. • - DB파일이 없으면 파일 생성 후 onCreate를 호출하여 테이블을 생성한다. • - 버전 변경 시 onUpgrade를 호출하여 테이블을 수정한다. • - 후에SQLiteDatabase객체가 리턴된다. • EnglishWord예제 • 영한 사전으로 영어 단어와 한글 해석을 필드로 가지는 일종의 맵이다. • 레이아웃은 쿼리 실행을 위한 버튼 4개와 결과 확인을 위한 에디트가 배치된다. • 4개의 버튼을 차례로 누르면 테이블에 레코드가 삽입, 삭제, 갱신되며 현재 레코드 목록을 아래쪽 에디트에 출력한다. [ EnglishWord 예제 실행 결과 ]
3. SQLite • EnglishWord예제
3. SQLite • EnglishWord예제 • 메인 액티비티에 4개의 버튼에 대한 클릭 핸들러가 작성되어 있으며 아래족에는 도우미 클래스가 작성되어 있다. • EngWord.db파일 - 예제 실행 후 생성된 DB파일이며,DDMS로 패키지 디렉토리를 살펴보면 database디렉토리가 생성되어 있으며 이 안에 있다. - 단순 파일이므로 복사, 이동, 삭제가 가능하다. • WordDBHelper클래스 - 도우미 클래스를 상속받아 정의된 클래스이며, 처음 정의한 것이므로 버전은 1이다. - onCreate, onUpgrade메서드를 호출한다. • onCreate - execSQL메서드로 CREATE TABLE 명령을 실행하여 dic테이블을 생성한다. - _id 필드 : PK로 사용. 차후 CP로 확장하거나 커서 어댑터와 연결을 위해 필요하다. - eng : 영어 단어 저장 - han : 한글 해석 저장 • ※ 일반적인 테이블 설계 지침상 PK는 레코드를 효율적으로 관리하기 위해 필수적이므로 가급적이면 _id 필드는 포함시키는 것이 좋다.
3. SQLite • onOpen - DB가 존재할 때 호출되며, 생성자로 전달된 DB를 열기만 하므로 재정의할 필요는 없다. • onUpgrade - 테이블 삭제 후 동일한 테이블을 재생성하여 업그레이드하는 흉내만 낸다. - 실제로는 변경된 구조의 테이블을 재생성하거나 ALTER TABLE문으로 변경된 스키마에 맞게 테이블의 구조만 수정해야 한다. • 액티비티는 WordDBHelper타입의 mHelper멤버를 미리 생성해 두며 이후 DBopen 메서드 호출 시 DB의 존재 여부와버전을 비교하여 DB를 생성, 업그레이드하거나 기존의 존재하는 DB를 오픈한다. • DB관리 업무는 도우미가 담당하므로 필요 시 DB를 오픈하여 사용하면 된다. • 도우미를 사용하지 않고 직접 DB생성 시엔 아래의 메서드를 사용한다. • SQLiteDatabaseContext.openOrCreateDatabase (String name, int mode, SQLiteDatabase.CursorFactory factory) • - 메서드를 사용하여 DB파일을 생성하고 리턴되는 db의 execSQL메서드를 통해 CREATE TABLE을 실행하면DB를 만들 수 있다. • - DB가 없는 경우, 이미 만들어진 경우, 버전이 변경된 경우 등을 사용자가 직접 판단해야 한다. • ※도우미는 개발자가 작성한 스크립트를 언제 어떤 조건에서 호출할 것인가를 대신 판단해 준다.
3. SQLite • 쿼리 실행 • 새 레코드 삽입 • DB에 기록하기 위해 getWritableDatabase메서드로 DB를 오픈하면 기록 가능한 DB객체가 리턴된다. - onCreate메서드를 호출하여 DB생성또는 onOpen메서드를 호출하여 기존의 DB open • DB파일의 존재 유무, 버전 비교 등을 종합적으로 판단, 어떤식으로 DB객체를 생성할 것인지는 도우미가 판단한다. - onCreate, onUpgrade 등의 메서드에 해당 동작 명령을 채워야 한다. • 이후 도우미가 리턴한 DB객체의 메서드를 호출하여 레코드를 관리한다. • ContentValues클래스 • 테이블에 삽입되는 레코드를 표현하며, 레코드 하나는 임의의 타입을 가지는 필드들의 집합이므로 테이블마다 형태가 다르다. • 빈 객체를 만든 후 put메서드를 호출하여 필드와 값의 쌍을 여러 개 저장한다. • 모든 기본 타입에 대해 오버로딩되어 있으므로 필드의 타입에 맞는 메서드를 호출한다. • void put (String key, Integer value) • void put (String key, String value) • void put (String key, Boolean value) • - ContentValues는 레코드에 포함된 필드의 이름과 값의 맵이고레코드에 포함되는 컬럼의 개수나 타입이 가변적이므로 맵 형태로 이름과 값의 배열을 전달받는다. • - _id 필드는 자동으로 증가되는 값이므로 별도로 값을 지정하지 않아도 된다.
3. SQLite • 삽입할 레코드가 준비되면 아래의 메서드로 삽입한다. • long SQLiteDatabase.insert (String table, String nullColumnhack, ContentValues values) • - 테이블 이름과 삽입할 레코드를 인수로 전달한다. • 별도의 삽입 메서드를 쓰는 대신 아래의 메서드로 SQL명령을 직접 실행 가능하다. • void execSQL (String sql) • - 이 메서드는 SELECT명령을 제외한 대부분의 명령을 직접 실행할 수 있다. • - 인수로 SQL문자열 하나만 전달되며, 이 문자열 안에 대상 테이블 이름과 삽입할 레코드의 필드값이 모두 기록된다. • - 예제에서는 두 가지 방법 모두 사용하여 레코드를 삽입하였다. • SQLite는 SQL을 잘 모르는 초보자를 위하여 SQL명령의 각 부분을 인수로 받아들이는 메서드도 제공한다. • 레코드를 삭제, 갱신할 때는 아래의 메서드를 사용한다. • int delete (String table, String whereClause, String[] whereArgs) • int update (String table, ContentValues values, String whereClause, String[] whereArgs) • - 삭제할 레코드를 지정하는 where절과 where절 내의 ? 기호를 대체할 문자열, 갱신 대상 필드의 값 등을 인수로 제공한다. • - 갱신 시 ContentValues에 갱신 대상 필드의 값을 지정하고 갱신 대상 행은 where절로 지정한다. • - 예제에서는 메서드 호출문을 사용하되 대응되는 SQL문도 주석으로 묶어 같이 작성하였다.
3. SQLite • 레코드 검색 메서드 • Cursor query (boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) • 검색 방법이 다양하므로 인수도 많이 필요하다. • 각각 SELECT 문의 WHERE절, ORDER BY 절, GROUP BY 절, HAVING, TOP절, DISTINCT옵션 등에 대응된다. • 모든 조건절을 다 사용하는 경우는 드물며 필요 없는 절에 대해서는 null로 지정한다. • 이 메서드를 사용하는 대신 rawQuery메서드로 SELECT문을 바로 실행 가능하다. • SELECT문은 결과셋을 리턴하기 위해 rawQuery메서드로 실행해야 한다. • 예제에서는 query메서드를 호출하는 것과 SELECT문을 직접 실행하는 두 가지 코드가 모두 작성되어 있다. • ※SQLite가 쿼리 메서드를 제공하는 이유는 SQL초보자에게 편의를 제공하기 위해서이며, 메서드의 인수 의미를 이해하려면 SQL문법을 알아야 한다. • ※쿼리 메서드는 전달받은 인수를 조립하여 SQL명령을 만들며 SQL명령으로 쿼리를 실행한다. • ※데이터베이스 프로그래밍을 위해선 가급적이면 메서드에 의존하는 것보다 범용적인 SQL문을 익힐 것을 권장한다.
3. SQLite • 쿼리 결과는 결과셋 자체가 리턴되지 않으며 위치를 가리키는 커서로 리턴된다. • 커서는 결과셋의 한 위치를 가리키는 일종의 포인터로 for문의 제어 변수와 유사하다. • 랜덤 액세스를 지원하므로 임의 위치로 이동하여 레코드를 읽을 수 있다. • 커서의 메서드 목록
3. SQLite • 쿼리를 실행한 후 리턴되는 커서는 결과셋의 첫 레코드 이전을 가리킨다. - 첫 레코드를 읽으려면 moveToNext, moveToFirst메서드를 먼저 호출하여 첫 레코드로 이동해야 한다. - moveToNext가 false를 리턴할 때까지 루프를 돌며 전체 결과셋을 순회한다. • 결과셋은 처음부터 끝까지 순회하며 한번에 다 읽는 것이 보통이므로, 쿼리를 읽는 코드는 통상 while(moveToNext){}의 형태이다. • 루프를 돌며 각 레코드의 컬럼값을 읽는다. • SQLite는 타입 점검을 느슨하게 수행하므로 반드시 컬럼의 타입과 같은 타입으로 읽을 필요는 없다. - 정수형 필드를 읽어 문자열로 출력할 때 getInt로 읽어 문자열로 변환할 필요 없이 getString으로 읽는 것이 편하다. - 예제 테이블의 eng, han은 둘 다 문자열 필드이므로 getString을 이용하여 읽는다. - 필드 번호를 분명히 알고 있을 때는 상수를 바로 사용 가능하지만, 그렇지 않을 시엔 getColumnIndex메서드로 컬럼 번호를 조사한 뒤 값을 읽어야 한다. • 결과셋을 하나의 문자열로 조립하여 에디트에 출력하되 비어 있을 시 빈 결과셋임을 출력한다. • 결과셋을 다 읽은 후 커서와DB는 반드시 닫아야 한다.
3. SQLite • 커서 바인딩 • 결과셋이 많을 때 쿼리로 전체를 읽어 화면에 출력하면 속도가 매우 느려진다. • 이 경우 커서를 어댑터에 바인딩해 놓고 어댑터가 필요할 때 커서를 이동시켜 현재 꼭 필요한 레코드만 읽어 어댑터 뷰 위젯에 출력한다. • 커서를 사용하는 어댑터의 생성자는 아래와 같다. • SimpleCursorAdapter (Context context, int layout, Cursor c, String[] from, int[] to) • 레코드를 출력할 레이아웃과 데이터 원본인 커서를 전달한다. • 레이아웃에 정보를 출력할 위젯을 배치한 후 열과 위젯을 짝지어준다. • - from : 커서의 열 이름 배열. • - to : 각 열이 출력될 위젯의 ID배열.
3. SQLite • ProductList예제 • 상품목록을 보여주는 데이터베이스를 생성하는 예제이다. • 도우미 클래스의 구조는 앞 예제와 거의 동일하며, 생성하는 테이블의 개수와 스키마만 다르다. • 레이아웃은 2개의 텍스트를 가지는 표준 레이아웃을 사용하고 name, price 필드를 각각 text1, text2에 연결했다.
3. SQLite • onCreate - product테이블 생성 - name : 상품의 이름 - price : 가격 정보 • 액티비티는 도우미 객체를 생성한 후 product 테이블의 모든 레코드를 검색하여 커서에 대입한다. • startManagingCursor메서드 - 액티비티의 생명주기에 맞춰 커서를 자동으로 관리하도록 요청한다. • 리스트 뷰에 커서 어댑터를 연결해 놓으면 DB의 내용이 커서로 읽혀저 리스트 뷰의 위젯에 나타난다. • sqlite3유틸리티 • 안드로이드가 제공하는 DB관리 유틸리티로, 이를 사용하면 명령행에서 DB를 직접 조작 가능하다. • 불필요한 레코드 삭제나 삽입한 레코드의 확인 시 편리하다. • adb shell 명령으로 쉘로 들어간 후 sqlite3뒤에 열고자 하는 DB의 절대 경로를 지정한다. [ ProductList 예제 실행 결과 ] [ 가격이 1000원 이상인 제품 출력 결과 ] • ※SQLite는 UTF-8포맷으로 데이터를 저장하므로 한글이 제대로 출력되지 않으며, 명령행의 코드 페이지를 65001로 바꾸고 글꼴을 한글 트루타입 폰트로 맞춰야만 결과를 확인할 수 있다.
4. CP (Content Provider) • URI (Uniform Resource Identifier) • 안드로이드의 보안 정책상 응용 프로그램이 만든 데이터는 기본적으로 혼자만 액세스 가능하며, 외부로 공개하려면 Content Provider(CP)를제공해야 한다. • CP는 안드로이드응용 프로그램을 구성하는 컴포넌트 중 하나로 데이터를 제공하는 역할을 하며, 응용 프로그램끼리 데이터를 공유하는 유일한 방법이다. • URI • 정보의 위치를 나타내는 고유한 명칭으로 웹상의 주소를 나타내는 URL보다 상위 개념이다. • 누가 어떤 정보를 제공하는지, 어떤 정보를 원하는지에 대한 정보가 작성된다. • 임의의 내용을 가질 수 있는 문자열 형태로, 제공자와 제공받는 자간의 약속이므로 국제표준(RFC2396)에 명시된 방법으로 작성해야 한다. • content://authority/path/id • - content://는 이 문자열이 URI임을 나타내는 접두이며 무조건 붙여야 한다. • - authority : 정보 제공자의 명칭. 중복이 불가능하므로 패키지명을 사용할 것을 권장한다. • - path : 정보의 종류를 지정하는 가상의 경로. 한 제공자가 여러 개의 정보 제공 시 경로를 통해 각 정보를 구분한다. • - id : 구체적으로 어떤 정보를 원하는지 지정. 전체 정보를 모두 읽을 시엔 생략 가능하다.
4. CP (Content Provider) • CP는 단수와 복수에 대해 두 가지 형태의 URI를 각각 정의해야 한다. • 단수 URI는 복수 URI뒤에 구체적인 정보를 지정하는 id가 추가된다. • URI의 예 – 가상의 회사 stockmarket에서 제공하는 정보 • content://com.stockmarket/stock // 주식 시세 정보 • content://com.stockmarket/fee // 수수료 정보 • content://com.stockmarket/stock/posco // posco 종목의 주식 시세 정보 • content://com.stockmarket/fee/register // 등록 수수료 정보 • - 이 회사는 주식 시세 정보와 수수료 정보를 공개하며 각 정보는 stock, fee라는 경로명으로 구분된다. • - 중복을 피하기 위해com.stockmarket라는 자사의 URI를 사용하였다. • 각 정보는 전체적으로 또는 특정 정보 하나만 참조 가능하다. • 정보 제공자와 정보의 종류에 따라 URI문자열을 만든 후 정적 메서드로 URI객체를 생성한다. • static Uri parse (String uriString) • - 이 메서드는 성능상의 문제로 인해 에러 처리를 하지 않는다. • ※ URI를 잘못 작성하면 예외 발생이 아닌 쓰레기값이 조사되므로 반드시 형식에 맞게 작성해야 한다. • ※ URI의 형식이 틀렸을 때의 동작은 정의되어 있지 않아 아주 위험하므로 충분한 주의를 기울이는 것이 좋다.
4. CP (Content Provider) • 아래는 URI의 path정보를 문자열 목록으로 조사하는 메서드이다. • List<String> getPathSegments () • - 이 목록의 0번째 요소가 path이며, 1번째 요소가 id이되 /를 제외한 문자열만 조사된다. • - URI로부터 요구하는 정보를 조사할 때 이 메서드를 호출한다. • URI로부터 문자열을 해석하여 정보를 추출하는 것은 번거로우며 문자열 형태이므로 비교 속도 역시 느리다. • UriMatcher유틸리티 클래스 • 문자열안의 요구 정보를 분석하여 정수 코드로 변환한다. • 아래의 메서드로 URI를 미리 분석해 두고 요구 코드를 정의해 둔다. • void addURI (String authority, String path, int code) • int match (Uri uri) • - addURI메서드 : authority, path의 쌍으로 정수 코드와 대응시켜 맵을 등록한다. • - path에서 * 은임의의 문자열과 대응되며, # 은 숫자 하나와 대응된다. • - match메서드 : uri를 분석하여 등록된 정수 코드를 리턴하며, uri에 해당 코드가 없을 시 -1을리턴한다. • CP는 UriMatcher가 분석해 놓은 정수 코드로 요청을 파악하여 정보를 리턴하면 된다. • 정수로부터 요청을 구분할 수 있으므로 switch-case문으로 분기할 수 있어 편리하다.
4. CP (Content Provider) • 자료공유 • CP를 만들기 위해 먼저 ContentProvider클래스를 상속받고, 정보를 관리 및 제공하는 메서드를 재정의해야 한다. • onCreate • CP가 로드될 때 호출되며 여기서 제공할 데이터를 준비한다. • DB만이 아닌 내부 배열이나 네트워크의 정보도 이용 가능하다. • DB에 들어있는 정보는 onCreate에서 DB를 열어두면 이용 가능하다. • 데이터의 MIME타입 조사 메서드 • SringgetType (Uri uri) • 정보의 개수에 따라 MIME타입의 형식이 다르며, 강제적인 규칙은 아니다. • 단수 : vnd.회사명.cursor.item/타입 • 복수 : vnd.회사명.cursor.dir/타입 • 그 외 실제 데이터를 제공 및 관리하는 query, insert, delete 등의 메서드를 제공하는 정보에 따라 재정의해야한다.
4. CP (Content Provider) • EWProvider예제 • EnglishWord프로젝트를 확장하여 CP컴포넌트를 추가한다. • CP는 응용 프로그램에 존재하는 데이터를 외부로 공개하는 역할을 한다.
4. CP (Content Provider) • EWProvider예제
4. CP (Content Provider) • 규정대로 URI를 작성하되 패키지명 다음에 액티비티 이름인 EnglishWord를 추가한다. • UriMtcher타입의 헬퍼 Matcher를 정적 멤버로 선언, 정적 초기화블록에서 두 개의 URI형식을 등록한다. - word패스로 끝나면 모든 단어를 전부 요청하고, word뒤에 임의의 단어가 있을 시엔 이 단어만 요청하며, 각각 1, 2의 정수값을 맵핑한다. • - 이후 CP의 나머지 코드는 match메서드가 리턴한 정수만 확인하면 된다. • onCreate에서 DB를 오픈한다. • - 액티비티에서 DB헬퍼를 제작해 두었으므로 getWritableDatabase메서드를 호출,DB객체를 오픈할 수 있다. • - getReadableDatabase로 열면 읽기만 가능한 DB객체를 오픈한다. • getType메서드 • - 규정대로 MIME타입을 정의한다. • query메서드 • - 실제 데이터를 제공한다. • - 인수로 전달된 uri를 분석. 요청받은 데이터를 커서에 실어 리턴한다. • - 단어 전체 : dic테이블 전체를 덤프하여 리턴. • - 한 단어만 : where절을 작성하여 요구한 단어만 조사한 뒤 리턴. • - id에 해당하는 값을 uri.getPathSegments().get(1)로 구하여 where절의 eng필드와 비교한다.
4. CP (Content Provider) • 조사된 정보는 커서로 리턴된다. - 클라이언트는 커서를 읽어 CP가 제공한 정보를 이용하며, 예제에서는 SQL문을 바로 실행했다. - 정렬 순서를 맞추려면 order by, 특정 열만 요구한다면 원하는 열을 SELECT문에 나열한다. - CP요청이 DB쿼리와 유사한 형태로 전달되므로 db의 query메서드를 호출해도 동일한 결과를 얻을 수 있다. • insert메서드 - 행 하나를 삽입하며, 하나의 레코드에 대해 수행되므로 단수, 복수를 구분할 필요 없다. - DB의 insert메서드로 레코드를 그대로 넘긴다. - 삽입 성공 시 notifyChange메서드를 호출하여 변경 사실을 통보하고, DB에 연결된 어댑터 뷰는 삽입된 레코드를 인식하기 위해 갱신된다. • delete, update 메서드 - 복수 : DB의 대응되는 메서드를 그대로 호출한다. - 단수 : 조건은 URI를 통해 전달될 수도 있고 selection인수를 통해 전달될 수 있기 때문에 URI의 끝에 있는id를 읽어 where절에 조건을 추가하고 둘 다 AND절로 연결한다. - 특정 조건을 만족하는 레코드만을 대상을 할 경우는조건절을 따로 전달해야 한다. - 삭제 및 갱신의 경우도 SQL문으로 실행 가능하다. • 마지막으로 CP를 매니페스트에 등록한다. - application태그 아래, activity와 같은 수준에 아래의 태그를 추가하여CP의 이름과 제공자명을 밝혀놓는다. • <provider android:name=“EWProvider” • android:authorities=“exam.data.EnglishWord”/> • - 매니페스트에 등록해 놓으면 이후 시스템은 정보를 요청하는 쪽의 URI와 매니페스트의URI를 비교하여 제공자가 일치하는 CP를 호출한다. • ※ 반드시 모든 메서드를 다 구현할 필요는 없으며, 필요에 따라 일부 생략 가능하다.