일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 스프링오류
- 트러블슈팅
- 동시성 제어
- 예외 핸들링
- nestjs
- 토스팀
- 대규모 트래픽
- 자바
- nginx
- 유난한 도전
- Jenkins
- connection reset by peer
- 분산시스템
- docker
- 3WayHandshake
- JavaScript
- 스프링 이미지
- 예외필터
- 동시성 문제
- 스프링기초
- 동시성문제
- 스프링jpa
- 토스책
- OS
- nestjs 예외
- 예외 커스텀
- nodejs
- 스프링
- Mysql이미지
- 스프링Entity
- Today
- Total
삽질블로그
[Android] 자바 스프링으로 이미지 Mysql에 전송 및 저장하기 (1) 본문
이번에 팀 프로젝트를 안드로이드로 진행하면서 이미지를 업로드하고 불러오는 코드를 작성해야 했다.
구글링을 통해 많은 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
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=seonhan98&logNo=221594027825
https://crazykim2.tistory.com/434
https://silversound-coding.tistory.com/21
'자바스프링' 카테고리의 다른 글
스프링 컴포넌트 스캔 @Conponent, @Controller, @Service, @Repository, Autowired 어노테이션 (0) | 2022.09.26 |
---|---|
회원관리 예제 Test코드 작성 (0) | 2022.09.25 |
스프링 회원관리 예제 (0) | 2022.09.25 |
스프링 기초 (0) | 2022.09.19 |
[Android] 자바 스프링으로 이미지 Mysql에 전송 및 저장하기 (2) (0) | 2022.09.18 |