Retrofit2는 안드로이드에서 REST API 통신을 하게 해주는 라이브러리다.
Retrofit2 라이브러리를 사용해 이미지를 보내고 AI로 해당 이미지의 분석 결과를 받는 코드를 작성해보았고, 사용법에 대해 조사해보았다.
dependencies {
...
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0") // GSON 변환기
implementation("com.squareup.okhttp3:logging-interceptor:4.9.2") // HTTP 요청 및 응답 로그 출력 인터셉터
}
API 응답을 받을 형식의 클래스를 정의한다.
import com.google.gson.annotations.SerializedName;
public class ObjectDetectResponse {
@SerializedName("name") String name;
@SerializedName("confidence") double confidence;
@SerializedName("xmin") double xmin;
@SerializedName("ymin") double ymin;
@SerializedName("xmax") double xmax;
@SerializedName("ymax") double ymax;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getConfidence() {
return confidence;
}
public void setConfidence(double confidence) {
this.confidence = confidence;
}
public double getXmin() {
return xmin;
}
public void setXmin(double xmin) {
this.xmin = xmin;
}
public double getYmin() {
return ymin;
}
public void setYmin(double ymin) {
this.ymin = ymin;
}
public double getXmax() {
return xmax;
}
public void setXmax(double xmax) {
this.xmax = xmax;
}
public double getYmax() {
return ymax;
}
public void setYmax(double ymax) {
this.ymax = ymax;
}
}
@SerializedName 어노테이션을 사용해 JSON 응답의 key 값과 매칭되도록 설정하였다.
@SerializedName("name") String name;
@SerializedName("confidence") double confidence;
@SerializedName("xmin") double xmin;
@SerializedName("ymin") double ymin;
@SerializedName("xmax") double xmax;
@SerializedName("ymax") double ymax;
그 후 각 값들의 Getter와 Setter를 만들어주었다.
API 통신을 위한 엔드포인트 인터페이스를 정의하였다.
import okhttp3.MultipartBody;
import retrofit2.Call;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
public interface ApiService {
@Multipart
@POST("object/detect")
Call<ObjectDetectResponse> uploadImage(@Part MultipartBody.Part image);
}
서버로 데이터를 전송하기 위해 @Post 어노테이션을 사용하였다.
@Post 후 괄호에 들어가는 "object/detect"가 엔드포인트가 된다.
사진 파일을 서버로 전송하기 때문에 @Multipart 와 @Part 어노테이션을 사용하였다.
Retrofit 인스턴스 생성과 API 호출 역할을 하는 클래스 RetrofitHelper 를 싱글톤 패턴으로 만들었다.
import android.util.Log;
import com.google.gson.GsonBuilder;
import java.util.concurrent.TimeUnit;
import kr.co.edoubles.carlostdetect.utils.ConstantsKt;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitHelper {
private static final String TAG = "RetrofitHelper";
private static RetrofitHelper instance;
private final ApiService apiService;
private RetrofitHelper() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ConstantsKt.BASE_URL)
.addConverterFactory(providesConverterFactory())
.client(providesOkHttpClient().build())
.build();
apiService = retrofit.create(ApiService.class);
}
public static synchronized RetrofitHelper getInstance() {
if (instance == null) {
instance = new RetrofitHelper();
}
return instance;
}
private static GsonConverterFactory providesConverterFactory() {
return GsonConverterFactory.create(new GsonBuilder().setLenient().create());
}
private static OkHttpClient.Builder providesOkHttpClient() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.addInterceptor(loggingInterceptor);
}
public void getResult(MultipartBody.Part image) {
Call<ObjectDetectResponse> result = apiService.uploadImage(image);
result.enqueue(new Callback<ObjectDetectResponse>() {
@Override
public void onResponse(Call<ObjectDetectResponse> call, Response<ObjectDetectResponse> response) {
if(response.isSuccessful()) {
Log.d(TAG, "Upload Success " + response.body());
// 이곳에 성공 시 실행 코드 작성
} else {
Log.d(TAG, "Upload failed: " + response.code());
}
}
@Override
public void onFailure(Call<ObjectDetectResponse> call, Throwable t) {
t.getStackTrace();
}
});
}
}
먼저 Retrofit 인스턴스를 생성하였다.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ConstantsKt.BASE_URL)
.addConverterFactory(providesConverterFactory())
.client(providesOkHttpClient().build())
.build();
.baseUrl() 에는 기본 URL을 설정해준다. ex) https://www.naver.com/
.addConverterFactory() 에는 Json 객체를 변환해주는 Gson 컨버터 객체를 넣어준다.
private static GsonConverterFactory providesConverterFactory() {
return GsonConverterFactory.create(new GsonBuilder().setLenient().create());
}
setLenient()를 사용하여 파싱을 자유롭게 해주었다.
.client() 를 사용하여 Timeout, 로깅 등을 설정하는 OkHttpClient를 설정해주었다.
private static OkHttpClient.Builder providesOkHttpClient() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.addInterceptor(loggingInterceptor);
}
그 후 생성한 Retrofit 인스턴스를 사용하여 ApiService 객체를 만들어주었다.
apiService = retrofit.create(ApiService.class);
API 요청을 보내는 메소드 getResult(MultipartBody.Part image) 를 만들었다.
public void getResult(MultipartBody.Part image) {
Call<ObjectDetectResponse> result = apiService.uploadImage(image);
result.enqueue(new Callback<ObjectDetectResponse>() {
@Override
public void onResponse(Call<ObjectDetectResponse> call, Response<ObjectDetectResponse> response) {
if(response.isSuccessful()) {
Log.d(TAG, "Upload Success " + response.body());
// 이곳에 성공 시 실행 코드 작성
} else {
Log.d(TAG, "Upload failed: " + response.code());
}
}
@Override
public void onFailure(Call<ObjectDetectResponse> call, Throwable t) {
t.getStackTrace();
}
});
}
ApiService 객체를 사용해 Retrofit API 요청 객체를 생성하였다.
Call<ObjectDetectResponse> result = apiService.uploadImage(image);
uploadImage() 메소드를 사용하면 기본 URL에 ApiService에서 @Post로 설정한 주소를 합친
기본 URL + object/detect
주소로 요청을 하게 된다.
Retrofit API 요청 객체에 enqueue() 를 사용하여 비동기 네트워크 요청을 실행한다.
result.enqueue(new Callback<ObjectDetectResponse>() {
@Override
public void onResponse(Call<ObjectDetectResponse> call, Response<ObjectDetectResponse> response) {
if(response.isSuccessful()) {
Log.d(TAG, "Upload Success " + response.body());
// 이곳에 성공 시 실행 코드 작성
} else {
Log.d(TAG, "Upload failed: " + response.code());
}
}
@Override
public void onFailure(Call<ObjectDetectResponse> call, Throwable t) {
t.getStackTrace();
}
});
요청에 성공하면 onResponse() 가 실행되고 response.body()에 응답 내용이 들어간다.
@GET("object/{id}")
Call<ObjectDetectResponse> uploadImage(@Path("id") int id);
@POST와 함께 서버에 데이터를 전송할 때 사용@POST("object/detect")
Call<ObjectDetectResponse> uploadImage(@Body String a);
@PUT("object/{id}")
Call<ObjectDetectResponse> uploadImage(@Path("id") int id, @Body ObjectDetectResponse objectDetectResponse);
@DELETE("object/{id}")
Call<Void> uploadImage(@Path("id") int id);
https 가 아닌 http 주소를 사용한다면
AndroidManifest.xml 의 <application> 태그 안에
android:usesCleartextTraffic="true"
를 추가해줘야 한다.