반응형
Android Activity 오류 해결 및 성능 최적화 방법
Android 앱 개발 시 Activity 관련 오류는 앱 충돌과 성능 저하의 주요 원인입니다. 이 글에서는 개발자들이 자주 마주치는 Activity 오류 유형별 해결책과 함께 실전에서 바로 적용할 수 있는 성능 최적화 기법을 상세히 다룹니다. 메모리 누수부터 ANR 방지, 레이아웃 최적화까지 Android 앱의 안정성과 속도를 높이는 핵심 노하우를 확인하세요.
목차
1. 자주 발생하는 Activity 오류 해결책
2. Activity 성능 최적화 핵심 기법
3. 실전 적용 사례 및 모니터링
#1. 자주 발생하는 Activity 오류 해결책
Android 개발에서 Activity는 앱의 핵심 구성 요소이지만, 잘못된 사용은 치명적인 오류를 발생시킵니다. 실무에서 자주 마주치는 7가지 주요 오류와 검증된 해결책을 살펴보겠습니다.
1) 메모리 누수(Memory Leak) 원인과 해결
메모리 누수는 Android 앱 개발에서 가장 흔하면서도 치명적인 문제입니다. Activity 컨텍스트를 잘못 관리하면 앱이 종료된 후에도 메모리에 남아 성능 저하와 비정상 종료를 유발합니다.
(1) 주요 발생 원인
① Activity 컨텍스트를 정적(static) 변수에 저장
② 내부 클래스에서 암시적으로 Activity 참조 유지
③ 비동기 작업에서 Activity 참조를 장시간 보유
② 내부 클래스에서 암시적으로 Activity 참조 유지
③ 비동기 작업에서 Activity 참조를 장시간 보유
(2) Application 컨텍스트 활용
긴 수명주기가 필요한 객체에는 Activity 컨텍스트 대신 Application 컨텍스트를 사용해야 합니다.
// 잘못된 방법 ❌
val context = this // Activity 컨텍스트
// 올바른 방법 ✅
val context = applicationContext
val context = this // Activity 컨텍스트
// 올바른 방법 ✅
val context = applicationContext
(3) 약한 참조(WeakReference) 사용
비동기 작업에서는 WeakReference를 사용하여 Activity가 종료되면 자동으로 참조가 해제되도록 합니다.
class MyAsyncTask(activity: MainActivity) {
// 약한 참조로 Activity 저장
private val weakActivity = WeakReference(activity)
fun doWork() {
// 작업 수행 후 UI 업데이트
val activity = weakActivity.get()
if (activity != null && !activity.isFinishing) {
// 안전하게 UI 작업 수행
}
}
}
// 약한 참조로 Activity 저장
private val weakActivity = WeakReference(activity)
fun doWork() {
// 작업 수행 후 UI 업데이트
val activity = weakActivity.get()
if (activity != null && !activity.isFinishing) {
// 안전하게 UI 작업 수행
}
}
}
(4) 생명주기 인식 컴포넌트 적용
ViewModel, LiveData, Coroutines의 LifecycleScope 등을 활용하면 생명주기에 맞춰 자동으로 리소스가 정리됩니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 생명주기에 맞게 자동으로 정리됨
lifecycleScope.launch {
// 비동기 작업 수행
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 생명주기에 맞게 자동으로 정리됨
lifecycleScope.launch {
// 비동기 작업 수행
}
}
}
. . . . .
2) 화면 회전 시 데이터 손실 방지
Android 기기를 회전하면 Activity가 재생성되면서 임시 데이터가 모두 사라지는 문제가 발생합니다. 이를 방지하기 위한 3가지 핵심 방법을 알아보겠습니다.
(1) ViewModel 활용 (권장)
ViewModel은 화면 회전과 같은 구성 변경에도 데이터를 자동으로 유지합니다.
// ViewModel 정의
class MainViewModel : ViewModel() {
var userName: String = ""
var userScore: Int = 0
}
// Activity에서 사용
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
binding.textUserName.text = viewModel.userName
}
}
class MainViewModel : ViewModel() {
var userName: String = ""
var userScore: Int = 0
}
// Activity에서 사용
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
binding.textUserName.text = viewModel.userName
}
}
(2) onSaveInstanceState() 활용
Bundle을 통해 데이터를 저장하고 복원할 수 있습니다.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("user_name", userName)
outState.putInt("score", score)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
userName = savedInstanceState.getString("user_name", "")
score = savedInstanceState.getInt("score", 0)
updateUI()
}
}
super.onSaveInstanceState(outState)
outState.putString("user_name", userName)
outState.putInt("score", score)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
userName = savedInstanceState.getString("user_name", "")
score = savedInstanceState.getInt("score", 0)
updateUI()
}
}
(3) SavedStateHandle 사용 (최신 권장 방식)
최신 Android에서는 ViewModel과 savedStateHandle을 함께 사용하는 방식을 권장합니다.
class MainViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
var userName: String
get() = savedStateHandle.get<String>("user_name") ?: ""
set(value) { savedStateHandle.set("user_name", value) }
}
var userName: String
get() = savedStateHandle.get<String>("user_name") ?: ""
set(value) { savedStateHandle.set("user_name", value) }
}
. . . . .
3) IllegalStateException 방지
Activity가 이미 종료되었거나 종료 중인 상태에서 UI를 업데이트하려고 할 때 발생하는 오류입니다.
(1) Activity 상태 확인
UI 업데이트 전에 Activity가 유효한 상태인지 확인합니다.
private fun updateUI(data: String) {
if (!isFinishing && !isDestroyed) {
binding.textView.text = data
}
}
if (!isFinishing && !isDestroyed) {
binding.textView.text = data
}
}
(2) 생명주기 인식 API 사용
LiveData나 Flow는 자동으로 생명주기를 인식하여 안전하게 처리됩니다.
viewModel.userData.observe(this) { data ->
// LiveData는 자동으로 생명주기를 인식
binding.textView.text = data
}
// LiveData는 자동으로 생명주기를 인식
binding.textView.text = data
}
(3) 작업 취소 구현
코루틴의 lifecycleScope를 사용하면 생명주기에 맞춰 작업이 자동으로 취소됩니다.
val job = lifecycleScope.launch {
val result = fetchData()
updateUI(result)
}
// onDestroy()에서 명시적 취소 불필요
val result = fetchData()
updateUI(result)
}
// onDestroy()에서 명시적 취소 불필요
. . . . .
4) ANR(Application Not Responding) 해결
앱이 메인 스레드에서 오래 걸리는 작업을 실행할 때 발생하는 오류로, "앱이 응답하지 않습니다" 대화상자가 표시됩니다.
(1) 코루틴으로 비동기 처리
lifecycleScope.launch(Dispatchers.IO) {
// 네트워크 요청, 파일 읽기 등 무거운 작업
val data = repository.fetchData()
withContext(Dispatchers.Main) {
binding.textView.text = data
}
}
// 네트워크 요청, 파일 읽기 등 무거운 작업
val data = repository.fetchData()
withContext(Dispatchers.Main) {
binding.textView.text = data
}
}
(2) WorkManager 활용
앱이 종료된 후에도 실행되어야 하는 백그라운드 작업에는 WorkManager를 사용합니다.
val workRequest = OneTimeWorkRequestBuilder<DataSyncWorker>()
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.build()
WorkManager.getInstance(context).enqueue(workRequest)
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.build()
WorkManager.getInstance(context).enqueue(workRequest)
(3) 지연 초기화 활용
무거운 객체는 필요할 때만 초기화되도록 합니다.
private val expensiveResource by lazy {
// 시간이 오래 걸리는 초기화 작업
HeavyResourceManager()
}
// 시간이 오래 걸리는 초기화 작업
HeavyResourceManager()
}
. . . . .
5) 프래그먼트 트랜잭션 오류 방지
Activity의 상태가 저장된 후 프래그먼트 트랜잭션을 수행할 때 발생하는 오류입니다.
(1) 올바른 생명주기 단계에서 실행
override fun onResumeFragments() {
super.onResumeFragments()
if (shouldShowDetailsFragment) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, DetailsFragment())
.commit()
}
}
super.onResumeFragments()
if (shouldShowDetailsFragment) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, DetailsFragment())
.commit()
}
}
(2) 상태 확인 후 트랜잭션
private fun showFragment() {
if (!isFinishing && !supportFragmentManager.isStateSaved) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MyFragment())
.commit()
}
}
if (!isFinishing && !supportFragmentManager.isStateSaved) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MyFragment())
.commit()
}
}
. . . . .
6) Activity 스택 관리
Activity 스택을 잘못 관리하면 사용자 경험이 저하되고 백 버튼 동작이 이상해집니다.
(1) launchMode 설정
| launchMode | 설명 |
|---|---|
| standard | 기본값, 항상 새 인스턴스 생성 |
| singleTop | 최상위에 있을 때만 재사용 |
| singleTask | 태스크당 하나의 인스턴스만 허용 |
| singleInstance | 전용 태스크에서 단일 인스턴스 |
(2) Intent 플래그 활용
val intent = Intent(this, HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
startActivity(intent)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
startActivity(intent)
. . . . .
7) Context 사용 오류 해결
잘못된 Context를 사용하면 메모리 누수와 예상치 못한 오류가 발생합니다.
(1) 적절한 Context 유형 선택
① UI 관련 작업 (대화상자, 토스트): Activity Context
② 앱 전체 리소스 접근 (데이터베이스, 파일): Application Context
② 앱 전체 리소스 접근 (데이터베이스, 파일): Application Context
(2) 싱글톤에서 Context 관리
// 잘못된 방법 ❌
object MySingleton {
private var activityContext: Context? = null
}
// 올바른 방법 ✅
object MySingleton {
private var appContext: Context? = null
fun init(context: Context) {
this.appContext = context.applicationContext
}
}
object MySingleton {
private var activityContext: Context? = null
}
// 올바른 방법 ✅
object MySingleton {
private var appContext: Context? = null
fun init(context: Context) {
this.appContext = context.applicationContext
}
}
#2. Activity 성능 최적화 핵심 기법
안정적인 앱을 만들었다면 이제 성능 최적화로 사용자 경험을 한 단계 높여야 합니다. 실전에서 검증된 7가지 최적화 기법을 소개합니다.
1) 레이아웃 최적화
복잡한 레이아웃은 렌더링 성능을 저하시키고 메모리를 과도하게 사용합니다.
(1) 뷰 계층 간소화
ConstraintLayout을 활용하여 중첩된 레이아웃을 최소화합니다.
(2) ViewBinding 사용
ViewBinding은 findViewById보다 효율적이고 타입 안전성을 보장합니다.
// build.gradle
android {
buildFeatures {
viewBinding true
}
}
// Activity에서 사용
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.buttonSubmit.setOnClickListener { }
}
}
android {
buildFeatures {
viewBinding true
}
}
// Activity에서 사용
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.buttonSubmit.setOnClickListener { }
}
}
(3) RecyclerView 활용
반복되는 항목은 RecyclerView와 ViewHolder 패턴을 적용합니다.
. . . . .
2) 생명주기에 맞춘 리소스 관리
리소스를 생명주기에 맞게 관리하면 메모리 누수를 방지하고 앱 성능을 향상시킬 수 있습니다.
(1) 리소스 초기화 및 해제 타이밍
class CameraActivity : AppCompatActivity() {
private var camera: Camera? = null
override fun onResume() {
super.onResume()
camera = Camera.open()
}
override fun onPause() {
super.onPause()
camera?.release()
camera = null
}
}
private var camera: Camera? = null
override fun onResume() {
super.onResume()
camera = Camera.open()
}
override fun onPause() {
super.onPause()
camera?.release()
camera = null
}
}
(2) DefaultLifecycleObserver 활용
생명주기 관찰자를 사용하여 리소스 관리를 모듈화합니다.
class LocationManager(private val activity: AppCompatActivity) : DefaultLifecycleObserver {
init {
activity.lifecycle.addObserver(this)
}
override fun onResume(owner: LifecycleOwner) {
startLocationUpdates()
}
override fun onPause(owner: LifecycleOwner) {
stopLocationUpdates()
}
}
init {
activity.lifecycle.addObserver(this)
}
override fun onResume(owner: LifecycleOwner) {
startLocationUpdates()
}
override fun onPause(owner: LifecycleOwner) {
stopLocationUpdates()
}
}
(3) 이미지 리소스 효율적 관리
비트맵을 적절한 크기로 로드하고 사용하지 않을 때 해제합니다.
private fun decodeSampledBitmap(res: Resources, resId: Int, reqWidth: Int, reqHeight: Int): Bitmap {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeResource(res, resId, options)
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
options.inJustDecodeBounds = false
return BitmapFactory.decodeResource(res, resId, options)
}
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeResource(res, resId, options)
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
options.inJustDecodeBounds = false
return BitmapFactory.decodeResource(res, resId, options)
}
. . . . .
3) 비동기 처리 최적화
효율적인 비동기 처리는 UI 응답성을 향상시키고 ANR을 방지합니다.
(1) 코루틴 효율적 사용
class MainActivity : AppCompatActivity() {
// 네트워크 요청
private fun fetchData() {
lifecycleScope.launch(Dispatchers.IO) {
try {
val result = api.getData()
withContext(Dispatchers.Main) {
updateUI(result)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
showError(e.message)
}
}
}
}
}
// 네트워크 요청
private fun fetchData() {
lifecycleScope.launch(Dispatchers.IO) {
try {
val result = api.getData()
withContext(Dispatchers.Main) {
updateUI(result)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
showError(e.message)
}
}
}
}
}
(2) 작업 취소 처리
class SearchActivity : AppCompatActivity() {
private var searchJob: Job? = null
private fun performSearch(query: String) {
searchJob?.cancel()
searchJob = lifecycleScope.launch {
delay(300) // 디바운싱
val results = repository.search(query)
updateSearchResults(results)
}
}
}
private var searchJob: Job? = null
private fun performSearch(query: String) {
searchJob?.cancel()
searchJob = lifecycleScope.launch {
delay(300) // 디바운싱
val results = repository.search(query)
updateSearchResults(results)
}
}
}
(3) Flow 활용
데이터 스트림을 효율적으로 처리하기 위해 Flow를 사용합니다.
class UserViewModel : ViewModel() {
private val _searchResults = MutableStateFlow<List<User>>(emptyList())
val searchResults = _searchResults.asStateFlow()
fun searchUsers(query: String) {
viewModelScope.launch {
userRepository.searchUsers(query)
.debounce(300)
.filter { it.isNotEmpty() }
.collect { results ->
_searchResults.value = results
}
}
}
}
private val _searchResults = MutableStateFlow<List<User>>(emptyList())
val searchResults = _searchResults.asStateFlow()
fun searchUsers(query: String) {
viewModelScope.launch {
userRepository.searchUsers(query)
.debounce(300)
.filter { it.isNotEmpty() }
.collect { results ->
_searchResults.value = results
}
}
}
}
. . . . .
4) 렌더링 성능 향상
(1) 하드웨어 가속 활성화
<!-- AndroidManifest.xml -->
<application
android:hardwareAccelerated="true">
</application>
<application
android:hardwareAccelerated="true">
</application>
(2) 오버드로우 줄이기
불필요한 배경이나 중첩된 투명 뷰를 제거합니다.
// 불필요한 배경 제거
view.background = null
// 중첩된 뷰에 동일한 배경색 설정 방지
containerView.setBackgroundColor(color)
childView.background = null
view.background = null
// 중첩된 뷰에 동일한 배경색 설정 방지
containerView.setBackgroundColor(color)
childView.background = null
(3) 애니메이션 최적화
// ObjectAnimator 사용
val animator = ObjectAnimator.ofFloat(view, View.ALPHA, 0f, 1f)
animator.duration = 300
animator.start()
val animator = ObjectAnimator.ofFloat(view, View.ALPHA, 0f, 1f)
animator.duration = 300
animator.start()
. . . . .
5) 메모리 사용 최적화
메모리를 효율적으로 사용하면 앱의 안정성과 성능이 향상됩니다.
(1) 효율적인 컬렉션 사용
// 정수 키 사용 시 SparseArray
val userMap = SparseArray<User>()
userMap.put(1, User("John"))
// 문자열 키 사용 시 ArrayMap
val configMap = ArrayMap<String, String>()
configMap["theme"] = "dark"
val userMap = SparseArray<User>()
userMap.put(1, User("John"))
// 문자열 키 사용 시 ArrayMap
val configMap = ArrayMap<String, String>()
configMap["theme"] = "dark"
(2) 메모리 캐시 구현
class BitmapCache {
private val maxMemory = Runtime.getRuntime().maxMemory() / 1024
private val cacheSize = (maxMemory / 4).toInt()
private val memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
override fun sizeOf(key: String, bitmap: Bitmap): Int {
return bitmap.byteCount / 1024
}
}
fun addBitmapToCache(key: String, bitmap: Bitmap) {
if (getBitmapFromCache(key) == null) {
memoryCache.put(key, bitmap)
}
}
}
private val maxMemory = Runtime.getRuntime().maxMemory() / 1024
private val cacheSize = (maxMemory / 4).toInt()
private val memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
override fun sizeOf(key: String, bitmap: Bitmap): Int {
return bitmap.byteCount / 1024
}
}
fun addBitmapToCache(key: String, bitmap: Bitmap) {
if (getBitmapFromCache(key) == null) {
memoryCache.put(key, bitmap)
}
}
}
. . . . .
6) 앱 시작 시간 단축
앱 시작 시간은 사용자 경험에 직접적인 영향을 미칩니다.
(1) 콜드 스타트 최적화
시작 화면을 테마로 구현하여 콜드 스타트 지연을 감춥니다.
<!-- res/values/styles.xml -->
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/splash_background</item>
</style>
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/splash_background</item>
</style>
(2) 초기화 작업 지연
필수적이지 않은 초기화 작업은 백그라운드로 이동하거나 필요할 때까지 지연시킵니다.
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
initializeCriticalComponents()
delayedInit()
}
private fun delayedInit() {
Looper.myQueue().addIdleHandler {
initializeNonCriticalComponents()
false
}
}
}
override fun onCreate() {
super.onCreate()
initializeCriticalComponents()
delayedInit()
}
private fun delayedInit() {
Looper.myQueue().addIdleHandler {
initializeNonCriticalComponents()
false
}
}
}
. . . . .
7) 효율적인 데이터 로딩
데이터를 효율적으로 로드하면 앱의 반응성이 향상됩니다.
(1) 페이징 구현
대량의 데이터는 Paging 3 라이브러리를 사용합니다.
class UserPagingSource(private val api: UserApi) : PagingSource<Int, User>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
val page = params.key ?: 1
return try {
val response = api.getUsers(page, params.loadSize)
LoadResult.Page(
data = response.users,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.isLastPage) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
val page = params.key ?: 1
return try {
val response = api.getUsers(page, params.loadSize)
LoadResult.Page(
data = response.users,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.isLastPage) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
(2) 캐싱 전략 구현
데이터를 메모리와 디스크에 적절히 캐싱합니다.
class DataRepository(
private val api: ApiService,
private val database: AppDatabase
) {
private val memoryCache = LruCache<String, Data>(100)
suspend fun getData(id: String, forceRefresh: Boolean = false): Data {
// 1. 메모리 캐시 확인
memoryCache.get(id)?.let {
if (!forceRefresh) return it
}
// 2. 로컬 데이터베이스 확인
if (!forceRefresh) {
val localData = database.dataDao().getById(id)
if (localData != null) {
memoryCache.put(id, localData)
return localData
}
}
// 3. 네트워크에서 로드
val networkData = api.fetchData(id)
memoryCache.put(id, networkData)
database.dataDao().insert(networkData)
return networkData
}
}
private val api: ApiService,
private val database: AppDatabase
) {
private val memoryCache = LruCache<String, Data>(100)
suspend fun getData(id: String, forceRefresh: Boolean = false): Data {
// 1. 메모리 캐시 확인
memoryCache.get(id)?.let {
if (!forceRefresh) return it
}
// 2. 로컬 데이터베이스 확인
if (!forceRefresh) {
val localData = database.dataDao().getById(id)
if (localData != null) {
memoryCache.put(id, localData)
return localData
}
}
// 3. 네트워크에서 로드
val networkData = api.fetchData(id)
memoryCache.put(id, networkData)
database.dataDao().insert(networkData)
return networkData
}
}
#3. 실전 적용 사례 및 모니터링
실무에서 Activity 성능을 지속적으로 모니터링하고 개선하기 위한 도구와 방법을 알아보겠습니다.
1) 성능 모니터링 도구
(1) Android Profiler
메모리, CPU, 네트워크 사용량을 실시간으로 모니터링합니다. Android Studio에서 제공하는 강력한 프로파일링 도구로 병목 지점을 정확히 파악할 수 있습니다.
(2) Android Vitals
Google Play Console에서 실제 사용자 환경에서의 앱 성능을 확인합니다. ANR 발생률, 크래시율, 배터리 소모량 등의 지표를 모니터링할 수 있습니다.
(3) StrictMode
개발 중에 메인 스레드 차단 및 디스크/네트워크 액세스를 감지합니다.
// 개발 빌드에서만 사용
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build()
)
}
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build()
)
}
. . . . .
2) 실전 적용 체크리스트
안정적이고 빠른 Android 앱을 만들기 위한 필수 체크리스트입니다.
① 메모리 관리: WeakReference 사용, Application Context 활용, 생명주기 인식 컴포넌트 적용
② 데이터 보존: ViewModel 또는 SavedStateHandle 사용으로 화면 회전 대응
③ 비동기 처리: 코루틴과 lifecycleScope로 ANR 방지
④ 레이아웃 최적화: ConstraintLayout과 ViewBinding 사용
⑤ 렌더링 개선: 하드웨어 가속, 오버드로우 최소화
⑥ 효율적인 컬렉션: SparseArray, ArrayMap 활용
⑦ 페이징: Paging 3 라이브러리로 대량 데이터 처리
⑧ 캐싱 전략: LruCache로 메모리 캐시 구현
⑨ 초기화 지연: lazy 초기화와 IdleHandler 활용
⑩ 모니터링: StrictMode와 Android Profiler로 지속적인 성능 검증
② 데이터 보존: ViewModel 또는 SavedStateHandle 사용으로 화면 회전 대응
③ 비동기 처리: 코루틴과 lifecycleScope로 ANR 방지
④ 레이아웃 최적화: ConstraintLayout과 ViewBinding 사용
⑤ 렌더링 개선: 하드웨어 가속, 오버드로우 최소화
⑥ 효율적인 컬렉션: SparseArray, ArrayMap 활용
⑦ 페이징: Paging 3 라이브러리로 대량 데이터 처리
⑧ 캐싱 전략: LruCache로 메모리 캐시 구현
⑨ 초기화 지연: lazy 초기화와 IdleHandler 활용
⑩ 모니터링: StrictMode와 Android Profiler로 지속적인 성능 검증
마무리
Android Activity의 자주 발생하는 오류를 해결하고 성능을 최적화하는 방법에 대해 알아보았습니다. 메모리 누수, ANR, 화면 회전 시 데이터 손실 등의 문제는 적절한 방법으로 해결할 수 있으며, 레이아웃 최적화, 효율적인 리소스 관리, 비동기 처리 개선을 통해 앱의 성능을 크게 향상시킬 수 있습니다.
앱 개발 시 이러한 베스트 프랙티스를 따르면 안정적이고 빠른 앱을 만들 수 있으며, 사용자 경험을 크게 향상시킬 수 있습니다. 가장 중요한 것은 생명주기를 제대로 이해하고 관리하는 것이며, 최신 Android 아키텍처 구성요소를 적극 활용하는 것입니다.
실수요자의 경우 생애최초 주택구입자에 대한 우대는 유지되므로, 실거주 목적의 주택 구입은 여전히 가능합니다. 지속적인 성능 모니터링과 개선을 통해 여러분의 앱이 더 안정적이고 빠르게 동작하기를 바랍니다!
긴 글 읽어주셔서 감사합니다.
끝.
끝.
반응형
'Development > Android' 카테고리의 다른 글
| [Android ] Android Intent Flag 완벽 가이드 - 화면 전환 제어 방법 (0) | 2020.04.08 |
|---|---|
| [Android] Android ArrayList 객체를 Intent로 전달하는 3가지 방법 (3) | 2020.04.08 |
| [Android] Android Picasso vs Glide 이미지 라이브러리 비교와 선택 기준 (0) | 2019.10.01 |
| [Android] Android ViewModel과 LiveData 실전 구현 방법 (아키텍처 가이드) (0) | 2019.09.23 |
| [Android] Android App Architecture 완벽 가이드 - MVVM 패턴과 권장 아키텍처 설계 방법 (0) | 2019.09.23 |