본문 바로가기
안드로이드 자바

[Android][Java] Retrofit 라이브러리를 사용해서 서버에 파일 업로드하기(클라이언트)

by teamnova 2023. 10. 9.
728x90

오늘은 Android 플랫폼에서 Retrofit을 사용해 서버로 파일을 업로드 하는 예제를 만들어 보겠습니다.

 

Retrofit에 대한 간략한 사용법은 다음 글에서 확인해주세요~

https://stickode.tistory.com/42

 

[Java][Android] retrofit2 사용법

오늘은 레트로핏2 라이브러리를 이용해서 서버와 통신하는 방법에 대해 알아보겠습니다. Gradle 의존성 추가 레트로핏2 라이브러리를 사용하기 위해 build.gradle(app) 파일에 retrofit 라이브러를 추가

stickode.tistory.com

 

코드 동작 순서입니다.

1. Capture 버튼 클릭

2. 카메라 권한 확인

3. 카메라 이미지 촬영

4. Send 버튼 클릭

5. 서버로 이미지 전송

 

 

MainActivity.java (메인 액티비티 JAVA 파일)

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;

public class MainActivity extends AppCompatActivity {

    private static int CAMERA_PERMISSION_CODE = 100;

    ImageView thumb; // 가져온 이미지를 표시할 썸네일
    Button send; // 서버로 전송하기 버튼
    
    File file; // 서버로 전송할 파일

	// 안드로이드 기기의 카메라를 통해서 이미지를 촬영할 때 사용할 Activity Launcher 초기화
    ActivityResultLauncher<Intent> launcher_capture = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @RequiresApi(api = Build.VERSION_CODES.P)
                @Override
                public void onActivityResult(ActivityResult result) {
                    if (result.getResultCode() == Activity.RESULT_OK) {
                        Log.d("launcher_capture Callback", "image capturing is succeed");

                        // 번들을 통해서 촬영 후 저장된 사진의 비트맵을 가져옴
                        Bundle extra = result.getData().getExtras();
                        Bitmap bitmap = (Bitmap) extra.get("data");

                        // ImageView에 촬영한 사진 보이게 설정
                        thumb.setImageBitmap(bitmap);

                        // 현재 ImageView가 숨김 상태일 경우 ImageView와 Send Button을 사용자에게 보이게 변경
                        if(thumb.getVisibility() != View.VISIBLE){
                            thumb.setVisibility(View.VISIBLE);
                            send.setVisibility(View.VISIBLE);
                        }

                        // 서버로 전송하기 위해 비트맵을 파일로 변환
                        file = saveBitmapToJpeg(bitmap, MainActivity.this);

                    }else if(result.getResultCode() == Activity.RESULT_CANCELED){
                        Log.d("launcher_capture Callback", "image capturing is canceled");
                    }else{
                        Log.e("launcher_capture Callback", "image capturing has failed");
                    }
                }
            });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        thumb = (ImageView) findViewById(R.id.imageView);

        Button capture = (Button) findViewById(R.id.capture);
        capture.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                getCameraPermission(); // 카메라 권한 확인
            }
        });

        send = (Button) findViewById(R.id.send);
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                getCameraPermission();
                if(file != null){ // File 객체 변수가 null 이 아닐 경우 파일 전송 진행

                    // File 객체로 RequestBody 객체 생성
                    RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);

                    // RequestBody로 MultipartBody.Part 객체 생성                            // (서버에서 확인할 파일 키, 파일 이름, RequestBody)
                    MultipartBody.Part filePart = MultipartBody.Part.createFormData("file_key", file.getName(), fileBody);

                    // Retrofit 객체 생성
                    Retrofit retrofit = new Retrofit.Builder()
                            .baseUrl(Retrofit_send_img.BASE_URL)
                            .addConverterFactory(ScalarsConverterFactory.create())
                            .addConverterFactory(GsonConverterFactory.create())
                            .build();

                    // Retrofit 객체를 사용해 비동기 통신 메소드가 있는 인터페이스 객체 생성
                    Retrofit_send_img api = retrofit.create(Retrofit_send_img.class);

                    // 인터페이스 객체를 사용해 Call 객체를 만들어 통신 시작
                    Call<String> call = api.test_send_img(filePart);
                    call.enqueue(new Callback<String>() {
                        @Override
                        public void onResponse(Call<String> call, Response<String> response) {
                            Log.e("Img Send Test response code : ", ""+response.code());
                            Log.e("Img Send Test response message : ", ""+response.body());
                            String jsonStr = response.body(); // 응답 결과
                            try {
                                // JSONObject로 변환
                                JSONObject jsonObject = new JSONObject(jsonStr);

                                if(jsonObject.getString("status").equals("success")){ // 업로드에 성공했을 경우
                                    Toast.makeText(MainActivity.this, "이미지 파일 업로드에 성공했습니다", Toast.LENGTH_SHORT).show();
                                }else{ // 업로드에 실패했을 경우
                                    Toast.makeText(MainActivity.this, "이미지 파일 업로드에 실패했습니다", Toast.LENGTH_SHORT).show();
                                    Log.e("Img Send Test fail message : ", jsonObject.getString("message"));
                                }
                            } catch (JSONException e) { // 업로드에 실패했을 경우
                                Toast.makeText(MainActivity.this, "이미지 파일 업로드에 실패했습니다", Toast.LENGTH_SHORT).show();
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void onFailure(Call<String> call, Throwable t) { // 서버와 통신에 실패했을 경우
                            Toast.makeText(MainActivity.this, "서버와 연결에 실패했습니다", Toast.LENGTH_SHORT).show();
                            Log.e("Img Send Test fail message : ", t.getMessage());
                        }
                    });
                }
            }
        });

    }

    // 권한 확인 메소드
    private void getCameraPermission(){
        // 현재 카메라 권한 여부 확인
        if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED){
            // 권한이 없을 경우 요청
            ActivityCompat.requestPermissions(this, new String[] {android.Manifest.permission.CAMERA}, CAMERA_PERMISSION_CODE);
        }else{
            // 권한이 있을 경우 카메라 촬영 진행
            Log.d("Check Camera Permission", "Permission Allowed");
            captureImage();
        }
    }

    // 이미지 촬영 메소드
    private void captureImage(){
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        launcher_capture.launch(intent);
    }

    // 비트맵을 파일로 변환하는 메소드
    public File saveBitmapToJpeg(Bitmap bitmap, Context context) {

        //내부저장소 캐시 경로를 받아옵니다.
        File storage = context.getCacheDir();

        //저장할 파일 이름
        String fileName = String.valueOf(System.currentTimeMillis()) + ".jpg";

        //storage 에 파일 인스턴스를 생성합니다.
        File imgFile = new File(storage, fileName);

        try {

            // 자동으로 빈 파일을 생성합니다.
            imgFile.createNewFile();

            // 파일을 쓸 수 있는 스트림을 준비합니다.
            FileOutputStream out = new FileOutputStream(imgFile);

            // compress 함수를 사용해 스트림에 비트맵을 저장합니다.
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);

            // 스트림 사용후 닫아줍니다.
            out.close();

            return imgFile;

        } catch (FileNotFoundException e) {
            Log.e("MyTag","FileNotFoundException : " + e.getMessage());
        } catch (IOException e) {
            Log.e("MyTag","IOException : " + e.getMessage());
        }

        return imgFile;
    }

}

 

activity_main.xml (메인 액티비티 레이아웃 파일)

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/capture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="50dp"
        android:text="capture"
        app:layout_constraintBottom_toTopOf="@+id/send"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="100dp"
        android:layout_marginTop="70dp"
        android:layout_marginEnd="100dp"
        android:layout_marginBottom="70dp"
        android:visibility="invisible"
        app:layout_constraintBottom_toTopOf="@+id/capture"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />

    <Button
        android:id="@+id/send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="50dp"
        android:text="send"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

Retrofit_send_img.java (Retrofit 인터페이스 JAVA 파일)

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

public interface Retrofit_send_img {
    String BASE_URL = "http://xxx.xxx.xxx.xxx/"; // 자신의 서버 IP 또는 도메인

    @Multipart
    @POST("send_img_test.php") // 파일 경로
    Call<String> test_send_img(
            @Part MultipartBody.Part file
    );
}

 

 

위처럼 코드를 작성하셨다면 클라이언트에서 서버로 이미지를 보낼 준비가 됐습니다!

다음은 서버에서 해당 이미지 파일을 받아 저장하는 예제를 만들어 보겠습니다.