[Android/kotlin]retrofit2 multipart를 사용한 Flask 서버 통신

Seomingi·2022년 10월 3일
0

출처 https://onedaycodeing.tistory.com/168

  1. 라이브러리와 안드로이드 버전을 수정한다
    버전이 맞지 않을 경우 아래 코드가 작동되지 않을수 있으므로 주의
    이 부분 떄문에 몇칠을 애먹었다 꼭 주의할것

build.gradle dependencies 추가할것
특히 이부분 주의
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.4.2'

implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.4.2'
    implementation 'com.google.android.material:material:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    implementation 'androidx.appcompat:appcompat:1.3.0'



    implementation 'com.squareup.retrofit2:converter-scalars:2.5.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
    implementation 'com.google.code.gson:gson:2.8.6'
    implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
  1. 먼저 레트로핏 기본설정을 한다 -> RetrofitSetting.kt
package opg.pp.server1

import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory

object RetrofitSetting {
    val API_BASE_URL = "서버주소"
    val httpClient = OkHttpClient.Builder()

    val baseBuilder = Retrofit.Builder()
        .baseUrl(API_BASE_URL)
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        .addConverterFactory(ScalarsConverterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())

    fun <S> createBaseService(serviceClass: Class<S>?): S {
        val retrofit = baseBuilder.client(httpClient.build()).build()
        return retrofit.create(serviceClass)
    }


}

아래 캡처 참고
\

2.인터페이스 설정 (나는 이미지를 보내는 목적으로 이미지만 설정)
RetrofitPath.kt

package opg.pp.server1

import okhttp3.MultipartBody
import retrofit2.Call
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part



interface RetrofitPath {
    @Multipart
    @POST("서버주소")
    fun profileSend(

        @Part imageFile : MultipartBody.Part
    ): Call<String>



}

\

  1. mainactivity.kt 작성
package opg.pp.server1

import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.widget.Constraints.TAG
import okhttp3.MediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.io.File


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val btn = findViewById<Button>(R.id.load_image)
        btn.setOnClickListener {getProFileImage()}

    }
    
   



    fun getProFileImage(){
        Log.d(ContentValues.TAG,"사진변경 호출")
        val chooserIntent = Intent(Intent.ACTION_CHOOSER)
        val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
        intent.type = "image/*"
        chooserIntent.putExtra(Intent.EXTRA_INTENT, intent)
        chooserIntent.putExtra(Intent.EXTRA_TITLE,"사용할 앱을 선택해주세요.")
        launcher.launch(chooserIntent)

    }

    fun absolutelyPath(path: Uri?, context : Context): String {
        var proj: Array<String> = arrayOf(MediaStore.Images.Media.DATA)
        var c: Cursor? = context.contentResolver.query(path!!, proj, null, null, null)
        var index = c?.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
        c?.moveToFirst()

        var result = c?.getString(index!!)

        return result!!
    }
    /////////////////////////////////////////////////
    var launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == RESULT_OK) {
            val imagePath = result.data!!.data

            val file = File(absolutelyPath(imagePath, this))
            val requestFile = RequestBody.create(MediaType.parse("image/*"), file)
            val body = MultipartBody.Part.createFormData("file", file.name, requestFile)

            Log.d(TAG,file.name)

            sendImage(body)

        }
    }

    fun sendImage(image : MultipartBody.Part) {
        val service = RetrofitSetting.createBaseService(RetrofitPath::class.java) //레트로핏 통신 설정
        val call = service.profileSend(image)!! //통신 API 패스 설정

        call.enqueue(object : Callback<String>{
            override fun onResponse(call: Call<String>, response: Response<String>) {
                if (response?.isSuccessful) {
                    Log.d("로그 ",""+response?.body().toString())
                    Toast.makeText(applicationContext,"통신성공",Toast.LENGTH_SHORT).show()
                }
                else {
                    Toast.makeText(applicationContext,"통신실패",Toast.LENGTH_SHORT).show()
                }
            }

            override fun onFailure(call: Call<String>, t: Throwable) {
                Log.d("로그 ",t.message.toString())
            }
        })
    }





}
  1. flask 서버 작성
  import io
from flask import Flask, jsonify, request
import torch
import torch.nn as nn
import torch.optim as optim
import os
import torchvision
from torchvision import datasets, models, transforms
from PIL import Image
import numpy as np
import time
from flask import Flask, render_template, request

app = Flask(__name__)



@app.route('/', methods=['POST','GET'])
def predict():
    if request.method == 'POST':
        # 이미지 바이트 데이터 받아오기 
        file = request.files['file']
        #image_bytes = file.read()
        file.save('파일저장위치' + (file.filename))

        return 'ok'


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8000, debug=True)
    
  1. 잘되지 않았던 통신부분
    서버 통신에 대해 잘알지 못했던 분야라 오랜시간 실패를 거듭했다
    안드로이드 스튜디오에서 서버로 보내는데 계속 통신오류가 났다
    그러다 우연히 블로그를 통해 나와 비슷한 상황이신 분에게 아이디어를 얻어 고쳤더니 잘되었다

    flask 서버 코드

    @app.route('/', methods=['POST','GET'])
    def predict():
       if request.method == 'POST':
           # 이미지 바이트 데이터 받아오기 
           file = request.files['file'] #이부분!!!
           #image_bytes = file.read()

    안드로이드 mainactivity.kt

    var launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
          if (result.resultCode == RESULT_OK) {
              val imagePath = result.data!!.data
    
              val file = File(absolutelyPath(imagePath, this))
              val requestFile = RequestBody.create(MediaType.parse("image/*"), file)
              val body = MultipartBody.Part.createFormData("file", file.name, requestFile) #이부분!!!
    
              Log.d(TAG,file.name)
    
              sendImage(body)
    
          }
      }

    flask서버 코드인 file 이름 부분과
    mainactivity.kt의 MultipartBody.Part.createFormData("file")
    의 이름이 일치하지 않아서 생긴 오류였다
    이것때문에 3일을 찾아봤다.. 결국 원리를 이해하지 못한 나의 잘못으로 삽질을 한것이였다.. 막상 오류 찾았더니 굉장히 허탈 ㅜㅜ
    다음번엔 틀리지 않기 위해 이글을 남긴다



이렇게 이미지가 잘 넘어오는것을 볼수있다 !!!

profile
One thing after another

0개의 댓글