안드로이드 연구소

[코딩테스트 제출 시 필독] 안드로이드 린트 검사(Android Lint) 본문

안드로이드 연구소

[코딩테스트 제출 시 필독] 안드로이드 린트 검사(Android Lint)

안드로이드 연구원 2023. 5. 4. 16:01

저번 메모리 누수 검사하는 법을 마치고

오늘부터는 또 여러분의 코드에 어떤 오류가 있는지 발견할 수 있는지 확인할 수 있는 방법을 소개시켜드리겠습니다.

그 바로 첫번째는 린트 검사입니다.

 

위키백과에서 Lint라는 단어를 검색하면 아래와 같이 문구가 나옵니다.

린트(lint) 또는 린터(linter)는 소스 코드를 분석하여 프로그램 오류, 버그, 스타일 오류, 의심스러운 구조체에 표시(flag)를 달아놓기 위한 도구들을 가리킨다.이 용어는 C 언어 소스 코드를 검사하는 유닉스 유틸리티에서 기원한다.

C언어를 사용할 때 오류를 발견을 도와준 도구가 현재 기원이 된거 같습니다.

그렇다면 안드로이드에서 린트검사는 어떤 도구일까요?

 

Q1. ChatGPT, 안드로이드 린트검사에 대해 설명해줘.

Android 애플리케이션에서 잠재적인 버그 및 기타 문제를 찾는 데 사용되는 정적 분석 도구입니다.
(static분석: 실행전 오류가 될 부분을 예측하는 검사 / Dynamic분석: 실행중 발견되는 검사)

메모리 누수, 성능 문제, 보안 취약성 및 기타 "잠재적 버그"와 같은 일반적인 문제를 확인합니다.
또 코드를 분석하여 충돌, 잘못된 동작 또는 보안 취약점으로 이어질 수 있는 문제를 감지합니다.

Android Lint가 문제를 감지하면 문제를 식별하고 해결 방법에 대한 정보를 제공하는 "보고서"를 생성하여
문제에 대한 설명, 코드에서 문제의 위치 및 해결 방법에 대한 제안이 포함됩니다.

개발자는 자신의 프로젝트와 관련된 문제를 감지하기 위해 자체 "사용자 지정 규칙"을 만들 수도 있습니다.

오늘 핵심 키워드에 대한 세가지에 대한 질문이 나왔습니다.

-어떤 잠재적 버그 종류들을 검사해주는지 => 어떤 검사들을 식별하는지

-보고서를 생성하고 어떻게 볼 수 있는지 => 린트 검사 실행 방법

-사용자 규칙은 어떻게 작성할 수 있는건지 

 

Q2. ChatGPT, 어떤 잠재적 버그의 종류를 검사 해주니?

1. 미사용 리소스
: Android Lint는 이미지, 레이아웃 및 문자열과 같이 애플리케이션에서 사용되지 않는 리소스를 감지할 수 있습니다.

2. 성능 문제
: Android Lint는 비효율적인 레이아웃, 그리기 작업 및 데이터베이스 쿼리 사용과 같은 잠재적인 성능 문제를 감지할 수 있습니다.

3. 보안 취약성
: Android Lint는 안전하지 않은 데이터 저장소를 사용하거나 입력 데이터를 적절하게 삭제하지 않는 것과 같은 잠재적인 보안 취약성을 감지할 수 있습니다.

4. 국제화(Internationalization) 문제
: Android Lint는 번역 문제를 일으킬 수 있는 리소스 대신 하드 코딩된 문자열을 사용하는 것과 같은 국제화 문제를 감지할 수 있습니다.

5. API 오용
: Android Lint는 더 이상 사용되지 않는 API를 사용하거나 API를 잘못 사용하는 등 Android API 사용과 관련된 문제를 감지할 수 있습니다.

6. 올바른 리소스 구성
: Android Lint는 잘못된 드로어블 해상도 또는 잘못된 유형의 리소스 사용과 같은 리소스 구성 문제를 감지할 수 있습니다.

7. 접근성 문제: Android Lint는 이미지에 대한 콘텐츠 설명을 제공하지 않거나 웹뷰에 대한 대체 텍스트를 제공하지 않는 것과 같은 잠재적인 접근성 문제를 감지할 수 있습니다.

 

2번은 성능문제에 대한 예시를 보면

아래 코드처럼 GridLayout 위젯에 많은 수의 뷰를 생성하여 애플리케이션의 성능을 저하시킬 수 있습니다.

그래서 그 밑의 코드처럼 GridLayoutManager를 사용한 recyclerview를 추천해주고 있습니다.

<GridLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:columnCount="3"
    android:rowCount="3">

    <ImageView
        android:id="@+id/image1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_columnWeight="1"
        android:layout_rowWeight="1"
        android:scaleType="centerCrop"
        android:src="@drawable/image1" />

    <ImageView
        android:id="@+id/image2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_columnWeight="1"
        android:layout_rowWeight="1"
        android:scaleType="centerCrop"
        android:src="@drawable/image2" />

    <!-- ...more images... -->

</GridLayout>
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
    app:spanCount="3" />

 

3번은 보안 취약성의 예시는

스토어나 구글이나 네이버같은 곳에 접근하는 키값을 아래처럼 하드코딩하게 되면

누군가 파일에 접근만 가능하다면 보안상 아주 위험합니다. 

그래서 안드로이드 린트는 두번째 코드처럼 KeyStrore에서 불러오는 방식을 제안합니다.

private val SECRET_KEY = "my_secret_key"
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val key = keyStore.getKey("my_key_alias", null) as SecretKey

 

4번은 국제화는 아래 첫번째 코드처럼 text를 하드코딩하면 디바이스 언어를 변경하면 변역에 문제가 생기니

두번째 코드처럼 @string으로 관리해줘야하는걸 얘기하네요.

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello" />
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello_message" />

 

이어서 7번 예시 api오용. 단어가 어색할 수 있는 간단하게 얘기하면 요즘은 사용하지 않는 코드들을 얘기합니다.

저는 최근까지도 아래 startActivityForResult를 정말 많이 써주었는데, 이는 최신 코드에서는 사용하지 않는 코드라고 합니다.

안드로이드 린트는 registerForActivityResult를 사용해서 이전 보다 결과값을 한눈에 볼 수 있고 깔끔하게 사용하길 추천합니다. 

val intent = Intent(this, MyActivity::class.java)
startActivityForResult(intent, REQUEST_CODE)
val intent = Intent(this, MyActivity::class.java)
val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        // handle the result here
    }
}
resultLauncher.launch(intent)

또 다른 api오용 사례들을 더 보여드리겠습니다.

아래 토스트메시지는 정말 많이 사용하는 코드인데 요즘은 makeText를 사용하지 않고 있다고 합니다.

안전하면서 메모리 효율을 향상시킬 수 있는 아래의 방식을 안드로이드 린트는 추천해줍니다.

Toast.makeText(this, "Hello, world!", Toast.LENGTH_SHORT).show()
val toast = Toast(this)
toast.duration = Toast.LENGTH_SHORT
toast.setText("Hello, world!")
toast.show()

또 handler.postDelayed 대신 coroutine을 사용하기 권장하고 있습니다.

val handler = Handler()
handler.postDelayed({ doSomething() }, 1000)
// Using CoroutineScope with delay
private val scope = CoroutineScope(Dispatchers.Main)
override fun onResume() {
    super.onResume()
    scope.launch {
        delay(1000)
        doSomething()
    }
}
override fun onPause() {
    super.onPause()
    scope.cancel()
}

마지막으로 더 안전하게 파일에 접근하기 위해서 getExternalFilesDir()대신 getExternalCacheDir() 권장하고 있습니다.

val file = File(getExternalFilesDir(null), "my_file.txt")
val file = File(getExternalCacheDir(), "my_file.txt")

 

Last, 접근성 문제의 예시들입니다.

아마 저 포함 많은 안드로이드 개발자분들이 놓치고 있는 부분이였을껀데

여러분들은 안드로이드 imageView에 대한 설명을 해주고 계셨는가요?

안드로이드에서는 시각 장애인들을 위해 imageview에 설명을 기입하길 권장하고 있습니다.

아래처럼 contentDescription에 설명을 달아놓으면 됩니다.

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/my_image"
    android:contentDescription="@string/image_description" />

Webview에서도 마찬가지로 3가지 기능이 필요한데

importantForAccessibility=yes 로 설정해 이 요소가 접근성에 중요함을 시스템에 알립니다.

accessibilityTraversalBefore을 설정하여 다음 포커스 순서를 설정합니다.

accessibilityDescription 마찬가지로 웹뷰에 대한 줄글 설명이 필요합니다.

<WebView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/my_webview"
    android:layout_gravity="center"
    android:visibility="visible"
    android:importantForAccessibility="yes"
    android:accessibilityTraversalBefore="@+id/my_button"
    android:accessibilityDescription="@string/webview_description" />

이런 문제가 있는 코드들을 다양하게 관리해준다니 안드로이드 린트 검사는 너무 편하고 좋은 툴이 아닐 수 가 없습니다. 

그렇다면 이제 어떻게 사용할 수 있는건지 확인하러 가봅시다.

 

 

Q3. 그러면 안드로이드 스튜디오에서 어떻게하면 린트 검사를 할 수 있니?

린트 검사 실행 방법은 두가지가 있습니다.

https://developer.android.com/studio/write/lint?hl=ko

 

첫번째 방법: 명령어로 실행

-(window) gradlew lint

-(Mac or Linux에서) Terminer > ./gradlew lint

(* zsh: permission denied: ./gradlew오류가 나오면 터미널에 $chmod +x gradlew 입력 후 다시 ./gradlew lint)

 

제 프로젝트 경우 6분정도로 시간이 꽤 걸렸습니다.

완료가 되면 아래 처럼 url주소가 나오게 되는데 웹브라우저에 한번 띄워보겠습니다.

Wrote HTML report to file:///Users/~~~/~~~/app/build/reports/lint-results.html
Wrote XML report to file:///Users/~~~/~~~/app/build/reports/lint-results.xml

참고로 이 url이 아까 chatGPT가 언급한 보고서 입니다.

이렇게 깔끔하고 어떤 오류들이 있는지 나옵니다. 

저는 4개의 에러와 2423개의 경고가 있네요;;;

제 오류를 예시로 확인해보시면 

첫번째 오류로 제가 onRequsetPermissionResult를 사용하면서

super.onRequestPermissionsResult(requestCode, permissions, grantResults)를 빼먹었네요.

바로 추가! 

두번째 오류로는 "너 왜 영어 번역 파일에 한글 썼니?"라고 하네요.

이런 것까지 친절하게 알려줘서 놀랬습니다.

세번째 오류, "너 ConstraintsLayout쓰면서 하위뷰 위치 지정을 안했니?".

이건 심각한 오류였네요.

경고도 하나만 확인해볼까요?

"Scrollview에서는 높이를 match_parent말고 wrap_content로 쓰셔야합니다."

 

 

두번째 방법: Analyze > Inspect Code 

그러면 오른쪽 하단 빌드 돌아가듯이 로딩프로그레스바가 생깁니다.

제 프로젝트 경우 대략 6~7분정도 소요됩니다.

오류들이 종류별로 나오게됩니다.

 

Q4. 어떻게 린트 검사 결과에 안나오게 예외처리할 수 있니?

만약에 린트검사가 판단한 해당 오류나 경고는

개발자의 설계한 구현일 경우 해당 오류를 어떻게 무시할 수 있을까요?

사실 그냥 매번 검사할 때마다 오류가 나와도 무시하는것도 방법이지만 다소 찝찝할 수 도 있습니다.

 

린트검사를 한번 실행하게되면 아래 코드에 빨간줄이 나오게 되는 

빨간줄에 마우스를 올려두면 Suppress: Add tool:ignore가 나오게됩니다.

누르면 자동으로 해당 코드가 추가됩니다.

그러면 다음 린트 검사 때는 해당 부분은 제외하고 실행됩니다.

 

 

 

린트 검사의 모든 것을 배워보았습니다. 

오늘 집가셔서 여러분의 안드로이드 프로젝트에 린트 검사를 해보는 것이 어떨까요?

오늘 린트검사 연구는 여기서 마치겠습니다. 감사합니다.

Comments