Android 애플리케이션은 인터넷에 있는 데이터에 액세스해야 하는 경우가 자주 있으며 인터넷 데이터는 여러가지 다른 형식으로 구조화될 수 있다. 이 기사에서는 Android 애플리케이션에서 세 가지 데이터 형식을 사용하여 작업하는 방법에 대해 살펴본다.
- XML
- JSON
- Google의 프로토콜 버퍼
먼저 CSV 데이터를 XML, JSON 및 프로토콜 버퍼 형식으로 변환하는 웹 서비스를 개발한다. 그런 다음 이러한 형식으로 웹 서비스에서 데이터를 끌어낼 수 있는 샘플 Android 애플리케이션을 빌드한 후 사용자에게 표시하기 위해 구문 분석한다.
이 기사에 있는 실습을 수행하려면 최신 Android SDK(참고자료 참조)와 Android 2.2 플랫폼이 필요하다. SDK를 사용하려면 JDK(Java™ Development Kit)도 설치되어 있어야 한다. 이 기사에서는 JDK 1.6.0_17을 사용했다. 실제 Android 장치는 필요하지 않으며 모든 코드는 SDK의 Android 에뮬레이터에서 실행된다. 이 기사에서는 Android 개발 자체에 대해서는 설명하지 않으므로 Android 프로그래밍에 익숙하도록 권장한다. 하지만 Java 프로그래밍 언어에 대한 지식만으로도 이 내용을 이해할 수 있을 것이다.
Android 애플리케이션이 사용하는 웹 서비스를 실행하는 Java 웹 애플리케이션 서버도 필요하다. 또는 서버측 코드를 Google App Engine에 전개할 수 있다. 전체 소스 코드를 얻으려면 다운로드를 참조한다.
Day Trader라는 단순한 Android 애플리케이션을 개발할 것이다. Day Trader를 사용하면 사용자가 하나 이상의 종목 기호를 입력하여 이러한 기호가 표시하는 종목의 최신 가격 정보를 검색할 수 있다. 사용자는 데이터에 사용할 형식(XML, JSON 또는 프로토콜 버퍼)을 지정할 수 있다. 실제 Android 애플리케이션은 일반적으로 이러한 선택사항을 제공하지 않지만 이를 구현하면 애플리케이션이 각 형식을 사용하여 작업하도록 만드는 방법을 알게 된다. 그림 1에서는 Day Trader 사용자 인터페이스를 보여 준다.
그림 1. 작동 중인 Day Trader 애플리케이션
텍스트 상자와 이 상자 옆에 있는 Add Stock 단추를 사용하면 관심 있는 각 종목의 기호를 입력할 수 있다. 사용자가 Download Stock Data 단추를 누르면 해당 종목 모두에 대한 데이터가 서버로부터 요청되어 애플리케이션에서 구문 분석된 후 화면에 표시된다. 기본적으로 데이터는 XML로 검색된다. 메뉴를 사용하면 데이터 형식을 XML, JSON 및 프로토콜 버퍼 사이에서 전환할 수 있다.
Listing 1에서는 그림 1에 있는 UI를 작성하는 데 사용된 레이아웃 XML을 보여 준다.
Listing 1. Day Trader 레이아웃 XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText android:id="@+id/symbol" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:width="120dip"/>
<Button android:id="@+id/addBtn" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/addBtnLbl"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/symList" />
<Button android:id="@+id/dlBtn" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dlBtnLbl"
/>
</LinearLayout>
<ListView android:id="@android:id/list"
android:layout_height="fill_parent" android:layout_width="fill_parent"
android:layout_weight="1"
/>
</LinearLayout>
|
Listing 1에 있는 코드는 대부분 간단하다. 그림 1에 표시된 입력 및 단추를 작성하는 데 필요한 몇 가지 위젯이 표시된다.
Android 위젯의 진정한 다용도 도구인 ListView도 표시된다. 이 ListView는 서버에서 다운로드한 주식 데이터로 채워진다.
Listing 2에서는 이 보기를 제어하는 Activity를 보여 준다.
Listing 2. Day Trader 기본 활동
public class Main extends ListActivity {
private int mode = XML; // default
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final EditText input = (EditText) findViewById(R.id.symbol);
final TextView symbolsList = (TextView) findViewById(R.id.symList);
final Button addButton = (Button) findViewById(R.id.addBtn);
final Button dlButton = (Button) findViewById(R.id.dlBtn);
addButton.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
String newSymbol = input.getText().toString();
if (symbolsList.getText() == null ||
symbolsList.getText().length() == 0){
symbolsList.setText(newSymbol);
} else {
StringBuilder sb =
new StringBuilder(symbolsList.getText());
sb.append(",");
sb.append(newSymbol);
symbolsList.setText(sb.toString());
}
input.setText("");
}
});
dlButton.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
String symList = symbolsList.getText().toString();
String[] symbols = symList.split(",");
symbolsList.setText("");
switch (mode){
case JSON :
new StockJsonParser().execute(symbols);
break;
case PROTOBUF :
new StockProtoBufParser().execute(symbols);
break;
default :
new StockXmlParser().execute(symbols);
break;
}
}
});
}
}
|
이 Activity는 레이아웃을 Listing 1에 있는 XML 파일로 설정하고 몇 가지 이벤트 핸들러를 연결한다.
먼저 Add Stock 단추에 대해 이 코드는 텍스트 상자에서 기호를 읽은 후
이러한 기호를 쉼표로 분리한 상태로 symList TextView에 추가
추가한다. 다음으로 Download 단추에 대해 핸들러는 해당 symList TextView에서 데이터를
읽은 후 mode 변수를 기반으로 세 가지 다른 클래스 중 하나를 사용하여 서버에서 데이터를 다운로드한다.
이 메뉴는 mode 변수의 값을 설정하지만 코드가 매우 평범하기 때문에 Listing 2에서 생략했다.
다양한 데이터 다운로드/구문 분석 클래스를 살펴보기 전에 서버에서 이 데이터를 제공하는 방법에 대해 알아본다.
애플리케이션을 위한 서버는 두 가지 사항을 수행할 수 있어야 한다. 먼저 종목 기호 목록을 가져와서 각각에 대한 데이터를 검색해야 한다. 다음으로 형식 매개변수를 승인하고 해당 형식을 기반으로 데이터를 인코딩해야 한다. XML 및 JSON 형식의 경우 서버는 텍스트로 직렬화된 주식 데이터를 리턴한다. 프로토콜 버퍼의 경우에는 바이너리 데이터를 전송해야 한다. Listing 3에서는 이러한 단계를 처리하는 서블릿을 보여 준다.
Listing 3. Stock Broker 서블릿
public class StockBrokerServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws IOException {
String[] symbols = request.getParameterValues("stock");
List<Stock> stocks = getStocks(symbols);
String format = request.getParameter("format");
String data = "";
if (format == null || format.equalsIgnoreCase("xml")){
data = Stock.toXml(stocks);
response.setContentType("text/xml");
} else if (format.equalsIgnoreCase("json")){
data = Stock.toJson(stocks);
response.setContentType("application/json");
} else if (format.equalsIgnoreCase("protobuf")){
Portfolio p = Stock.toProtoBuf(stocks);
response.setContentType("application/octet-stream");
response.setContentLength(p.getSerializedSize());
p.writeTo(response.getOutputStream());
response.flushBuffer();
return;
}
response.setContentLength(data.length());
response.getWriter().print(data);
response.flushBuffer();
response.getWriter().close();
}
public List<Stock> getStocks(String... symbols) throws IOException{
StringBuilder sb = new StringBuilder();
for (String symbol : symbols){
sb.append(symbol);
sb.append('+');
}
sb.deleteCharAt(sb.length() - 1);
String urlStr =
"http://finance.yahoo.com/d/quotes.csv?f=sb2n&s=" +
sb.toString();
URL url = new URL(urlStr);
HttpURLConnection conn =
(HttpURLConnection) url.openConnection();
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String quote = reader.readLine();
List<Stock> stocks = new ArrayList<Stock>(symbols.length);
while (quote != null){
String[] values = quote.split(",");
Stock s =
new Stock(values[0], values[2],
Double.parseDouble(values[1]));
stocks.add(s);
quote = reader.readLine();
}
return stocks;
}
}
|
이 서블릿은 HTTP GET 요청만 지원하는 단순 Java 서블릿이다.
이 서블릿은 주식 및 형식-요청 매개변수의 값을 판독한다. 그런 다음 getStocks()
메소드를 호출한다. 이 메소드는 Yahoo! Finance에 대한 호출을 작성하여 주식 데이터를 가져온다.
Yahoo!는 데이터에 대해 CSV 형식만 지원하기 때문에 getStocks() 메소드는
해당 데이터를 Stock 오브젝트의 목록으로 구문 분석한다.
Listing 4에서는 단순 데이터 구조를 보여 준다.
Listing 4. Stock 데이터 구조
public class Stock {
private final String symbol;
private final String name;
private final double price;
//getters and setters omitted
public String toXml(){
return "<stock><symbol>" + symbol +
"</symbol><name><![CDATA[" +
name + "]]></name><price>" + price +
"</price></stock>";
}
public String toJson(){
return "{ 'stock' : { 'symbol' : " +symbol +", 'name':" + name +
", 'price': '" + price + "'}}";
}
public static String toXml(List<Stock> stocks){
StringBuilder xml = new StringBuilder("<stocks>");
for (Stock s : stocks){
xml.append(s.toXml());
}
xml.append("</stocks>");
return xml.toString();
}
public static String toJson(List<Stock> stocks){
StringBuilder json = new StringBuilder("{'stocks' : [");
for (Stock s : stocks){
json.append(s.toJson());
json.append(',');
}
json.deleteCharAt(json.length() - 1);
json.append("]}");
return json.toString();
}
}
|
각 Stock에는 세 개의 특성(
symbol, name 및 price
)과 자체를 XML 문자열 또는 JSON 문자열로 변환하는 편의 메소드가 포함되어 있다.
또한 Stock 오브젝트의 목록을 XML 또는 JSON으로 변환하는 유틸리티 메소드도 포함되어 있다.
Listing 3으로 돌아가서 Stock 오브젝트의 목록은 형식 요청 매개변수에 따라 XML 또는 JSON 문자열로 변환된 후 다시 클라이언트에 전송된다.
XML 유스 케이스와 JSON 유스 케이스는 매우 비슷하고 간단하다. 프로토콜 버퍼 케이스의 경우에는 프로토콜 버퍼 형식으로 코드 읽기 및 쓰기 오브젝트를 생성해야 한다. 이를 수행하려면 프로토콜 버퍼 스펙 형식을 사용하여 데이터 구조를 정의해야 한다. Listing 5에 예제가 있다.
Listing 5. 주식에 대한 프로토콜 버퍼 메시지
package stocks;
option java_package = "org.developerworks.stocks";
message Quote{
required string symbol = 1;
required string name = 2;
required double price = 3;
}
message Portfolio{
repeated Quote quote = 1;
}
|
IDL(Interface Description Language)과 비슷한 프로토콜 버퍼 메시지 형식은 다양한 언어와 함께 사용할 수 있도록 언어와 독립적이도록 되어 있다.
여기서는 프로토콜 버퍼 컴파일러(protoc)를 실행하여 Listing 5에 있는 코드를 클라이언트 및 서버에 사용할 Java 클래스로 컴파일한다.
Java 클래스로의 프로토콜 버퍼 메시지 컴파일에 대한 세부 사항은 프로토콜 버퍼 개발자 안내서(참고자료 참조)를 참조한다.
Listing 3에서는 toProtoBuf() 메소드가 Stock
오브젝트의 목록을 Portfolio 메시지로 변환했다. Listing 6에서는 해당 메소드의 구현을 보여 준다.
Listing 6. 포트폴리오 메시지 작성하기
public static Stocks.Portfolio toProtoBuf(List<Stock> stocks){
List<Stocks.Quote> quotes = new ArrayList<Stocks.Quote>(stocks.size());
for (Stock s : stocks){
Quote q =
Quote.newBuilder()
.setName(s.name)
.setSymbol(s.symbol)
.setPrice(s.price)
.build();
quotes.add(q);
}
return Portfolio.newBuilder().addAllQuote(quotes).build();
}
|
Listing 6에 있는 코드에서는 Listing 5에 있는 메시지에서 생성된 코드(Quote 및
Portfolio 클래스)를 사용한다. 사용자는 각각의 Stock 오브젝트로부터
Quote를 빌드한 후 Listing 3에 있는 서블릿에 리턴되는 Portfolio
오브젝트에 추가하기만 하면 된다. Listing 3에서 서블릿은 클라이언트에 대해 직접적인 스트림을 열고 생성된 코드를 사용하여
바이너리 프로토콜 버퍼 데이터를 스트림에 작성한다.
지금까지 서버에서 Android 애플리케이션에 전송될 데이터를 작성하는 방법에 대해 살펴봤다. 이제는 이 애플리케이션이 이 데이터를 구문 분석하는 방법에 대해 알아본다.
Listing 2에 있는 기본 Activity는 서버가 전송할 수 있는 다양한 형식으로 데이터에 대해 작업해야 한다.
또한 적절한 형식으로 데이터를 요청해야 하며 데이터가 구문 분석되면 데이터로 ListView를 채워야 한다.
따라서 데이터 형식과 관계없이 기능의 많은 부분이 공통된다.
먼저 Listing 7과 같이 이 공통 기능을 캡슐화하는 추상 기본 클래스를 작성한다.
Listing 7. 데이터 구문 분석기의 기본 클래스
abstract class BaseStockParser extends AsyncTask<String, Integer, Stock[]>{
String urlStr = "http://protostocks.appspot.com/stockbroker?format=";
protected BaseStockParser(String format){
urlStr += format;
}
private String makeUrlString(String... symbols) {
StringBuilder sb = new StringBuilder(urlStr);
for (int i=0;i<symbols.length;i++){
sb.append("&stock=");
sb.append(symbols[i]);
}
return sb.toString();
}
protected InputStream getData(String[] symbols) throws Exception{
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet(new URI(makeUrlString(symbols)));
HttpResponse response = client.execute(request);
return response.getEntity().getContent();
}
@Override
protected void onPostExecute(Stock[] stocks){
ArrayAdapter<Stock> adapter =
new ArrayAdapter<Stock>(Main.this, R.layout.stock,
stocks );
setListAdapter(adapter);
}
}
|
Listing 7에 있는 기본 클래스는 android.os.AsyncTask를 확장한다.
이 기본 클래스는 비동기 조작에 공통적으로 사용되는 클래스이다. 이 클래스는 기본 UI 스레드에서 요청을 작성하는 데
필요한 스레드 및 핸들러의 작성을 추상화한다. 이 클래스는 입력 및 출력 데이터 유형을 기반으로
매개변수화된다. 모든 구문 분석기에 대해 입력은 동일하다(종목 기호에 대한 문자열). 또한 출력도
항상 동일하다(Stock 오브젝트의 배열). 기본 클래스는 사용할 데이터
형식을 지정하는 문자열인 format을 사용한다. 그러면 이 클래스에는
적절한 HTTP 요청을 작성하고 스트리밍 응답을 리턴하는 데 필요한 메소드가 있게 된다.
마지막으로 이 클래스는 AsyncTask의 onPostExecute() 메소드를
대체하고 구문 분석기에서 리턴되는 데이터를 사용하여 Activity의 ListView에 대한 Adapter를 작성한다.
세 구문 분석기 모두에 공통된 기능에 대해 살펴봤으니 이제는 좀 더 구체적인 구문 분석 코드에 대해 설명한다(XML 구문 분석기부터 시작함).
Android SDK는 표준 DOM 및 SAX를 포함하여 XML에 대해 작업하는 여러가지 방법을 제공한다. 메모리 사용량이 더 많은 상황의 경우에는 SDK의 풀(pull) 구문 분석기를 사용할 수 있다. 대부분의 시간 동안 SAX는 가장 빠른 방법이었기 때문에 Android에는 SAX를 더 편리하게 사용할 수 있게 하는 편의 API가 일부 포함되어 있다. Listing 8에서는 Day Trader 애플리케이션의 XML 구문 분석기를 보여 준다.
Listing 8. XML 구문 분석기 구현
private class StockXmlParser extends BaseStockParser{
public StockXmlParser(){
super("xml");
}
@Override
protected Stock[] doInBackground(String... symbols) {
ArrayList<Stock> stocks = new ArrayList<Stock>(symbols.length);
try{
ContentHandler handler = newHandler(stocks);
Xml.parse(getData(symbols), Xml.Encoding.UTF_8, handler);
} catch (Exception e){
Log.e("DayTrader", "Exception getting XML data", e);
}
Stock[] array = new Stock[symbols.length];
return stocks.toArray(array);
}
private ContentHandler newHandler(final ArrayList<Stock> stocks){
RootElement root = new RootElement("stocks");
Element stock = root.getChild("stock");
final Stock currentStock = new Stock();
stock.setEndElementListener(
new EndElementListener(){
public void end() {
stocks.add((Stock) currentStock.clone());
}
}
);
stock.getChild("name").setEndTextElementListener(
new EndTextElementListener(){
public void end(String body) {
currentStock.setName(body);
}
}
);
stock.getChild("symbol").setEndTextElementListener(
new EndTextElementListener(){
public void end(String body) {
currentStock.setSymbol(body);
}
}
);
stock.getChild("price").setEndTextElementListener(
new EndTextElementListener(){
public void end(String body) {
currentStock.setPrice(Double.parseDouble(body));
}
}
);
return root.getContentHandler();
}
}
|
Listing 8에 있는 코드는 대부분 ContentHandler를 작성하는 newHandler() 메소드에 있다.
SAX 구문 분석에 익숙한 경우에는 ContentHandler가 SAX 구문 분석기에 의해
발생한 다양한 이벤트에 대한 반응으로 구문 분석된 데이터를 작성한다는 것을 알고 있다.
newHandler() 메소드는 Android 편의 API를 사용하여 이벤트
핸들러를 사용하는 ContentHandler를 지정한다. 이 코드는
단순히 구문 분석기가 다양한 태그를 만나면 발생되는 이벤트를 청취한 후 데이터를
선택하여 Stock 오브젝트의 목록에 추가한다.
ContentHandler가 작성되면 Xml.parse() 메소드가 호출되어
기본 클래스에서 제공한 InputStream을 구문 분석하고 Stock 오브젝트의 배열을 리턴한다.
이렇게 하면 신속하게 XML을 구문 분석할 수 있지만 Android에서 제공하는 편의 API를 사용하더라도 여전히 너무 장황하다.
XML은 Android의 중요한 구성원이며 XML에 의존하는 웹 서비스가 얼마나 많은지를 생각하면 이것은 바람직한 것이다. 또한 많은 서비스에서는 자주 사용되는 또다른 형식인 JSON도 지원한다. JSON은 XML보다 좀더 간결하지만 사람이 읽을 수 있기 때문에 작업을 편리하게 하고 JSON을 사용하는 애플리케이션의 디버깅을 쉽게 만든다. Android에는 JSON 구문 분석기가 포함되어 있다. (모바일에서 필요하지 않은 몇 가지 클래스가 제거된 것을 제외하고는 JSON.org 웹 사이트에서 가져올 수 있는 구문 분석기와 동일하다.) Listing 9에서는 작동 중인 JSON을 보여 준다.
Listing 9. JSON 구문 분석기 구현
private class StockJsonParser extends BaseStockParser{
public StockJsonParser(){
super("json");
}
@Override
protected Stock[] doInBackground(String... symbols) {
Stock[] stocks = new Stock[symbols.length];
try{
StringBuilder json = new StringBuilder();
BufferedReader reader =
new BufferedReader(
new InputStreamReader(getData(symbols)));
String line = reader.readLine();
while (line != null){
json.append(line);
line = reader.readLine();
}
JSONObject jsonObj = new JSONObject(json.toString());
JSONArray stockArray = jsonObj.getJSONArray("stocks");
for (int i=0;i<stocks.length;i++){
JSONObject object =
stockArray.getJSONObject(i).getJSONObject("stock");
stocks[i] = new Stock(object.getString("symbol"),
object.getString("name"),
object.getDouble("price"));
}
} catch (Exception e){
Log.e("DayTrader", "Exception getting JSON data", e);
}
return stocks;
}
}
|
Android에서 JSON 구문 분석기를 얼마나 쉽게 사용할 수 있는지 알 수 있다. 스트림을 서버에서 JSON 구문 분석기에
전달하는 문자열로 변환한다. 오브젝트 그래프를 살펴보고 Stock 오브젝트의 배열을 작성한다.
XML DOM 구문 분석에 대해 작업한 경험이 있는 경우에는 프로그래밍 모델이 거의 동일하기 때문에 이 작업이 익숙하다.
DOM과 마찬가지로 JSON 구문 분석기를 사용하는 데도 메모리가 많이 소비될 수 있다. Listing
9에서 서버의 모든 데이터는 문자열로 표시된 후 JSONObject로 표시되고 마지막으로 Stock 오브젝트의 배열로 표시된다.
달리 말하면 정확하게 동일한 데이터가 세 가지 다른 방식으로 표시된다. 데이터 양이 많은 경우에
이것이 어떤 문제가 되는지 알 수 있다. 물론 메소드의 끝에 다다르면 이러한 세 가지 데이터 표시 중
두 가지는 범위에서 제거되어 가비지 콜렉터에 의해 회수될 수 있다. 하지만 가비지 콜렉션을
더 빈번하게 트리거하면 비정상적인 속도 저하가 발생하여 사용자 경험에 부정적인 영향을 미칠 수 있다.
메모리 효율 및 성능이 중요한 경우에는 프로토콜 버퍼를 사용하는 구문 분석기가 더 낫다.
프로토콜 버퍼는 네트워크를 통해 데이터를 전송하기 위해 XML보다 빠르도록 설계된 Google에서 개발한 언어가 특정되지 않은 데이터 직렬화 형식이다. 프로토콜 버퍼는 서버 간 호출에 대한 Google의 사실상의 표준이다. Google은 이 형식과 C++, Java 및 Python 프로그래밍 언어를 위한 해당 바인딩 도구를 오픈 소스로 사용할 수 있도록 했다.
Listing 3과 Listing 6에서 프로토콜 버퍼가 바이너리 형식이라는 것을 알았다. 예상한 대로 이때문에 데이터가 매우 간결해진다. 클라이언트와 서버 모두에서 gzip 압축을 사용할 수 있는 경우에는 XML 및 JSON을 사용하여 비슷한 크기의 메시지를 얻을 수도 있지만 여전히 프로토콜 버퍼는 크기 면에서 장점이 있다. 또한 프로토콜 버퍼는 매우 신속하게 구문 분석할 수 있는 형식이다. 마지막으로 프로토콜 버퍼는 매우 단순한 API를 제공한다. Listing 10에서는 예제 구문 분석기 구현을 보여 준다.
Listing 10. 프로토콜 버퍼 구문 분석기 구현
private class StockProtoBufParser extends BaseStockParser{
public StockProtoBufParser(){
super("protobuf");
}
@Override
protected Stock[] doInBackground(String... symbols) {
Stock[] stocks = new Stock[symbols.length];
try{
Stocks.Portfolio portfolio =
Stocks.Portfolio.parseFrom(getData(symbols));
for (int i=0;i<symbols.length;i++){
stocks[i] = Stock.fromQuote(portfolio.getQuote(i));
}
} catch (Exception e){
Log.e("DayTrader", "Exception getting ProtocolBuffer data", e);
}
return stocks;
}
}
|
Listing 3과 마찬가지로 헬퍼 클래스(이 예제에서는 프로토콜 버퍼
컴파일러에 의해 생성됨)를 사용한다. 이 헬퍼 클래스는 서버에서 사용하는 것과 동일한
헬퍼 클래스이다. 한 번 컴파일하고 나면 서버와 클라이언트 모두에서 공유할 수 있다.
따라서 더 편리하게 서버의 스트림에서 직접 읽은 후 Stock 오브젝트로
변환할 수 있다. 이는 뛰어난 성능도 제공하게 되는 단순한 프로그래밍이다. 이제 이러한
성능이 XML 및 JSON과 얼마나 비교되는지 살펴본다.
성능 비교에는 일반적으로 일종의 마이크로 벤치마크가 수반되며 이러한 벤치마크는 매우
편향되거나 실수로 부정확해지기 쉽다. 마이크로 벤치마크가 공정성을 유지하도록 설계되어
있는 경우에도 다수의 임의 요소에 의해 결과의 공정성이 의심될 수 있다. 이러한 경고에도
불구하고 필자는 이 마이크로 벤치마크를 사용하여 XML(약 1300밀리초), JSON(약 1150밀리초) 및
프로토콜 버퍼(약 750밀리초)의 성능을 비교했다. 벤치마크에서는 200개 종목에 대한 요청을
서버에 전송한 후 요청이 전송된 때부터 데이터가 ListView를 위해 Adapter를
작성하는 데 사용할 준비가 될 때까지의 시간을 측정했다. 이러한 작업은 3G 네트워크를 사용하는 두 가지
장치(Motorola Droid와 HTC Evo)에서 각 데이터 형식에 대해 50회씩 수행되었다. 그림 2에 결과가 표시되어 있다.
그림 2. 데이터 형식 속도 비교하기
그림 2에 있는 그래프에서는 이 마이크로 벤치마크의 경우 프로토콜 버퍼(약 750밀리초)가 XML(약 1300밀리초)보다 두 배 가까이 빠르다는 것을 보여 준다. 네트워크를 통해 전송되어 휴대용 장치에서 처리되는 데이터의 성능에 영향을 미치는 요인은 많다. 눈에 띄는 요인은 네트워크를 통해 전송되는 데이터의 양이다. 바이너리 형식인 프로토콜 버퍼는 실제로 XML 및 JSON과 같은 텍스트 형식보다 훨씬 적은 데이터를 네트워크를 통해 전송한다. 하지만 텍스트 형식은 웹 서버와 Android 장치에서 모두 지원하는 표준 기술인 gzip을 사용하여 효율적으로 압축할 수 있다. 그림 3에서는 gzip을 사용하거나 사용하지 않은 상태에서 유선으로 전송되는 데이터의 크기를 보여 준다.
그림 3. 형식별 데이터 크기
그림 3을 보면 HTML, JavaScript 및 CSS와 같은 웹 형식은 말할 것도 없이 XML 및 JSON과 같은 텍스트 컨텐츠에 압축이 미치는 영향이 얼마나 큰지 더 잘 알 수 있다. 프로토콜 버퍼의 데이터(약 6KB)는 원래 크기의 XML(약 17.5KB) 또는 JSON(약 13.5KB)보다 훨씬 작다. 하지만 압축을 사용하면 JSON 및 XML(둘 다 약 3KB)은 프로토콜 버퍼보다 더 작다. 이 예제에서는 압축된 XML 및 JSON으로 된 메시지의 크기가 프로토콜 버퍼로 인코딩된 메시지 크기의 절반 정도 밖에 되지 않는다.
그림 2로 돌아가서 살펴보면 속도 차이는 네트워크를 통한 메시지 크기로는 확실하게 설명할 수 없다. 프로토콜 버퍼 메시지는 XML 또는 JSON 인코딩된 메시지보다 크지만 프로토콜 버퍼를 사용하면 여전히 사용자가 데이터를 확인하기 위해 기다리는 시간의 절반을 절감할 수 있다. 이것이 Android 애플리케이션에 프로토콜 버퍼를 사용해야 한다는 것을 의미하는 것인가? 물론 이에 대한 결정은 미리 정해져 있지 않다. 전송되는 데이터의 양이 적으면 세 가지 형식 사이의 차이는 미미하다. 대용량 데이터의 경우에는 프로토콜 버퍼가 차이를 보일 것이다. 하지만 이 경우와 같은 인위적인 벤치마크가 자체 애플리케이션을 테스트하는 것을 대체할 수는 없다.
이 기사에서는 인터넷에서 자주 사용되는 두 가지 데이터 형식인 XML과 JSON을 사용한 작업에 대해 자세히 살펴봤다. 또한 세 번째 형식인 프로토콜 버퍼에 대해서도 살펴봤다. 소프트웨어 엔지니어링의 다른 모든 사항과 마찬가지로 기술을 선택하는 것은 상충 관계에 관한 문제이다. Android와 같은 제한된 환경을 위해 개발하는 경우에는 이러한 의사결정의 결과가 미치는 영향이 크다. 이러한 결과에 대해 지금 알고 있는 추가적인 지식이 우수한 Android 애플리케이션을 작성하는 데 도움이 되길 바란다.
| 설명 | 이름 | 크기 | 다운로드 방식 |
|---|---|---|---|
| Article source code | daytrader.stockbroker.zip | 4.81MB | HTTP |
교육
- Android Developers 사이트: Android에 관한 최신 뉴스를 보고 Android API 참조서에 액세스할 수 있다.
- Working with XML on Android(Michael Galpin, developerWorks, 2009년 6월): Android에서 XML에 대해 작업하는 옵션과 이러한 옵션을 사용하여 자체 Android 애플리케이션을 빌드하는 방법에 대해 살펴보자.
- Android 개발 소개(Frank Ableson, developerWorks, 2009년 5월): Android 플랫폼에 대한 이 소개 기사에서 Android에 대해 살펴보고 기본적인 Android 애플리케이션을 코딩해 보자.
- 프로토콜 버퍼 개발자 안내서: 프로토콜 버퍼를 사용한 개발에 대해 자세히 살펴보자.
- Introducing JSON: JSON 구문에 대해 알아보자.
- To XML and Back: Using JSON in Android(Frank Ableson, Linux Magazine, 2010년 3월): Android에서의 JSON 사용에 대해 자세히 살펴보자.
- Open Handset Alliance: Android의 스폰서 웹 사이트를 방문해 보자.
- Understanding SAX(Nicholas Chase, developerWorks, 2003년 7월): 이 튜토리얼을 읽고 SAX 구문 분석의 전문가가 되어 보자.
- DOM 이해하기 (한글)(Nicholas Chase, developerworks, 2007년 3월): 이 튜토리얼에서 DOM 구문 분석에 대해 자세히 살펴보자.
- 필자의 더 많은 기사(Michael Galpin, developerWorks, 2006년 4월 - 현재): XML, HTML 5, Eclipse, Apache Geronimo, Ajax, 추가 Google API 및 기타 기술에 대한 기사를 읽어보자.
- My developerWorks: 자신만의 developerWorks 환경을 꾸며보자.
- IBM XML 인증: XML 및 관련 기술에 대한 IBM 인증 개발자가 되는 방법을 찾아볼 수 있다.
- XML Technical library: developerWorks XML 영역에서 다양한 기술 관련 기사와 팁, 튜토리얼, 표준 및 IBM Redbook을 볼 수 있다.
- developerWorks 기술 행사 및 웹 캐스트: 이러한 세션에 참가하여 최신 기술에 대한 정보를 얻을 수 있다.
- Twitter의 developerWorks 페이지: 오늘 가입하여 developerWorks 트윗을 팔로우하자.
- developerWorks
팟캐스트: 소프트웨어 개발자의 흥미로운 인터뷰와 토론을 확인할 수 있다.
제품 및 기술 얻기
- Android SDK: Android SDK를 다운로드하자. 1.5 이상의 버전을 이 기사의 실습에 사용할 수 있다.
- Android Open Source Project: Android의 오픈 소스 코드를 다운로드할 수 있다.
- Java Development Kit: 이 기사에 사용된 JDK 1.6.0_17을 위한 전체 플랫폼 및 런타임 환경을 다운로드하자.
-
프로토콜 버퍼: 프로토콜 버퍼 컴파일러를 다운로드하자.
- IBM 제품 평가판:
다운로드하거나 IBM SOA Sandbox의 온라인 시험판을 살펴보고
DB2®, Lotus®, Rational®, Tivoli® 및 WebSphere®의 애플리케이션 개발 도구와 미들웨어 제품을 사용해 볼 수 있다.
토론
- XML 영역 토론 포럼: 여러 XML 관련 토론에 참여해 볼 수 있다.
- developerWorks 포럼 & 블로그를 통해 developerWorks 커뮤니티에 참여하자.
