소곤소곤 ad

2015년 2월 24일 화요일

카카오(Kakao) SDK 설치시에 발생하는 오류들 (Eclipse/Android)

카카오(Kakao, 카카오톡, 카톡, Kakaotalk) SDK 설치시에 발생하는 오류들 (Eclipse/Android)


카카오는 여러가지 API들을 공개하고 있는데, https://developers.kakao.com/ 를 방문하면 자세한 정보를 얻을 수 있다. 그런데 여기서 제공하는 SDK를 그냥 설치하면 에러가 쏟아진다. 여기저기를 검색해봐도 이 오류들에 대한 설명도 해결방법도 찾기가 힘들었다.






여러군데서 오류가 발생한다. 오류가 발생한 부분의 소스코드를 살펴봐도 왜인지 잘 모르겠다. 

자, 심호흡을 한번 하고, 이 오류들을 싸그리 없애보자!


1. google_play_service_lib 라이브러리 위치



첫번째 할 일은, google_play_service_lib 의 위치를 제대로 지정해 주는 것이다. 빨갛게 X표시된 것은 카카오 SDK를 만드신분의 개발환경으로 이 라이브러리의 경로가 지정되어 있기 때문에 문제가 생겼음을 알 수 있다. 이것들을 제거하고, 내 개발환경의 google_play_service_lib 프로젝트를 지정해주면 해결. (이 라이브러리를 설치하는 방법에 대한 것은 이 글의 범위를 벗어난다)




2. Eclipse의 TextEncoding 문제

여전히 소스코드 여러곳에서 에러가 발생하는데, 이번 문제는 애매한 코드영역에서 발생한다. 이 문제의 주요한 원인은, 카카오 SDK를 만드신분이 UTF-8코드의 한글로 주석을 달아서이다.  필요충분조건인 또하나의 원인은, 내 개발환경이 UTF-8이 아니기 때문이다. 




UTF-8로 작성된 주석들이 윈도우개발환경에서 코드페이지가 맞지 않는 경우에는 코드들과 섞여버린다. 즉, 주석 다음줄의 코드까지 주석처리되어 버려서 오류들이 발생하는 것.

문제 해결하는 방법은 둘 중 하나이다. 

1) 내 개발환경을 모두 UTF-8로 바꾼다. 


Window - Preference - General - Content Types - Text - Java Source File 을 찾아서 맨 아래 Default encoding 에 "UTF-8"로 적어주고 업데이트 하면 된다. 아니면 더 쉬운 방법이 있다! 그것은 바로... 


2) 카카오 SDK 소스코드내의 한글 주석을 영문으로 바꾸던가 지워버린다. 

(오류나는 부분)

(수정된 소스)

엔터 하나 때려넣고 수정 완료! -_-v


3. WebView와 Android 버전문제


SDK에 포함된 WebView의 일부 기능들은 안드로이드 API 버전 "11" 부터 지원하는데, Kakao SDK의 AndroidManifest.xml에는 

minSdkVersion="10" 

으로 되어 있다. 11로 바꿔준다. 이게 문제가 된다면 빌드가 끝나고 나중에 나중에 슬그머니 10으로 바꿔 놓으면 된다. 카카오 개발자도 이렇게 하지 않았을까 싶다 ㅎㅎ


4. PowerManger.isInteractive() 함수


이 오류가 생긴 이유는 카카오 SDK개발자님이 최신기능을 사용할수 있다고 뽐내려고 했기 때문으로 추정된다. 이 함수는 Android SDK 버전 "20" (롤리팝)부터 지원하는 신기능이다. 킷캣 이전버전에서는 지원하지 않아 오류가 생긴다. sdk의 Build Target을 20이상으로 해줘야 한다. 그런데 그래봤자 구버전에서는 isScreenOn()을 사용하도록 코딩되어 있으므로, 아예 이 부분을 삭제해 버리는것이 낫겠다는 판단. 

(롤리팝의 이 기능을 사용하기 위해 앱빌드 타겟을 20으로 강제하는것보다는, 타겟을 19이하로 하게 내버려두고 동작은 그냥 운영체제에 맡기는게 낫지 않았을까?)

 private boolean isApplicationActive(Context context) {
        try {
            final PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            return powerManager.isScreenOn();

            /*
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
                return powerManager.isScreenOn();
            } else {
                return powerManager.isInteractive();
            }
            */
        } catch (Exception e) {
            // nothing to do;
        }

        return true;
    }

이렇게 수정하여 드디어, 오류없이 컴파일 성공!! 야호~


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);
    }
   });