MySQL에 이미지 저장하기 | Android Study

hoya·2021년 9월 13일
3

Android Study

목록 보기
3/19
post-thumbnail

😀 시작

프로그램을 만들 때 데이터베이스에 이미지 파일을 저장할 일이 종종 생기는데, 안드로이드에서는 해당 작업을 어떻게 수행하는지 알아보도록 하자. 해당 포스팅에서 통신 라이브러리는 Retrofit2 를 사용한다.

Retrofit2에 대한 이해가 부족하다면 여기로 😊


🙃 실습

📑 MySQL


create table measurement (
    mnum int not null auto_increment primary key,
    id varchar(50),
    image longblob,
    time int,
    start_time varchar(30),
    end_time varchar(30),
    dist double,
    kcal double,
    FOREIGN KEY measurement(id) references user(id)
) default character set utf8 collate utf8_general_ci;

ALTER TABLE measurement auto_increment = 1;

select * from measurement;

이번 포스팅에서 사용할 테이블은 위의 내용과 같다. 기본적으로 해당 테이블은 유저의 자전거 기록 측정 데이터를 보관할 용도로 사용된다.

MySQL을 설명하는 포스팅이 아니기 때문에 자질구레한 것들은 넘어가고, 주의깊게 봐야하는 부분은 이미지를 보관할 때 longblob 데이터형을 사용한다는 것이다.

🤔 blob?

Binary Large Object

위의 설명 그대로 바이너리 형태로 이미지, 비디오와 같이 큰 객체, 즉 멀티미디어 객체를 저장하는 용도로 사용된다. 크기에 따라 tinyblob, blob, mediumblob, longblob 으로 사용된다.


⚡ PHP

<?php
  $con = mysqli_connect("YOUR IP", "YOUR DB ID", "YOUR DB PASSWORD", "YOUR DB SCHEMA");
  mysqli_query($con, 'SET NAMES utf8');

  $requestMethod = $_SERVER["REQUEST_METHOD"];


  switch($requestMethod) {
		case 'GET' : // GET 방식, 주행기록 출력
			$id = $_GET["id"];
			
			$statement = mysqli_prepare($con, "select * from measurement where id = ?");
			mysqli_stmt_bind_param($statement, "s", $id);
			mysqli_stmt_execute($statement);

			mysqli_stmt_store_result($statement);
			mysqli_stmt_bind_result($statement, $mnum, $id, $image, $time, $start_time, $end_time, $avg_speed, $dist, $kcal);

			$response = array();
			$response["success"] = false;
			$result = array();

			while(mysqli_stmt_fetch($statement)){
			$image = base64_encode($image);
			$response["success"] = true;
			$response["mnum"] = $mnum;
			$response["id"] = $id;
			$response["image"] = $image;
			$response["time"] = $time;
			$response["start_time"] = $start_time;
			$response["end_time"] = $end_time;
			$response["avg_speed"] = $avg_speed;
			$response["dist"] = $dist;
			$response["kcal"] = $kcal;
			array_push($result, array(
				"success"=>$response["success"], 
				"mnum" => $response["mnum"],
				"id" => $response["id"],
				"image" => $response["image"],
				"time" => $response["time"],
				"start_time" => $response["start_time"],
				"end_time" => $response["end_time"],
				"avg_speed" => $response["avg_speed"],
				"dist" => $response["dist"],  
				"kcal" => $response["kcal"]));
			}
			echo json_encode($result);
			break;
		case 'POST' : // POST 방식, 주행기록 추가
			$id = $_POST["id"];
			$image = $_POST["image"];
			$time = $_POST["time"];
			$dist = $_POST["dist"];
			$kcal = $_POST["kcal"];
			$avg_speed = $_POST["avg_speed"];
			$start_time = $_POST["start_time"];
			$end_time = $_POST["end_time"];
			
			$image = base64_decode($image);
			
				
			$statement = mysqli_prepare($con, "INSERT INTO measurement(id,image, time, start_time, end_time, avg_speed, dist,kcal) VALUES (?,?,?,?,?,?,?,?)");	
			mysqli_stmt_bind_param($statement, "ssissddd", $id, $image, $time, $start_time, $end_time, $avg_speed, $dist, $kcal);
			mysqli_stmt_execute($statement);
				
			$response = array();
			$response["success"] = true;
			echo json_encode($response);
			break;
					
		default : 
			break;
  }
  mysqli_close($con);
?>

안드로이드에서 서버로 요청하는 HTTP 메소드가 어떤 것인지 파악한 후, 메소드에 맞춰 필요한 데이터를 MySQL 과 주고받는 코드이다. 해당 PHP 코드에 대한 기본적인 설명은 따로 포스팅이 존재하니 도움이 될 것이다. 😊

안드로이드에서 MySQL로 이미지를 보낼 때 Base64 문자열 디코딩 (base64_decode)을 해주고, 반대로 MySQL에서 안드로이드로 이미지를 보낼때는 Base64 문자열 인코딩(base64_encode)을 실시해주면 된다.

모든 요청마다 이미지를 인코딩/디코딩 하는 과정에서 서버에서 많은 처리 연산이 일어나므로 속도가 느려지는 것은 감안해야 한다. 😖

🤔 Base64?

바이너리 데이터를 공통 ASCII 영역의 문자로만 이루어진 문자열로 바꾸는 인코딩

바이너리 데이터를 전송할 때 일반 ASCII로 인코딩하여 전송하면 문제가 발생한다.

ASCII는 기본적으로 문자를 7비트 인코딩으로 표현하는데, 8비트를 채우기 위해 나머지 1비트를 처리하는 방식이 시스템별로 달라 시스템 간 데이터를 전달하기에 안전하지 않다는 것이다.

그래서 그 점을 보완하고자 Base64가 등장해 ASCII 중 제어문자와 일부 특수문자를 제외한 64개의 안전한 출력 문자만 사용하게끔 도와주는 것이다. 초기 문자보다 데이터의 크기가 커지는 단점이 있지만 웹 상에서 공통적으로 사용할 수 있기 때문에 많이 쓰인다.


이제 안드로이드에서 취해야 할 행동들을 정의하면 된다.

📌 데이터 모델 설정

public class Measure {

    @SerializedName("success")
    private boolean success;
    @SerializedName("mnum")
    private int mnum;
    @SerializedName("id")
    private String id;
    @SerializedName("image")
    private String image;
    @SerializedName("time")
    private int time;
    @SerializedName("start_time")
    private String start_time;
    @SerializedName("end_time")
    private String end_time;
    @SerializedName("avg_speed")
    private double avg_speed;
    @SerializedName("dist")
    private double dist;
    @SerializedName("kcal")
    private double kcal;

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }
    
    . . . 중략
    
}

데이터 모델은 늘 그렇듯 받아올 데이터를 그대로 설정해주면 된다.


📌 서비스 인터페이스 정의

public interface MeasureService {

    String MEASURE_URL = BuildConfig.SERVER; // 라즈베리파이 서버

    @GET("/android/userinfo/measure/measurement.php")
    Call<List<Measure>> getMeasure(
            @Query("id") String id
    );

    @FormUrlEncoded
    @POST("/android/userinfo/measure/measurement.php")
    Call<CheckSuccess> insertMeasure(
            @Field("id") String id,
            @Field("image") String image,
            @Field("time") int time,
            @Field("start_time") String start_time,
            @Field("end_time") String end_time,
            @Field("avg_speed") double avg_speed,
            @Field("dist") double dist,
            @Field("kcal") double kcal
    );
}

annotation 으로 HTTP 메소드를 지정하고, 보낼 데이터를 정의한다. Retrofit2 에 대한 이해가 있다면 어렵지 않을 것이다.


📌 이미지 MySQL에 저장하기

해당 다이얼로그에서 확인 버튼을 누르면 사진 속의 비트맵을 MySQL에 전송하는 코드를 구성해보도록 하자.

	Bitmap bitmap = your_bitmap; // 데이터베이스로 보낼 비트맵 선언
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 바이트 배열 데이터 출력 클래스 선언
        bitmap.compress(Bitmap.CompressFormat.PNG, 50, baos);
        // 비트맵을 PNG로 저장하며 압축한다. 해당 코드에서는 50%로 압축
        byte[] bytes = baos.toByteArray();
        // 압축 내용을 바이트 배열로 변환 후 bytes 변수에 저장
        
        String mapImage_String = Base64.encodeToString(bytes, Base64.DEFAULT);
        // 바이트 형식에서 스트링 형식으로 변환
        // 개행문자에 조심해야 한다면 매개변수로 Base64.NO_WRAP 사용
        
            insertButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                Gson gson = new GsonBuilder().setLenient().create();
                Retrofit retrofit = new Retrofit.Builder()
                        .baseUrl(MeasureService.MEASURE_URL)
                        .addConverterFactory(GsonConverterFactory.create(gson))
                        .build();

                MeasureService retrofitAPI = retrofit.create(MeasureService.class);
                
                // 데이터베이스 저장
                retrofitAPI.insertMeasure(userId, mapImage_String, timer, s_time, f_time, avg_speed, sum_dist, 10.00).enqueue(new Callback<CheckSuccess>() {
                    @Override
                    public void onResponse(Call<CheckSuccess> call, retrofit2.Response<CheckSuccess> response) {
                        if (response.isSuccessful()) {
                            Log.d("Measurement", "Response");
                            dismiss();
                        } else {
                            Toast.makeText(context, "오류 발생", Toast.LENGTH_SHORT);
                            dismiss();
                        }
                    }

                    @Override
                    public void onFailure(Call<CheckSuccess> call, Throwable t) {
                        Log.d("Measurement", "Not Response");
                        loadingDialog.dismiss();
                        t.printStackTrace();
                    }
                });
            }
        });

코드를 보았다면 이제 어떤식으로 돌아가는지는 감이 올 것이다. 🤗

이미지를 Base64 와 함께 문자열 형태로 바꾸는 것이 포인트이다. Retrofit 통신을 통해 MySQL로 데이터를 전송하게 되며, MySQL 에서는 이 Base64 형태의 문자열을 디코딩한 후 Blob 데이터로 이미지를 저장하게 된다.


📌 MySQL에서 이미지 불러오기

위의 사진과 같이 리사이클러뷰에 이미지를 담아내보자.


    String mapString = datas.get(position).getImage(); // 데이터에서 이미지를 받아온다.
    Bitmap bitmap = StringToBitmaps(mapString);
            
    // String -> Bitmap 변환
    public static Bitmap StringToBitmaps(String image) {
        try {
            byte[] encodeByte = Base64.decode(image, Base64.DEFAULT);
            // Base64 코드를 디코딩하여 바이트 형태로 저장
            Bitmap bitmap = BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.length);
            // 바이트 형태를 디코딩하여 비트맵 형태로 저장
            return bitmap;
        } catch (Exception e) {
            e.getMessage();
            return null;
        }
    }

리사이클러뷰에서 이미지를 넣는 부분이다.

크게 어려울 점은 없다. PHP에서 Base64 로 인코딩하여 안드로이드로 전송하니 안드로이드에선 그대로 디코딩하여 사용하면 된다.


코드를 이해했다면 흐름도 같이 이해할 수 있었을 것이다.
Base64를 이용해 이미지를 보내는 것이 포인트이다.
흐름을 완벽히 이해해 안드로이드에서 이미지를 자유자재로 다룰 수 있길 바란다.


참고 및 출처

Base64 인코딩이란
mysql - MySQL-Base64 및 BLOB
Android에서 image BLOB MySQL에 저장하기

profile
즐겁게 하자 🤭

2개의 댓글

comment-user-thumbnail
2022년 11월 16일

저 안녕하세요 혹시 이 코드들은 어디에 작성을 해야하나요?

답글 달기
comment-user-thumbnail
2023년 1월 24일

잘보고 갑니다. 역시 이미지같은 것들은 웹서버를 이용하는게 편하겠군요..

답글 달기