GDSC HUFS 3기/Android with Kotlin Team 2

[2팀] 34 Android AsyncTask의 구조와 제작 사항

제주도감귤쥬스 2021. 12. 6. 20:15

이 글은 이것이 안드로이드다 with 코틀린(개정판)를 참고하여 작성하였습니다.

작성자 : 강소영

개발환경은 Windows, Android Studio입니다.

 

 

Intro. AsyncTask?

"비동기 작업"이라는 뜻. 메소드를 실행시킴과 동시에 반환값이 기대되는 동기(sync) 작업의 반댓말이다. 

Background 작업과 UI 변경 작업을 간편하게 관리하고 구현하기 위해 사용한다.

 

동기적으로 테스크를 실행한 후 다른 테스크를 실행하려면 먼저 실행된 테스크가 종료되기를 기다려야 한다.

그러나 비동기적으로 테스크를 실행하면 먼저 실행된 테스크가 종료되기 전에 다른 테스크를 실행할 수 있다.

 

AsyncTask는 메인스레드에서 생성된 후 실행되고, 메인 스레드에서 처리시간이 오래 걸리는 작업을 백그라운드 스레드에 넘겨서 메인스레드 작업을 계속할 수 있게 해준다.

 

핵심은 AsyncTask가 백그라운드 스레드라는 별도의 스레드에서 작업을 수행하는 것으로, 메인 스레드에서 오래 걸리는 작업을 AsyncTask에 실행시켜 놓고 메인 스레드는 다음 작업을 바로 할 수 있기 때문에 프로그래밍 효율이 올라간다.

 

공공도서관의 위치를 출력하는 앱 만들기

서울시에서 제공하는 열린데이터 광장의 오픈API를 사용해서 지도에 공공도서관의 위치를 출력하는 앱을 만들어보는것을 통해 AsyncTask를 사용해보자.

 

1. 서울시 열린데이터 광장에서 인증키 신청

서울시 열린데이터 광장 검색->회원가입 후 로그인->서울 열린데이터 광장 사이트에서 도서관 위치 정보 검색->서울특별시 공공도서관 현황정보 클릭->화면 내려서 "미리보기" 탭에 Open API 클릭->"인증키 신청" 클릭.

 

참고용 사진.

 

필수 약관에 동의하고 필수 입력항목을 채우면 인증키 신청이 가능하다.

 

2. 인증키 복사 & 샘플 URL 변경 후 데이터 열람

신청 후 나의 화면->인증키 관리에 들어가면 신청한 인증키가 나온다. 인증키 복사를 눌러서 메모장이나 기타 저장공간에 붙여넣기 해둔다.

 

이후 다시 서울시 공공도서관 Open API로 돌아가면 샘플 URL을 복사하여 주소창에 입력한다.

주소 형식이 http://.../sample/xml/.../ 형식으로 되어있을텐데, sample을 복사해둔 인증키로 바꾸어 입력하고, xml을 json으로 바꾸어 입력해서 검색한다.

 

sample/xml

 

주소의 일부분을 인증키와 json으로 바꾸어서 입력.

 

검색했을 때 이와 같은 데이터들이 나오면 제대로 한 것.

 

주소의 맨 뒤 슬래시로 나누어져있는 두 숫자는 요청시작위치와 요청종료위치를 의미하는 것으로, 1/1000 이런식으로 바꾸어서 검색하면 더 많은 데이터를 불러올 수 있다. 

 

주소의 각 부분은 전부 의미를 담고 있다. 열린데이터 광장에서 어떤 부분이 무엇을 의미하는지 알 수 있다.

 

이번 실습에서는 데이터를 많이 사용하지 않고 1/5까지만 불러와서 사용하도록 한다. 데이터를 드래그해 복사해둔다. 하나라도 빠뜨리면 꼬일 수 있으니 주의하도록 하자.

 

3. 코드 구성하기-데이터 패키지 생성

java->생성해둔 packcage->(우클릭)New->Package 클릭하여 .data를 추가해 생성한다.

data 패키지 생성.

 

File->Settings->Plugins->json to Kotlin Class 검색해서 설치 후 재시작한다.

data 패키지 우클릭->New->Kotlin data class File from JSON 클릭해 복사해둔 데이터를 붙여넣기해준다. 붙여넣기를 하면 한 줄로 나오는데, Format을 클릭하면 여러 줄로 나온다.

 

Format 클릭 전.

 

Format 클릭 후.

Class Name을 지정해주고 Generate를 클릭해서 데이터 디렉토리 안에 클래스를 생성해준다. 예시로는 이름을 Library로 지정했다.

 

이 Library 클래스는 하나로 구성된 것이 아니라 여러개가 단개별로 구성되어있다. 위쪽에 첨부되어있는 Format 클릭을 한 코드를 보면 중괄호마다 클래스가 생성되어있는것을 알 수 있다. Library는 코드 전체를 감싸고 있는 코드, SeoulPubliclibraryInfo, Row, RESULT 각각은 특정 정보를 감싸고 있는 코드이므로 따로 클래스가 생성된다.

(즉, 라이브러리 클래스는 서울퍼블릭라이브러리인포, 로우, 리절트로 구성된 클래스라는것.) 

 

여러개의 클래스로 구성되어있는 Library 클래스.

*주의할 점*

코드 속 클래스 이름이 row와 같이 소문자로 시작하더라도 겉으로 표시될때는 Row처럼 첫 글자가 대문자로 시작되는 경우가 있다. 이런 경우 변수명 때문에 오류가 발생할 때가 있으므로 상황마다 적절하게 변경해주어야 한다.

 

SeoulOpenApi라는 이름의 새로운 클래스를 생성하고 앞으로 사용할 두 개의 값인 API주소와 API key(아까 복사해둔 인증키)를 변수로 선언한다.

class SeoulOpenApi {
	companion object {
    	val DOMAIN = "Http://openapi.seoul.go.kr:8088/"
        val API_KEY = "..."
    }
}

 

Retrofit을 사용하기 위해 

Gradle Scripts->build.grale을 통해 gradle 파일을 연 후 람다 문법을 사용한다.(Java8 버전을 사용하는 것이므로 세팅을 해주어야 한다. 코드 작성을 통해 세팅하면 된다.)

 

compileOptions {
	sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
    }

 

*Retrofit : (기계 속에 원래 없던 부품 등을) 새로 장착하다 / 넣다 / 제공하다

안드로이드에서의 Retrofit : 서버와의 HTTP 통신을 통해 전달된 데이터를 앱에서 특정 형태로 받아볼 수 있게 하는 라이브러리. Retrofit 관련 코드는 오픈소스로 제공되므로 구글링하여 필요한 코드 복사해서 사용 가능.

 

이후 dependecies 안에 다음과 같은 코드를 작성해준다.

implementation 'com.squareup.retrofit2:retrofit:2.9.0' //맨 뒤의 숫자는 버전을 나타낸다. 버전은 최신 버전이나 예전 버전이나 상관 없다. 또한 이 코드는 구글링으로 얻을 수 있다.

 

웹 페이지에서 가져오는 데이터를 코틀린 클래스로 변환해주어야하기 때문에 컨버터를 사용하도록 한다. 바로 위에 첨부된 코드의 살짝 변경된 코드를 작성해주면 된다.

implementation 'com.squareup.retrofit2:converter-gson:2.9.0' //위 코드와 버전을 꼭 똑같이 맞춰주어야 한다.

 

코드 작성을 완료하면 Sync Now를 클릭해준다.

 

맨 오른쪽에 있는 Sync Now 클릭.

 

Sync가 완료되었으면 SeoulOpenApi클래스로 이동해 인터페이스를 선언하여 서비스를 정의해준다.

interface SeoulOpenService {
	@GET( value: "{api_key}/json/SeoulPublicLibraryInfo/1/{limit}") //limit 대신 1000, 5와 같이 숫자를 적어서 처음부터 정보를 제한해도 됨. 그러나 limit를 사용하면 좀 더 유동적인 프로그램으로 만들어줌.
	fun getLibraries(@Path( value: "api_key") key:String, @Path( value: "limit") limit:Int) : Call <Library>
    
    /*API KEY 넘기기 & limit를 통해 데이터를 가져오고싶은만큼만 가져올 수 있도록 하기. 
    limit는 예시로 이름을 지은 것으로 end와 같은 다른 이름으로 써도 된다. 
    다만 어떤 의도를 가진 것인지 알아보기 쉬운 이름을 짓는 것이 좋다.
    +)Call을 선언할 때 Call(retrofit2)를 클릭해주면 된다.*/

 

*인터넷에서 데이터를 가져오는 것이기 때문에 manifests에 인터넷 퍼미션을 꼭 추가해주어야 한다.

아래와 같은 코드를 작성해주면 된다.

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

 

4. 메인 엑티비티에서 코드 작성

override fun onMapReady(googleMap: GoogleMap) { //구글맵 불러오기
	mMap = googleMap
 	
    loadLibraries() //지도에 도서관이 표시되도록 함.

fun loadLibraries() { //loadLibraries를 통해 데이트를 API에서 가져온다.
	val retrofit = Retrofit.Builder()//retroFit 정의
    	.baseUrl(SeoulOpenApi.DOMAIN) //근간(base)이 되는 Url 불러오기
   		.addConverterFactory(GsonConverterFactory.create())
    	.build()
  	
    val service = retrofit.create(SeoulOpenService::class.java)
    
    service.getLibraries(SeoulOpenApi.API_KEY, limit: 200)//limit: 뒤의 숫자는 데이터를 몇 개 가져올 것인가를 정하는 것이다. 코드를 구성하는 사람 마음대로 가져오면 된다.
    	.enqueue( callback: object : CallBack<Library>) {//인터페이스가 Callback이라는 형태로 만들어져있고 이를 사용하는 것이다. (service를 비동기(Async)로 실행시킬 것이기 때문에
        	override fun onFailure(call: Call<Library>, t: Throwable) { //Ctrl + i를 통해 불러온 함수.
            	Toast.makeText( context: this@MainActivity, "데이터를 가져올 수 없습니다.", Toast.LENGTH_LONG).show() //데이터를 불러오는 것을 실패했을 때 이를 알려주도록 하는 코드.
            }
            
            override fun onResponse(call: Call<Library>, response: Response<Library>) { //데이터를 가져오는 것을 성공하면 response에 응답이 넘어온다.
            	val result = response.body()
            	showLibraries(result)                 
                
fun showLibraries(result:Library?) { //body에 null이 들어오는 경우가 있으므로 체크하는 코드 작성. (Library 클릭->SeoulLibraryInfo로 들어가보면 row변수를 반복문으로 돌려야한다는 것을 알 수 있다.

	result?.let { it: Library//result가 let이 아닐때는 it로 받는다.
    	val latlngBounds = LatLngBounds.Builder() //
    	for(library in it.SeoulPublicLibraryInfo.row) { //row에서 라이브러리가 하나씩 꺼내지도록 함.
            val position = LatLng(library.XCNTS.toDouble(), library.YDNTS.toDouble
            val marker = MarkerOptions().position(position).title(library.LBRRY_NAME)//라이브러리 하나마다 마커 생성. 클릭했을 때 도서관 이름이 보일 수 있도록 코드 작성.
            mMap.addMarker(marker)
            
            latlngBounds.include(position) //위쪽에서 for문 전 작성한 LetLngBounds를 통해 position값을 넣어주면 마커들이 보일 수 있는 만큼 카메라를 위치시켜준다. 
        }
        
        val bounds = latlunBounds.build //전체 bound 계산
        val padding = 0//마커로 지정한 곳의 좌표가 짤리는 경우가 발생할 수 있으므로 padding을 통해 여윳값 주기
        
        val camera = CameraUpdateFactory.newLetLngBounds(bounds, padding) //카메라 범주 설정
        mMap.moveCamera(camera) //해당 위치로 카메라 이동.
        	
}
        
}

 

*안드로이드는 기본적으로 http 또는 https를 사용할 수 없다. 따라서 다음과 같은 코드를 menifests에 추가해 에러가 나지 않도록 한다.

android:usesCleartextTraffic="true"//application 태그 안에 적어주면 된다.

 

Emulator를 실행했을 때 아래와 같이 나오면 성공.

 

서울시 내 도서관 표시.

 

클릭하면 도서관 이름이 표시됨.