본문 바로가기
Android/기본

[Android] 발생할 수 있는 Exception종류와 처리 방법

by LoseyKim 2024. 6. 14.

안녕하세요.

개발을 하다 보면 다양한 예외 상황에 직면하게 돼요. 이러한 예외는 프로그램의 흐름을 방해하고 예상치 못한 종료를 초래할 수 있기 때문에, 각 예외의 특성을 이해하고 적절히 처리하는 것이 중요해요.

오늘은 개발 중 자주 발생하는 Exception과 그 처리 방법에 대해 알아볼게요.


1. NullPointerException

NullPointerException은 null 객체의 속성이나 메서드를 호출하려고 할 때 발생해요. Kotlin은 기본적으로 null-safety를 지원하지만, 자바 라이브러리와의 상호작용 등으로 인해 발생할 수 있어요.

val str: String? = null
val length = str!!.length // NullPointerException 발생

이를 방지하기 위해서는 안전 호출 연산자(?.)를 사용하거나, non-null 확인(!!)을 지양하는 것이 좋아요.

val length = str?.length ?: 0 // 안전하게 처리

2. IllegalArgumentException

잘못된 인자가 함수나 메서드에 전달될 때 발생해요. 예를 들어, 예상 범위 밖의 값을 전달하거나, 필수 인자를 null로 전달할 때 발생해요.

fun checkAge(age: Int) {
    require(age > 0) { "Age must be positive" }
}

checkAge(-5) // IllegalArgumentException 발생

함수나 메서드를 호출하기 전에 인자의 유효성을 검사하고, require나 check 등의 Kotlin 표준 함수를 사용하여 명시적으로 조건을 확인해요.

fun checkAge(age: Int) {
    require(age > 0) { "Age must be positive" }
}

3. IndexOutOfBoundsException

리스트, 배열 등의 인덱스가 유효 범위를 벗어날 때 발생해요.

val list = listOf(1, 2, 3)
val element = list[3] // IndexOutOfBoundsException 발생

항상 인덱스 범위를 확인하는 습관을 기르세요.

if (index in list.indices) {
    val element = list[index]
}

4. ClassCastException

객체를 잘못된 타입으로 캐스팅하려고 할 때 발생해요.

val obj: Any = "String"
val num = obj as Int // ClassCastException 발생

타입을 안전하게 캐스팅하려면 as? 연산자를 사용하세요.

val num = obj as? Int

5. NumberFormatException

숫자 형식이 아닌 문자열을 숫자로 변환하려고 할 때 발생해요.

val number = "abc".toInt() // NumberFormatException 발생

변환 전 문자열이 유효한지 확인하는 것이 좋아요.

val number = "123".toIntOrNull() ?: 0

6. IOException

입출력 작업 중에 발생하는 예외로, 파일 읽기/쓰기 또는 네트워크 작업에서 문제가 생길 때 발생해요 .

try {
    val input = File("file.txt").inputStream()
} catch (e: IOException) {
    e.printStackTrace()
}

입출력 작업을 수행할 때는 항상 예외 처리를 통해 오류에 대비해야 해요. 또한, 파일이나 네트워크 자원을 사용할 때는 적절한 자원 해제를 보장하기 위해 use 블록을 사용하면 좋아요.

try {
    File("file.txt").inputStream().use { input ->
        // 파일 읽기 작업
    }
} catch (e: IOException) {
    e.printStackTrace()
}

7. FileNotFoundException

파일을 찾을 수 없을 때 발생하는 예외예요.

try {
    val input = File("non_existent_file.txt").inputStream()
} catch (e: FileNotFoundException) {
    e.printStackTrace()
}

파일을 열기 전에 해당 파일이 존재하는지 확인하고, 파일 경로가 올바른지 검사하는 것이 중요해요.

val file = File("non_existent_file.txt")
if (file.exists()) {
    file.inputStream().use { input ->
        // 파일 읽기 작업
    }
} else {
    println("File not found")
}

8. OutOfMemoryError

메모리가 부족할 때 발생하는 예외예요. 큰 이미지를 처리하거나 메모리를 많이 사용하는 작업을 할 때 발생할 수 있어요.

try {
    val bitmap = BitmapFactory.decodeFile("large_image.jpg")
} catch (e: OutOfMemoryError) {
    e.printStackTrace()
}

이미지를 처리할 때는 적절한 크기로 리사이즈하거나, 메모리 사용량을 줄이기 위해 BitmapFactory.Options를 사용하여 이미지를 샘플링하는 것이 좋아요.

val options = BitmapFactory.Options().apply {
    inSampleSize = 4 // 원본 크기의 1/4로 축소
}
val bitmap = BitmapFactory.decodeFile("large_image.jpg", options)

9. ConcurrentModificationException

컬렉션을 반복하는 동안 해당 컬렉션을 수정하려고 할 때 발생해요.

val list = mutableListOf(1, 2, 3)
for (item in list) {
    list.add(4) // ConcurrentModificationException 발생
}

컬렉션을 안전하게 수정하려면 Iterator를 사용하세요.

val iterator = list.iterator()
while (iterator.hasNext()) {
    val item = iterator.next()
    iterator.remove()
}

10. SQLiteException

SQLite 데이터베이스 작업 중에 발생하는 예외예요.

try {
    val db = SQLiteDatabase.openDatabase("non_existent.db", null, SQLiteDatabase.OPEN_READWRITE)
} catch (e: SQLiteException) {
    e.printStackTrace()
}

데이터베이스 작업을 수행하기 전에 데이터베이스 파일의 경로가 올바른지 확인하고, 데이터베이스가 존재하는지 확인해야 해요. 또한, 데이터베이스 작업을 예외 처리 블록으로 감싸서 오류 발생 시 적절히 대응할 수 있도록 해요.

try {
    val db = SQLiteDatabase.openOrCreateDatabase("my_database.db", null)
    // 데이터베이스 작업 수행
} catch (e: SQLiteException) {
    e.printStackTrace()
}

11. SecurityException

보안 권한이 없을 때 발생하는 예외예요.

try {
    val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
} catch (e: SecurityException) {
    e.printStackTrace()
}

필요한 권한을 사전에 요청하고, 권한이 허용되었는지 확인한 후에 작업을 수행해야 해요. 안드로이드에서는 런타임 권한 요청을 통해 사용자에게 필요한 권한을 요청할 수 있어요.

if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) 
    == PackageManager.PERMISSION_GRANTED) {
    val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
} else {
    ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_LOCATION_PERMISSION)
}

12. NetworkOnMainThreadException

네트워크 작업을 메인 스레드에서 수행하려고 할 때 발생해요. 안드로이드에서는 네트워크 작업을 백그라운드 스레드에서 수행해야 해요.

try {
    val url = URL("https://www.example.com")
    val connection = url.openConnection() as HttpURLConnection
    connection.inputStream // NetworkOnMainThreadException 발생
} catch (e: NetworkOnMainThreadException) {
    e.printStackTrace()
}

백그라운드 스레드에서 네트워크 작업을 수행하려면 AsyncTask나 Coroutine을 사용하세요.

CoroutineScope(Dispatchers.IO).launch {
    val url = URL("https://www.example.com")
    val connection = url.openConnection() as HttpURLConnection
    connection.inputStream
}