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

[JAVA][Android] 이미지 서버에 업로드하기

by teamnova 2024. 6. 24.

안녕하세요.

 

오늘은 안드로이드에서 이미지를 서버에 업로드 하는 방법에 대해 알아보겠습니다.

해당 예제는 이전에 작성했던 TedImagePicker를 사용해 이미지를 여러장 불러오는 예제에 있는 코드를 사용해서 작성되었습니다.

이전 글과 관련된 내용은 아래 링크를 참고해주세요.

 

2024.06.16 - [안드로이드 자바] - [JAVA][Android] TedImagePicker 라이브러리로 이미지 여러장 가져오기

 

[JAVA][Android] TedImagePicker 라이브러리로 이미지 여러장 가져오기

안녕하세요.지난번에는 TedImagePicker 라이브러리를 사용해서 이미지를 1장 가져오는 방법에 대해 알아보았습니다.2024.06.11 - [안드로이드 자바] - [JAVA][Android] TedImagePicker 라이브러리로 이미지 1개

stickode.tistory.com

 

1. AndroidManifast

 

서버와 통신을 주고 받기 위해서는 매니페스트 파일에 인터넷 사용권한을 선언해야 합니다.

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

 

또, http 통신을 사용할 예정이므로, application 태그 안에 다음과 같이 usesCleartextTraffic 을 허용합니다.

<application
        android:usesCleartextTraffic="true"
       ...>
...
</application>
 

 

2. 레이아웃 파일 수정

 

이미지 업로드 버튼을 레이아웃 파일에 추가합니다.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_photo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="32dp"
        android:text="사진 가져오기"
        app:layout_constraintBottom_toTopOf="@+id/btn_upload"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="50dp"
        app:layout_constraintBottom_toTopOf="@+id/btn_photo"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_upload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="50dp"
        android:text="사진 업로드"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

 

3. MainActivity 수정

 

TedImagePicker 로 가져온 이미지의 Uri를 이용해서 서버로 이미지를 업로드 하기 위한 코드를 다음과 같이 매인 액티비티에 추가합니다.

서버와 통신하는 작업은 UI쓰레드에서 처리할 수 없기 때문에 Executor를 생성해서 처리하도록 합니다.

 

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.widget.Button;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import gun0912.tedimagepicker.builder.TedImagePicker;
import gun0912.tedimagepicker.builder.type.MediaType;

public class MainActivity extends AppCompatActivity {

//(...)
    Button btnUpload;
    private List<Uri> imageUriList = new ArrayList<>();
//(...)

    private ExecutorService executorService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
//(...)

        // ExecutorService 초기화
        executorService = Executors.newFixedThreadPool(4);

//(...)

        // 업로드 버튼 클릭 리스너 설정
        btnUpload.setOnClickListener(v -> {
            // 선택된 이미지가 존재하는지 확인
            if (!imageUriList.isEmpty()) {
                long timestamp = System.currentTimeMillis();
                for (int i = 0; i < imageUriList.size(); i++) {
                    String fileName = timestamp + "_" + (i + 1) + getFileExtension(imageUriList.get(i));
                    executorService.execute(new UploadTask(imageUriList.get(i), fileName));
                }
            } else {
                Toast.makeText(this, "선택된 이미지가 없습니다.", Toast.LENGTH_SHORT).show();
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (executorService != null && !executorService.isShutdown()) {
            executorService.shutdown();
        }
    }

    private class UploadTask implements Runnable {
        private Uri uri;
        private String fileName;

        public UploadTask(Uri uri, String fileName) {
            this.uri = uri;
            this.fileName = fileName;
        }

        @Override
        public void run() {
            HttpURLConnection connection = null;
            DataOutputStream outputStream = null;
            InputStream inputStream = null;

            String urlServer = "http://본인서버ip또는도메인주소/upload.php";
            String lineEnd = "\r\n";
            String twoHyphens = "--";
            String boundary = "*****";

            int bytesRead, bytesAvailable, bufferSize;
            byte[] buffer;
            int maxBufferSize = 1 * 1024 * 1024;

            try {
                // URI로부터 InputStream을 얻는다.
                inputStream = getContentResolver().openInputStream(uri);
                if (inputStream == null) {
                    throw new IOException("파일을 열 수 없습니다.");
                }

                URL url = new URL(urlServer);
                connection = (HttpURLConnection) url.openConnection();

                // 연결 설정 (POST, input, output 등 설정)
                connection.setDoInput(true);
                connection.setDoOutput(true);
                connection.setUseCaches(false);

                connection.setRequestMethod("POST");
                connection.setRequestProperty("Connection", "Keep-Alive");
                connection.setRequestProperty("ENCTYPE", "multipart/form-data");
                connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);

                outputStream = new DataOutputStream(connection.getOutputStream());

                outputStream.writeBytes(twoHyphens + boundary + lineEnd);
                outputStream.writeBytes("Content-Disposition: form-data; name=\"uploaded_file\";filename=\""
                        + fileName + "\"" + lineEnd);

                outputStream.writeBytes(lineEnd);

                buffer = new byte[maxBufferSize];
                bytesRead = inputStream.read(buffer, 0, maxBufferSize);

                while (bytesRead > 0) {
                    outputStream.write(buffer, 0, bytesRead);
                    bytesRead = inputStream.read(buffer, 0, maxBufferSize);
                }

                outputStream.writeBytes(lineEnd);
                outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

                // 응답 코드를 확인하고 파일 입력 스트림을 닫는다.
                int serverResponseCode = connection.getResponseCode();
                String serverResponseMessage = connection.getResponseMessage();

                outputStream.flush();
                outputStream.close();
                inputStream.close();

                // 서버 응답을 읽는다.
                InputStream responseStream = connection.getInputStream();
                StringBuilder sb = new StringBuilder();
                int ch;
                while ((ch = responseStream.read()) != -1) {
                    sb.append((char) ch);
                }
                responseStream.close();

                // 결과를 메인 스레드로 전달
                runOnUiThread(() -> Toast.makeText(MainActivity.this, sb.toString(), Toast.LENGTH_SHORT).show());
                Log.i("UploadTask", "응답 : " + sb.toString());

            } catch (Exception e) {
                Log.e("UploadTask", "Exception: ", e);
                runOnUiThread(() -> Toast.makeText(MainActivity.this, "업로드 실패 : " + e.getMessage(), Toast.LENGTH_SHORT).show());
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }
    }

    // URI에서 파일 확장자를 가져오는 메서드
    private String getFileExtension(@NonNull Uri uri) {
        String extension = null;

        // DocumentProvider
        if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
            ContentResolver contentResolver = getContentResolver();
            MimeTypeMap mime = MimeTypeMap.getSingleton();
            extension = mime.getExtensionFromMimeType(contentResolver.getType(uri));
        } else {
            // File scheme
            extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(new File(uri.getPath())).toString());
        }

        if (extension != null) {
            return "." + extension;
        } else {
            return "";
        }
    }

}

 

 

4. 서버측 PHP스크립트 작성

upload.php
<?php
$target_dir = "upload/";
if (!is_dir($target_dir)) {
    mkdir($target_dir, 0777, true);
}

$response = array();

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    if (isset($_FILES['uploaded_file'])) {
        $target_file = $target_dir . basename($_FILES['uploaded_file']['name']);
        if (move_uploaded_file($_FILES['uploaded_file']['tmp_name'], $target_file)) {
            $response['status'] = "success";
            $response['message'] = "File uploaded successfully";
        } else {
            $response['status'] = "error";
            $response['message'] = "Failed to upload file";
            $response['error'] = $_FILES['uploaded_file']['error'];
        }
    } else {
        $response['status'] = "error";
        $response['message'] = "No file uploaded";
    }
} else {
    $response['status'] = "error";
    $response['message'] = "Invalid request method";
}

echo json_encode($response);
?>