Today's

길을 나서지 않으면 그 길에서 만날 수 있는 사람을 만날 수 없다

모바일 앱(안드로이드)

안드로이드 앱 만들기 : retrofit xml 파싱 : 제주도 버스 정보

Billcorea 2021. 12. 25. 09:05
반응형

앱에서 지원하고 싶은 것중 우선 나의 주변에 버스 정류소를 찾는다. 어떻게 찾을까 ?   모든 데이터는 data.go.kr 공공데이터 포털을 중심으로 ... 찾아보니 제주도에서 제공하는 버스 정보가 있다.

 

http://www.jeju.go.kr/help/open.htm?page=3&act=view&seq=967654 

다만, 가이드의 정보를 기준으로 보면 http:// 으로 시작하는 기본url 과 xml 형식으로 자료를 전송한다는 것이 조금 예전 방식인 것 같은 생각이 들었다.  

 

이제 앱에 retrofit 통신을 하기 위한 준비를 해 보자.  gradle 파일에 수정을 

    // 데이터 주고 받기
    implementation 'com.squareup.retrofit2:retrofit:2.7.2'
    implementation 'com.squareup.retrofit2:converter-simplexml:2.1.0'

retrofit 과 xml 파싱을 위해서 추가 했다.

 

다음은 manifest 에 internet 접속을 위한 권한 등록

 

    <uses-permission android:name="android.permission.INTERNET" />

이제 준비는 되었고.  그럼 데이터를 가져오기 위해서 일단 open api 가 제공하는 데이터 구조를 확인해 보아야겠다.

 

xml 샘플
xml 마지막 부분

데이터는 이렇게 조회가 되고 있고, open api 가이드의 내용도 이와 같다.  그래서 일단은 데이터를 받아올 class 을 만들어 보았다. 

 

맨 먼저 제일 바깥쪽에 구성되는 class 부터

import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;


@Root(name="response", strict = false)
public class StationBean {
    @Element(name="header")
    HeaderClass headerClass;
    @Element(name="body")
    BodyClass bodyClass ;

    public HeaderClass getHeaderClass() {
        return headerClass;
    }

    public BodyClass getBodyClass() {
        return bodyClass;
    }

    public void setHeaderClass(HeaderClass headerClass) {
        this.headerClass = headerClass;
    }

    public void setBodyClass(BodyClass bodyClass) {
        this.bodyClass = bodyClass;
    }
}

Root 는 response 로 구성 되며, 그 안에는 header 와 body 가 들어간다. 

다음은 header class

import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;

@Root(name="header", strict = false)
public class HeaderClass {
    @Element(name="resultCode")
    String resultCode;
    @Element(name="resultMsg")
    String resultMsg ;

    public String getResultCode() {
        return resultCode;
    }

    public String getResultMsg() {
        return resultMsg;
    }

    public void setResultCode(String resultCode) {
        this.resultCode = resultCode;
    }

    public void setResultMsg(String resultMsg) {
        this.resultMsg = resultMsg;
    }
}

header 에는 resultCode, 와 resultMsg 만 들어 있고, 실제 데이터는 body 에 들어간다.  다음은 body class

import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;

import java.util.ArrayList;

@Root(name="body", strict = false)
public class BodyClass {
    @ElementList(entry = "items")
    ArrayList<ItemClass> items ;
    @Element(name="numOfRows")
    String numOfRows ;
    @Element(name="pageNo")
    String pageNo ;
    @Element(name="totalCount")
    String totalCount ;

    public ArrayList<ItemClass> getItems() {
        return items;
    }

    public String getNumOfRows() {
        return numOfRows;
    }

    public String getPageNo() {
        return pageNo;
    }

    public String getTotalCount() {
        return totalCount;
    }

    public void setItems(ArrayList<ItemClass> items) {
        this.items = items;
    }

    public void setNumOfRows(String numOfRows) {
        this.numOfRows = numOfRows;
    }

    public void setPageNo(String pageNo) {
        this.pageNo = pageNo;
    }

    public void setTotalCount(String totalCount) {
        this.totalCount = totalCount;
    }
}

body 에는 데이터가 들어가는 items 와 건수등을 표시하는 데이터 들이 들어 있는데, 구현하면서 조금 방황(?)한 부분은 ArrayList 로 표시하는 items 에 대한 구현을 어떻게 해야 하는 가에 대한 부분 이었다.  gson 으로 파싱을 할 때는 그런거 없이 수월했는데, xml 형식에서는 EntryList 가 표현이 되도록 구현을 해야 되는 것이었다.

 

다음은 item이 들어가는 item class 

import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;

@Root(name="item", strict = false)
public class ItemClass {
    @Element(name="dirTp")
    String dirTp ;
    @Element(name="govNm")
    String govNm;
    @Element(name="localX")
    double localX ;
    @Element(name="localY")
    double localY ;
    @Element(name="mobiNum")
    String mobiNum ;
    @Element(name="stationId")
    String stationId ;
    @Element(name="stationNm")
    String stationNm ;
    @Element(name="upd")
    String upd ;
    @Element(name="useYn")
    String useYn ;

    public double getLocalX() {
        return localX;
    }

    public double getLocalY() {
        return localY;
    }

    public String getMobiNum() {
        return mobiNum;
    }

    public String getDirTp() {
        return dirTp;
    }

    public String getGovNm() {
        return govNm;
    }

    public String getStationId() {
        return stationId;
    }

    public String getStationNm() {
        return stationNm;
    }

    public String getUpd() {
        return upd;
    }

    public String getUseYn() {
        return useYn;
    }

    public void setDirTp(String dirTp) {
        this.dirTp = dirTp;
    }

    public void setGovNm(String govNm) {
        this.govNm = govNm;
    }

    public void setLocalX(double localX) {
        this.localX = localX;
    }

    public void setLocalY(double localY) {
        this.localY = localY;
    }

    public void setMobiNum(String mobiNum) {
        this.mobiNum = mobiNum;
    }

    public void setStationId(String stationId) {
        this.stationId = stationId;
    }

    public void setStationNm(String stationNm) {
        this.stationNm = stationNm;
    }

    public void setUpd(String upd) {
        this.upd = upd;
    }

    public void setUseYn(String useYn) {
        this.useYn = useYn;
    }
}

이렇게 구현 하면 데이터를 받아올 준비는 되었다.  이제 interface 을 구현해 볼 차례 인데,    openapi 에는 호출시 파라미터를 last 와 serviceKey 를 받는다고 되어 있지만, 현재( 2021.12.19 )에는 아무것도 전달하지 않아도 되고, last 뒤에 날자만 전달해도 된다. last 을 넣어보내면 최근 업데이트 된 정보만 받아오는 것으로 확인이 되었다.  다만, 나의 앱에서는 정보가 처음이라, 전체 데이터를 받기 위해서 아무것도 파라미터로 사용하지 않다. 

 

import retrofit2.Call;
import retrofit2.http.GET;

public interface RetrofitApi {
    String baseURL = "http://busopen.jeju.go.kr";
    String getStation = "/OpenAPI/service/bis/Station";

    @GET(getStation)
    Call<StationBean> getStationInfo();
}

 interface class 는 이렇게 구성을 했다. query 가 없어서 아무것도 전달하지는 않았다.

이번에는 activity 에서 호출을 해 보도록 하겠다.

 

public class MainActivity extends AppCompatActivity {

   ...
   
    Retrofit retrofit ;
    RetrofitApi service ;
    
    ...
    
    
	private void doGetBusStation() {

        retrofit = new Retrofit.Builder()
                .baseUrl(RetrofitApi.baseURL)
                .client(new OkHttpClient())
                .addConverterFactory(SimpleXmlConverterFactory.create())
                .build();
        service = retrofit.create(RetrofitApi.class);

        service.getStationInfo().enqueue(new Callback<StationBean>() {
            @Override
            public void onResponse(Call<StationBean> call, Response<StationBean> response) {
                Log.e(TAG, "response=" + response.code() + " Total="  + response.body().getBodyClass().getTotalCount());
                for(ItemClass item : response.body().getBodyClass().getItems()) {
                    Log.e(TAG, item.getStationNm());
                }
            }

            @Override
            public void onFailure(Call<StationBean> call, Throwable t) {
                Log.e(TAG, "ERROR=" + t.toString()) ;
            }
        });

    }
    
    
    ...
    
    
}

아직 데이터를 받아와서 어떻게 하고자 하는 지 결정을 하지 못했다. 일단, 데이터가 들어 오는 것은 확인을 하였다.  이 구현을 하는데,  CLEARTEXT communication to XXXX not permitted by network security policy 을 만나게 되면, 어떻게 할 것인가를 고민하지 말고 다음을 따라해 보면 된다. 이유는 http:// 으로 된 주소에 통신을 시도하기 때문에 발생하는 보안 문제인데,  나 앱이 모든 호출에서 http:// 로 할 것이 아니기 때문에 특정 url 만 http 로 호출할 것이라고 등록을 해 주면 된다. 

 

먼저 xml 파일을 하나 만든다.  res/xml/network_config.xml 이라고 .. 그안에는 다음과 같이 넣어 준다. 

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">busopen.jeju.go.kr</domain>
    </domain-config>
</network-security-config>

특정 주소는 http 로 통신을 하겠다는 것이다.  보안정책 때문에 누군가 싫어할지 모르지만.

그리고 다음은 manifest 에 다음을 추가해 준다. 

 

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_info_logo"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_info_logo_round"
        android:supportsRtl="true"
        android:networkSecurityConfig="@xml/network_config"

networkSecurityConfig 을 추가해 주면 끝.

 

응답 로그

제주도에는 4358개이 정류소 정보가 있나 보다.  다음엔 이것을 저장해서 데이터로 활용해 보도록 하겠다.

 

반응형