IT 프로그래밍-Android

[Android] 서버에서 데이터 불러오기

godsangin 2019. 12. 23. 17:54
반응형

안녕하세요. godsangin입니다. 오랜만에 글을 올리게 되었는데요.

최근에 GCP(Google Cloud Platform)을 사용해 보는 관계로 이런 저런 시행착오가 있어서 글을 못썼습니다...ㅠㅠ

오늘은 서버에서 데이터를 불러오기 예제를 진행해보겠습니다.

여러분은 이미지를 포함하는 데이터베이스를 어떻게 설계하시나요 ?? 저는 테이블 안에 url속성을 주고 해당 url에 이미지를 저장한 url을 저장해주는 방식으로 설계하였습니다.(현업에서는 어떻게 하시는지 정말 궁금..)

우선 아래는 제가 만든 Skin이라는 클래스(테이블)입니다.

class Skin(){
    var sid = 0
    var pid = 0
    var title: String? = null
    var content: String? = null
    var type = 0
    var cost = 0
    var download = 0
    var star = 0
    var url: String? = null
}

실제로는 Pacelable을 상속하는 조금 복잡하게 생긴 클래스입니다.(intent로 다른 엑티비티에 넘겨주어야 했기에..)

자 ! 아무튼 객체가 생성되었고 같은 정보로 정의된 테이블이 있다고 가정해봅시다.

그렇다면 Skin이라는 데이터 테이블(select * from Skin)을 전부 조회해서 불러와봅시다.

fun getSkinsTask(){
        AsyncTask.execute(object: Runnable {
            override fun run() {
                var inputStream:InputStream? = null
                try{
                    val myGCPEndpoint = URL("http://YOUR_SERVER_ENDPOINT/YOUR_API_CALL?PARAMS")
                    val myConnection = myGCPEndpoint.openConnection() as HttpURLConnection
                    myConnection.setRequestProperty("User-Agent", "my-rest-app-v0.1")
                    myConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8")
                    myConnection.requestMethod = "GET"
                    myConnection.readTimeout = 10000
                    myConnection.doInput = true

                    myConnection.connect()

                    val response = myConnection.responseCode
                    Log.d("response==", response.toString())

                    if(response == 200){
                        inputStream = myConnection.inputStream
                        runBlocking{
                            var job = launch {
                                skins = readJsonStream(inputStream)
                            }
                            job.join()
                            if(skins != null){
                                connectionListener.responseEnd()
                            }
                        }
                    }
                }catch(e: Exception){
                    e.printStackTrace()
                    Log.e("response==", "Error:" + e.message)
                }finally {
                    if(inputStream != null){
                        inputStream.close()
                    }
                }
            }
        })
    }

 

서버와 통신하기 위해서는 메인 스레드와는 무관한 작업으로 실행해야 하기 때문에 AsynTask객체를 사용해줍니다.

connection객체에 필요한 속성을 써 주고(request method, timeout, doinput 등) connect메소드를 수행시킵니다.

그리고 connection으로부터 돌려받을 값인 inputStream을 파싱하는 작업이 필요합니다. 이 예제에서는 코루틴을 사용하여 동기적으로 변수를 가져오는 방법을 사용하였습니다.(저도 코루틴에 대해 깊은 이해가 없기 때문에 아래 블로그를 참조하였습니다.)

 

(수정)Background Task자체도 동기적으로 진행되는 코드이기 때문에 코루틴문은 사용하지 않아도 무방할 것 같습니다..!(그래도 알아두면 좋잖아요..ㅜㅜ)

 

코루틴 사용법 맛보기 - https://wooooooak.github.io/kotlin/2019/03/17/kotlin_coroutin/

 

코틀린 코루틴 사용법 맛보기 · 쾌락코딩

코틀린 코루틴 사용법 맛보기 17 Mar 2019 | kotlin coroutine 코루틴이 완전히 처음이라면 코틀린 코루틴 개념익히기 를 읽고 돌아오자! 클라이언트 앱을 만들기 위해서 비동기 처리는 필수적이다. 따라서 클라이언트를 개발할 수 있는 언어, 라이브러리, 프레임워크에는 비동기 처리를 쉽게 도와주는 도구들이 존재한다. 물론 RxKotlin, coroutine 같은 도구들을 사용하지 않고도 처리할 순 있겠지만 상당히 번거로운 작업이 될 것이며,

wooooooak.github.io

변수를 동기적으로 가져와야 하는 이유는 서버로부터 데이터를 불러온 뒤에 다음 블럭의 코드가 실행되어야 하기 때문입니다.(빈 참조변수를 그대로 view에 바인딩하면 빈 값이 바인드 되겠지요..)

데이터를 다 불러왔다면(데이터가 null이 아닐경우) 커스텀한 connectionListener의 respondEnd함수를 호출합니다. 여기서 respondEnd함수는 불러온 데이터를 통하여 view에 바인드해줍니다.(이미지 + 객체)

 

오늘 살펴볼 부분은 readJsonStream입니다. 

fun readJsonStream(inputStream:InputStream):List<Skin>{
        val reader = JsonReader(InputStreamReader(inputStream, "UTF-8"))
        try{
            return readMessagesArray(reader)
        }finally {
            reader.close()
        }
}

fun readMessagesArray(reader:JsonReader):List<Skin>{
        val messages = ArrayList<Skin>()
        reader.beginArray()
        while(reader.hasNext()){
            messages.add(Skin(reader))
        }
        reader.endArray()
        return messages
}

위와 같이 불러온 inputStream을 JsonReader로 생성하고 JsonReader를 통하여 JSON으로 표현된 SkinList데이터를 List로 파싱하여 반환하는 것입니다.

이를 위하여 앞서 만들었던 Skin클래스를 아래와 같이 바꿔줍니다.

import android.util.JsonReader
import android.util.Log

class Skin(){
    var sid = 0
    var pid = 0
    var title: String? = null
    var content: String? = null
    var type = 0
    var cost = 0
    var download = 0
    var star = 0
    var url: String? = null

    constructor(reader: JsonReader):this(){
        reader.beginObject()
        while(reader.hasNext()){
            var name = reader.nextName()
            if(name.equals("sid")){
                sid = reader.nextInt()
            }else if(name.equals("pid")){
                pid = reader.nextInt()
            }else if(name.equals("title")){
                title = reader.nextString()
            }else if(name.equals("content")){
                content = reader.nextString()
            }else if(name.equals("type")){
                type = reader.nextInt()
            }else if(name.equals("cost")){
                cost = reader.nextInt()
            }else if(name.equals("download")){
                download = reader.nextInt()
            }else if(name.equals("star")){
                star = reader.nextInt()
            }else if(name.equals("url")){
                url = reader.nextString()
            }else{
                reader.skipValue()
            }
        }
        reader.endObject()
    }
}

JSON객체를 하나씩 읽어서 각각의 속성에 저장해줍니다. 변수 개수가 안맞거나 필드명이 다르면 계속해서 오류가 납니다.ㅠㅠㅠ

이렇게 해서 데이터를 전부 불러온 다음에 listener를 통하여 다른 Thread를 생성하여 View작업을 진행합니다.(background작업에서는 view를 바꾸는 작업을 할 수 없습니다.)

저는 이렇게 불러온 Skin이라는 데이터를 통하여 받은 이미지 url을 통하여 다시 한번 이미지를 불러오는 과정을 거쳐 이미지를 표현할 수 있었습니다.

 

아래는 전체 코드입니다. 아마 대부분이 본인 환경에 맞춰 바꾸실 것이라고 생각하기 때문에 view작업에 관한 코드는 생략하도록 하겠습니다.

궁금하신 점이 있다면 편하게 댓글 달아주시면 감사하겠습니다~~!

import android.os.*
import androidx.appcompat.app.AppCompatActivity
import android.util.JsonReader
import android.util.Log
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.myhome.rpgkeyboard.R
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.InputStream
import java.io.InputStreamReader
import java.lang.Exception
import java.net.HttpURLConnection
import java.net.URL

class SomethingActivity : AppCompatActivity(){
    lateinit var mRecyclerView:RecyclerView
    lateinit var settingHeader:ConstraintLayout
    lateinit var recyclerAdapter:SkinAdapter
    var skins :List<Skin>? = null
    val connectionListener = object: ConnectionListener{
        override fun responseEnd() {//데이터를 전부 받아왔을 경우 실행됨
            object: Thread(){
                override fun run() {
                    super.run()
                    val msg = adapterFrontTask.obtainMessage()
                    adapterFrontTask.sendMessage(msg)
                }
            }.run()
            adapterFrontTask
        }
    }
    val adapterFrontTask:Handler = object:Handler(){
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            recyclerAdapter =
                SkinAdapter(applicationContext, skins!!)
//        val gl = GridLayoutManager(applicationContext, 2)
            val lm = LinearLayoutManager(applicationContext)
            mRecyclerView.layoutManager = lm

            mRecyclerView.setHasFixedSize(true)
            mRecyclerView.adapter = recyclerAdapter
            recyclerAdapter.notifyDataSetChanged()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_setting_theme)

        mRecyclerView = findViewById(R.id.theme_recyclerview)
        settingHeader = findViewById(R.id.setting_header)
        
        getSkinsTask()

    }

    fun getSkinsTask(){
        AsyncTask.execute(object: Runnable {
            override fun run() {
                var inputStream:InputStream? = null
                try{
                    val myGCPEndpoint = URL("http://YOUR_SERVER_ENDPOINT/YOUR_API_CALL?PARAMS")
                    val myConnection = myGCPEndpoint.openConnection() as HttpURLConnection
                    myConnection.setRequestProperty("User-Agent", "my-rest-app-v0.1")
                    myConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8")
                    myConnection.requestMethod = "GET"
                    myConnection.readTimeout = 10000
                    myConnection.doInput = true

                    myConnection.connect()

                    val response = myConnection.responseCode
                    Log.d("response==", response.toString())

                    if(response == 200){
                        inputStream = myConnection.inputStream
                        runBlocking{
                            var job = launch {
                                skins = readJsonStream(inputStream)
                            }
                            job.join()
                            if(skins != null){
                                connectionListener.responseEnd()
                            }
                        }
                    }
                }catch(e: Exception){
                    e.printStackTrace()
                    Log.e("response==", "Error:" + e.message)
                }finally {
                    if(inputStream != null){
                        inputStream.close()
                    }
                }
            }
        })
    }

    fun readJsonStream(inputStream:InputStream):List<Skin>{
        val reader = JsonReader(InputStreamReader(inputStream, "UTF-8"))
        try{
            return readMessagesArray(reader)
        }finally {
            reader.close()
        }
    }

    fun readMessagesArray(reader:JsonReader):List<Skin>{
        val messages = ArrayList<Skin>()
        reader.beginArray()
        while(reader.hasNext()){
            messages.add(Skin(reader))
        }
        reader.endArray()
        return messages
    }


}

 

아래는 제가 참고한 자바로 작성하신 분의 블로그입니다 :)

 

참고 사이트 - https://readystory.tistory.com/18

 

Android에서 JsonReader로 JSON 데이터 읽기

안녕하세요? Ready 입니다. 오늘은 안드로이드에서 서버로부터 JSON 데이터를 읽어오는 방법에 대해서 소개 해드리겠습니다! 우선 설명에 앞서 대부분의 내용은 안드로이드 공식 레퍼런스 https://developer.andro..

readystory.tistory.com