한 번만 조회하는게 아닌 지속적인 가격을 불러와서 폰으로 확인하고 싶다.
binance API 호출
공식 문서 : https://www.binance.com/en/binance-api
가격, 그중에서 선물 가격을 조회 할 것이기 때문에 https://binance-docs.github.io/apidocs/futures/en/#testnet 에서 확인.
위 주소로 조회해보자.
simple-websocket-client 크롬 확장프로그램으로 웹소켓 테스트
구분 | value |
Server Location | wss://fstream.binance.com/stream?btcusdt@markPrice |
구독 | { "method": "SUBSCRIBE", "params":["btcusdt@markPrice"], "id": 1 } |
구독해지 | { "method": "UNSUBSCRIBE", "params":["btcusdt@markPrice"], "id": 1 } |
3초 혹은 매초마다 데이터를 전송한다.
simple-websocket-client 크롬 확장프로그램으로 웹소켓 테스트 결과
p가 market price 이걸 써야한다.
Android Studio websocket 통신
// BinanceWebSocketListener.kt
class BinanceWebSocketListener(
private val textView: TextView,
): WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response?) {
Log.i(TAG, "onOpen: ${response?.toString()}")
webSocket.send("""|
|{
|"method": "SUBSCRIBE",
|"params": ["btcusdt@markPrice"],
|"id": 1
|}
""".trimMargin())
}
override fun onMessage(webSocket: WebSocket?, text: String) {
Log.i(TAG,"onMessage: $text")
textView.findFragment<Fragment>()
text.marketPrice?.also { textView.text = it }
}
override fun onMessage(webSocket: WebSocket?, bytes: ByteString) {
Log.i(TAG, "onMessage: $bytes")
bytes.toString().marketPrice?.also { textView.text = it }
}
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
Log.i(TAG,"onClosing: $code $reason")
webSocket.send("""|
|{
|"method": "UNSUBSCRIBE",
|"params": ["btcusdt@markPrice"],
|"id": 1
|}
""".trimMargin())
webSocket.close(NORMAL_CLOSURE_STATUS, null)
webSocket.cancel()
}
override fun onFailure(webSocket: WebSocket?, t: Throwable, response: Response?) {
Log.i(TAG,"onFailure: " + t.message)
}
private val String.marketPrice: String?
get() = try {
JSONObject(this).getJSONObject("data")
.getString(MARKET_PRICE_NAME)
} catch (e: JSONException) {
null
}
companion object {
private const val URL = "wss://fstream.binance.com/stream?btcusdt@markPrice"
private const val TAG = "BinanceWebSocketListener"
private const val NORMAL_CLOSURE_STATUS = 1000
private const val MARKET_PRICE_NAME = "p"
val request: Request
get() = Request.Builder().url(URL).build()
}
}
WebSocketListener를 상속받고 binance와 통신하는 BinanceWebSocketListener를 만든다.
onMessage에서 textView의 text를 전달받은 가격으로 변경한다.
// FirstFragment.kt
class FirstFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null
private lateinit var client: OkHttpClient // add #1
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentFirstBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// ...
// add #2
client = OkHttpClient()
val listener = BinanceWebSocketListener(binding.textviewFirst)
client.newWebSocket(BinanceWebSocketListener.request, listener)
// ...
}
override fun onDestroyView() {
super.onDestroyView()
client.dispatcher().executorService().shutdown() // add #3
_binding = null
}
}
#2에서 view가 생성될 때 webSocket을 만들어 등록한다.
#3에서 해당 view가 삭제될 때 등록한 websocket도 종료되게 한다.
이렇게 하면
"only the original thread that created a view hierarchy can touch its views."
이런 에러가 발생하면서 text를 변경할 수 없었다.
구글링 해보니 메인 쓰레드(original thread)에서만 ui를 변경할 수 있어서 해당 에러가 발생하는 거였다.
// BinanceWebSocketListener.kt
class BinanceWebSocketListener(
private val textView: TextView,
): WebSocketListener() {
// ...
override fun onMessage(webSocket: WebSocket?, text: String) {
Log.i(TAG,"onMessage: $text")
textView.findFragment<Fragment>()
// change
text.marketPrice?.also { textView.runOnUiThread { textView.text = it }}
}
override fun onMessage(webSocket: WebSocket?, bytes: ByteString) {
Log.i(TAG, "onMessage: $bytes")
// change
bytes.toString().marketPrice?.also { textView.runOnUiThread { textView.text = it }}
}
// add
private fun TextView.runOnUiThread(action: () -> Unit) {
val fragment = try {
this.findFragment<Fragment>()
} catch (e: IllegalStateException) {
null
} ?: return
if (!fragment.isAdded) return
fragment.activity?.runOnUiThread(action)
}
}
그래서 위와 같이 파라미터로 전달받은 TextView의 Fragment > Activity를 거슬로 올라가 찾아서 runonUiThread안에서 text 변경 코드를 추가하면 정상 작동하였다.
위젯으로 만들고 싶었는데 안드로이드 잘 모르겠어서 생각보다 작업하는데 오래걸렸다.
다른 날에 마저 해야겠다.
부록) Binance RSA Key Pair 생성
필요할 줄 알고 먼저 발급받았던 rsa key pair 관련 정리
공식 문서.
1. https://github.com/binance/rsa-key-generator/releases 접속해서 os에 맞는 generator 다운로드
2. generate key pair 클릭하여 공개키, 비밀키 생성
3. binance API 생성
https://www.binance.com/en/my/settings/api-management
자체 생성 API Key 클릭.
아까 생성했던 공개키 복붙.
API Key 라벨 이름 적고 완료.
이메일, 모바일 인증을 모두 받아야 된다.
API Key가 생성되었다.
설정을 조금 보면, 일단은 가격 조회만 할 것이기 때문에 API restrictions는 수정할 필요가 없고.
IP 제한은 빨간색 글자에 의하면 제한이 없는 경우엔 권장하지 않는다. 나머지는 읽기 권한 이외의 권한을 선택한 경우기 때문에 일단 무시.
백엔드단 서버를 한 대 두고 거기서만 API로 바이낸스랑 통신을 해서 데이터를 가져오면서 백엔드 서버의 IP를 화이트리스트로 관리. 앱에서 백엔드 사이엔 별도로 인증. 을 해야겠지만 지금은 다 패스.
참고
https://stackoverflow.com/questions/16425146/runonuithread-in-fragment
'IT' 카테고리의 다른 글
Visual Studio Code에서 파일 저장시 마지막 줄 NewLine 추가 (0) | 2024.02.03 |
---|---|
2023 codejam (0) | 2023.02.26 |
[AWS] 1500 만원 청구서. 해킹.. (11) | 2022.12.09 |
[Window] CMD 드라이브 이동 (0) | 2021.04.17 |
[PyCharm] 현재 파일 프로젝트 뷰에 표시 (0) | 2021.01.02 |
댓글