Open eunja511005 opened 1 year ago
2. res/layout/activity_main.xml 파일 수정
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/selectedImage"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerInParent="true"
android:src="@drawable/ic_launcher_foreground" />
<EditText
android:id="@+id/nameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/selectedImage"
android:hint="Enter Name"
android:padding="16dp" />
<Button
android:id="@+id/selectImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/nameEditText"
android:layout_centerHorizontal="true"
android:text="Select Image" />
<Button
android:id="@+id/uploadButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/selectImageButton"
android:layout_centerHorizontal="true"
android:text="Upload" />
</RelativeLayout>
3. java/com/example/mysendimageapplication/MainActivity.java 수정
MainActivity.java 파일을 열고, 다음과 같이 selectImageButton 버튼 클릭 이벤트를 처리합니다.
public class MainActivity extends AppCompatActivity {
private static final int PICK_IMAGE_REQUEST = 1;
private ImageView selectedImage;
private EditText nameEditText;
private Uri selectedImageUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
selectedImage = findViewById(R.id.selectedImage);
nameEditText = findViewById(R.id.nameEditText);
Button selectImageButton = findViewById(R.id.selectImageButton);
selectImageButton.setOnClickListener(view -> openGallery());
}
private void openGallery() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, PICK_IMAGE_REQUEST);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null) {
selectedImageUri = data.getData();
selectedImage.setImageURI(selectedImageUri);
}
}
}
4. Retrofit 라이브러리를 사용하기 위해 build.gradle 파일에 아래 의존성을 추가
// Retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
implementation 'com.squareup.retrofit2:converter-gson:2.7.1'
implementation 'com.squareup.okhttp3:logging-interceptor:4.3.1'
5. Retrofit 서비스 인터페이스를 정의합니다. ApiService.java 파일을 생성하고 다음과 같이 작성합니다.
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
public interface ApiService {
@Multipart
@POST("save")
Call<ApiResponse> saveUserManage(
@Part MultipartBody.Part file,
@Part("name") RequestBody name
);
}
6. AndroidManifest.xml 권한 추가
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
7. 실행을 위한 모바일 장치 생성
중요 : 안드로이드 스튜디오는 꼭 관리자 권한으로 실행해야 설치할 파일들이 제대로 설치된다.
8. 모바일 개발자 모드 켜기
9. 모바일로 배포
10. HTTPS가 아니라 HTTP로 전송해도 오류 안나게 하려면 아래 내용 추가해야 함
android:usesCleartextTraffic="true"
[중요] 아래 오류 발생시 해결 방법
com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
최종 MainActivity 소스
package com.example.mysendimageapplication;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Base64;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.File;
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;
public class MainActivity extends AppCompatActivity {
private static final String BASE_URL = "http://192.168.219.106:8080/";
private ImageView selectedImage;
private EditText nameEditText;
private Uri selectedImageUri;
private String token;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
selectedImage = findViewById(R.id.selectedImage);
nameEditText = findViewById(R.id.nameEditText);
Button selectImageButton = findViewById(R.id.selectImageButton);
selectImageButton.setOnClickListener(view -> openGallery());
Button uploadButton = findViewById(R.id.uploadButton);
uploadButton.setOnClickListener(view -> getToken());
}
private void openGallery() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
imagePickerLauncher.launch(intent);
}
private ActivityResultLauncher<Intent> imagePickerLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
selectedImageUri = result.getData().getData();
selectedImage.setImageURI(selectedImageUri);
}
}
);
private ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
// 권한이 허용된 경우
uploadData();
} else {
// 권한이 거부된 경우
// 사용자에게 권한 필요 안내 등을 표시할 수 있음
}
});
private void uploadData() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService apiService = retrofit.create(ApiService.class);
if (token != null) {
// 추가할 Authorization 헤더
String authorizationHeader = "Bearer " + token;
String name = nameEditText.getText().toString();
RequestBody requestFile = RequestBody.create(MediaType.parse("image/*"), new File(getRealPathFromURI(selectedImageUri)));
MultipartBody.Part photoPart = MultipartBody.Part.createFormData("file", "image.jpg", requestFile);
RequestBody nameBody = RequestBody.create(MediaType.parse("text/plain"), name);
Call<ApiResponse> call = apiService.saveUserManage(authorizationHeader, photoPart, nameBody);
call.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
if (response.isSuccessful()) {
// 서버 응답 처리
ApiResponse apiResponse = response.body();
if (apiResponse != null) {
// 성공한 경우
Log.d("API_SUCCESS", apiResponse.getMessage());
}
} else {
// 서버 응답은 받았으나 성공하지 않은 경우
Log.e("API_ERROR", "Server responded with error code: " + response.code());
}
}
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
// 요청 실패
Log.e("API_FAILURE", "API request failed", t);
}
});
}
} else {
// 권한이 허용되지 않은 경우
requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE);
}
}
private String getRealPathFromURI(Uri contentUri) {
String[] projection = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(contentUri, projection, null, null, null);
if (cursor == null) return null;
int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
String path = cursor.getString(columnIndex);
cursor.close();
return path;
}
private void getToken() {
Gson gson = new GsonBuilder()
.setLenient()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL) // 실제 서버 주소로 변경해야 함
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
ApiService apiService = retrofit.create(ApiService.class);
UserInfoDTO userInfoDTO = new UserInfoDTO();
userInfoDTO.setUsername("apiUser");
userInfoDTO.setPassword("jw0713!@");
Call<String> call = apiService.getToken(userInfoDTO);
call.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
if (response.isSuccessful()) {
token = response.body();
uploadData();
} else {
// 서버 응답은 받았으나 인증 실패 또는 에러인 경우
Log.e("API_ERROR", "Server responded with error code: " + response.code());
try {
String errorBody = response.errorBody().string();
Log.e("API_ERROR_BODY", "Error body: " + errorBody);
} catch (IOException e) {
Log.e("API_ERROR", "Error while parsing error body", e);
}
}
}
@Override
public void onFailure(Call<String> call, Throwable t) {
// 요청 실패
Log.e("API_FAILURE", "API request failed", t);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && data != null) {
selectedImageUri = data.getData();
selectedImage.setImageURI(selectedImageUri);
}
}
}
파일을 받기 위한 서버 소스
@PostMapping("/mobile/image/save")
public @ResponseBody ApiResponse saveUserManage(@RequestParam("file") MultipartFile file, UserManageDTO userManageDTO) throws IOException {
userManageDTO.setPicture(fileUtil.saveImage(file, "openImg/mobile"));
//userManageService.mergeUser(userManageDTO);
return new ApiResponse<>(true, "Success save", null);
}