API에서 데이터를 받아와 View에 출력해주는데 이미지 부분을 Picasso 라이브러리를 이용해서 출력해보도록 하겠습니다.
Android앱을 제작하다보면 이미지를 표시해야하는 경우들이 많이 발생합니다. 그럴때는 이미지 라이브러리를 이용해 사용자들에게 보여주어야하는데 이미지 라이브러리들 중 가장 대표하는 것이 Glide와 Picasso입니다.
Glide와 비교해서 기능 항목이 적은 편입니다.
캐싱 방법 : 전달받은 이미지를 사이즈 변환 없이 저장합니다.
메모리 사용량 : 원본 이미지를 저장함으로 상대적으로 많은 메모리를 사용합니다.
로딩 시간 : 인터넷 이미지 로딩 빠릅니다.
-> 캐시 이미지 로딩 느림
이미지 품질 : Glide에 비해 비트 수가 높아 고품질 이미지를 표현할 수 있습니다.
GIF를 지원하지 않습니다.
Glide와 비교해서 Picasso의 가장 큰 장점은 고화질의 이미지를 출력해줄 수 있다는 것입니다.
글의 마지막에 작성하겠지만 위의 사항은 사용시 주의해야 할 부분이기도 합니다. Picasso의 경우 이미지를 원본 그대로 가지고 오기 때문에 많이 불러오다 보면 메모리 부족으로 앱이 강제종료될 수 도있습니다.
주의사항은 마지막 부분에 작성하도록 하고 사용법에 대해 설명드리겠습니다.
우선 라이브러리를 사용하기 위해 다음과 같이 gradle에서 implementation해줍니다.
//build.gradle(Module)
// 버전의 차이는 있을 수 있습니다!!!
//API 통신을 위한 Retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//Picasso 라이브러리
implementation 'com.squareup.picasso:picasso:2.71828'
// MVVM 패턴 및 코루틴(비동기)을 위한 선언
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9
또한 ViewBinding 사용을 위해 ViewBinding = true를 android{} 안에 선언 해줍니다.
우선 통신을 위한 작업들을 먼저 해보겠습니다.
Json을 통해 데이터를 받아올텐데 해당 Json의 형태에 맞게 Data Class를 선언해줍니다.
data class RecyclerList(val items: ArrayList<RecyclerData>)
data class RecyclerData(val name: String, val description: String ,val owner: Owner)
data class Owner(val avatar_url : String)
다음은 API의 Url 변수를 선언해주고 Retrofit Instance 메서드를 생성하겠습니다.
companion object{
val baseUrl = "https://api.github.com/search/"
fun getRetroInstance() : Retrofit{
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
다음은 비동기 작업을 통해 데이터를 API에서 받아오는 메서드를 생성해보겠습니다.
@GET("repositories")
suspend fun getDataFromApi(@Query("q")query : String): RecyclerList
해당 API에서 Data를 받아오는 EndPoint는 repositories이기 때문에 GET 어노테이션 Value로 넣어주고 Query를 통해 위치가 바뀌어도 값을 동적으로 받아옵니다
미리 형태를 선언해 놓은 RecyclerList 사용해 쉽게 파싱하도록 해줍니다.
RecyclerView에 사용될 Adpater 및 ViewHolder를 선언해줍니다
class RecyclerAdapter : RecyclerView.Adapter<RecyclerAdapter.MyViewHolder>(){
init {
}
var items = ArrayList<RecyclerData>()
// 데이터를 받아오고 난뒤 notifyDataSetChanged를
통해 RecycelrView를 새로 업데이트 해줍니다.
fun setUpdateData(items : ArrayList<RecyclerData>){
this.items = items
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = RecyclerListBinding.inflate(LayoutInflater.from(parent.context),parent,false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(items.get(position))
}
override fun getItemCount(): Int {
return items.size
}
MyViewHolder에서 Picasso 라이브러리를 사용하게됩니다.
class MyViewHolder(private val binding: RecyclerListBinding) : RecyclerView.ViewHolder(binding.root){
val imageView = binding.iamgeVIew
val textTitle = binding.tvTitle
val textContent = binding.tvContent
fun bind(data : RecyclerData){
textTitle.text = data.name
textContent.text = data.description
val url = data.owner.avatar_url
Picasso.get()
.load(url)
.into(imageView)
}
}
load() : 이미지의 경로
into() : 받아온 이미지를 받을 공간
위에서는 사용하지 않았지만 다음과 같은 함수도 있습니다.
error() : 에러시, 대체할 이미지
resize() : 이미지 크기 조정
placeholder(): 다운로드 중 표시할 이미지
transform() : 이미지 후 처리
등과 같은 기능들이 있습니다.
lateinit var recyclerListLiveData : MutableLiveData<RecyclerList>
init {
recyclerListLiveData = MutableLiveData()
}
fun getRecyclerListObserver() : MutableLiveData<RecyclerList>{
return recyclerListLiveData
}
fun makeApiCall() {
viewModelScope.launch(Dispatchers.IO){
val retroInstance = RetroInstance.getRetroInstance().create(RetroService::class.java)
val response = retroInstance.getDataFromApi("ny")
recyclerListLiveData.postValue(response)
}
}
apicall을 만들어주고 받아온 데이터를 MutableLiveData() 형태로 선언해줍니다.
ViewModel에 선언된 call기능을 Fragment에서 사용할 수 있도록 ViewModelProvider를 통해 Fragment에서 사용할 수 있도록 합니다.
Acitivty에서는 SupportFragmentManager를 통해 Activity에서 Fragment로 화면전환을 하고
MainActivity.kt
private fun setUpFragment(){
supportFragmentManager.beginTransaction().replace(R.id.fragmentContainer,RecyclerListFragment.newInstance()).commit()
}
}
Framgnet에서는
위에서 말한것 처럼 API CALL을 사용할 수있게 가져와줍니다.
private fun viewModelProvider(){
val viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
viewModel.getRecyclerListObserver().observe(viewLifecycleOwner, Observer<RecyclerList> {
if(it != null) {
recyclerAdapter.setUpdateData(it.items)
} else {
Toast.makeText(activity, "Error in getting data", Toast.LENGTH_SHORT).show()
}
})
viewModel.makeApiCall()
}
다음은 RecyclerView에 어댑터 및 표시 방식을 선언해줍니다.
private fun initViewModel() = with(binding){
recyclerview.layoutManager = LinearLayoutManager(activity)
val decoration = DividerItemDecoration(activity, DividerItemDecoration.VERTICAL)
recyclerview.addItemDecoration(decoration)
recyclerAdapter = RecyclerAdapter()
recyclerview.adapter = recyclerAdapter
}
Kotlin의 with을 사용해서 binding을 앞에 붙이지 않더라도 값을 받아올 수 있게 해줍니다.
이렇게 하면 모든 준비가 끝났습니다.
API 통신을 해야하기에 AndroidManifest에 INTERNET 권한 설정도 해줘야합니다.
전체 코드는 깃 허브 주소를 남겨두도록 하겠습니다.
https://github.com/heetaeheo/MVVM-Using-Coroutine-Retrofit-Picasso.git
피카소에서는 into를 통해서 이미지를 할당하는데 Bitmap의 경우 fit이나 centerCrop과 같은 기능을 사용할 수 없기에 후처리하는 커스텀 함수를 생성하고 transform을 통해 후처리를 해줘야합니다.
onBitmapLoaded를 통해 넘어온 bitmap을 가공해서 사용할 경우 가공된 bitmap을 캐시할 수 없기 때문에 메모리 누수가 발생합니다.
그렇기에 onBitmapLoaded로 넘어오기 이전에 이미지 처리를 해야 가공된 이미지까지 캐시를 해 메모리 누수를 막을 수 있습니다.
https://jasonbla.tistory.com/15
https://square.github.io/picasso
https://github.com/square/picasso