Ver2.0 안드로이드 네트워크 통신의 끝판왕 🚀

안드로이드 네트워크 통신의 끝판왕 🚀
Retrofit과 OkHttp로 앱을 더 쿨하게 만들어보자!
안녕하세요 개발자 여러분~! 오늘은 안드로이드 앱 개발할 때 네트워크 통신의 필수템인 Retrofit과 OkHttp에 대해 함께 알아볼게요! 서버랑 통신 없이 앱 만들기? 그거 이제 불가능한 시대잖아요 ㅋㅋㅋ 요즘 앱들 보면 다 서버랑 데이터 주고받는 게 기본이죠! 😎
혹시 아직도 AsyncTask나 HttpURLConnection 쓰고 계신가요? 그렇다면 지금이 바로 업그레이드할 타이밍! Retrofit과 OkHttp는 Java/Kotlin 개발자들 사이에서 네트워크 통신의 끝판왕으로 불리는 라이브러리랍니다. 재능넷에서도 안드로이드 개발 관련 재능을 찾아보면 이 두 라이브러리를 활용한 프로젝트가 정말 많더라고요! 그만큼 현업에서 필수로 사용되는 기술이니 꼭 마스터해두세요~
📚 목차
- 네트워크 통신의 기본 개념
- OkHttp란 무엇인가?
- Retrofit의 등장과 특징
- Retrofit + OkHttp 설정하기
- API 인터페이스 정의하기
- 요청 보내고 응답 처리하기
- 고급 기능 활용하기
- 실전 예제: 날씨 앱 만들기
- 성능 최적화 팁
- 마무리 및 추천 자료
1. 네트워크 통신의 기본 개념 🌐
우리가 앱을 만들 때 서버와 통신하는 건 마치 카톡으로 친구랑 대화하는 것과 비슷해요! 메시지 보내고(Request) 답장 받고(Response) 하는 과정이죠. 근데 이 과정이 생각보다 복잡하답니다. 😅
HTTP 통신이란? 🤔
HTTP(HyperText Transfer Protocol)는 웹에서 데이터를 주고받는 약속(프로토콜)이에요. 우리가 웹사이트 접속할 때도 이 HTTP를 사용하죠! 안드로이드 앱에서도 서버와 통신할 때 이 HTTP를 사용해요.
HTTP 메소드에는 여러 종류가 있어요:
- GET: 데이터 조회할 때 (예: 게시글 목록 불러오기)
- POST: 새 데이터 만들 때 (예: 회원가입, 글쓰기)
- PUT: 데이터 전체 수정할 때 (예: 프로필 전체 업데이트)
- PATCH: 데이터 일부 수정할 때 (예: 닉네임만 변경)
- DELETE: 데이터 삭제할 때 (예: 게시글 삭제)
예전에는 안드로이드에서 네트워크 통신하려면 HttpURLConnection이나 AsyncTask 같은 걸 써야 했는데... 솔직히 코드가 너무 길고 복잡했어요. ㅠㅠ 그래서 등장한 게 바로 OkHttp와 Retrofit이랍니다! 👏
2. OkHttp란 무엇인가? 🔍
OkHttp는 Square라는 회사에서 만든 초강력 HTTP 클라이언트예요! 쉽게 말하면 서버와 통신을 담당하는 택배기사 같은 존재랍니다. 😄
OkHttp의 장점 💪
- 연결 풀링: 같은 서버로 여러 요청을 보낼 때 연결을 재사용해서 성능이 빵빵해요!
- GZIP 압축: 데이터를 압축해서 다운로드 속도가 빨라져요. 데이터 요금 절약에도 굿!
- 응답 캐싱: 같은 요청은 캐시해서 불필요한 네트워크 통신을 줄여줘요.
- 실패 시 자동 재시도: 네트워크 불안정할 때도 알아서 다시 시도해줘요. 짱편해!
- 모던한 TLS 기능: 보안도 확실하게 챙겨줘요.
OkHttp만으로도 네트워크 통신이 가능하지만, 코드가 좀 길어질 수 있어요. 간단한 GET 요청 예시를 볼까요?
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.example.com/users")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
// 응답 처리
String responseData = response.body().string();
System.out.println(responseData);
} catch (IOException e) {
e.printStackTrace();
}
음... 간단한 요청인데도 코드가 좀 많죠? 😅 게다가 이건 동기 방식이라 UI 스레드에서 직접 호출하면 NetworkOnMainThreadException 에러가 발생해요! 비동기로 처리하려면 더 복잡해지고... 이런 불편함을 해결하기 위해 Retrofit이 등장했답니다! 🎉
3. Retrofit의 등장과 특징 ✨
Retrofit도 Square에서 만든 라이브러리인데요, OkHttp를 기반으로 만들어졌어요. Retrofit은 REST API를 자바 인터페이스로 변환해주는 마법 같은 라이브러리랍니다! 🧙♂️
Retrofit의 슈퍼파워 🦸♀️
- 인터페이스 기반 API 정의: 메소드 몇 개만 선언하면 끝! 나머지는 Retrofit이 알아서~
- 어노테이션으로 HTTP 요청 설정:
@GET,@POST같은 어노테이션으로 직관적! - 자동 JSON 변환: Gson, Moshi 등과 연동해서 JSON을 자바 객체로 자동 변환!
- 비동기 처리 내장:
Call객체로 비동기 처리가 쉬워요. - RxJava, Coroutines 지원: 모던한 비동기 처리 방식과 찰떡궁합!
Retrofit을 사용하면 아까 봤던 OkHttp 코드가 얼마나 간단해지는지 볼까요? 😮
// API 인터페이스 정의
public interface ApiService {
@GET("users")
Call<List<User>> getUsers();
}
// Retrofit 객체 생성
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
// API 서비스 생성
ApiService service = retrofit.create(ApiService.class);
// API 호출
service.getUsers().enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
if (response.isSuccessful()) {
List<User> users = response.body();
// 데이터 처리
}
}
@Override
public void onFailure(Call<List<User>> call, Throwable t) {
// 에러 처리
}
});
어때요? 훨씬 깔끔하죠? 😎 인터페이스만 정의하면 실제 구현은 Retrofit이 알아서 해주니까 개발자는 비즈니스 로직에만 집중할 수 있어요. 이런 마법같은 기능 때문에 재능넷에서도 안드로이드 개발자들이 Retrofit을 많이 활용한답니다!
4. Retrofit + OkHttp 설정하기 🛠️
자, 이제 실제로 프로젝트에 Retrofit과 OkHttp를 추가해볼까요? 먼저 build.gradle 파일에 의존성을 추가해야 해요!
build.gradle (app)
dependencies {
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// OkHttp
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
// Gson (JSON 파싱용)
implementation 'com.google.code.gson:gson:2.9.0'
}
의존성을 추가했으면 Sync Now를 눌러서 라이브러리를 다운로드 받아주세요! 그리고 인터넷 권한도 추가해야 해요.
AndroidManifest.xml
<manifest ...>
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
이제 OkHttp 클라이언트와 Retrofit 인스턴스를 설정해볼게요! 보통은 싱글톤 패턴으로 만들어서 앱 전체에서 공유해요.
RetrofitClient.java
public class RetrofitClient {
private static final String BASE_URL = "https://api.example.com/";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
// OkHttp 클라이언트 설정
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
// 로깅 인터셉터 추가 (개발 중에 요청/응답 로그 확인용)
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addInterceptor(logging);
// 타임아웃 설정
httpClient.connectTimeout(15, TimeUnit.SECONDS);
httpClient.readTimeout(15, TimeUnit.SECONDS);
httpClient.writeTimeout(15, TimeUnit.SECONDS);
// Retrofit 빌더 설정
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
}
return retrofit;
}
}
로깅 인터셉터를 추가하면 개발할 때 네트워크 요청/응답 내용을 로그로 확인할 수 있어서 디버깅이 훨씬 쉬워져요! 근데 배포 버전에선 보안을 위해 꼭 제거하거나 레벨을 NONE으로 설정해야 해요! 😉
💡 프로덕션 팁!
실제 앱 출시할 때는 개발용과 프로덕션용 API URL을 분리하는 게 좋아요. BuildConfig나 Flavor를 활용하면 빌드 타입에 따라 자동으로 다른 URL을 사용할 수 있답니다!
5. API 인터페이스 정의하기 📝
Retrofit의 진짜 매력은 API 인터페이스를 정의하는 방식이에요! 어노테이션만으로 HTTP 요청을 설정할 수 있어서 코드가 엄청 깔끔해져요. 😍
예를 들어, 사용자 정보를 가져오는 API를 정의해볼게요:
public interface UserApiService {
// 모든 사용자 목록 가져오기
@GET("users")
Call<List<User>> getUsers();
// 특정 사용자 정보 가져오기
@GET("users/{id}")
Call<User> getUserById(@Path("id") int userId);
// 새 사용자 등록하기
@POST("users")
Call<User> createUser(@Body User user);
// 사용자 정보 업데이트
@PUT("users/{id}")
Call<User> updateUser(@Path("id") int userId, @Body User user);
// 사용자 삭제
@DELETE("users/{id}")
Call<Void> deleteUser(@Path("id") int userId);
// 쿼리 파라미터 사용 예시
@GET("users")
Call<List<User>> searchUsers(@Query("name") String name, @Query("age") int age);
}
위 코드에서 사용된 어노테이션들을 살펴볼게요:
Retrofit 어노테이션 총정리 📌
- @GET, @POST, @PUT, @DELETE, @PATCH: HTTP 메소드를 지정해요.
- @Path: URL의 동적 부분을 치환해요. 예:
users/{id}에서 {id} 부분 - @Query: URL 쿼리 파라미터를 추가해요. 예:
?name=홍길동&age=25 - @QueryMap: 여러 쿼리 파라미터를 Map으로 한번에 전달해요.
- @Body: 요청 본문에 객체를 JSON으로 변환해서 전송해요.
- @Field: form-urlencoded 형식으로 데이터를 전송할 때 사용해요.
- @FormUrlEncoded: @Field를 사용할 때 메소드에 붙여줘야 해요.
- @Multipart: 파일 업로드 같은 멀티파트 요청을 할 때 사용해요.
- @Part: 멀티파트 요청의 각 부분을 정의해요.
- @Header: HTTP 헤더를 추가해요.
- @Headers: 여러 HTTP 헤더를 한번에 추가해요.
이제 API 응답을 받을 모델 클래스도 만들어볼게요. Gson이 JSON을 자동으로 이 객체로 변환해줄 거예요!
User.java
public class User {
private int id;
private String name;
private String email;
private int age;
// 기본 생성자 (Gson이 필요로 함)
public User() {
}
// 생성자
public User(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
// Getter와 Setter (생략)
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
JSON 필드명이 Java 변수명과 다를 경우에는 @SerializedName 어노테이션을 사용하면 돼요:
public class User {
private int id;
@SerializedName("user_name") // JSON에서는 user_name으로 표기
private String name;
@SerializedName("user_email") // JSON에서는 user_email로 표기
private String email;
private int age;
// 나머지 코드는 동일
}
이렇게 하면 JSON의 user_name이 Java 객체의 name으로 자동 매핑돼요! 진짜 편하죠? ㅋㅋㅋ
6. 요청 보내고 응답 처리하기 📤📥
API 인터페이스를 정의했으니 이제 실제로 요청을 보내고 응답을 처리해볼게요! Retrofit은 동기식과 비동기식 두 가지 방법을 모두 지원해요.
비동기 요청 (권장)
// API 서비스 인스턴스 생성
UserApiService apiService = RetrofitClient.getClient().create(UserApiService.class);
// 비동기 요청 보내기
apiService.getUsers().enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
if (response.isSuccessful()) {
List<User> users = response.body();
// 성공적으로 데이터를 받았을 때 처리
for (User user : users) {
Log.d("API", "User: " + user.getName());
}
} else {
// 서버에서 오류 응답을 보냈을 때 처리
Log.e("API", "Error: " + response.code());
try {
Log.e("API", "Error Body: " + response.errorBody().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Call<List<User>> call, Throwable t) {
// 네트워크 오류 등으로 요청 자체가 실패했을 때 처리
Log.e("API", "Request failed: " + t.getMessage());
}
});
enqueue() 메소드를 사용하면 백그라운드 스레드에서 요청이 처리되기 때문에 UI 스레드를 차단하지 않아요. 안드로이드에서는 이 방식을 권장해요! 😊
동기 요청 (주의: UI 스레드에서 사용 금지!)
// 반드시 백그라운드 스레드에서 실행해야 함!
new Thread(new Runnable() {
@Override
public void run() {
try {
Response<List<User>> response = apiService.getUsers().execute();
if (response.isSuccessful()) {
List<User> users = response.body();
// 데이터 처리
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
동기 요청은 execute() 메소드를 사용하는데, 절대로 UI 스레드에서 호출하면 안 돼요! 앱이 뻗어버려요! 😱
실전 예제: 사용자 등록하기
// 새 사용자 객체 생성
User newUser = new User("홍길동", "hong@example.com", 25);
// POST 요청 보내기
apiService.createUser(newUser).enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
User createdUser = response.body();
Toast.makeText(MainActivity.this,
"사용자 등록 성공! ID: " + createdUser.getId(),
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this,
"등록 실패: " + response.code(),
Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
Toast.makeText(MainActivity.this,
"네트워크 오류: " + t.getMessage(),
Toast.LENGTH_SHORT).show();
}
});
안드로이드에서는 ViewModel과 LiveData를 함께 사용하면 더 깔끔한 코드를 작성할 수 있어요! 재능넷에서도 많은 안드로이드 개발자들이 이런 방식으로 코드를 작성한답니다. 😎
ViewModel과 함께 사용하기
public class UserViewModel extends ViewModel {
private MutableLiveData<List<User>> users = new MutableLiveData<>();
private MutableLiveData<String> error = new MutableLiveData<>();
private UserApiService apiService;
public UserViewModel() {
apiService = RetrofitClient.getClient().create(UserApiService.class);
}
public LiveData<List<User>> getUsers() {
return users;
}
public LiveData<String> getError() {
return error;
}
public void loadUsers() {
apiService.getUsers().enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
if (response.isSuccessful()) {
users.setValue(response.body());
} else {
error.setValue("오류 코드: " + response.code());
}
}
@Override
public void onFailure(Call<List<User>> call, Throwable t) {
error.setValue("네트워크 오류: " + t.getMessage());
}
});
}
}
Activity나 Fragment에서는 이렇게 사용하면 돼요:
UserViewModel viewModel = new ViewModelProvider(this).get(UserViewModel.class);
viewModel.getUsers().observe(this, users -> {
// 사용자 목록 업데이트
adapter.setUsers(users);
});
viewModel.getError().observe(this, errorMsg -> {
if (errorMsg != null) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}
});
// 데이터 로드 시작
viewModel.loadUsers();
이렇게 하면 화면 회전 같은 설정 변경이 일어나도 데이터가 유지되고, UI 로직과 데이터 로직이 깔끔하게 분리돼요! 👍
7. 고급 기능 활용하기 🚀
Retrofit과 OkHttp는 기본 기능만으로도 충분히 강력하지만, 고급 기능을 활용하면 더 멋진 앱을 만들 수 있어요! 몇 가지 꿀팁을 알려드릴게요~ 🍯
1. 인터셉터로 헤더 자동 추가하기
모든 요청에 인증 토큰 같은 헤더를 추가하고 싶다면? 매번 코드를 작성하기 귀찮죠! 인터셉터를 사용하면 한 번에 해결할 수 있어요!
public class AuthInterceptor implements Interceptor {
private String authToken;
public AuthInterceptor(String token) {
this.authToken = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
// 원본 요청에 헤더 추가
Request newRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer " + authToken)
.header("Content-Type", "application/json")
.build();
// 수정된 요청으로 진행
return chain.proceed(newRequest);
}
}
// OkHttpClient에 인터셉터 추가
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new AuthInterceptor("your_auth_token_here"))
.build();
// Retrofit 빌더에 클라이언트 설정
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
2. 응답 캐싱으로 오프라인 지원하기
인터넷이 끊겨도 앱이 동작하게 하고 싶다면? OkHttp의 캐싱 기능을 활용해보세요!
// 캐시 디렉토리와 크기 설정
File cacheDir = new File(context.getCacheDir(), "http-cache");
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(cacheDir, cacheSize);
// 네트워크 인터셉터 추가
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.header("Cache-Control", "public, max-age=60") // 60초 동안 캐시 유효
.build();
}
})
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 오프라인 상태 확인
if (!isNetworkAvailable(context)) {
// 오프라인이면 캐시된 응답 사용 (7일까지)
request = request.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 7)
.build();
}
return chain.proceed(request);
}
})
.build();
3. 파일 업로드하기
프로필 사진 같은 파일을 업로드하고 싶다면? @Multipart와 @Part를 사용하세요!
public interface ApiService {
@Multipart
@POST("users/{id}/photo")
Call<ResponseBody> uploadPhoto(
@Path("id") int userId,
@Part MultipartBody.Part photo,
@Part("description") RequestBody description
);
}
// 사용 예시
File file = new File(imagePath);
RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpeg"), file);
MultipartBody.Part photoPart = MultipartBody.Part.createFormData("photo", file.getName(), requestFile);
RequestBody description = RequestBody.create(MediaType.parse("text/plain"), "프로필 사진");
apiService.uploadPhoto(userId, photoPart, description).enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
Toast.makeText(context, "사진 업로드 성공!", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Toast.makeText(context, "업로드 실패: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
4. 동적 URL 사용하기
API 서버 URL이 런타임에 바뀔 수 있다면? @Url 어노테이션을 사용해보세요!
public interface ApiService {
@GET
Call<ResponseBody> downloadFile(@Url String fileUrl);
}
// 사용 예시
String fileUrl = "https://example.com/files/document.pdf";
apiService.downloadFile(fileUrl).enqueue(new Callback<ResponseBody>() {
// 콜백 처리
});
5. RxJava와 함께 사용하기
비동기 처리를 더 우아하게 하고 싶다면? RxJava와 함께 사용해보세요!
// 의존성 추가
// implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
// implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
// Retrofit 설정
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
// API 인터페이스
public interface ApiService {
@GET("users")
Observable<List<User>> getUsers();
}
// 사용 예시
apiService.getUsers()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
users -> {
// 성공 처리
adapter.setUsers(users);
},
error -> {
// 에러 처리
Toast.makeText(context, "오류: " + error.getMessage(), Toast.LENGTH_SHORT).show();
}
);
💡 코루틴 사용자를 위한 팁!
Kotlin 코루틴을 사용한다면 suspend 함수로 더 간단하게 네트워크 요청을 처리할 수 있어요!
// 의존성 추가
// implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
// API 인터페이스
interface ApiService {
@GET("users")
suspend fun getUsers(): List<User>
}
// 사용 예시
viewModelScope.launch {
try {
val users = apiService.getUsers()
// 성공 처리
} catch (e: Exception) {
// 에러 처리
}
}
이런 고급 기능들을 활용하면 앱의 품질이 한층 더 업그레이드될 거예요! 재능넷에서 안드로이드 개발 관련 프로젝트를 의뢰하실 때 이런 기술들을 요구사항에 포함시켜보세요~ 😉
8. 실전 예제: 날씨 앱 만들기 ☀️
지금까지 배운 내용을 활용해서 간단한 날씨 앱을 만들어볼게요! OpenWeatherMap API를 사용할 거예요. (사용하려면 회원가입 후 API 키를 받아야 해요!)
1단계: 모델 클래스 정의하기
public class WeatherResponse {
private Main main;
private List<Weather> weather;
private Wind wind;
private String name; // 도시 이름
// Getter와 Setter (생략)
public static class Main {
private float temp;
private float feels_like;
private float temp_min;
private float temp_max;
private int humidity;
// Getter와 Setter (생략)
}
public static class Weather {
private String main;
private String description;
private String icon;
// Getter와 Setter (생략)
}
public static class Wind {
private float speed;
// Getter와 Setter (생략)
}
}
2단계: API 인터페이스 정의하기
public interface WeatherApiService {
@GET("weather")
Call<WeatherResponse> getCurrentWeather(
@Query("q") String city,
@Query("units") String units,
@Query("appid") String apiKey
);
}
3단계: Retrofit 클라이언트 설정하기
public class WeatherApiClient {
private static final String BASE_URL = "https://api.openweathermap.org/data/2.5/";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
4단계: ViewModel 만들기
public class WeatherViewModel extends ViewModel {
private static final String API_KEY = "your_api_key_here";
private static final String UNITS = "metric"; // 섭씨 온도 사용
private MutableLiveData<WeatherResponse> weatherData = new MutableLiveData<>();
private MutableLiveData<String> errorMessage = new MutableLiveData<>();
private WeatherApiService apiService;
public WeatherViewModel() {
apiService = WeatherApiClient.getClient().create(WeatherApiService.class);
}
public LiveData<WeatherResponse> getWeatherData() {
return weatherData;
}
public LiveData<String> getErrorMessage() {
return errorMessage;
}
public void loadWeatherData(String city) {
apiService.getCurrentWeather(city, UNITS, API_KEY)
.enqueue(new Callback<WeatherResponse>() {
@Override
public void onResponse(Call<WeatherResponse> call, Response<WeatherResponse> response) {
if (response.isSuccessful()) {
weatherData.setValue(response.body());
} else {
try {
errorMessage.setValue("오류: " + response.errorBody().string());
} catch (IOException e) {
errorMessage.setValue("오류: " + response.code());
}
}
}
@Override
public void onFailure(Call<WeatherResponse> call, Throwable t) {
errorMessage.setValue("네트워크 오류: " + t.getMessage());
}
});
}
}
5단계: Activity에서 사용하기
public class MainActivity extends AppCompatActivity {
private WeatherViewModel viewModel;
private TextView tvCity, tvTemperature, tvDescription, tvFeelsLike, tvHumidity, tvWind;
private EditText etCity;
private Button btnSearch;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 뷰 초기화
tvCity = findViewById(R.id.tv_city);
tvTemperature = findViewById(R.id.tv_temperature);
tvDescription = findViewById(R.id.tv_description);
tvFeelsLike = findViewById(R.id.tv_feels_like);
tvHumidity = findViewById(R.id.tv_humidity);
tvWind = findViewById(R.id.tv_wind);
etCity = findViewById(R.id.et_city);
btnSearch = findViewById(R.id.btn_search);
progressBar = findViewById(R.id.progress_bar);
// ViewModel 초기화
viewModel = new ViewModelProvider(this).get(WeatherViewModel.class);
// 관찰자 설정
viewModel.getWeatherData().observe(this, this::updateUI);
viewModel.getErrorMessage().observe(this, message -> {
progressBar.setVisibility(View.GONE);
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
});
// 검색 버튼 클릭 리스너
btnSearch.setOnClickListener(v -> {
String city = etCity.getText().toString().trim();
if (!city.isEmpty()) {
progressBar.setVisibility(View.VISIBLE);
viewModel.loadWeatherData(city);
}
});
// 초기 데이터 로드 (서울)
viewModel.loadWeatherData("Seoul");
}
private void updateUI(WeatherResponse weather) {
progressBar.setVisibility(View.GONE);
tvCity.setText(weather.getName());
tvTemperature.setText(String.format("%.1f°C", weather.getMain().getTemp()));
if (!weather.getWeather().isEmpty()) {
tvDescription.setText(weather.getWeather().get(0).getDescription());
}
tvFeelsLike.setText(String.format("체감 온도: %.1f°C", weather.getMain().getFeels_like()));
tvHumidity.setText(String.format("습도: %d%%", weather.getMain().getHumidity()));
tvWind.setText(String.format("풍속: %.1f m/s", weather.getWind().getSpeed()));
}
}
6단계: 레이아웃 만들기
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/et_city"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="도시 이름 입력"
android:inputType="text" />
<Button
android:id="@+id/btn_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="검색" />
</LinearLayout>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="16dp"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_city"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_temperature"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:textSize="48sp" />
<TextView
android:id="@+id/tv_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:textSize="18sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_feels_like"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_humidity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_wind"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
이렇게 하면 간단한 날씨 앱이 완성됩니다! 도시 이름을 입력하고 검색하면 해당 도시의 현재 날씨 정보를 보여줘요. 😄
이 예제를 확장해서 5일 예보를 추가하거나, 위치 기반으로 자동으로 날씨를 가져오게 할 수도 있어요! 재능넷에서 안드로이드 개발자를 찾아 더 멋진 날씨 앱을 만들어보는 것도 좋은 아이디어겠죠? 🌈
9. 성능 최적화 팁 🚀
Retrofit과 OkHttp를 사용할 때 앱 성능을 극대화하기 위한 꿀팁들을 알려드릴게요! 이 팁들을 적용하면 앱이 더 빠르고 안정적으로 동작할 거예요. 😎
네트워크 최적화 팁 💯
- 연결 풀링 활용하기: OkHttp는 기본적으로 연결 풀링을 지원해요. 같은 서버로 여러 요청을 보낼 때 연결을 재사용해서 성능이 향상돼요.
- 타임아웃 설정하기: 네트워크 요청이 너무 오래 걸리면 사용자 경험이 나빠져요. 적절한 타임아웃을 설정하세요.
OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) .build(); - GZIP 압축 활용하기: OkHttp는 기본적으로 GZIP 압축을 지원해요. 서버가 GZIP을 지원한다면 자동으로 데이터를 압축해서 전송해요.
- 캐싱 활용하기: 자주 변경되지 않는 데이터는 캐싱해서 네트워크 요청을 줄이세요.
File cacheDir = new File(context.getCacheDir(), "http-cache"); int cacheSize = 10 * 1024 * 1024; // 10 MB Cache cache = new Cache(cacheDir, cacheSize); OkHttpClient client = new OkHttpClient.Builder() .cache(cache) .build(); - 불필요한 요청 줄이기: 화면 회전 같은 설정 변경 시 불필요한 네트워크 요청이 발생하지 않도록 ViewModel과 LiveData를 활용하세요.
메모리 관리 팁 🧠
- 싱글톤 패턴 사용하기: Retrofit과 OkHttp 인스턴스는 무거우니 싱글톤으로 관리하세요.
- 메모리 누수 방지하기: Activity나 Fragment가 소멸될 때 네트워크 콜백을 취소하세요.
private Call<List<User>> call; @Override protected void onDestroy() { super.onDestroy(); if (call != null && !call.isCanceled()) { call.cancel(); } } - 적절한 스레드 관리: 네트워크 요청은 백그라운드 스레드에서, UI 업데이트는 메인 스레드에서 처리하세요. Retrofit의
enqueue()메소드는 이를 자동으로 처리해줘요. - 큰 응답 처리하기: 매우 큰 JSON 응답을 처리할 때는 스트리밍 파서를 고려하세요. Gson 대신 Moshi나 Jackson을 사용할 수도 있어요.
배터리 최적화 팁 🔋
- 불필요한 폴링 피하기: 주기적으로 서버를 폴링하는 대신 필요할 때만 데이터를 가져오거나, 푸시 알림을 활용하세요.
- 배치 처리 활용하기: 여러 개의 작은 요청 대신 하나의 큰 요청으로 배치 처리하세요.
- 네트워크 상태 확인하기: 네트워크가 연결되지 않았을 때는 요청을 시도하지 않고 캐시된 데이터를 보여주세요.
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); - 데이터 사용량 줄이기: 이미지나 비디오 같은 큰 리소스는 필요할 때만 로드하고, 가능하면 압축된 버전을 사용하세요.
프로 개발자 팁 🏆
- 에러 처리 강화하기: 모든 가능한 에러 상황(네트워크 오류, 서버 오류, 타임아웃 등)을 처리하세요.
- 사용자 피드백 제공하기: 네트워크 작업 중에는 로딩 인디케이터를 보여주고, 오류 발생 시 적절한 메시지를 표시하세요.
- 단위 테스트 작성하기: Retrofit은 MockWebServer를 사용해 네트워크 요청을 테스트할 수 있어요.
// 테스트 의존성 추가 // testImplementation 'com.squareup.okhttp3:mockwebserver:4.9.3' @Test public void testApiCall() throws Exception { // 가짜 서버 설정 MockWebServer mockServer = new MockWebServer(); mockServer.enqueue(new MockResponse().setBody("{\"name\":\"John\"}")); mockServer.start(); // Retrofit 설정 (가짜 서버 URL 사용) Retrofit retrofit = new Retrofit.Builder() .baseUrl(mockServer.url("/")) .addConverterFactory(GsonConverterFactory.create()) .build(); // API 호출 테스트 ApiService service = retrofit.create(ApiService.class); Response<User> response = service.getUser(1).execute(); // 검증 assertTrue(response.isSuccessful()); assertEquals("John", response.body().getName()); // 서버 종료 mockServer.shutdown(); } - ProGuard 규칙 추가하기: 앱을 난독화할 때 Retrofit과 관련 라이브러리의 ProGuard 규칙을 추가하세요.
- API 버전 관리하기: API가 변경될 때를 대비해 버전 관리 전략을 세우세요. 인터셉터를 사용해 API 버전 헤더를 추가할 수 있어요.
이런 최적화 팁들을 적용하면 앱이 더 빠르고, 안정적이고, 배터리 효율적으로 동작할 거예요! 재능넷에서 안드로이드 개발자를 찾을 때 이런 최적화 기술을 아는 개발자를 선택하면 더 좋은 앱을 만들 수 있을 거예요! 👍
10. 마무리 및 추천 자료 📚
우와~ 여기까지 오느라 수고 많으셨어요! 😄 이제 여러분은 Retrofit과 OkHttp를 사용해서 안드로이드 앱에서 네트워크 통신을 할 수 있는 진정한 네트워크 마스터가 되셨어요! 👏
배운 내용 정리 📋
- HTTP 통신의 기본 개념과 메소드(GET, POST, PUT, DELETE 등)
- OkHttp의 특징과 장점(연결 풀링, GZIP 압축, 캐싱 등)
- Retrofit의 강력한 기능(인터페이스 기반 API 정의, 어노테이션 등)
- Retrofit과 OkHttp 설정 방법
- API 인터페이스 정의 및 다양한 어노테이션 활용법
- 동기/비동기 요청 처리 방법
- 인터셉터, 캐싱, 파일 업로드 등 고급 기능
- 실전 예제: 날씨 앱 구현
- 성능, 메모리, 배터리 최적화 팁
Retrofit과 OkHttp는 정말 강력한 라이브러리지만, 계속 발전하고 있어요. 더 깊이 공부하고 싶다면 아래 자료들을 참고해보세요!
추천 학습 자료 📚
- 공식 문서
- Retrofit: https://square.github.io/retrofit/
- OkHttp: https://square.github.io/okhttp/
- GitHub 저장소
- Retrofit: https://github.com/square/retrofit
- OkHttp: https://github.com/square/okhttp
- 온라인 강의
- Udacity: Android Networking
- Coursera: Android App Development
- YouTube: Coding in Flow, Coding with Mitch 채널
- 블로그 및 아티클
- Medium의 ProAndroidDev
- Android Developers 블로그
- Jake Wharton의 블로그 (Retrofit 개발자)
네트워크 통신은 현대 앱 개발에서 정말 중요한 부분이에요. Retrofit과 OkHttp를 마스터하면 더 효율적이고 안정적인 앱을 만들 수 있어요! 😊
혹시 이 글을 읽고 나서도 궁금한 점이 있거나, 실제 프로젝트에 적용하는 데 어려움을 겪고 계신다면, 재능넷에서 안드로이드 개발 전문가를 찾아보세요! 다양한 경험과 지식을 가진 개발자들이 여러분의 프로젝트를 도와드릴 거예요. 🚀
네트워크 통신의 세계는 끝이 없어요! 계속해서 공부하고, 실험하고, 새로운 기술을 탐험해보세요. 여러분의 앱이 더 멋지게 발전하길 바랍니다! 화이팅! 💪
댓글 작성
이 글에 대한 여러분의 생각을 들려주세요
로그인이 필요합니다
댓글을 작성하려면 먼저 로그인해주세요.
댓글 0
지식인의 숲 - 지적 재산권 보호 고지
지적 재산권 보호 고지
- 저작권 및 소유권: 본 컨텐츠는 재능넷의 독점 AI 기술로 생성되었으며, 대한민국 저작권법 및 국제 저작권 협약에 의해 보호됩니다.
- AI 생성 컨텐츠의 법적 지위: 본 AI 생성 컨텐츠는 재능넷의 지적 창작물로 인정되며, 관련 법규에 따라 저작권 보호를 받습니다.
- 사용 제한: 재능넷의 명시적 서면 동의 없이 본 컨텐츠를 복제, 수정, 배포, 또는 상업적으로 활용하는 행위는 엄격히 금지됩니다.
- 데이터 수집 금지: 본 컨텐츠에 대한 무단 스크래핑, 크롤링, 및 자동화된 데이터 수집은 법적 제재의 대상이 됩니다.
- AI 학습 제한: 재능넷의 AI 생성 컨텐츠를 타 AI 모델 학습에 무단 사용하는 행위는 금지되며, 이는 지적 재산권 침해로 간주됩니다.
아직 댓글이 없습니다.