삽질블로그

[Android] 자바 스프링으로 이미지 Mysql에 전송 및 저장하기 (1) 본문

자바스프링

[Android] 자바 스프링으로 이미지 Mysql에 전송 및 저장하기 (1)

삽질블로그 2022. 9. 16. 16:48

이번에 팀 프로젝트를 안드로이드로 진행하면서 이미지를 업로드하고 불러오는 코드를 작성해야 했다.

구글링을 통해 많은 Multipart로 올리는 방식?, 웹서버에 이미지를 업로드 하고 데베에는 그 경로를 저장하는 방식 같이

다양한 방법이 있는 것은 확인했는데 스프링도 안드로이드도 처음이라 모든게 어려웠다.

나는 이번 프로젝트에서 안드로이드에서 이미지 -> 비트맵 ->  바이트 배열 -> 스트링으로 변환 후 mysql에 직접 저장하는 방식으로 했다.

프로젝트가 끝나서 이미지를 저장했던 코드를 보기쉽게 새로 작성해서 정리해보고자 한다.

 

 

프로필에 사진을 추가하는 화면 UI

프로필 화면 클릭 시 권한 부여

 

 

프로필 사진 누르면 두 가지 옵션 선택 가능

 

에뮬레이터로 사진 촬영 후 이미지 등록했을 때 화면

 

앨범에서 이미지 등록했을 때 화면

 

 

AndroidManifest.xml 파일에 추가했던 권한들

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

 

res -> menu -> main.menu 파일 생성 후 코드 추가

 

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/one"
        android:title="CAMERA">
    </item>
    <item
        android:id="@+id/two"
        android:title="GALLERY">
    </item>
</menu>

 

ImageFragment.class

프로필 사진 클릭 시 동작하는 코드

profileImage.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        
        //res->menu->main_menu에 적어놨던 코드 가져오기
        PopupMenu pop = new PopupMenu(mContext, view);
        //카메라 or 갤러리 선택하는 코드 
        pop.getMenuInflater().inflate(R.menu.main_menu, pop.getMenu());
        pop.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                switch (menuItem.getItemId()) {
                    case R.id.one:
                        // 카메라 동작
                        sendTakePhotoIntent();
                        break;
                    case R.id.two:
                        //갤러리 동작
                        getAlbum();
                        break;
                }
                return true;
            }
        });
        pop.show();
        checkPermission();
    }
});

 

ImageFragment.class

안드로이드에서 카메라를 사용하려면 권한 확인

// 카메라 권한 확인
    public void checkPermission(){
        int permission = ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA);
        if(permission == PackageManager.PERMISSION_DENIED){
            ActivityCompat.requestPermissions((Activity) mContext,new String[]{Manifest.permission.CAMERA},0);
        }
    }

    //Permission에 대한 승인 완료확인 코드
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        //super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode==0){
            if(grantResults[0]==0){
                Toast.makeText(mContext,"카메라 권한 승인완료",Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(mContext,"카메라 권한 승인 거절",Toast.LENGTH_SHORT).show();
            }
        }
    }

 

ImageFragment.class

사진 앨범 or 직접 촬영

// 갤러리에서 이미지 가져오기
private void getAlbum(){
    isCamera = false;
    Intent intent = new Intent();
    intent.setType("image/*");
    intent.setAction(Intent.ACTION_GET_CONTENT);
    activityResultLauncher.launch(intent); // 프로필에 이미지 등록
}

// 이미지 직접 촬영
private void sendTakePhotoIntent() {
    isCamera = true;
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    activityResultLauncher.launch(takePictureIntent); // 프로필에 이미지 등록
}

 

ImageFragment.class

fragment라서 onCreateView 안에서 선언

// 나는 activityResultLauncher를 제일 위에 전역변수로 선언함 or 밑에 주석 코드처럼 선언해도됨
// private ActivityResultLauncher<Intent> activityResultLauncher = new  activityResultLauncher;
activityResultLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                String animalFace = "";
                if(result.getResultCode() == RESULT_OK) {
                    Bitmap bitMap = null;
                    Uri uri = null;
                    if(isCamera == true) {
                        //카메라로 직접 저장
                        Bundle bundle = result.getData().getExtras();
                        bitMap = (Bitmap) bundle.get("data");
                        // 이미지 이미지뷰에 비트맵으로 등록
                        profileImage.setImageBitmap(processing.rotate(bitMap, 90));
                    }
                    else {
                        // 앨범에서 사진 선택
                        Intent intent = result.getData();
                        uri = intent.getData();
                        // uri 비트맵으로 변경
                        try {
                            // mContext는 위에서 private Context mContext; 선언
                            bitMap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), uri);
                            // 이미지 이미지뷰에 비트맵으로 등록
                            profileImage.setImageBitmap(bitMap);

                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        //sendImagePath(uri);
                    }
                    //위에서 비트맵으로 변환한 이미지를 바이트 스트링으로 변환 후 스프링으로 전송
                    String image = processing.bitmapToByteArray(bitMap);
                    storeImageToDatabase(image);
                }
            }
        }
);

 

DataProcessing.class

utiliy -> DataProcessing 클래스에 비트맵 -> 바이트 변환하는 코드 따로 빼서 사용

bitmapToByteArray에 비트맵 넣으면 스트링으로 반환해줌

반환한 바이트 스트링값 ex) 01010110001~~~을 storeImageToDatabase로 전송

public String bitmapToByteArray(Bitmap bitmap) {
    String image = "";
    ByteArrayOutputStream stream = new ByteArrayOutputStream() ;
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream) ;
    byte[] byteArray = stream.toByteArray() ;
    image = byteArrayToBinaryString(byteArray);
    return image;
}

/**바이너리 바이트 배열을 스트링으로 바꾸어주는 메서드 */
public String byteArrayToBinaryString(byte[] b) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < b.length; ++i) {
        sb.append(byteToBinaryString(b[i]));
    }
    return sb.toString();
}

/**바이너리 바이트를 스트링으로 바꾸어주는 메서드 */
public String byteToBinaryString(byte n) {
    StringBuilder sb = new StringBuilder("00000000");
    for (int bit = 0; bit < 8; bit++) {
        if (((n >> bit) & 1) > 0) {
            sb.setCharAt(7 - bit, '1');
        }
    }
    return sb.toString();
}

 

ImageFragment.class

storeImagetoDatabase에서 스프링으로 바이트 스트링값 전송 

메소드 사용하기 전 retrofit 초기화해서 사용해야함 

나는 onCreateView에서 setRetrofit() 사용했음

private void storeImageToDatabase(String image) {
    Call<String> call = userService.insertImage(image);

    call.enqueue(new Callback<String>() {
        @Override
        public void onResponse(Call<String> call, Response<String> response) {
            String result = response.body();    // 웹서버로부터 응답받은 데이터가 들어있다.
            if (result != null) {
                System.out.println("Sucess");
            }
        }
        @Override
        public void onFailure(Call<String> call, Throwable t) { // 이거는 걍 통신에서 실패
            System.out.println(t);
        }
    });
}

 

ImageFragment.class

private void setRetrofit() {
    // 응답시간 지연해주는 코드, 사용하진 않았음
    OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
            .connectTimeout(1, TimeUnit.MINUTES)
            .readTimeout(3, TimeUnit.SECONDS)
            .writeTimeout(3, TimeUnit.SECONDS)
            .build();

    retrofit = new Retrofit.Builder()
            .baseUrl(Constants.SERVER_ADDRESS)
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    userService = retrofit.create(UserService.class);
}

 

UserService 인터페이스, POST로 전송

@POST("user/insertImage")
Call<String> insertImage(
        @Query("imageFile") String imageFile);

 

비트맵 크기가 너무 클 때 사이즈 줄이는 코드

//비트맵 사이즈 변경
private Bitmap resize(Bitmap bm){
    Configuration config=getResources().getConfiguration();
    if(config.smallestScreenWidthDp>=800)
        bm = Bitmap.createScaledBitmap(bm, 400, 240, true);
    else if(config.smallestScreenWidthDp>=600)
        bm = Bitmap.createScaledBitmap(bm, 300, 180, true);
    else if(config.smallestScreenWidthDp>=400)
        bm = Bitmap.createScaledBitmap(bm, 200, 120, true);
    else if(config.smallestScreenWidthDp>=360)
        bm = Bitmap.createScaledBitmap(bm, 180, 108, true);
    else
        bm = Bitmap.createScaledBitmap(bm, 160, 96, true);
    return bm;
}

 

비트맵 이미지 회전하는 코드

// 사진 회전하는 함수
public Bitmap rotate(Bitmap bitmap, float degree) {
    Matrix matrix = new Matrix();
    matrix.postRotate(degree);
    return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}

 

 

ImageFragment 전체 코드

 

package com.example.findingidealtypeapp.userService;

import static android.app.Activity.RESULT_OK;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;

import com.example.findingidealtypeapp.MainActivity;
import com.example.findingidealtypeapp.R;
import com.example.findingidealtypeapp.userServiceApi.myPageService.MyPageResponse;
import com.example.findingidealtypeapp.userServiceApi.UserService;
import com.example.findingidealtypeapp.utility.Constants;
import com.example.findingidealtypeapp.utility.DataProcessing;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;


public class ImageFragment extends Fragment {

    private TextView profileName, numberFollow, numberFollowing;
    private ImageView profileImage;
    private UserService userService;
    private Button profileEditButton;
    private ListView setList;
    private MainActivity activity;
    private ViewGroup rootView;
    private Retrofit retrofit;
    private ArrayAdapter arrayAdapter;
    private Context mContext;
    private String myEmail;
    private ActivityResultLauncher<Intent> activityResultLauncher;
    MyPageResponse myPageResponse = new MyPageResponse();
    private DataProcessing processing = new DataProcessing();
    private boolean isCamera = true;

    private List<String> menus = Arrays.asList(
            "도움말","안내","로그아웃"
    );

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        rootView = (ViewGroup)inflater.inflate(R.layout.fragment_image, container, false);
        profileName = rootView.findViewById(R.id.profile_name);
        numberFollow = rootView.findViewById(R.id.follow);
        numberFollowing = rootView.findViewById(R.id.following);
        profileEditButton = rootView.findViewById(R.id.profile_edit_button);
        setList = rootView.findViewById(R.id.setList);
        profileImage = rootView.findViewById(R.id.profile_image);
        profileEditButton = rootView.findViewById(R.id.profile_edit_button);
        mContext =container.getContext();

        arrayAdapter=new ArrayAdapter(mContext, android.R.layout.simple_expandable_list_item_1,menus);
        setList.setAdapter(arrayAdapter);

        //레트로핏 초기화
        setRetrofit();

        profileEditButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(getContext(), ProfileEditActivity.class);
                startActivity(intent);
            }
        });


        profileImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //res->menu->main_menu에 적어놨던 코드 가져오기
                PopupMenu pop = new PopupMenu(mContext, view);
                //카메라 or 갤러리 선택하는 코드
                pop.getMenuInflater().inflate(R.menu.main_menu, pop.getMenu());
                pop.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    @Override
                    public boolean onMenuItemClick(MenuItem menuItem) {
                        switch (menuItem.getItemId()) {
                            case R.id.one:
                                // 카메라 동작
                                sendTakePhotoIntent();
                                break;
                            case R.id.two:
                                //갤러리 동작
                                getAlbum();
                                break;
                        }
                        return true;
                    }
                });
                pop.show();
                checkPermission();
            }
        });

        // 나는 activityResultLauncher를 제일 위에 전역변수로 선언함 or 밑에 주석 코드처럼 선언해도됨
        // private ActivityResultLauncher<Intent> activityResultLauncher = new  activityResultLauncher;
        activityResultLauncher = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                new ActivityResultCallback<ActivityResult>() {
                    @Override
                    public void onActivityResult(ActivityResult result) {
                        String animalFace = "";
                        if(result.getResultCode() == RESULT_OK) {
                            Bitmap bitMap = null;
                            Uri uri = null;
                            if(isCamera == true) {
                                //카메라로 직접 저장
                                Bundle bundle = result.getData().getExtras();
                                bitMap = (Bitmap) bundle.get("data");
                                // 이미지 이미지뷰에 비트맵으로 등록
                                profileImage.setImageBitmap(processing.rotate(bitMap, 90));
                            }
                            else {
                                // 앨범에서 사진 선택
                                Intent intent = result.getData();
                                uri = intent.getData();
                                // uri 비트맵으로 변경
                                try {
                                    // mContext는 위에서 private Context mContext; 선언
                                    bitMap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), uri);
                                    // 이미지 이미지뷰에 비트맵으로 등록
                                    profileImage.setImageBitmap(bitMap);

                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                //sendImagePath(uri);
                            }
                            //위에서 비트맵으로 변환한 이미지를 바이트 스트링으로 변환 후 스프링으로 전송
                            String image = processing.bitmapToByteArray(bitMap);
                            storeImageToDatabase(image);
                        }
                    }
                }
        );
        return rootView;
    }



    // 카메라 권한 확인
    public void checkPermission(){
        int permission = ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA);
        if(permission == PackageManager.PERMISSION_DENIED){
            ActivityCompat.requestPermissions((Activity) mContext,new String[]{Manifest.permission.CAMERA},0);
        }
    }

    //Permission에 대한 승인 완료확인 코드
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        //super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode==0){
            if(grantResults[0]==0){
                Toast.makeText(mContext,"카메라 권한 승인완료",Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(mContext,"카메라 권한 승인 거절",Toast.LENGTH_SHORT).show();
            }
        }
    }

    // 갤러리에서 이미지 가져오기
    private void getAlbum(){
        isCamera = false;
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        activityResultLauncher.launch(intent); // 프로필에 이미지 등록
    }

    // 이미지 직접 촬영
    private void sendTakePhotoIntent() {
        isCamera = true;
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        activityResultLauncher.launch(takePictureIntent); // 프로필에 이미지 등록
    }


    //비트맵 사이즈 변경
    private Bitmap resize(Bitmap bm){
        Configuration config=getResources().getConfiguration();
        if(config.smallestScreenWidthDp>=800)
            bm = Bitmap.createScaledBitmap(bm, 400, 240, true);
        else if(config.smallestScreenWidthDp>=600)
            bm = Bitmap.createScaledBitmap(bm, 300, 180, true);
        else if(config.smallestScreenWidthDp>=400)
            bm = Bitmap.createScaledBitmap(bm, 200, 120, true);
        else if(config.smallestScreenWidthDp>=360)
            bm = Bitmap.createScaledBitmap(bm, 180, 108, true);
        else
            bm = Bitmap.createScaledBitmap(bm, 160, 96, true);
        return bm;
    }

    private void storeImageToDatabase(String image) {
        Call<String> call = userService.insertImage(image);

        call.enqueue(new Callback<String>() {
            @Override
            public void onResponse(Call<String> call, Response<String> response) {
                String result = response.body();    // 웹서버로부터 응답받은 데이터가 들어있다.
                if (result != null) {
                    System.out.println("Sucess");
                }
            }
            @Override
            public void onFailure(Call<String> call, Throwable t) { // 이거는 걍 통신에서 실패
                System.out.println(t);
            }
        });
    }

    private void setRetrofit() {
        // 응답시간 지연해주는 코드, 사용하진 않았음
        OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
                .connectTimeout(1, TimeUnit.MINUTES)
                .readTimeout(3, TimeUnit.SECONDS)
                .writeTimeout(3, TimeUnit.SECONDS)
                .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(Constants.SERVER_ADDRESS)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        userService = retrofit.create(UserService.class);
    }

}

스프링에서 이미지를 받아와서 mysql에 저장하는 코드는 다음 글에

 

 

참고자료

https://daldalhanstory.tistory.com/199

 

안드로이드 BLOB으로 서버에 이미지 간단히 저장하는 법 / 우분투 서버 사용 phpmyadmin/ mysql이용 / TI

오랜만에 글을 쓴다. 최근 주말 동안 비트코인이라는 신세계에 빠져서 안드로이드의 신경을 많이 쓰지 못했다. 그래서 그런지 막상 하려니까 머리가 복잡해지고 힘들었지만, 금방 다시 집중이

daldalhanstory.tistory.com

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=seonhan98&logNo=221594027825 

 

[ Android : Java ] 디바이스의 카메라와 갤러리를 이용하여 프로필 이미지 바꾸기 (구현)

1차 작성 : 2019-07-24 2차 작성 : 2019-07-25 최종작성 : 2019-07-26 GitHub에 android_camera 프로젝트...

blog.naver.com

https://crazykim2.tistory.com/434

 

[Android Studio] 비트맵(Bitmap) <-> 바이트(Byte) 변환 방법

안녕하세요 안드로이드 스튜디오[Android Studio] 프로젝트를 하는 중에 갤러리의 이미지를 비트맵으로 받아서 ImageView에 보여주고 서버에 전송하는 프로그램을 짜게 되었습니다 서버에 전송하기

crazykim2.tistory.com

https://silversound-coding.tistory.com/21

 

[Android] 서버로 이미지 업로드하기 (Java)

이미지 업로드 방식에는 다양한 방법이 있다. 하지만 나는 다른 방식들을 잘 이해하지 못해서, JSON으로 압축한 이미지 코드를 서버에 저장하는 형식을 사용하였다. 이 방식의 단점은 JSON string이

silversound-coding.tistory.com

 

Comments