[안드로이드] Retrofit2(레트로핏2) 통신 / 오픈 API, 데이터베이스와 데이터 주고 받기
Retrofit은 HttpURLConnection, Volley, OkHttp 등 통신 라이브러리들 중 가장 좋은 성능을 보여주는 통신 라이브러리이다. 빠르고 코드 자체가 그렇게 복잡하지 않아 자주 사용된다고 한다.
이글에서는 레트로핏을 이용하여 안드로이드 <-> PHP <-> 데이터베이스 통신과 오픈 API에서 데이터를 가져오는 것을 구현해 보도록 하겠다.
레트로핏 통신에는 인터페이스가 필요한데, 이 인터페이스를 레트로핏에 전달에 전달하면 레트로핏에서 함수를 구현하여 통신을 진행해 주는 Service 객체를 반환해 준다.(이 Service 객체는 Call 객체를 반환한다)
Service 객체는 개발자가 만든 클래스가 아니며 Service 내의 함수는 개발자가 등록한 인터페이스와 같은 이름으로 구현된다. 그래서 Service 객체를 획득하여 실제 통신이 필요한 순간 Service의 함수를 호출하여 Call 객체를 반환 받고 사용하면 된다.
주의. 여기서 나오는 Service는 백그라운드에서 사용되는 Service가 아님
레트로핏을 사용하기 위해서 2개 라이브러리를 추가
implementation 'com.squareup.retrofit2:retrofit:2.6.4' //Retrofit2
implementation 'com.squareup.retrofit2:converter-gson:2.6.4' // JSON -> Gson 변환기
통신을 할 것이므로 퍼미션 추가
<uses-permission android:name="android.permission.INTERNET" />
안드로이드 API 29 이상부터는 http 허용을 위해 추가
res -> xml -> network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true"/>
</network-security-config>
Manifest 내 application에 추가
android:networkSecurityConfig="@xml/network_security_config"
데이터베이스 상태
PHP
retrofit1.php (@GET 형식 통신에 쓰일 것, 서버로 보내는 값이 없음)
<?php
$con = mysqli_connect("호스트", "아이디", "비밀번호", "DB명");
mysqli_set_charset($con,"utf8");
$res = mysqli_query($con,"SELECT count, name, age, major FROM android_database");
$result = array();
while($row = mysqli_fetch_array($res)){
array_push($result,
array('count' =>$row[0], 'name'=>$row[1], 'age'=>$row[2],'major'=>$row[3]));
}
echo json_encode(array("result"=>$result));
?>
retrofit2.php (@POST 방식 통신에서 쓰일 것, 서버로 데이터 값을 보낸 것을 $number로 받는 것을 볼 수 있음)
<?php
$con = mysqli_connect("호스트", "아이디", "비밀번호", "DB명");
mysqli_set_charset($con,"utf8");
$number = isset($_POST["count"]) ? $_POST["count"] : ''; //POST로 넘어온 데이터
$res = mysqli_query($con,"SELECT count, name, major FROM android_database where count='$number'");
$result = array();
while($row = mysqli_fetch_array($res)){
array_push($result,
array('count' =>$row[0], 'name'=>$row[1],'major'=>$row[2]));
}
echo json_encode(array("result"=>$result));
?>
서버에 있는 데이터베이스로부터 나온 결과 값을 JSON으로 반환한 것이다.
데이터 추상화 클래스로 Model을 정의해주어야 한다. 통신해서 받아온 데이터를 사용하기 전에 저장한다고 생각하면 된다.
count, name, age, major 4개의 데이터를 가져올 것이다.
PostResult.java
public class PostResult {
//student class 내에 데이터가 존재하므로
//student class에 대한 객체 생성 후 객체를 ArrayList로 선언해준다.
//여기에는 @SerializedName("result")로 받아온 데이터들이 들어간다.
//result는 위에 JSON 파일에 배열 이름이다.
@SerializedName("result")
@Expose
private ArrayList<student> student = null;
public ArrayList<student> getStudent() {
return student;
}
public void setStudent(ArrayList<student> student) {
this.student = student;
}
public class student{
//@SerializedName("")은 가지고 올 데이터의 {key, value}가 있다면 key값으로 일치시켜서 적어준다.
//@SerializedName("")만 맞춰주면 바로 아래 변수명이 달라도 값을 제대로 받을 수 있다.
@SerializedName("count")
private int count;
@SerializedName("name")
private String student_name;
@SerializedName("age")
private int age;
@SerializedName("major")
private String major;
public int getcount() {
return count;
}
public void setcount(int count) {
this.count = count;
}
public String getname() {
return student_name;
}
public void setname(String name) {
this.student_name = name;
}
public int getage() {
return age;
}
public void setage(int age) {
this.age = age;
}
public String getmajor() {
return major;
}
public void setmajor(String major) {
this.major = major;
}
}
}
RetrofitService1.java (인터페이스로 선언해주어야 함)
통신 방식에는 @GET, @POST, @PUT, @DELETE, @HEAD 등이 있다.
여기서는 @GET과 @POST만 해보도록 하겠다.
참고) 기본 base 주소를 https://www.naver.com/ 이라고 한다면
@GET("edit/next") 은 https://www.naver.com/edit/next 가 될 것이다.
여기에 Call<model> getData(@Query("data") String data1, @Query("setter") String setter1); 으로 선언한다면
https://www.naver.com/edit/next?data=data1&setter=setter1가 될 것이다.
다시 기본 base 주소를 https://www.naver.com/ 로 가정하고,
@GET("edit/{abc}")
Call<model> getData(@Path("abc") String abc); abc문자열이 = "getter"라면
https://www.naver.com/edit/getter가 될 것이다.
public interface RetrofitService1 {
//GET방식 통신, baseUrl 뒤로 붙을 경로, 질의, 문자열 등을 지정할 수 있다.
//뒤에 붙을 만한게 없어서 자체적 주소를 넣어주었다.
@GET("retrofit1.php")
Call<PostResult> getInfo();
//Field는 보통 POST 방식을 사용할 때 사용된다.
//Field 사용 시 @FormUrlEncoded 추가
//HashMap 방식으로 데이터를 받아서 서버로 보내줄 것 이다.
@FormUrlEncoded
@POST("retrofit2.php")
Call<PostResult> getInfo1(@FieldMap HashMap<String, Integer> hashMap);
/*@FormUrlEncoded
@POST("retrofit2.php")
Call<PostResult> getInfo1(@Field("count") int count);*/
//@Field 방식으로 데이터를 하나하나 선언해서 넘겨줄 수도 있음
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
Button b1, b2;
boolean status = false;
Retrofit retrofit;
RetrofitService1 retrofitService1;
ArrayList<PostResult.student> list = new ArrayList<>();
Call<PostResult> call;
String baseurl = "도메인명"; //반드시 '/'로 끝맺음
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
b1 = findViewById(R.id.button1);
b2 = findViewById(R.id.button2);
retrofit = new Retrofit.Builder()
.baseUrl(baseurl)
.addConverterFactory(GsonConverterFactory.create()) //JSON -> Gson 변환기 등록
.build();
retrofitService1 = retrofit.create(RetrofitService1.class); //retroft 변수로 인터페이스 객체 구현
//@GET방식 통신
b1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
status = false;
call = retrofitService1.getInfo(); //인터페이스 내에서 사용할 메소드 설정
requestCall();
}
});
//@POST방식 통신
b2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
status = true;
HashMap<String,Integer> hashMap = new HashMap<>();
hashMap.put("count",3); //count(key) , 3(value)
call = retrofitService1.getInfo1(hashMap); //데이터 넣어서 인터페이스에서 사용할 메소드 호출
//call = retrofitService1.getInfo1(1); field로 할 경우 (파라미터, 파라미터 ... 늘리기 가능)
requestCall();
}
});
}
void requestCall(){
call.enqueue(new Callback<PostResult>() { //enqueue로 비동기 통신 실행
//통신 성공 시
@Override
public void onResponse(Call<PostResult> call, Response<PostResult> response) {
if(response.isSuccessful()){ //통신 성공 시
if(status == false) {
PostResult result = response.body();
ArrayList<PostResult.student> student = result.getStudent();
for (int i = 0; i < student.size(); i++) {
PostResult.student temp = student.get(i);
list.add(temp);
Log.i("retrofit1.php", "결과(저장x 값): " + temp.getname() + " / " + temp.getage() + " / " + temp.getmajor());
}
for (int i = 0; i < student.size(); i++) {
PostResult.student valuelist = list.get(i);
Log.i("retrofit1.php", "결과(저장 후 가져온 값: " + valuelist.getname() + " / " + valuelist.getage() + " / " + valuelist.getmajor());
}
}
else{ //status가 true일 경우
PostResult result = response.body();
ArrayList<PostResult.student> student = result.getStudent();
for (int i = 0; i < student.size(); i++) {
PostResult.student temp = student.get(i);
list.add(temp);
Log.i("retrofit2.php", "결과(저장x 값): " + temp.getname() + " / " + temp.getmajor());
}
for (int i = 0; i < student.size(); i++) {
PostResult.student valuelist = list.get(i);
Log.i("retreofit2.php", "결과(저장 후 가져온 값: " + valuelist.getname() + " / " + valuelist.getmajor());
}
}
}
else{
Log.i("TAG","연결 실패 onResponse()");
}
}
//통신 실패 시
@Override
public void onFailure(Call<PostResult> call, Throwable t) {
Log.i("TAG","연결 실패 onFailure()");
}
list = new ArrayList<>(); //하나의 Model에 2객체 인스턴스를 생성해서 사용하므로 ArrayList 객체 새로 생성(초기화용도)
});
}
}
xml 파일은 버튼 2개만 구현해주신 다음, 눌러보면서 Log값 확인해보면 결과 값을 볼 수 있다.
결과
status가 False 일 경우
status가 True 일 경우
다음으로 오픈 API의 데이터를 가져와 보도록 하겠다.
www.kobis.or.kr/kobisopenapi/homepg/apiservice/searchServiceInfo.do
영화진흥위원회 오픈API
제공서비스 영화관입장권통합전산망이 제공하는 오픈API서비스 모음입니다. 사용 가능한 서비스를 확인하고 서비스별 인터페이스 정보를 조회합니다.
www.kobis.or.kr
로그인 후 개인 키를 받고 사용할 수 있다.
방금까진 JSON 파일에 데이터가 많지가 않아서 직접 Model을 만들고 데이터를 보내고 가져왔었다.
항상 이렇게 하기엔 간단하지만 시간도 더 오래 걸리므로 JSON을 Java 코드로 바로 제공해주는 사이트가 있다.
이 사이트에서 우리가 보고 있는 JSON 파일(아래)을 복사하여 붙여넣기 해준다. (변환할 때 JSON -> Gson으로)
그럼 아래와 같이 자바 코드들을 볼 수 있다.
Preview에 나온 그대로 파일을 만들어 준다.
그러면 총 3개의 파일이 나올 것 이다.(BoxOfficeResult.java, DailyBoxOfficeList.java, 결과.java)
3개 파일 보기
public class DailyBoxOfficeList {
@SerializedName("rnum")
@Expose
private String rnum;
@SerializedName("rank")
@Expose
private String rank;
@SerializedName("rankInten")
@Expose
private String rankInten;
@SerializedName("rankOldAndNew")
@Expose
private String rankOldAndNew;
@SerializedName("movieCd")
@Expose
private String movieCd;
@SerializedName("movieNm")
@Expose
private String movieNm;
@SerializedName("openDt")
@Expose
private String openDt;
@SerializedName("salesAmt")
@Expose
private String salesAmt;
@SerializedName("salesShare")
@Expose
private String salesShare;
@SerializedName("salesInten")
@Expose
private String salesInten;
@SerializedName("salesChange")
@Expose
private String salesChange;
@SerializedName("salesAcc")
@Expose
private String salesAcc;
@SerializedName("audiCnt")
@Expose
private String audiCnt;
@SerializedName("audiInten")
@Expose
private String audiInten;
@SerializedName("audiChange")
@Expose
private String audiChange;
@SerializedName("audiAcc")
@Expose
private String audiAcc;
@SerializedName("scrnCnt")
@Expose
private String scrnCnt;
@SerializedName("showCnt")
@Expose
private String showCnt;
public String getRnum() {
return rnum;
}
public void setRnum(String rnum) {
this.rnum = rnum;
}
public String getRank() {
return rank;
}
public void setRank(String rank) {
this.rank = rank;
}
public String getRankInten() {
return rankInten;
}
public void setRankInten(String rankInten) {
this.rankInten = rankInten;
}
public String getRankOldAndNew() {
return rankOldAndNew;
}
public void setRankOldAndNew(String rankOldAndNew) {
this.rankOldAndNew = rankOldAndNew;
}
public String getMovieCd() {
return movieCd;
}
public void setMovieCd(String movieCd) {
this.movieCd = movieCd;
}
public String getMovieNm() {
return movieNm;
}
public void setMovieNm(String movieNm) {
this.movieNm = movieNm;
}
public String getOpenDt() {
return openDt;
}
public void setOpenDt(String openDt) {
this.openDt = openDt;
}
public String getSalesAmt() {
return salesAmt;
}
public void setSalesAmt(String salesAmt) {
this.salesAmt = salesAmt;
}
public String getSalesShare() {
return salesShare;
}
public void setSalesShare(String salesShare) {
this.salesShare = salesShare;
}
public String getSalesInten() {
return salesInten;
}
public void setSalesInten(String salesInten) {
this.salesInten = salesInten;
}
public String getSalesChange() {
return salesChange;
}
public void setSalesChange(String salesChange) {
this.salesChange = salesChange;
}
public String getSalesAcc() {
return salesAcc;
}
public void setSalesAcc(String salesAcc) {
this.salesAcc = salesAcc;
}
public String getAudiCnt() {
return audiCnt;
}
public void setAudiCnt(String audiCnt) {
this.audiCnt = audiCnt;
}
public String getAudiInten() {
return audiInten;
}
public void setAudiInten(String audiInten) {
this.audiInten = audiInten;
}
public String getAudiChange() {
return audiChange;
}
public void setAudiChange(String audiChange) {
this.audiChange = audiChange;
}
public String getAudiAcc() {
return audiAcc;
}
public void setAudiAcc(String audiAcc) {
this.audiAcc = audiAcc;
}
public String getScrnCnt() {
return scrnCnt;
}
public void setScrnCnt(String scrnCnt) {
this.scrnCnt = scrnCnt;
}
public String getShowCnt() {
return showCnt;
}
public void setShowCnt(String showCnt) {
this.showCnt = showCnt;
}
}
=====================================================================
public class BoxOfficeResult {
@SerializedName("boxofficeType")
@Expose
private String boxofficeType;
@SerializedName("showRange")
@Expose
private String showRange;
@SerializedName("dailyBoxOfficeList")
@Expose
private List<DailyBoxOfficeList> dailyBoxOfficeList = null;
public String getBoxofficeType() {
return boxofficeType;
}
public void setBoxofficeType(String boxofficeType) {
this.boxofficeType = boxofficeType;
}
public String getShowRange() {
return showRange;
}
public void setShowRange(String showRange) {
this.showRange = showRange;
}
public List<DailyBoxOfficeList> getDailyBoxOfficeList() {
return dailyBoxOfficeList;
}
public void setDailyBoxOfficeList(List<DailyBoxOfficeList> dailyBoxOfficeList) {
this.dailyBoxOfficeList = dailyBoxOfficeList;
}
}
=====================================================================
//내 결과 파일
public class Film_result {
@SerializedName("boxOfficeResult")
@Expose
private BoxOfficeResult boxOfficeResult;
public BoxOfficeResult getBoxOfficeResult() {
return boxOfficeResult;
}
public void setBoxOfficeResult(BoxOfficeResult boxOfficeResult) {
this.boxOfficeResult = boxOfficeResult;
}
}
Film_Service.java (인터페이스)
public interface Film_Service {
@GET("kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json")
Call<Film_result> get_data(@Query("key") String key, @Query("targetDt") String targetDt);
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity";
Button b1;
Retrofit retrofit;
Film_Service film_service;
Call<Film_result> call;
String baseurl = "http://kobis.or.kr/";
String private_key = "로그인 후 발급 받은 API 키";
List<DailyBoxOfficeList> list = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
b1 = findViewById(R.id.button1);
request_retrofit(); //통신 시작
b1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for(int i=0;i<list.size();i++) {
//MovieNm은 영화명, OpenDt는 개봉일 / 데이터는 많지만 이 두개만 출력해보도록 하겠다.
Log.i(TAG, list.get(i).getMovieNm() + " / " + list.get(i).getOpenDt());
}
}
});
}
public void request_retrofit(){
retrofit = new Retrofit.Builder()
.baseUrl(baseurl)
.addConverterFactory(GsonConverterFactory.create())
.build();
film_service = retrofit.create(Film_Service.class);
call = film_service.get_data(private_key, "20210118");
call.enqueue(new Callback<Film_result>() {
@Override
public void onResponse(Call<Film_result> call, Response<Film_result> response) {
//통신 성공 시
if(response.isSuccessful()){
Film_result result = response.body();
BoxOfficeResult boxOfficeResult = result.getBoxOfficeResult();
List<DailyBoxOfficeList> dailyBoxOfficeLists = boxOfficeResult.getDailyBoxOfficeList();
for(int i=0;i<dailyBoxOfficeLists.size();i++){
DailyBoxOfficeList temp = dailyBoxOfficeLists.get(i);
list.add(temp);
}
}
else{
}
}
@Override
public void onFailure(Call<Film_result> call, Throwable t) {
//통신 실패 시
}
});
}
}
xml에는 버튼 하나만 추가해주고, 버튼을 눌러서 Log 값을 확인하면 된다.
결과
주의. 오픈 API에 들어가면 맨 아래에 JSON 데이터를 볼 수 있는 응답 예시가 있다. http://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=f5eef3421c602c6cb7ea224104795888&targetDt=20120101
.json하고 ?하면서 붙는 값들에 key와 targetDt가 있을텐데 이 값은 인터페이스 란의 설명을 보면 무슨 말인지 알 수 있다.
감사합니다.
참고: 깡샘의 안드로이드 프로그래밍