오늘은 비동기 처리와 관련하여 포스팅을 하려고 한다.
우선 하기 전에 아무 API부터 불러와보자.
가장 간단해 보이는 okhttp3 를 사용하려고 한다.
앱 수준의 gradle에
//http통신
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'
을 추가해주자.
그리고 MainActivity 외에
package com.example.asyncex
import android.util.Log
import okhttp3.*
import org.json.JSONArray
import org.json.JSONObject
import java.io.IOException
import java.lang.Exception
class HttpConnector {
val TAG = "HttpConnector"
fun httpConnectGet(getUrl : String) :String{
val clsHttp = OkHttpClient()
val clsReq : Request = Request.Builder().url( getUrl ).build()
var result: String=""
Log.d(TAG, "httpConnectGet: "+"호출")
clsHttp.newCall(clsReq).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d("failGet","")
}
override fun onResponse(call: Call, response: Response) {
result = response.body()!!.string()
Log.d("RESPONSE_GET", result)
//Log.d("RESPONSE", response.toString())
}
})
Log.d(TAG, "httpConnectGet: 최종리턴" + result)
return result
}
}
위와 같은 클래스를 추가해주었다.
또한 MainActivity는 간단하게 아래와 같이 입력했다.
package com.example.asyncex
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
val httpconnector : HttpConnector = HttpConnector()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val url : String = "https://infuser.odcloud.kr/oas/docs?namespace=15044439/v1"
}
}
url은 고민하다가 공공데이터 포털을 이용했다. 그런데 저 URL도 사실 마음에 들지 않는다. 왜냐하면 인증키를 발급받았음에도 어디에 인증키를 넣어야 할지 몰랐기에, 해당 데이터 파일의 메타데이터에 관한 url을 이용할 수 밖에 없었기 때문이다.
다음에 기회가 되면 공공데이터포털을 다시 한 번 이용해 봐야겠다.
아무튼 이런식으로 하면 log를 찍어 보았을 때 잘 데이터가 들어오기는 한다.
2022-10-02 23:52:37.715 19942-19972/com.example.asyncex D/RESPONSE_GET: {"swagger":"2.0","info":{"version":"1","title":"서울교통공사_지하철개통정보","description":""},
(이하생략)
그러나 이 데이터를 파싱하고 MainActivity를 비롯한 다른 액티비티들에서 호출하려면 함수의 리턴값으로 돌려줘야 하는데, 리턴 직전(위 코드에서는 "최종리턴")의 result 값을 보면 비어있다.
2022-10-02 23:52:37.630 19942-19942/com.example.asyncex D/HttpConnector: httpConnectGet: 최종리턴
알고 보니 데이터를 가져오는 함수가 이미 비동기 처리가 되어있기 때문에, 함수가 리턴되는 시점보다 데이터를 가져오는 시간이 더 오래 걸려서 최종 리턴에 빈 값이 들어오는 것이었다. 물론 찾아보니 비동기처리를 하지 않는 방법도 있었다.
var result2 : Response = clsHttp.newCall(clsReq).execute()
try{
var res2 = result2.body()!!.string()
Log.d(TAG, "httpConnectGet: 동기처리 - "+res2)
}catch (e : Exception){
Log.d(TAG, "httpConnectGet: 동기처리 오류")
}
이런식으로 했는데 예전에는 됐었는데 이제는 오류가 뜬다.. 별도의 쓰레드에서 실행하라는 오류인 것 같다.
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.asyncex/com.example.asyncex.MainActivity}: android.os.NetworkOnMainThreadException
아무튼! 이 글의 위 동기처리는 본 글의 목적과도 맞지 않으므로, 계속 비동기 처리를 구현해 보겠다. 물론 데이터를 가져오는 부분(newCall하고 enqueue)은 이미 비동기 처리가 되어 있지만, 비동기 처리가 되어 있다 보니 별도의 리턴값을 리턴시켜주기 위해서는 콜백객체를 만드는 것이 필요했다.
서론이 약간 길었는데, 여기까지가 서론이었고
이제부터가 본론이 되겠다.
- 인터페이스 생성
데이터를 가져오는 HttpConnector.kt 안에 아래와 같이 추가해주자. 물론 클래스 영역 밖에 선언해 줘야 한다.
interface HttpListener {
fun loadData(data : String) : String
}
- 인터페이스 상속 및 메소드 오버라이드
MainActivity에 콜백함수를 구현해보자. 우선 방금 만든 인터페이스를 상속하고
class MainActivity : AppCompatActivity(), HttpListener {
(이하생략)
콜백함수를 오버라이드(구현) 해주자.
//http 콜백
override fun loadData(data : String) : String{ // override 함
var res = ""
Log.d("Main", "loadData: 여기는 콜백함수"+data)
return res
}
물론 둘 다 MainActivity이다!
- 콜백 리스너 등록
다시 HttpConnector.kt로 돌아와서, 콜백 리스너를 등록해 주어야 한다.
HttpConnector 클래스 내부에
var callBack : HttpListener? = null
fun setHttpListener(listener: HttpListener) {
this.callBack = listener
}
와 같이 추가해주자. 리스너의 역할에 대해서는 추후 공부가 더 필요하다.
참고로 callBack 변수 선언 시 ?를 붙이는 이유는, 우선 null로 초기화를 해주었기 때문에 HttpListener형 변수이지만 null이 들어갈 수 있다는 뜻에서 ?를 붙여주는 것이다.
왜 굳이 null로 초기화를 해주었냐고 하면 다른 방법을 찾을 수 없어서다. 클래스 밖에서 바로 callBack변수에 listener를 등록을 해주는 방법을 못찾았다. 또한 set합수 안에서 callBack을 선언해주면 지역변수가 되기에 set함수 밖에서는 사용할 수 없다. 어쩔수 없이 전역변수로 만들어 null로 초기화를 해준 다음, set함수를 만들어 listener을 달아주는 방법을 취할 수 밖에 없었다. (이 set함수는 MainActivity에서 호출 할 예정)
(참고)
var notNull:Int = null //오류
var notNull:Int? = null //정상
- 콜백 함수 호출
이제 콜백 함수가 호출됐으면 하는 지점, 즉 데이터를 가져오고 난 후에 콜백함수를 호출해주자.
callBack!!.loadVer(name)
!!를 붙이는 이유는 코틀린 문법 중 하나인 널처리와 관련된 것이다. !!를 붙이지 않으면
Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type HttpListener?
와 같은 오류가 뜨는데, 이는 쉽게 말하면
'callBack이 null이면 어떡할건데?'
라는 뜻이다. 우리가 위에서 callBack을 일단 null로 설정해 주었기 때문이다.
그래서 !!를 붙이면 이거는 null이 아니니깐 걱정하지 마! 라는 뜻이다.
사실 !!를 빼서 등장하는 오류에 추천 교정을 보면 ?도 있고 !!도 있는데, ?가 좀 더 안전한 방법인 듯 하나 여기서는 크게 차이가 없을 것 같아 !!로 하였다.
- 리스너 등록 및 데이터 호출
마지막이다. 이제 MainActivity에서 httpConnectGet함수를 호출해주자. 그전에, 방금 만든 set함수를 호출해 줘야 하므로,
httpconnector.setHttpListener(this)
httpconnector.httpConnectGet(url)
이렇게 추가해주면 끝난다.
이를 최종적으로 실행하면
2022-10-03 01:19:47.268 15818-15849/com.example.asyncex D/Main: loadData: 여기는 콜백함수{"swagger":"2.0","info":{"version":"1","title":"서울교통공사_지하철개통정보","description":""},
(이하생략)
과 같이 로그가 찍히는 것을 볼 수 있다.
이렇게 하면 HttpConnector.kt를 만들어 놓고 MainActivity나 다른 Activity에서도 쉽게 부를 수 있다.
하나 궁금한 것은, 저 콜백함수는 리턴을 못하나?이다. 아무래도 일반적인 함수의 호출보다는 반대의 개념이다 보니(사실 아직 잘 모르겠지만,,),
override fun loadData(data : String) : String{
와 같이 리턴형을 선언해 주긴 했지만, 저 리턴값을 어디서 호출할 수 있는지,
애초에 저 콜백함수의 리턴값은 어디서도 받을 수 없는 건지 등의 추가 공부가 필요할 듯 하다.