본문 바로가기
한국 20대 개발자의 성장기

[kotlin] 안드로이드 레트로핏2 이미지 전송(업로드) / Android retrofit2 image upload to php

by 멍동구 2022. 12. 6.
728x90

안드로이드 코틀린 언어로 Retrofit2 를 활용하여서 이미지 전송 해보겠습니다.

 

php 는 제가 모르는 관계로 게시하지 못했습니다.

 

  1. 환경구성
  2. 인터페이스
  3. 이미지 업로드

 

의 순서로 작성해보겠습니다.

 

1. 환경구성

ViewBinding - build.gradle (:app)

.
.
.
kotlinOptions {
        jvmTarget = '1.8'
    }
    viewBinding{
        enabled true
    }
}

http 설정 - androidManifest.xml

android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"

Permission - build.gradle (:app)

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

implementation - build.gradle (:app)

dependencies {

.
.
.
    
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation "com.squareup.retrofit2:converter-gson:2.8.1"

    implementation 'com.google.code.gson:gson:2.9.0'
    implementation 'com.squareup.retrofit2:converter-scalars:2.5.0'

    implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
}

그래들 파일에서 꼭  Sync Now 해주세요.

 


 

2. 인터페이스 구성

 

APIS.kt

interface APIS {
    @Multipart
    @POST(PHP파일주소)
    fun sendImage(
        @Part upload : MultipartBody.Part?,
    ):Call<String>

    companion object{
        private const val url = 서버주소

        fun create():APIS{
            val interceptor = HttpLoggingInterceptor()
                .apply {
                    level = HttpLoggingInterceptor.Level.BODY
                }

            val client = OkHttpClient.Builder()
                .addInterceptor(interceptor).build()

            val gson = GsonBuilder()
                .setLenient()
                .create()

            return Retrofit.Builder()
                .baseUrl(url)
                .client(client)
                .addConverterFactory(ScalarsConverterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build()
                .create(APIS::class.java)
        }
    }
}

인터셉터를 사용하여서 이미지 업로드되는 부분을 상세하게 확인해보았습니다.

 


 

3.이미지 업로드

class SendImageActivity : AppCompatActivity() {
    private val binding by lazy { ActivitySendImageBinding.inflate(layoutInflater) }
    val api = APIS.create() // 인터페이스
    private val TAG = "이미지전송로그"
    private lateinit var imageResult: ActivityResultLauncher<Intent>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        imageResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            if (result.resultCode == RESULT_OK) {
				if (result.data?.clipData != null){
                    // 여러장( 미구현 )
                }else{
                // 한장
                    result.data?.data?.let { uri->
                        val imageUri :Uri? = result.data?.data
                        if (imageUri!=null){
                            val file = File(getPath(this,imageUri))
                            var requestBody = file.asRequestBody("image/jpg".toMediaTypeOrNull())
                            var body = MultipartBody.Part.createFormData("upload", createFileName(), requestBody)

                            api.sendImages(body).enqueue(object :Callback<String>{
                                override fun onResponse(
                                    call: Call<String>,
                                    response: Response<String>
                                ) {
                                    Log.d(TAG, "onResponse: ${response.body()}")
                                }

                                override fun onFailure(call: Call<String>, t: Throwable) {
                                    Log.e(TAG, "onFailure: ${t.message}", )
                                }
                            })
                        }
                    }
                }

            }
        }


        binding.btnLoad.setOnClickListener {
            selectImage()
        }
    }


    private fun selectImage() {
        val writePms =
            ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
        val readPms =
            ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)

        if (writePms == PackageManager.PERMISSION_DENIED||readPms == PackageManager.PERMISSION_DENIED) {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE),
                1
            )

        } else {
            val intent = Intent()
            intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
            intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true)
            intent.action = Intent.ACTION_GET_CONTENT
                imageResult.launch(intent)
        }
    }

    private fun createFileName(): String {
        val sdf = SimpleDateFormat("yyyyMMdd_HHmmss")
        val filename = sdf.format(System.currentTimeMillis())
        return "$filename.jpg"
    }
    @Nullable
    private fun getPath(context: Context,uri:Uri):String?{
        val contentResolver = context.contentResolver ?: return null
        val filePath :String=(context.applicationInfo.dataDir+File.separator+System.currentTimeMillis())
        val file = File(filePath)
        try {
            val inputStream = contentResolver.openInputStream(uri) ?: return null
            val outputStream = FileOutputStream(file)
            val buf = ByteArray(1024)
            var len : Int
            while (inputStream.read(buf).also { len = it }>0)outputStream.write(buf,0,len)
            outputStream.close()
            inputStream.close()

        }catch (e:Exception){
            Log.e(TAG, "getPath: ${e.message}", )
            return null
        }
        return file.absolutePath
    }
}

버튼을 클릭하면 권한을 확인합니다

권한이 허락되어 있으면 갤러리를 엽니다.

 

multiple 이므로 여러장 선택이 가능하지만 아직 미구현입니다.

 

인텐트를 통하여 갤러리 사진 주소를 가져와서 그 주소를 절대주소로 변환합니다.

변환된 절대주소를 이용하여 File로 변환합니다

변환된 파일을 MultiPartBody로 변환후 api를 이용해 파일을 전송합니다.

 

php로 파일을 업로드 합니다.

 

 


 

궁금하신 부분이나 코드에 문제점이 있으면 댓글로 남겨주세요 감사합니다.