[kotlin] 통신 라이브러리(Volley, Retrofit)

ywown·2021년 11월 14일
0

코틀린 개념

목록 보기
6/8
post-custom-banner

『Do it! 깡샘의 안드로이드 앱 프로그래밍 with 코틀린』 교재를 바탕으로 정리한 내용입니다.

HTTP 통신 기본 설정

  • 네트워크 통신을 위한 퍼미션 선언
    <uses-permission android:name="android.permission.INTERNET" />
  • 안드로이드 앱은 기본으로 HTTPS 보안 프로토콜 사용하므로, 일반 HTTPS 프로토콜로 통신하려면 특정 도메인만 허용하도록 선언해줘야 함
    1. res/xml 폴더에 xml 파일을 만들어 태그에 HTTP 프로토콜로 접속 허용할 IP나 도메인 작성
    2. 매니페스트 태그에 networkSecurityConfig 속성으로 xml 파일 등록
    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
    	<domain-config cleartextTrafficPermitted="true">
    		<domain includeSubdomains="true">xxx.xxx.xxx.xxx</domain>
    	</domain-config>
    </network-security-config>
    <application
    	(... 생략 ...)
    	android:networkSecurityConfig="@xml/network_security_config">
  • 모든 HTTP 통신을 허용하려면 매니페스트 usesCleartextTraffic 속성을 true로 설정
    <application
    	(... 생략 ...)
    	android:usesCleartextTraffic="true">

1. Volley 라이브러리

  • Volley : 구글에서 제공하는 HTTP 통신 라이브러리
  • Volley 라이브러리 등록
implementation 'com.android.volley:volley:1.2.1'
  • Volley 핵심 클래스
    • RequestQueue : 서버에 요청을 보내는 역할
    • XXXRequest : XXX 타입의 결과를 받는 요청 정보

문자열 데이터 요청하기 - StringRequest

  • StringRequest 생성자 매개변수
    • method : HTTP 메서드
    • url : 서버 URL
    • listener : 서버로부터 결과를 받을 때 호출할 콜백
    • listener : 서버 연동 실패 시 호출할 콜백
    // 문자열 요청 정의
    val stringRequest = StringRequest(
    	Request.Method.GET,
    	url,
    	Response.Listener<String> {
    		Log.d("kkang", "server data : $it")
    	},
    	Response.ErrorListener { error ->
    		Log.d("kkang", "error......$error")
    	})
  • RequestQueue 객체 이용해 StringRequest에 담은 정보 서버에 요청
    // 서버에 요청
    val queue = Volley.newRequestQueue(this)
    queue.add(stringRequest)
  • 서버에 요청할 때 데이터 함께 전달해야 하는 경우,
    • GET 방식에서는 간단하게 URL 뒤에 추가하면 되지만
    • POST 방식에서는 StringRequest를 상속받은 클래스를 이용해야 함
    // POST 방식으로 데이터 전송 
    val stringRequest = object : StringRequest(
    	Request.Method.POST,
    		url,
    		Response.Listener<String> {
    			Log.d("kkang", "server data : $it")
    		},
    		Response.ErrorListener { error ->
    			Log.d("kkang", "error......$error")
    		}) {
    		override fun getParams(): MutableMap<String, String> {
    			return mutableMapOf<String, String>("one" to "hello", "two" to "world")
    			// MutableMap 객체에 전달할 데이터 담아서 반환하면 서버에 요청할 때 알아서 함께 전송해 줌
    	}
    }

이미지 데이터 요청하기 - ImageRequest

  • ImageRequest 생성자에 지정해야 하는 매개변수
    • url : 서버 URL
    • listener : 결과 가져오는 콜백
    • maxWidth, maxHeight : 지정한 값으로 이미지 크기 조절해서 전달. 0으로 설정 시 크기 조절 없이 서버가 보낸 이미지 그대로 받음
    • scaleType : 영역에 맞게 이미지 크기 확대 또는 축소하는 스케일 타입
    • decodeConfig : 이미지 형식
    • errorListener : 오류 콜백
    val imageRequest = ImageRequest(
    	url,
    	Response.Listener { response -> binding.imageView.setImageBitmap(response) },
      0,
    	0,
    	ImageView.ScaleType.CENTER_CROP,
    	null,
    	Response.ErrorListener { error ->
    		Log.d("kkang", "error......$error")
    	})
    
    val queue = Volley.newRequestQueue(this)
    queue.add(imageRequest)

화면 출력용 이미지 데이터 요청하기 - NetworkImageView

  • NetworkImageView는 Volley에서 제공하는 이미지 출력용 뷰
    • XML 작성하고, 이 객체의 setImageUrl() 함수만 호출하면 서버에서 이미지를 가져오는 통신부터 출력하는 것까지 자동으로 실행됨
    <com.android.volley.toolbox.NetworkImageView
    	android:id="@+id/networkImageView"
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content" />
  • ImageLoader 객체를 setImageUrl() 함수의 매개변수로 지정하면, 서버 이미지 가져오기 전 ImageLoader의 getBitmap() 함수가 자동호출
    → 함수 반환값이 null 이면 서버에 요청을 보내고 Bitmap 객체이면 그대로 화면에 출력
    → null을 반환하여 서버로부터 이미지를 가져오면 putBitmap()함수 호출되어 서버 이미지를 매개변수로 전달
    val queue = Volley.newRequestQueue(this)
    // queue.add(imageRequest)
    val imgMap = HashMap<String, Bitmap>()
    val imageLoader = ImageLoader(queue, object  : ImageLoader.ImageCache {
    	override fun getBitmap(url: String?): Bitmap? {
    		return imgMap[url]
    	}
    
    	override fun putBitmap(url: String?, bitmap: Bitmap?) {
    		imgMap[url] = bitmap
    	}
    })
    
    binding.networkImageView.setImageUrl(url, imageLoader)

JSON 데이터 요청하기 - JsonObjectRequest

  • 생성자 매개변수
    • method : HTTP 메서드
    • url : 서버 URL
    • JSONObject : 서버에 요청 시 전송할 데이터 지정
    • listener : 서버로부터 결과를 받을 때 호출할 콜백
    • listener : 서버 연동 실패 시 호출할 콜백
    val jsonRequest = JsonObjectRequest(
    	Request.Method.GET,
    	url,
    	null,
    	Response.Listener<JSONObject> { response ->   //JSON을 파싱한 JSONObject 객체 전달
    		val title = response.getString("title")
    		val date = response.getString("date")
    		Log.d("kkang", "$title, $date")
    	},
    	Response.ErrorListener { error -> Log.d("kkang", "error......$error") }
    )
    
    val queue = Volley.newRequestQueue(this)
    queue.add(jsonRequest)

JSON 배열 요청하기 - JsonArrayRequest

  • 생성자 매개변수
    • url : 서버 URL
    • JSONArray : 서버에 요청 시 전송할 데이터 배열 지정
    • listener : 서버로부터 결과를 받을 때 호출할 콜백
    • listener : 서버 연동 실패 시 호출할 콜백
    val jsonArrayRequest = JsonArrayRequest(
    	Request.Method.GET,
    	url,
    	null,
    	Response.Listener<JSONArray> { response ->
    		for (i in 0 until response.length()) {
    			val jsonObject = response[i] as JSONObject
    			val title = jsonObject.getString("title")
    			val date = jsonObject.getString("date")
    			Log.d("kkang", "$title, $date")
    		}
    	},
    	Response.ErrorListener { error -> Log.d("kkang", "error......$error") }
    )
    
    val queue = Volley.newRequestQueue(this)
    queue.add(jsonArrayRequest)

2. Retrofit 라이브러리

  • Retrofit : 스퀘어에서 만든 HTTP 통신 라이브러리. 2015년에 Retrofit2가 나왔으므로 지금 사용되는 Retrofit은 대부분 Retrofit2

Retrofit 구조

  1. 통신용 함수를 선언한 인터페이스 작성
  2. Retrofit에 인터페이스 전달
  3. Retrofit이 통신용 서비스 객체 반환
    • 인터페이스를 Retrofit에 알려 주면 인터페이스 정보를 보고 실제 통신할 때 필요한 코드를 담은 서비스 객체를 만들어 줌
  4. 서비스의 통신용 함수 호출한 뒤 Call 객체 반환
  5. Call 객체의 enqueue()함수를 호출해 네트워크 통신 수행

Retrofit 라이브러리 선언

  • gson을 이용해 모델 객체로 변환해주는 converter-gson 함께 등록
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.google.code.gson:gson:2.8.6'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

모델 클래스 선언 - VO 클래스

  • 모델 클래스란 서버와 주고받는 데이터를 표현하는 클래스로, JSON, XML 데이터를 파싱해 모델 클래스 객체에 담는 것을 자동화해 주는 역할을 함
  • 데이터를 담을 모델 클래스를 선언하고 클래스 정보만 알려 주면 모델 클래스의 객체를 알아서 생성해 그 객체에 데이터를 담아 줌
{

	"id" : 7,

	"email" : "michael.lawson@reqres.in",

	"first_name" : "Michael",

	"last_name" : "Lawson",

	"avatar" : "https://reqres.in/img/faces/7-image.jpg"

}

→ 위의 JSON정보를 담는 모델 클래스

data class UserModel(
	var id: String,
	@SerializedName("first_name")    // 키와 프로퍼티 이름이 다른 경우 어노테이션 사용
	var firstName: String,
//	@SerializedName("last_name")    // last_name -> lastName 자동으로 저장 
	var lastName: String,
	var avatar: String,
	var avatarBitmap: Bitmap
)
  • 모델 클래스 작성 시 여러 클래스로 분리한 후 조합해서 사용 가능
{

	"page" : 2,

	"per_page" :6,

	"total" : 12,

	"total_pages" : 2,

	"data" : [

		{

			"id" : 7,

			"email" : "michael.lawson@reqres.in",

			"first_name" : "Michael",

			"last_name" : "Lawson",

			"avatar" : "https://reqres.in/img/faces/7-image.jpg"

		},

		{

			"id" : 7,

			"email" : "michael.lawson@reqres.in",

			"first_name" : "Michael",

			"last_name" : "Lawson",

			"avatar" : "https://reqres.in/img/faces/7-image.jpg"

		}

	],

}

→ 위의 JSON정보를 담는 모델 클래스

data class UserListModel(	
	var page: String,
  @SerializedName("per_page")
  var perPage: String,
  var total: String,
  @SerializedName("total_pages")
  var totalPages: String,
  var data: List<UserModel>?
)

서비스 인터페이스 정의

  • 네트워크 통신이 필요한 순간에 호출할 함수를 포함하는 인터페이스 작성하는 단계 (가장 중요!)
  • 실제로 통신하는 클래스는 애너테이션을 참조해 Retrofit이 자동으로 생성해 줌
    interface INetworkService {
    	// @GET : 서버 연동 시 GET 방식으로 하겠다는 의미
    	// @Query : 서버에 전달되는 데이터
    	// @Url : 요청 URL
    	@GET("api/users")    
    	fun  doGetUSerList(@Query("page") page: String): Call<UserListModel>
    	@GET  
    	fun getAvatarImage(@Url url: String): Call<ResponseBody>
    }

Retrofit 객체 생성

  • 가장 먼저 Retrofit 객체 생성하는 코드 실행
  • Retrofit 객체 생성 코드는 초기 설정을 하기 때문에 한 번만 생성하면 됨
  • baseUrl을 "https://reqres.in/"으로 설정한 후 @GET("api/users") 처럼 경로 지정하면 서버 요청 URL은 "https://reqres.in/api/users"가
    val retrofit: Retrofit
    	get() = Retrofit.Builder()
    		.baseUrl("https://reqres.in/")
    		.addConverterFactory(GsonConverterFactory.create()) //GsonConverter 이용하겠다는 의미
    		.build()

인터페이스 타입의 서비스 객체 얻기

  • Retrofit의 create() 함수에 앞에서 만든 서비스 인터페이스 타입 전달하면, 인터페이스를 구현한 클래스의 객체를 반환해줌 → 실제 네트워크 필요할 때 이 객체의 함수 호출
    val networkService: INetworkService = retrofit.create(INetworkService::class.java)

네트워크 통신 시도

  • Retrofit 객체로 얻은 서비스 객체의 함수만 호출하면 됨
    val userListModel = networkService.doGetUSerList("1")
  • Call 객체 반환하면 enqueue()함수 호출해 통신 수행
    • 통신 성공 시 onResponse()함수를, 실패 시 onFailure() 함수를 호출함
    userListCall.enqueue(object : Callback<UserListModel> {
    	override fun onResponse(call: Call<UserListModel>, response: Response<UserListModel>) {
    		val userList = response.body()
    		(... 생략 ...)
    	}
    
    	override fun onFailure(call: Call<UserListModel>, t: Throwable) {
    		call.cancel()
    	}
    })

Retrofit 애너테이션

  • Retrofit은 개발자가 작성한 서비스 인터페이스에 따라 통신을 수행하기 때문에 어떤 애너테이션을 작성하는 지가 핵심 !

  • @GET, @POST, @PUT, @DELETE, @HEAD

    • HTTP 메서드를 정의하는 애너테이션
        // 인터페이스에 선언한 함수
        @GET("users/list?sort=desc")
        fun  test1(): Call<UserModel>
        
        // Call 객체를 얻는 구문
        val call: Call<UserModel> = networkService.test1()
        
        //최종 서버 요청 URL
        https://reqres.in/users/list?sort=desc
  • @Path
    • URL의 경로를 동적으로 지정하기 위해 사용
    • 동적 데이터가 들어갈 데이터를 함수의 매개변수로 받기 위해 @Path 애너테이션 사용
        // 인터페이스에 선언한 함수
        @GET("group/{id}/users/{name}")
        fun  test2(
        	@Path("id") userId: String,
        	@Path("name") arg2: String,
        ): Call<UserModel>
        
        // Call 객체를 얻는 구문
        val call: Call<UserModel> = networkService.test2("10", "kkang")
        
        //최종 서버 요청 URL
        https://reqres.in/group/10/users/kkang
  • @Query
    • 경로에 ?를 이용해 서버에 전달할 데이터를 지정할 수도 있지만, 함수의 매개변수 값을 서버에 전달하기 위해서는 @Query 사용
    // 인터페이스에 선언한 함수
    @GET("group/users")
    fun  test3(
    	@Query("sort") arg1: String,
    	@Query("name") arg2: String,
    ): Call<UserModel>
    
    // Call 객체를 얻는 구문
    val call: Call<UserModel> = networkService.test3("age", "kkang")
    
    //최종 서버 요청 URL
    https://reqres.in/group/users?sort=age&name=kkang
  • @QueryMap
    • 서버에 전송할 데이터가 많은 경우 전송할 데이터를 Map 타입의 매개변수로 받는 방식으로 구현
// 인터페이스에 선언한 함수
@GET("group/users")
fun  test4(
	@QueryMap options: Map<String, String>,
	@Query("name") name: String,
): Call<UserModel>

// Call 객체를 얻는 구문
val call: Call<UserModel> = networkService.test4(
	mapOf<String, String>("one" to "hello", "two" to "world"), "kkang")

//최종 서버 요청 URL
https://reqres.in/group/users?one=hello&two=world&name=kkang
  • @Body
    • 서버에 전송할 데이터를 모델 객체로 지정하고 싶은 경우에 사용
    • JSON 문자열은 데이터 스트림으로 전송하므로 @Body는 @GET에서는 사용할 수 없으며 @POST와 함게 사용해야 함
    • @Body로 지정한 모델의 데이터는 객체의 내용을 JSON 문자열로 만들어 URL이 아닌 데이터 스트림으로 서버에 전송
    // 인터페이스에 선언한 함수
    @GET("group/users")
    fun  test5(
    	@Body user: UserModel,
    	@Query("name") name: String,
    ): Call<UserModel>
    
    // Call 객체를 얻는 구문
    val call: Call<UserModel> = networkService.test5(
    	UserModel(id="1", firstName = "gildong", lastName = "hong", avatar = "someurl"),
    	"kkang"
    )
    
    //최종 서버 요청 URL
    https://reqres.in/group/users?name=kkang
    
    //서버에 스트림으로 전송되는 데이터
    {"id":"1", "first_name":"gildong", "last_name":"hong", "avatar":"someurl"}
  • @FormUrlEncoded, @Field
    • 데이터를 URL 인코딩 형태로 만들어 전송할 때 사용
    • 서버 전송 데이터를 '키=값' 형태의 URL 인코딩으로 전송
    • @Field 애너테이션이 추가된 데이터를 인코딩해서 전송하며, @FormUrlEncoded 애너테이션을 사용할 때만 적용 가능함
    • @FormUrlEncoded는 @POST 방식에서만 사용 가능
    // 인터페이스에 선언한 함수
    @FormUrlEncoded
    @POST("users/edit")
    fun  test6(
    	@Field("first_name") first: String?,
    	@Field("last_name") last: String?,
    	@Query("name") name: String?,
    ): Call<UserModel>
    
    // Call 객체를 얻는 구문
    val call: Call<UserModel> = networkService.test6(
    	"gildong 길동",
    	"hong 홍", 
    	"kkang"
    )
    
    //최종 서버 요청 URL
    https://reqres.in/users/edit?name=kkang
  • @Header
    • 서버 요청에서 헤더값 조정하고 싶은 경우 사용
    @Headers("Cache-Control: max-age=640000")
    @GET("widget/list")
    fun test8(): Call<UserModel>
  • @Url
    • baseUrl 무시하고 전혀 다른 URL을 지정하고 싶은 경우에 사용
    // 인터페이스에 선언한 함수
    @GET
    fun test9(@Url url: String, @Query("name") name: String): Call<UserModel>
    
    // Call 객체를 얻는 구문
    val call = networkService.test9("http://www.google.com", "kkang")
    
    //최종 서버 요청 URL
    http://www.google.com/?name=kkang
profile
기록
post-custom-banner

0개의 댓글