소곤소곤 ad

2015년 2월 24일 화요일

HttpURLConnection을 이용하여 간단하게 이미지 다운로드받기,Example for Downloading an image with HttpURLConnection (no Apache)

HttpURLConnection을 이용하여 간단하게 이미지 다운로드받기 Example for Downloading an image with HttpURLConnection (no Apache)


실시간으로 웹서버에서 이미지를 가져다가 출력할 필요가 종종 있다. WebView나 HttpGet등의 고차원적인 기능의 라이브러리를 이용할 수도 있겠지만, 복잡한 옵션없이 아주 간단하게 이미지만 가져오고 싶다면?

정보의 바다에서 쉽게 샘플 소스를 구할 수 있을것 같았지만, 제대로 문제없이 동작하는 샘플을 구할 수가 없었다. 여러가지 제한사항들이 있는데...

1) 크로스플랫폼에서 (나 같은경우는 Android + Windows Desktop) 동작해야 한다.
2) java.* 이외의 외부라이브러리를 가급적 사용하지 않고 구현해야 한다 - 여러가지 이유가 있지만, 순수 java라이브러리만으로 가볍게 구현하고 싶었다.

기본적인 구현은 아래와 같다.

 static private boolean getHttpFile(String url, String fileName) throws MalformedURLException, IOException
 {
  HttpURLConnection conn;

  conn = (HttpURLConnection) new URL( url ).openConnection();
  
  conn.setConnectTimeout( timeOut );
  conn.setReadTimeout( timeOut );
// oops! conn.setDoOutput(true);
  conn.setUseCaches(true);
  conn.setRequestMethod( "GET" );

  conn.setRequestProperty("Connection", "Keep-Alive" );
  conn.setRequestProperty("Content-Type", "image/png");
//  conn.setRequestProperty("Content-Type","application/x-www-form-urlendcoded");
  
  conn.connect();
  
  int response = conn.getResponseCode(); // System.out.println(String.format("HTTP_RESPONSE:%s CODE:%d TYPE:%s SIZE:%d", url, response, conn.getContentType(), conn.getContentLength()));
  if( response == HttpURLConnection.HTTP_OK ){
   InputStream is = conn.getInputStream();
   FileOutputStream outputStream = new FileOutputStream(fileName);
   
   if ("gzip".equals(conn.getContentEncoding())){
    System.out.println("zipped image");
       is = new GZIPInputStream(is);
   }
   
                        // opens an output stream to save into file
   int bytesRead = -1;
                        byte[] buffer = new byte[2048];
                        while ((bytesRead = is.read(buffer)) != -1) {
                            outputStream.write(buffer, 0, bytesRead);
                        }
                        is.close();
                        outputStream.close();
            
                        return true;
  } 
  return false;
 }


setDoOutput(true)
일단, HttpURLConnection.setDoOutput(true);를 절대 하면 안된다. 안드로이드 4.0 이후부터는 이것이 true인 경우 method가 강제적으로 "POST"방식으로 변경되어져서 결과적으로는 데이터를 가져올 수 없고, HTTP/405 Error 를 만나게 된다.

getInputStream()의 함정
인터넷에서 구하는 샘플소스들은

  if( response == HttpURLConnection.HTTP_OK ){ 
                    return conn.getInputStream();
                }

이렇게 InputStream을 돌려주면서 끝나는데, 사실 InputStream은 그 끝을 알 수 없는 파이프의 한쪽만을 보여주는 형태라서, 파일사이즈가 좀 커지면 다 받지 않았는데도 리턴한다. 가볍게 생각하고 InputStream으로부터 이미지를 저장해보면 난리가 난다.

여기에서는 InputStream이 종료될때까지 2k씩 계속 받아다가 파일로 저장한다. 메모리를 더 써도 된다면 팍팍 읽어다가 팍팍 저장!


사용하기, 그리고 예외처리들

 static public Texture getHttpTexture(String fileId, FileHandle handle)
 {
  String url = "http://xxxx.com/images/" + fileId+ ".png";
  
  try {
   if (getHttpFile(url, handle.path()) ){
    try {
     Texture td = new Texture(handle);
     td.setFilter(TextureFilter.Linear,  TextureFilter.Linear);
     return td;
    } catch ( GdxRuntimeException e){
     e.printStackTrace();
    }
   }
  } catch (MalformedURLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return null;
 }

이 함수는 libGDX 엔진기반의 게임개발에서 사용하는 실제 코드이다. 정해진 웹서버에서 fileID 형태의 png 파일을 다운로드 받고, 이를 이용하여 Texture를 생성한다.


Thread로 다운로드받고 이미지 그리기

마지막으로 큰 걸림돌인데, 여태까지 했던 작업들은 모두 별도 Thread 로 돌아야 한다. Activity.runOnUIThread()에 넣거나, View.postDelayed()를 이용해서 다운로드받고 UI를 갱신해주면 된다. 실제로 우리가 만들고 있는 게임에서는 libGDX의 postRunnable를 이용해서 오래걸리는 작업을 처리하도록 했다.

   Gdx.app.postRunnable(new Runnable(){
    @Override
    public void run() {
     Texture tx = HttpGetImage.getHttpImage(fileID, fileHandle);
     TextureRegionDrawable trd = new TextureRegionDrawable(new TextureRegion(tx));
     imageSample.setDrawable(trd);
    }
   });

댓글 1개: