본문 바로가기
안드로이드

[안드로이드] InputStream을 닫는데 왜 오래 걸릴까?

by 호군 2013. 4. 19.
반응형


개발 버전 : 안드로이드 프로요(버전 2.2)


안드로이드 프로젝트를 진행하다가 HTTP로 파일을 다운로드하는 기능을 구현해야 하는 일이 있었습니다. 파일 다운로드를 할 때는 당연히 사용자에게 현재 다운로드 상황을 보여줘야 합니다. 그래서 HTTP 해더에서 Content-Length 값을 가져와서 사용해야 합니다. 그리고 해당 클래스는 Download를 수행하는 클래스이기 때문에 다운로드 할 주소와 다운로드 될 경로를 모두 맴버변수로 갖고 있습니다. 또한 메소드 단위를 최소화 하기 위해 다운로드 할 파일의 주소에 대한 스트림을 얻어오는 메소드와 다운로드 할 파일의 크기를 얻어오는 메소드로 나누었습니다. 하지만 이 두 부분을 나누면서 속도 문제가 발생했습니다. 제가 실험한 코드 수정 과정을 쭉 적어보겠습니다.


처음에는 DefaultHttpClient 를 사용해서 Content-Length의 값을 가져왔습니다. 그런데 이 값을 가져오는데 이상하게 속도가 오래 걸렸습니다. 로그를 통해서 확인해보니 스트림을 닫을 때 시간이 너무 오래 걸려서 Content-Length를 얻어오는데 많은 시간이 소모된 경우였습니다. 소스 코드는 아래와 같습니다.

private long getSourceContentLength() throws IllegalArgumentException, ClientProtocolException, IOException {

HttpClient client = new DefaultHttpClient();

HttpResponse response = client.execute( new HttpGet(getSourcePath()) );

Header[] headers = response.getHeaders("Content-Length");

if ( response.getEntity() != null ) {

response.getEntity().consumeContent();

}

if ( headers != null && headers.length > 0 ) {

try {

return Long.parseLong( headers[0].getValue() );

} catch (NumberFormatException e) {

}

}

return 0;

}


시간이 오래 걸리는 부분은 'response.getEntity().consumeContent()' 라인이였습니다. 처음에는 DefaultHttpClient 클래스가 문제(?)인가 했습니다. 사실 그럴리가 없는데 말이죠ㅠ


하여튼 다른 방법으로 DefaultHttpClient 클래스 대신 HttpURLConnection 클래스를 사용해보기로 했습니다. 코드는 아래와 같습니다.

private int getUrlContentLength(String path) throws MalformedURLException, ProtocolException, IOException {

URL url = new URL( path );

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setRequestMethod("GET");

conn.connect();


int length = conn.getContentLength();

conn.disconnect();

return length > 0 ? length : 0;

}


역시 결과는 똑같이 스트림을 닫을 때, 오랜 시간이 걸렸습니다.여기서 오랜 시간이 걸리는 코드는 'conn.disconnect()' 입니다.


스트림을 닫는 시간을 측정해보니 약 30초 정도가 걸렸습니다. 그리고 해당 파일을 다운로드 하는데도 약 30초 정도가 걸렸습니다. 아마도 스트림을 닫는데 스트림을 끝까지 읽고 종료하고 있는게 아닌가라는 생각이 듭니다. 그리고 실제 파일을 다운로드 하고 스트림을 닫는 동작은 빠르게 수행되기 때문에 더 그런게 아닐까라고 생각듭니다.


이번엔 스트림을 강제로 닫으면 어떻게 되는지 테스트 해봤습니다. 이 때는 DefaultHttpClient 클래스를 사용해서 Content-Length를 얻어왔습니다. 그리고 ConnectionManager를 가져와서 shutdown() 메소드를 호출하여 강제로 스트림을 닫았습니다. 코드는 아래와 같습니다.

private long getSourceContentLength() throws IllegalArgumentException, ClientProtocolException, IOException {

HttpClient client = new DefaultHttpClient();

HttpResponse response = client.execute( new HttpGet(getSourcePath()) );

Header[] headers = response.getHeaders("Content-Length");

if ( response.getEntity() != null ) {

//response.getEntity().consumeContent();

client.getConnectionManager().shutdown();

}

if ( headers != null && headers.length > 0 ) {

try {

return Long.parseLong( headers[0].getValue() );

} catch (NumberFormatException e) {

}

}

return 0;

}


스트림을 강제로 종료하니 이전과 다르게 Content-Length를 바로 반환해줬습니다. 하지만 강제로 닫는 행위는 여러모로 마음 속 깊은 곳을 불편하게 만듭니다.


이 문제의 발단은 스트림을 얻어오는 메소드와 크기를 얻어오는 메소드를 분리하면서 발생한 문제였습니다. 다양한 해결 방법이 있겠지만, 쉽게 생각 해 볼 수 있는 방법은 두 메소드에서 공통된 객체를 넘겨주거나 맴버변수로 파일 크기 값을 담아두는 방법이 있습니다. 즉, 이 두 메소드가 HttpResponse를 매개변수로 받고 각 값을 반환하거나 또는 스트림을 얻기 전에 파일 크기를 얻어와서 맴버변수에 저장하는 방법을 사용해도 간단히 해결될 수 있는 문제였습니다.


길게 길게 돌아왔는데, 결론에 쓸게 없군요. "코드 구조를 변경해라"가 되나요..?

제가 이 글을 적는 이유는 해당 코드에 이상이 없다고 생각하고 쉽게 지나쳐버린 부분이기 때문입니다. 일반적으로는 스트림을 얻으면 끝까지 읽지만, 헤더 값이나 일부 값만 얻어 오는 경우에는 저와 같이 스트림을 닫는데 오랜 시간이 걸리지 않을까 생각됩니다.


만약 Header의 내용만 필요하면 GET메소드 대신 HEAD 메소드로 요청해보세요. 그러면 Stream을 닫을 필요도 없고, 불필요한 데이터를 가져오지 않기 때문에 더 빠르게 데이터를 가져올 수 있습니다.


스트림을 닫을 때, 왜 이렇게 오래 걸리는지 이유를 아시거나 틀린 부분이 보이시면 댓글로 알려주세요^^



마지막 휴우님의 [벅연] 안드로이드 HttpClient와 HttpURLConnection 글을 한번 읽어보세요.




반응형