20230921_TIL

이상훈·2023년 9월 20일

TIL

목록 보기
46/83

12-1. MiseYa(미세먼지 앱)

1. Gradle에 라이브러리 추가

	implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
implementation "com.github.skydoves:powerspinner:1.2.6"

AndroidManifest.xml

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

<application
    android:allowBackup="true"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/Theme.MiseYa"
    android:usesCleartextTraffic="true"
    tools:targetApi="31">
    <activity
        android:name=".MainActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#9ED2EC"
tools:context=".MainActivity">

<com.skydoves.powerspinner.PowerSpinnerView
    android:id="@+id/spinnerView_sido"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:background="@color/md_green_200"
    android:foreground="?attr/selectableItemBackground"
    android:gravity="center"
    android:hint="도시 선택"
    android:padding="10dp"
    android:textColor="@color/white_93"
    android:textColorHint="@color/white_70"
    android:textSize="14.5sp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintEnd_toStartOf="@+id/spinnerView_goo"
    app:spinner_arrow_gravity="end"
    app:spinner_arrow_tint="@color/yellow"
    app:spinner_divider_color="@color/white_70"
    app:spinner_divider_show="true"
    app:spinner_divider_size="0.4dp"
    app:spinner_item_array="@array/Sido"
    app:spinner_item_height="46dp"
    app:spinner_popup_animation="normal"
    app:spinner_popup_background="@color/background800"
    app:spinner_popup_elevation="14dp"
    tools:ignore="HardcodedText,UnusedAttribute" />

<com.skydoves.powerspinner.PowerSpinnerView
    android:id="@+id/spinnerView_goo"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:background="@color/md_green_100"
    android:foreground="?attr/selectableItemBackground"
    android:gravity="center"
    android:hint="지역 선택"
    android:padding="10dp"
    android:textColor="@color/white_93"
    android:textColorHint="@color/white_70"
    android:textSize="14.5sp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toEndOf="@+id/spinnerView_sido"
    app:layout_constraintTop_toTopOf="parent"
    app:spinner_arrow_gravity="end"
    app:spinner_arrow_tint="@color/yellow"
    app:spinner_divider_color="@color/white_70"
    app:spinner_divider_show="true"
    app:spinner_divider_size="0.4dp"
    app:spinner_item_height="46dp"
    app:spinner_popup_animation="normal"
    app:spinner_popup_background="@color/background800"
    app:spinner_popup_elevation="14dp"
    tools:ignore="HardcodedText,UnusedAttribute" />


<ImageView
    android:id="@+id/iv_face"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="8dp"
    android:layout_marginTop="8dp"
    android:layout_marginEnd="8dp"
    android:layout_marginBottom="8dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:srcCompat="@drawable/mise1" />

<TextView
    android:id="@+id/tv_p10value"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    android:text=" - ㎍/㎥"
    android:textSize="18sp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/iv_face" />

<TextView
    android:id="@+id/tv_p10grade"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="20dp"
    android:text=""
    android:textColor="#048578"
    android:textSize="30sp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/tv_p10value" />

<TextView
    android:id="@+id/tv_cityname"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="8dp"
    android:layout_marginTop="50dp"
    android:layout_marginEnd="8dp"
    android:layout_marginBottom="8dp"
    android:text="도시를 선택해 주세요."
    android:textColor="#242323"
    android:textSize="36sp"
    android:textStyle="bold"
    app:layout_constraintBottom_toTopOf="@+id/iv_face"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<TextView
    android:id="@+id/tv_date"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="8dp"
    android:layout_marginTop="16dp"
    android:layout_marginEnd="8dp"
    android:text=""
    android:textSize="18sp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/tv_cityname" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

package com.android.miseya

import android.graphics.Color
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.android.miseya.data.DustItem
import com.android.miseya.databinding.ActivityMainBinding
import com.android.miseya.retrofit.NetWorkClient
import com.skydoves.powerspinner.IconSpinnerAdapter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
var items = mutableListOf()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)

    binding.spinnerViewSido.setOnSpinnerItemSelectedListener<String> { _, _, _, text ->
        communicateNetWork(setUpDustParameter(text))
    }

    binding.spinnerViewGoo.setOnSpinnerItemSelectedListener<String> { _, _, _, text ->

        Log.d("miseya", "selectedItem: spinnerViewGoo selected >  $text")
        var selectedItem = items.filter { f -> f.stationName == text }
        Log.d("miseya", "selectedItem: sidoName > " + selectedItem[0].sidoName)
        Log.d("miseya", "selectedItem: pm10Value > " + selectedItem[0].pm10Value)

        binding.tvCityname.text = selectedItem[0].sidoName + "  " + selectedItem[0].stationName
        binding.tvDate.text = selectedItem[0].dataTime
        binding.tvP10value.text = selectedItem[0].pm10Value + " ㎍/㎥"

        when (getGrade(selectedItem[0].pm10Value)) {
            1 -> {
                binding.mainBg.setBackgroundColor(Color.parseColor("#9ED2EC"))
                binding.ivFace.setImageResource(R.drawable.mise1)
                binding.tvP10grade.text = "좋음"
            }

            2 -> {
                binding.mainBg.setBackgroundColor(Color.parseColor("#D6A478"))
                binding.ivFace.setImageResource(R.drawable.mise2)
                binding.tvP10grade.text = "보통"
            }

            3 -> {
                binding.mainBg.setBackgroundColor(Color.parseColor("#DF7766"))
                binding.ivFace.setImageResource(R.drawable.mise3)
                binding.tvP10grade.text = "나쁨"
            }

            4 -> {
                binding.mainBg.setBackgroundColor(Color.parseColor("#BB3320"))
                binding.ivFace.setImageResource(R.drawable.mise4)
                binding.tvP10grade.text = "매우나쁨"
            }
        }
    }
}

private fun communicateNetWork(param: HashMap<String, String>) = lifecycleScope.launch() {
    val responseData = NetWorkClient.dustNetWork.getDust(param)
    Log.d("Parsing Dust ::", responseData.toString())

    val adapter = IconSpinnerAdapter(binding.spinnerViewGoo)
    items = responseData.response.dustBody.dustItem!!

    val goo = ArrayList<String>()
    items.forEach {
        Log.d("add Item :", it.stationName)
        goo.add(it.stationName)
    }

    runOnUiThread {
        binding.spinnerViewGoo.setItems(goo)
    }

}

private fun setUpDustParameter(sido: String): HashMap<String, String> {
    val authKey =
        "YBZ(....)SVg1pEC39CVbmsA=="

    return hashMapOf(
        "serviceKey" to authKey,
        "returnType" to "json",
        "numOfRows" to "100",
        "pageNo" to "1",
        "sidoName" to sido,
        "ver" to "1.0"
    )
}

fun getGrade(value: String): Int {
    val mValue = value.toInt()
    var grade = 1
    grade = if (mValue >= 0 && mValue <= 30) {
        1
    } else if (mValue >= 31 && mValue <= 80) {
        2
    } else if (mValue >= 81 && mValue <= 100) {
        3
    } else 4
    return grade
}

}

NetWorkClient.kt

object NetWorkClient {

private const val DUST_BASE_URL = "http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/"


private fun createOkHttpClient(): OkHttpClient {
    val interceptor = HttpLoggingInterceptor()

    if (BuildConfig.DEBUG)
        interceptor.level = HttpLoggingInterceptor.Level.BODY
    else
        interceptor.level = HttpLoggingInterceptor.Level.NONE

    return OkHttpClient.Builder()
        .connectTimeout(20, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
        .writeTimeout(20, TimeUnit.SECONDS)
        .addNetworkInterceptor(interceptor)
        .build()
    }

private val dustRetrofit = Retrofit.Builder()
    .baseUrl(DUST_BASE_URL).addConverterFactory(GsonConverterFactory.create()).client(
        createOkHttpClient()
    ).build()

val dustNetWork: NetWorkInterface = dustRetrofit.create(NetWorkInterface::class.java)

}

NetWorkInterface.kt

interface NetWorkInterface {
@GET("getCtprvnRltmMesureDnsty") //시도별 실시간 측정정보 조회 주소
suspend fun getDust(@QueryMap param: HashMap<String, String>): Dust
}

DustDTO.kt

data class Dust(val response: DustResponse)

data class DustResponse(
@SerializedName("body")
val dustBody: DustBody,
@SerializedName("header")
val dustHeader: DustHeader
)

data class DustBody(
val totalCount: Int,
@SerializedName("items")
val dustItem: MutableList?,
val pageNo: Int,
val numOfRows: Int
)

data class DustHeader(
val resultCode: String,
val resultMsg: String
)

data class DustItem(
val so2Grade: String,
val coFlag: String?,
val khaiValue: String,
val so2Value: String,
val coValue: String,
val pm25Flag: String?,
val pm10Flag: String?,
val o3Grade: String,
val pm10Value: String,
val khaiGrade: String,
val pm25Value: String,
val sidoName: String,
val no2Flag: String?,
val no2Grade: String,
val o3Flag: String?,
val pm25Grade: String,
val so2Flag: String?,
val dataTime: String,
val coGrade: String,
val no2Value: String,
val stationName: String,
val pm10Grade: String,
val o3Value: String
)

13-1. 앱 개발 프로세스

2. Feature Set

ㆍ전화 알림 : 아기가 잠에서 깨어 울기 시작하면 등록된 다른 휴대폰으로 전화를 걸어주는 기능
ㆍ원격 명령 : 그래도 걱정된다면 원격 명령 기능으로 현재 상태를 확인하고 소리를 듣기 위해 발신을 요청할 수 있는 기능
ㆍ나만의 테마 : 아기 얼굴 사진과 이름 등록, 아이성별에 따른 배경 테마등 나만의 테마 설정 기능
ㆍ자장가 재생 : 아기의 꿀잠을 위한 자장가 재생

광고 기획

  1. 무료 + 광고
  • 앱 사용은 전적으로 무료이지만, 대신 앱 내에 삽입된 배너광고를 통해 수익을 얻는 방법
  • 유저가 앱 내에 삽입된 광고를 클릭할 때마다 소액의 수익 발생
  • 광고로 인해 앱의 사용성이 저해되거나 유저가 귀찮아 할 수 있음
  • 광고의 위치 및 방법에 대한 신중한 검토 필요
  1. 유료
  • 앱을 다운로드하기 위해 결제가 필요
  • 앱이 돈을 지불할 정도의 가치가 있는지 고민
  • 안드로이드보다 iOS 기기에서 더 높은 수익률을 보임
  1. 무료 + 인앱결제
  • 처음 다운로드할 때는 무료이지만, 앱 내에서 이루어지는 추가 구매를 통해 수익을 내는 모델
  • 모바일 게임에서 많이 이용
  • 앱에 대한 유저의 충성도가 강할수록 높은 수익률을 보임
  1. 프리미엄 모델
  • 기본적인 기능은 무료로 이용할 수 있고, 앱 내에서 이루어지는 추가 구매를 통해 수익을 내는 모델
  • 다운로드는 무료이지만, 무료 버전에서는 제한된 기능만을 사용할 수 있다는 점 때문에 유저들의 프리미엄 버전 구매율이 높음 (플레이스토어 전체 매출의 70% 차지)
  1. 무료 체험판 앱 모델
  • 유저는 앱을 무료로 다운로드하고 일정 기간 동안 앱을 체험해볼 수 있음
  • 앱을 무료로 체험할 수 있게 함으로서 유저의 흥미를 끈 뒤, 모바일 앱을 구매하도록 유도
  • 프로모션 캠페인이 있는 경우, 마케터가 유저에게 무료 체험 기간을 제공하는 방식으로 이용

4. APP Development

개발자가 갖춰야 할 기본 지식

ㆍ자료구조(Data Structure)
ㆍ데이터 통신(Data Communication)
ㆍ알고리즘(Algorithm)
ㆍ소프트웨어 공학(Software Engineering)

5. Testing

ㆍUI Tests : 사용자를 대신해서 애플리케이션의 UI를 테스트한다.
◦실제 디바이스나 에뮬레이터의 실행이 필요하기 때문에
◦세 단계중에 가장 느리고 비용이 많이 드는 테스트입니다.

ㆍIntegration Tests : UI는 제외하고 개발자가 작성한 코드가 어떻게 안드로이드 프레임워크와 상호작용을 하는지 확인해볼 필요가 있을 경우. 이러한 테스트는 에뮬레이터나 실기기가 필요가 없다. 이러한 구현 테스팅은 Roboelectric 이라는 툴이 안드로이드에서 가장 대표적인 툴이다.

ㆍUnit Tests(단위테스트) : System Under Test(SUT)는 개발자가 집중해서 지켜보는 하나의 클래스이다. 모든 종속성은 올바르게 작동하는 것으로 간주한다. 그래서 해당 종속성은 mock 또는 stub이 됩니다. 이러한 테스트들은 실제 디바이스나 에뮬레이터가 필요 없기 때문에 빠르게 적은 비용으로 작성할 수 있습니다. Android에서 가장 일반적으로 사용되는 단위 테스트 도구는 JUnit과 Mockito 입니다.

6. Launch

ㆍ플레이스토어 앱 등록을 위해서는, 먼저 구글 개발자 계정을 만들어주셔야 합니다.
ㆍ구글 개발자 콘솔사이트 : https://play.google.com/apps/publish/
(계정을 등록한 뒤, 이용료 25$를 결제)

7. Marketing

  1. 앱의 품질이 앱 홍보/오랜 사용의 기본 조건임
  2. 높은 앱 순위가 최고의 홍보 방안
  3. 개발자나 유저들과 소통할 수 있는 공간(블로그, SNS 등)은 필수이나 사용 시기는 출시 전부터
  4. 잘 모르면 다른 업체의 마케팅 방안을 참고하는 것도 방법이겠음.

Google Play 순위 상승 요건

  • 신규 설치 수가 높아야
  • 앱 실행 빈도가 높아야
  • 설치수가 매일 지속되어야
  • 앱이 삭제되지 않고 오래 유지 되어야
  • 업데이트는 반영하지 않는 것 같음

14-1. 디버깅

1. 디버깅이란??

ㆍ디버깅은 모든 소프트웨어에서 소스 코드의 오류 또는 버그를 찾아서 수정하는 과정
ㆍ소프트웨어가 예상대로 작동하지 않으면 프로그래머는 오류가 일어나는 원인을 알아내기 위해 코드를 분석합니다.
ㆍ디버깅 도구를 사용해 소프트웨어를 제어된 환경에서 실행하고 코드를 단계별로 확인하여 문제를 분석하고 수정합니다.

2. 디버깅이란 용어는 어디에서 유래했나요?

ㆍ디버깅이라는 용어의 유래는 1940년대에 하버드 대학에서 근무했던 Grace Hopper 제독 시절로 거슬러 올라갑니다. 그녀의 동료 중 한 명이 대학의 컴퓨터 작동을 방해하는 나방을 발견하자, 그녀는 동료들에게 '시스템을 디버깅하고 있구나'라고 말했습니다.
ㆍ 컴퓨터 프로그래머들은 1950년대에 버그와 디버깅이라는 용어를 처음 사용한 것으로 기록되었고, 1960년대 초에 프로그래밍 커뮤니티에서 디버깅이라는 용어를 일반적으로 사용하게 되었습니다.

3. 디버깅이 중요한 이유는 무엇인가요?

ㆍ컴퓨터 프로그래밍은 추상적이고 개념적인 활동인 만큼, 버그와 오류가 발생하기 마련입니다.
ㆍ 컴퓨터는 전자 신호의 형태로 데이터를 조작합니다. 프로그래밍 언어는 사람이 컴퓨터와 더 효율적으로 상호 작용할 수 있도록 이 정보를 추상화합니다. 모든 유형의 소프트웨어에는 여러 가지 추상화 계층이 있으며, 애플리케이션이 정상적으로 작동하도록 여러 구성 요소가 서로 통신합니다.
ㆍ오류가 발생할 경우 문제를 찾아 해결하기가 어려울 수 있습니다. 디버깅 도구와 전략은 문제를 더 신속하게 해결하고 개발자의 생산성을 높이는 데 도움이 됩니다.
ㆍ결과적으로 소프트웨어 품질과 최종 사용자 경험이 모두 개선됩니다.

4. 디버깅 프로세스는 어떻게 작동하나요?

디버깅 프로세스에는 일반적으로 다음 단계가 필요합니다.

▼ 오류 식별

개발자, 테스터 및 최종 사용자가 소프트웨어를 테스트하거나 사용하는 동안 발견한 버그를 보고합니다. 개발자가 버그의 원인이 된 정확한 코드 줄 또는 코드 모듈을 찾습니다. 이 프로세스는 번거롭고 시간이 많이 걸릴 수 있습니다.

▼ 오류 분석

코더가 모든 프로그램 상태 변경 및 데이터 값을 기록하여 오류를 분석합니다. 또한 소프트웨어 기능에 미치는 영향을 기준으로 버그 수정의 우선 순위를 정합니다. 또한 소프트웨어 팀이 개발 목표와 요구 사항에 따라 버그 수정 일정을 정합니다.

▼ 수정 및 검증

개발자가 버그를 수정하고 테스트를 실행하여 소프트웨어가 계속 정상적으로 작동하는지 확인합니다. 미래에 그 버그가 재발할지 확인하기 위해 새로운 테스트를 작성할 수도 있습니다.

▼ 디버깅 vs. 테스트

디버깅과 테스트는 소프트웨어 프로그램이 제대로 실행되도록 보장하는 보완 프로세스입니다. 프로그래머는 코드의 전체 섹션 또는 일부를 작성한 후, 버그와 오류를 식별하기 위해 테스트합니다. 버그가 발견되면, 코더가 디버깅 프로세스를 시작하고 소프트웨어에서 오류를 제거할 수 있습니다.

5. 디버깅이 필요한 코딩 오류는 무엇인가요?

소프트웨어 결함은 소프트웨어 개발에 내재된 복잡성으로 인해 발생합니다. 고객이 소프트웨어를 예상치 못한 방식으로 사용하기 때문에, 소프트웨어가 가동된 후에도 사소한 프로덕션 오류가 관찰됩니다. 디버깅 프로세스를 수행해야 하는 몇 가지 일반적인 유형의 오류가 아래에 나와 있습니다.

▼ 구문 오류

구문 오류는 컴퓨터 프로그램에 잘못 입력된 명령문이 있을 때 발생하는 버그입니다. 워드 프로세싱의 오타 또는 철자 오류와 같습니다. 구문 오류가 있으면 프로그램이 컴파일되거나 실행되지 않습니다. 코드 편집 소프트웨어는 일반적으로 이 오류를 강조 표시합니다.

▼ 의미론적 오류

의미론적 오류는 프로그래밍 명령문을 잘못 사용할 때 발생합니다. 예를 들어 x/(2 π)라는 표현식을 Python으로 변환하는 경우 다음과 같이 작성할 수 있습니다.
y = x / 2 * math.pi
하지만 곱셈과 나눗셈은 Python에서 동일한 우선 순위를 가지며, 왼쪽에서 오른쪽으로 계산되기 때문에 이 문은 올바르지 않습니다. 따라서 이 표현식이 (xπ)/2로 계산되어 버그가 발생합니다.

▼ 논리 오류

논리 오류는 프로그래머가 컴퓨터 프로그램의 단계적 프로세스나 알고리즘을 잘못 입력할 때 발생합니다. 예를 들어 코드에서 너무 일찍 루프가 종료되거나 잘못된 if-then 결과가 있을 수 있습니다. 여러 가지 입력/출력 시나리오의 코드를 단계별로 살펴보면 논리 오류를 찾아낼 수 있습니다.

▼ 런타임 오류

런타임 오류는 소프트웨어 코드가 실행되는 컴퓨팅 환경으로 인해 발생합니다. 이 오류의 예로는 메모리 공간 부족 또는 스택 오버플로가 있습니다. try-catch 블록의 명령문을 둘러싸거나 적절한 메시지로 예외를 로깅하여 런타임 오류를 해결할 수 있습니다.

6. 일반적인 디버깅 전략은 어떤 것들이 있나요?

프로그래머가 오류를 최소화하고 디버깅에 필요한 시간을 줄이기 위해 사용하는 몇 가지 전략이 있습니다.

▼ 점진적 프로그램 개발

점진적 개발은 코드의 작은 부분을 자주 테스트할 수 있도록, 관리가 용이한 섹션으로 나누어 프로그램을 개발하는 방식입니다. 이렇게 함으로써, 프로그래머는 발견한 모든 버그를 현지화할 수 있습니다. 또한 코드의 큰 섹션을 작성한 후 여러 개의 오류를 해결하는 것이 아니라, 한 번에 하나의 버그만 해결할 수 있게 해줍니다.

▼ 역추적

역추적은 특히 소규모 프로그램에서 널리 사용되는 디버깅 방식입니다. 개발자는 치명적인 오류가 발생한 위치부터 역방향으로 작업하여, 코드에서 정확한 발생 지점을 찾습니다. 아쉽게도, 이 프로세스는 코드 줄의 수가 증가할수록 실행하기가 어려워집니다.

▼ 원격 디버깅

원격 디버깅은 로컬 시스템과 분리된 환경에서 실행되는 애플리케이션을 디버깅하는 것입니다. 예를 들어 원격으로 설치된 디버깅 도구를 사용하여 버그를 해결할 수 있습니다.

▼ 로깅

대부분의 컴퓨터 프로그램은 내부 데이터와 실행 시간 및 운영 체제 상태와 같은 기타 중요한 정보를 로그 파일에 기록합니다. 개발자는 이 로그 파일을 조사하여 버그를 찾고 해결합니다. 또한 로그 분석기와 같은 도구를 사용하여 로그 파일의 처리를 자동화합니다.

▼ 클라우드 디버깅

개발자가 로컬 시스템에서 클라우드 아키텍처를 에뮬레이트해야 하기 때문에, 복잡한 클라우드 애플리케이션을 디버깅하기는 어렵습니다. 시간 경과에 따라, 클라우드 환경과 에뮬레이트 환경 간에 구성 차이가 발생할 수 있습니다. 이로 인해 프로덕션에서 버그가 증가하고 개발 주기가 길어집니다. 보다 효율적인 클라우드 디버깅을 위해서는 특수한 도구가 필요합니다.

14-2. 안드로이드 앱 디버깅

1. 안드로이드에서 발생하는 버그 종류 및 해결 방법

1) Syntax Error

ㆍ코틀린 문법 오류, Android API의 Method 이름, 리소스 명, 오타 등등 미리 정의되지 않은 구문을 사용할 때 발생한다.
ㆍAndroid Studio 코드 편집 화면에서 빨간줄로 표시된다.
ㆍ대부분 컴파일 과정에서 빌드 에러가 발생한다.
ㆍBuild Output 창에서 에러 발생 위치 확인 후 수정한다.

2) Runtime Erroe

ㆍ컴파일은 정상이고 앱도 실행되지만 앱 구동중에 발생하는 에러다.
ㆍ앱 구동중 메모리 부족이나 배열의 잘못된 주소값 할당, 객체의 생성자가 실행되지도 않았는데 객체를 사용하려고 할 때, 특정 뷰(View)를 사용할 때에는 그에 맞는 id를 지정해주어야 하는데 그렇지 못 할 경우 등등.. 너무나 다양함.
ㆍ이 에러가 발생하면 폰의 앱 구동화면에 아래와 같이 표시됨.

ㆍ로그켓 화면에서 에러 위치 및 Exception 정보를 확인하고 수정한다.

ㆍjava.lang.IndexOutOfBoundsException: Index: 100, Size: 1
ㆍarray size가 1인데, 100번 index를 요청했네..

3) Logical Error

ㆍ소스 코드 컴파일도 정상적으로 되고 런타임상 에러가 발생하는 것도 아닌 개발자의 의도와는 다르게 동작하는 에러를 뜻한다.
ㆍ버튼을 클릭하면 팝업이 뜨게 만들었으나 팝업이 아닌 새로운 페이지가 뜨거나 아무 동작을 안 하거나 하는 것처럼 시스템상 프로그램이 멈추거나 하지는 않지만, 의도와는 다르게 동작하는 것을 말한다.

ㆍ코드는 정상이나 모든 지역에서 미세먼지가 '보통'으로 표시됨.

<디버깅 모드 사용하기!!>
가장 먼저 코드의 특정 포인트에서 어떤 값이 들어가 있는지, 메소드는 잘 실행되고 있는지 등을 알기 위해 내가 확인하고 싶은 코드에 break point(중단점)를 걸어야 한다.

  1. break point(중단점) 걸기

ㆍ내가 확인하고 싶은 코드 줄에, 아래와 같이 좌측 줄번호 옆을 클릭해 빨간색 포인트(break point)를 찍는다.

ㆍ메소드에 break point를 찍으면, 아래와 같이 다이아몬드로 찍히는데, 이를 사용하면 속도가 많이 느려진다고 한다. 그래서 메서드 내부에 포인트를 찍는 것을 추천

  1. 디버깅 모드로 실행하기

ㆍbreak point 걸고 벌레 모양 아이콘 선택한다.

ㆍ앱이 실행되고 break point가 걸려있는 코드가 실행 될 때 debug 탭이 자동으로 열린다.

  1. 디버깅 툴 아이콘 사용법

ㆍDebug 탭에는 디버깅을 위한 여러 아이콘 버튼들이 있다.

Resume Program

다음 중단점이 있을 때까지 앱을 실행함

Show Execution Point

클릭하면 현재 진행 중인 코드로 이동

Step Over

break point에서 코드의 다음 줄로 이동

Step into

메소드 호출 내에서 첫 번째 줄로 이동

Step Over와의 차이점은,

ㆍstep over는 줄단위의 코드들에 대해 디버깅
ㆍstep into는 메소드 단위로 이동하도록

보통은 메소드 안으로 들어가 step over로 디버깅하다가, 메서드를 디버깅하기 위해 step into 하면 된다.

Force Step Into

Settings의 "Debugger" 옵션의 "Stepping"에 들어가면 "Do not step into the classes" 항목이 있다.

Step Into의 경우 이 항목을 스킵하면서 디버깅을 실행하는데, 이와 다르게 Force Step Into는 모든 코드를 거치면서 디버깅을 하는 모드이다

Step Out

Step into와 반대로, 현재 메서드 외부에서 다음 줄로 이동

Run to Cursor

디버깅을 하는 중에 커서가 있는 곳으로 바로 이동해서 디버깅을 진행할 수 있다

Evaluate Expression

break point로 captured 된 변수를 이용해 expression 또는 함수 등을 실행할 수 있다.

4. 디버그 모드로 변수값 변경하기

아래와 같이 set value를 통해 직접 변수 값을 변경할 수 있다.

Variables 탭 내에서 원하는 변수를 선택 후, 우 클릭하면 아래와 같이 Set Value를 클릭해 직접 변경할 수 있다.

5. 디버그 모드로 로그 출력하기

Shift + 원하는 줄 선택(break point 찍듯이)하면 아래와 같은 설정 팝업이 뜬다.

Log 부분에 Evaluate and log 체크박스에 체크하고, 해당 줄이 실행될 때 출력할 로그를 작성한다.

profile
열심히 하자

0개의 댓글