이것이 안드로이드다 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 | 센서 관련 |
SMS | SMS 관련 |
STORAGE | 저장소 관련 |
서명 권한은 권한을 사용하려는 앱이 권한을 정의하는 앱과 동일한 인증서로 서명된 경우 시스템이 자동으로 권한을 부여하는 권한입니다.
권한들은 비슷하 권한끼리 그룹으로 묶여 있으며 그룹 내의 한 권한이 부여되어 있다면 다른 권한들 역시 자동으로 부여됩니다.
위험 권한은 사용하려면 AndroidManifest.xml
에 권한을 명세하고, 부가적으로 소스 코드에 권한 요청 및 처리 로직을 작성해야 합니다. 소스 코드에서 위험 권한을 처리하는 과정은 다음과 같습니다.
해당 권한이 이미 승인되어 있는지 확인하는 과정입니다.
// 권한 승인 상태 가져오기
val permission = ContextCompat.checkSelfPermission(this, 권한)
// 권한 승인 상태 구분
if (permission === PackageManager.PERMISSION_GRANTED) {
// 권한이 승인된 상태
} else {
// 권한이 승인되지 않은 상태 ➡ 권한 요청 프로세스 진행
}
사용자에게 권한 승인을 요청하는 팝업을 띄웁니다.
ActivityCompat.requestPermissions(this, arrayOf(권한 목록), requestCode)
권한 승인 요청이 처리되면 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는 데이터를 키와 값의 쌍으로 저장합니다. 데이터는 XML 형식으로 된 파일로 저장되며 앱이 종료되도 남아 있습니다.
Editor
꺼내기putInt()
, putString()
등의 메서드로 데이터 저장하기apply()
로 파일에 반영하기val shared = getSharedPreferences("이름", Context.MODE_PRIVATE)
val editor = shared.edit()
editor.putString("키", "값")
editor.apply()
getInt()
, getString()
등의 메서드로 데이터 읽어오기val shared = getSharedPreferences("이름", Context.MODE_PRIVATE)
shared.getString("키", "기본값")
안드로이드는 레이아웃 파일을 이용해서 화면을 구성하지 않아도, 설정화면을 만들 수 있는 SharedPreferences API를 제공합니다.
preferences.xml
파일에 설정화면에서 사용할 화면 구조를 XML로 정의해두면 안드로이드가 정의된 XML의 구조를 분석해 화면을 그려줍니다.
<PreferenceScreen>
<PreferenceCategory
android:title="기능 설정"
app:iconSpaceReserved="false">
</PreferenceCategory>
</PreferenceScreen>
<PreferenceScreen>
태그는 preferences.xml
의 최상위 태그이며 <PreferenceCategory>
태그는 설정화면에 보여질 카테고리를 구성합니다. 카테고리는 주로 입력 필드의 그룹명을 출력하는 용도로 사용됩니다.
입력 필드
이름 설명 CheckBoxPreference 체크박스 SwitchPreference 스위치 EditTextPreference 값을 직접 입력 ListPreference 목록
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", "")