ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • HttpURLConnection을 이용한 HTTP 통신
    Android/Network 2019. 7. 22. 23:59

    클라우드로이드

    HttpURLConnection은 앞 포스팅에서 말했듯이 Java 표준 라이브러리에 포함되어 있지만 버그가 있어서 Apache Http 라이브러리인 HttpClient에 의해 뭍혀 졌고 버그가 수정되었지만 기존 Apache의 지속적인 사용과 좀더 좋은 라이브러리가 나오면서 뭍혀버린 클래스이다.

     

    먼저 앞으로 나올 Volley와 Retrofit 또한 동일 UI와 동일 기능을 선보이는 앱을 구현할 것이다.

    단순하게 타이틀 텍스트와 이미지 한장을 출력시키고 끝내보도록 하겠다..


    1. AndroidMenifest.xml 과 Gradle Dependency 설정하기

     

    HTTP 통신에 있어서 가장 중요한 것은 Internet이다

    까먹지 않고 펄미션을 등록해주도록 하자

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.example.myapplication">
    
        <uses-permission android:name="android.permission.INTERNET"/>
    
        <application
                android:allowBackup="true"
                android:icon="@mipmap/ic_launcher"
                android:label="@string/app_name"
                android:roundIcon="@mipmap/ic_launcher_round"
                android:supportsRtl="true"
                android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN"/>
    
                    <category android:name="android.intent.category.LAUNCHER"/>
                </intent-filter>
            </activity>
        </application>
    
    </manifest>

    Gradle Dependency는 리스트 출력을 위한 recyclerView와 이미지 출력을 위한 glider를 사용하도록 하겠다.

        implementation 'androidx.recyclerview:recyclerview:1.0.0'
        implementation 'com.github.bumptech.glide:glide:3.8.0'

    2. UI와 Item 작성하기

     

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
    
        <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/main_recycler"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>

    photo_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
        <TextView
                android:id="@+id/photo_tittle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_constraintTop_toTopOf="parent"/>
    
        <ImageView
                android:id="@+id/photo_image"
                android:layout_width="240dp"
                android:layout_height="200dp"
                app:layout_constraintTop_toBottomOf="@id/album_tittle"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>

    3. Recycler 구조 만들기

     

    Photo.kt

    나중에 HTTP 통신을 통해 데이터를 받아와 저장시킬 데이터 클래스

    data class Photo(
        val albumId: Int,
        val id: Int,
        val title: String,
        val url: String,
        val thumbnailUrl: String
    )

    PhotoAdapter.kt

    Recycler에 필요한 어뎁터 역할 ViewHolder는 InnerClass로 구현

    class PhotoAdapter(
        private val context: Context
    ) : ListAdapter<Photo, PhotoViewHolder>(DIFF_CALL) {
    
        companion object {
            val DIFF_CALL = object : DiffUtil.ItemCallback<Photo>() {
                override fun areItemsTheSame(oldItem: Photo, newItem: Photo): Boolean {
                    return oldItem.id == newItem.id
                }
    
                override fun areContentsTheSame(oldItem: Photo, newItem: Photo): Boolean {
                    return oldItem == newItem
                }
            }
        }
    
        override fun onBindViewHolder(holder: PhotoViewHolder, position: Int) {
            position.log()
            with(getItem(position)) {
                holder.tvTitle.text = title
                Glide.with(context)
                    .load(url)
                    .into(holder.ivImage)
            }
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoViewHolder {
            return PhotoViewHolder(
                LayoutInflater.from(parent.context).inflate(
                    R.layout.photo_item,
                    parent,
                    false
                )
            )
        }
    
        inner class PhotoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            val tvTitle: TextView = itemView.photo_tittle
            val ivImage: ImageView = itemView.photo_image
        }
    
    }

    4. 본격적인 HttpURLConnection을 통한 HTTP 통신 구조 만들기

    class HttpClient(
    	private val onPostExecuteLambda: (data: List<Photo>) -> Unit
    ) : AsyncTask<Void, Void, String>() {
    
        override fun doInBackground(vararg params: Void): String {
    
            // 먼저 http 통신을 할 주소를 입력 해줍니다.
            val url = URL("http://192.168.0.2:3000/photos")
            // url을 열어주고 HttpURLConnection으로 캐스팅을 해줍니다.
            // 기본적으로 url.openConnection() 은 HttpConnection으로 반환되기에...
            val urlConnection = url.openConnection() as HttpURLConnection
    
            // 통신을 하면서 데이터를 받아오기 위해 스트림을 이용하여 리더를 받아오고
            val reader = BufferedReader(InputStreamReader(urlConnection.inputStream))
            val builder = StringBuilder()
    
            // 데이터를 받아줍니다.
            var result: String?
            while (true) {
                result = reader.readLine() ?: break
                builder.append(result)
            }
    
            // 데이터 전송이 다 끝났으니 연결을 종료합니다.
            urlConnection.disconnect()
            reader.close()
    
            return builder.toString()
        }
    
        override fun onPostExecute(result: String) {
            super.onPostExecute(result)
    
            // 입력 받은 Json(String)을 Array<Photo> 형식으로 파싱을 해줍니다.
            val photoList = Gson().fromJson<Array<Photo>>(result, Array<Photo>)
    
            onPostExecuteLambda.invoke(photoList.toList())
        }
    }

    5. 마무리 Activity 작업

    class MainActivity : AppCompatActivity() {
    
        private lateinit var photoAdapter: PhotoAdapter
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            photoAdapter = PhotoAdapter(this)
            main_recycler.adapter = photoAdapter
            main_recycler.layoutManager = LinearLayoutManager(this)
            main_recycler.setHasFixedSize(true)
    
            HttpClient {
                it.log()
                photoAdapter.submitList(it)
            }.execute()
    
        }
    
    }
    

    위의 HttpURLConnection 보면 URL 정하고 열고! 스트림 받고! 데이터 받고! 스트림 닫고! Connection 닫고!

    데이터 받는데 코드가 엄청 길고 불편합니다...

     

    실제 업무에서는 저런 방식을 사용하지 않으니 그냥 저런것이 있다! 라고만 아시면 좋을꺼 같습니다.


    ps. Json Server 관련해서 안돼요!

     

    1. 개발 환경이 노트북일때는 같은 네트워크에 있어야지 작동합니다. 서버가 로컬이기 때문에 같은 네트워크가 아닌이상은 통신이 되지 않습니다.

     

    2. 에뮬로 돌리시면 잘되지만 폰에 바로 인스톨 하고 실행시키면 아마 연결 실패가 뜹니다.

    서버 시작 명령어를 아래와 같이 수정해주세요

     

    json-server --host 192.168.0.2 --watch db.json

     

    댓글

Designed by Tistory.