[Kotlin] 6장. 앱 개발 - 권한, 파일 입출력, SharedPreferences

이것이 안드로이드다 with 코틀린(고돈호 지음) 으로 공부한 내용을 정리한 글입니다.

권한

안드로이드 앱에서는 민감한 사용자 데이터 및 특정 시스템 기능에 액세스하려고 할 경우 권한이 필요합니다. 필요한 권한은 AndroidManifest.xml에 명세하거나 소스 코드에 명세할 수 있습니다.

권한 명세와 기능 명세

권한 명세는 해당 데이터나 기능의 사용여부를 설정하고, 기능 명세는 해당 기능이 없는 기기가 플레이 스토어에서 앱을 내려받는 것을 방지합니다.

권한 명세

AndroidManifest.xml 파일에 <uses-permission/> 태그로 권한을 명세합니다.

<uses-permission android:name="android.permission.INTERNET"/>

기능 명세

기능 명세는 AndroidManifest.xml 파일에 따로 추가하지 않아도 해당 기능을 사용할 때 시스템이 자동으로 부여하지만 <uses-feature/> 태그로 직접 명세할 수도 있습니다.

<uses-feature android:name="android.hardware.camera" android:required="true" />

AndroidManifest.xml 파일에 기능 명세가 작성되고 required 속성이 true로 설정되면 플레이 스토어 검색 조건으로 사용됩니다.

권한의 보호 수준

권한의 보호 수준은 일반 권한, 위험 권한, 서명 권한으로 나뉘며 보호 수준에 따라 앱 실행시 권한에 대해 사용자에게 확인 요청이 필요한지 여부를 결정합니다.

일반 권한

일반 권한은 설치 시 사용자에게 권한 승인을 묻는 팝업창을 보여줍니다.

권한설명
ACCESS_NETWORK_STATE네트워크 연결 상태 확인
ACCESS_WIFI_STATE와이파이 상태 확인
BLUETOOTH블루투스 상태 확인
INTERNET네트워크 및 인터넷 사용
NFC기기 간 근거리 통신 사용
SET_ALARM알람 설정
VIBRATE진동 설정

위험 권한

위험 권한은 앱이 사용자의 개인정보와 관련된 데이터나 기능을 액세스하거나 다른 앱 및 기기의 작동에 영향을 줄 우려가 있는 권한입니다. 위험 권한은 targetSdkVersion이 23 이상으로 설정되어야지 정상으로 동작합니다.

위험 권한 그룹설명
CALENDAR캘린더 관련
CAMERA카메라 관련
CONTACTS주소록 관련
LOCATION위치 정보 관련
MICROPHONE마이크 녹음 관련
PHONE기기 및 통화 관련
SENSORS센서 관련
SMSSMS 관련
STORAGE저장소 관련

서명 권한

서명 권한은 권한을 사용하려는 앱이 권한을 정의하는 앱과 동일한 인증서로 서명된 경우 시스템이 자동으로 권한을 부여하는 권한입니다.

권한 그룹

권한들은 비슷하 권한끼리 그룹으로 묶여 있으며 그룹 내의 한 권한이 부여되어 있다면 다른 권한들 역시 자동으로 부여됩니다.

위험 권한 처리

위험 권한은 사용하려면 AndroidManifest.xml에 권한을 명세하고, 부가적으로 소스 코드에 권한 요청 및 처리 로직을 작성해야 합니다. 소스 코드에서 위험 권한을 처리하는 과정은 다음과 같습니다.

1단계: 권한에 대한 사용자 승인 확인

해당 권한이 이미 승인되어 있는지 확인하는 과정입니다.

// 권한 승인 상태 가져오기
val permission = ContextCompat.checkSelfPermission(this, 권한)

// 권한 승인 상태 구분
if (permission === PackageManager.PERMISSION_GRANTED) {
    // 권한이 승인된 상태
} else {
    // 권한이 승인되지 않은 상태 ➡ 권한 요청 프로세스 진행
}

2단계: 사용자에게 승인 요청

사용자에게 권한 승인을 요청하는 팝업을 띄웁니다.

ActivityCompat.requestPermissions(this, arrayOf(권한 목록), requestCode)

3단계: 사용자 승인 후 처리

권한 승인 요청이 처리되면 onRequestPermissionsResult() 메서드가 자동 호출되며 이 메서드로 권한 승인 요청의 결과가 전달됩니다.

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<String>,
    grantResults: IntArray
) {
    if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        // 권한 승인
    } else {
        // 권한 거부
    }
}

grantResults
grantResults의 엘리먼트 순서는 ActivityCompat.requestPermissions에서 요청 권한 목록의 순서와 동일함

파일 입출력

저장소의 종류와 권한

안드로이드는 리눅스 위에 가상 머신이 동작하는 플랫폼이기 때문에 리눅스 기반의 파일 시스템을 사용합니다. 리눅스 파일 시스템은 설치된 앱마다 리눅스 사용자 아이디와 그에 해당하는 디렉터리가 할당되며 각각의 디렉터리는 해당 사용자만 접근할 수 있습니다. 이 디렉터리처럼 특정 앱의 사용자만 접근할 수 있는 영역을 내부 저장소라고 하고, 모든 앱이 공용으로 사용할 수 있는 영역을 외부 저장소라고 합니다.

내부 저장소

내부 저장소는 주로 내 앱에서만 사용하는 데이터를 저장하며 특별한 권한이 없어도 읽고 쓸 수 있습니다.

외부 저장소

외부 저장소는 일종의 공용 공간이기 때문에 외부 저장소에 저장된 파일에 접근하려면 외부 저장소 접근 권한을 명세해야 합니다.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

외부 저장소에는 서로 다른 앱 간에 공유가 필요한 데이터를 저장합니다.

내부 저장소 파일 읽기 / 쓰기

파일이 텍스트 파일이냐 아니냐에 따라서 파일을 읽고 쓰기 위해 사용하는 API가 달라집니다.

파일 정보 가져오기

파일에 대한 정보는 File 객체를 통해 얻을 수 있습니다.

val file = File("경로")

// 파일의 존재 여부
if (file.exists()) {
    // ...
}

// 경로에 해당하는 것이 파일인지 여부
if (file.isFile) {
    // ...
}

// 경로에 해당하는 것이 디렉터리인지 여부
if (file.isDirectory) {
    // ...
}

// 파일 또는 디렉터리에 이름을 반환
val name = file.name

// 경로에 파일 생성
file.createNewFile()

// 경로에 디렉터리 생성. 중간 경로가 없다면 중간 경로도 자동으로 생성
file.mkdirs()

// 삭제
file.delete()

// 절대 경로 반환
val path = file.absolutePath

파일을 읽고 쓰는 스트림

파일의 실제 데이터를 읽고 쓰려면 Stream 이라는 복잡한 클래스를 사용합니다. 바이트 단위 스트림은 InputStream / OutputStream 클래스를 사용하고 문자 단위 스트림은 Reader / Writer 클래스를 사용합니다.

텍스트 파일 읽기

val file = File(Path)
if(!file.exists()) 
    return ""
    
val reader = FileReader(file)
val buffer = BufferedReader(reader)
var temp = ""
val result = StringBuffer()
while (true) {
    temp = buffer.readLine()
    if (temp == null)
        break
    else
        result.append(buffer)
}
buffer.close()

var inputText = result.toString()

텍스트 파일 쓰기

val dir = File(directory)
if(!dir.exists()) {
    dir.mkdirs()
}
val writer = FileWriter(directory + "/" + filename)
val buffer = BufferedWriter(writer)
buffer.write(content)
buffer.close()

SharedPreferences

SharedPreferences는 간단한 데이터의 저장을 목적으로 사용합니다. SharedPreferences는 내부 저장소를 이용하기 때문에 권한 설정이 필요 없고 훨씬 간단한 코드로 사용할 수 있습니다. SharedPreferences는 주로 로그인 정보나 앱의 상태를 저장하는 용도로 사용합니다.

SharedPreferences를 사용하고 데이터 저장하기

SharedPreferences는 데이터를 키와 값의 쌍으로 저장합니다. 데이터는 XML 형식으로 된 파일로 저장되며 앱이 종료되도 남아 있습니다.

SharedPreferences에 데이터 저장

  1. SharedPreferences 생성하기
  2. Editor 꺼내기
  3. putInt(), putString() 등의 메서드로 데이터 저장하기
  4. apply()로 파일에 반영하기
val shared = getSharedPreferences("이름", Context.MODE_PRIVATE)
val editor = shared.edit()
editor.putString("키", "값")
editor.apply()

SharedPreferences의 데이터 읽기

  1. SharedPreferences 생성하기
  2. getInt(), getString() 등의 메서드로 데이터 읽어오기
val shared = getSharedPreferences("이름", Context.MODE_PRIVATE)
shared.getString("키", "기본값")

설정화면 만들기

안드로이드는 레이아웃 파일을 이용해서 화면을 구성하지 않아도, 설정화면을 만들 수 있는 SharedPreferences API를 제공합니다.

PreferenceScreen 화면 정의하기

preferences.xml 파일에 설정화면에서 사용할 화면 구조를 XML로 정의해두면 안드로이드가 정의된 XML의 구조를 분석해 화면을 그려줍니다.

<PreferenceScreen>
  <PreferenceCategory
      android:title="기능 설정"
      app:iconSpaceReserved="false">
  </PreferenceCategory>
</PreferenceScreen>

<PreferenceScreen> 태그는 preferences.xml의 최상위 태그이며 <PreferenceCategory> 태그는 설정화면에 보여질 카테고리를 구성합니다. 카테고리는 주로 입력 필드의 그룹명을 출력하는 용도로 사용됩니다.

입력 필드

이름설명
CheckBoxPreference체크박스
SwitchPreference스위치
EditTextPreference값을 직접 입력
ListPreference목록

PreferenceFragment 생성

class SettingFragment : PreferenceFrgmentCompat() {
    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        addPreferencesFromResource(R.xml.preferences)
    }
}

설정값 사용

PreferenceScreen에서 값을 조절하면 설정값이 자동으로 지정된 SharedPreferences 파일에 저장됩니다.

val shared = PreferenceManager.getDefaultSharedPreferences(this)

val name = shared.getString("key", "")

좋은 웹페이지 즐겨찾기