월별 글 목록: 2011년 12월월

Android WebView에서 getUrl() 메소드 값이 현재 페이지가 아닌경우..

Android WebView가 제공하는 getUrl() 메쏘드는 WebView를 통해서 가져온 웹 페이지의 URL을 넘겨줍니다.
대체로 getUrl() 메쏘드는 redirect된 사이트의 url을 가져옵니다.. 하지만, 간혹 redirect 하는 페이지에서 현재 사이트이 url이 아닌, redirect 전의 url을 가져오는 경우가 있습니다..

그래서, 위와 같은 문제를 해결하기 위한 방안으로, android webkit의 WebViewClient를 상속받아서 WebView의 페이지 로더로 바인딩을 해서 처리하면 해결할 수 있습니다..

* WebClient를 상속받은 클래스

import android.webkit.WebView;
import android.webkit.WebViewClient;
public class OAuthWebClient extends WebViewClient {
 @Override
 public boolean shouldOverrideUrlLoading(WebView view, String url) {
  view.loadUrl(url);
  return true;
 }
 
 @Override
 public void onPageFinished(WebView view, String url) {
  // WebView의 페이지 로드가 완료되면 콜백의 형태로 이 메쏘드가 호출됩니다.. 좀 더 정확하게는 WebView가 이벤트 발생하는 경우 WebViewClient의 선언된 메쏘드들을 호출하고, 요 형태는 전형적인 옵저버 패턴의 모습입니다.
 }
}

* WebView에서 위의 클래스를 사용하는 형태
  – 아래 코드는 WebView에서 url이 바뀌는 것을 주기적으로 2초마다 모니터링하는 스켈레톤 코드의 형태이다.

webView.setWebViewClient(new OAuthWebClient()); 
Timer timer = new Timer();
TimerTask monitorThread = new TimerTask() {
    @Override
    public void run() {  
        if(webView == null || webView.getUrl() == null)
            return;
        
        // url이 현재 webView의 url이 아닐수 있기 때문에, 위에서 선언한 클래스의 onPageFinished() 메쏘드를 활용할 필요가 있다.
        String url = webView.getUrl();
    }
}
timer.schedule(monitorThread, 10* 1000, 2000); // 10초 후에, 매 2초마다 monitorThread를 실행

결론으로, WebView의 getUrl()메쏘드가 잘 동작하지만, 실제로 잘 못 가져오는 문제가 있기 때문에, 정확한 URL을 가져오기 위해서는 위의 WebViewClient를 상속한 OAuthWebClient같은 클래스를 만들어서, 좀 더 정확하게 의도한 바를 오버라이딩을 통해서 구현할 수 있을 것이다.

고성능 앱 서비스를 위한 아키텍처링..

아래 내용은 제가 테스트한 내용이 아니라, 경험을 기반으로 제가 생각하는 고성능의 서비스를 위한 아키텍처링에 대한 내용이고, 아래 내용은 앱<->API 서버 간의 성능향상을 위한 방안에 대한 내용이다.

요즘 상당히 많은 앱들이 네트웍으로 데이터를 전송받아서 랜더링하고, 유저가 만들어낸 데이터를 전송하기도 한다. 그래서, 네트웍으로 데이터를 주고 받는 앱들은 서버(보통 웹서버)와의 통신에서 JSON을 표준(많이 사용하는 추세) 프로토콜처럼 사용하고 있다. JSON을 사용하는 아키텍처가 가지고 있는 성능저하 요소를 살펴보면, 아래의 목록을 예상할 수 있습니다.

– JSON의 사용은 파싱에 드는 비용, 데이터 사이즈로 인한 전송(3G 대비)속도 저하가 예상이 된다.
– HTTP/HTTPS 프로토콜을 사용하는 서버(웹 서버)를 사용해서, 연결과 데이터 전송과 수신에 오버헤드(HTTP 프로토콜의 헤더만 읽어봐도 이해가 되실 듯)가 발생한다. 

그래서, 위의 성능저하 요소를 걷어내고 고성능의 앱을 개발하기 위한 방법으로, 아래의 방식을 고려할 필요가 있다. 아래의 형태로 개발하게 되면, 개발 비용이 증가하는 단점이 있다. 하지만, 네트웍을 많이 사용하는 앱(예로 카카오톡이나 채팅앱)이라면 연결기반의 프로토콜을 서버가 제공하고, 앱이 그 프로토콜을 사용한다면, 매번 연결하지 않고 네트웍으로 데이터를 보내고 받는 것이, 위의 비 연결지향의 프로토콜인 HTTP/HTTPS와는 비교할 수 없을 정도로 빠른것을 알게 될 것이다.

– JSON 보다는 고 성능의 데이터 직렬화 라이브러리(MessagePack, Thrift, Google Protocol Buffers)를 사용한다.
– 소켓(SSL 적용) 서버(Netty 프레임윅 등으로 개발)로 연결지향 프로토콜 기반에서 데이터를 처리한다. 이 방식의 경우에는 소켓 서버에서 클라이언트의 idle time을 적절하게 계산해서 주기적으로 클라이언트의 소켓을 제거해 주는 로직이 필요합니다.. 이 로직이 없을 경우, file descriptor가 부족해 지겠죠.. ^^

당장은, 필요하다고 느끼지 못하거나, 너무 오버한다고 생각할 수 있으나, 기본적으로 유저가 많은 포털의 앱이나, 이미 많은 유저가 사용하는 앱들은 프로토콜과 데이터 형식만 바꿔도 상당한 성능향상을 가져올 수 있을 것이라 생각한다. ^^

Eclipse ADT에서 Layout의 selector xml의 이미지 버튼등이 안 보일때..

이클립스에서 문법이 전혀 틀리지 않았는데..
selector.xml이 layout에서 전혀 보이지 않는 문제가 종종 발생을 합니다..
몬가 살펴보니, Error Log를 보라고 해서, 살펴보니 아래와 같네요..

org.xmlpull.v1.XmlPullParserException: Binary XML file line #3: <item> tag requires a ‘drawable’ attribute or child tag defining a drawable
…………….
….
..

간단한 selector인데 흠.. drawable attribute도 있구요..
흠… 아무리 해도 안되네요.. ^^;;

그래서, 이클립스를 다시 재시작을 했습니다.. 잘됩니다..
젠장, 그래서 결론은 문제가 없어 보이는데, 에러가 발생하면, 재시작.. 재시작이 중요한 해결책이 될 수 있겠네요.. ^^

안드로이드(Android) Dialog의 cancel()과 dismiss()의 차이

안드로이드(Android)에서 다이얼로그(Dialog)는 앱이 자주 사용하는 UI 컴포넌트이다. 다이얼로그를 보여주는 데는 show() 세드를 사용하고, 종료하는데는 cancel(), dismiss() 메서드를 사용할 수 있다. 종료하는 이 두 메서드에 대해서 API 문서에서는 아래와 같이 설명하고 있다. UI 경험이 별로 없다보니 잘 감이 안 온다. 화면에서 다이얼로그가 종료되는 동작은 같기에 차이가 없어 보인다.

종료하는데 사용하는 두 메서드의 API 문서를 살펴보면 다음과 같다.

void cancel()

Cancel the dialog.
void dismiss()

Dismiss this dialog, removing it from the screen.

이 두 메서드의 차이에 대해서 살펴보자. API 문서만으로 차이점을 알기가 쉽지 않으니, 다이얼로그 API 소스에서 차이를 확인해보자.

1. cancel() 메서드 소스

    /**
     * Cancel the dialog.  This is essentially the same as calling {@link #dismiss()}, but it will
     * also call your {@link DialogInterface.OnCancelListener} (if registered).
     */
    public void cancel() {
        if (!mCanceled && mCancelMessage != null) {
            mCanceled = true;
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mCancelMessage).sendToTarget();
        }
        dismiss();
    }

위 canel() 메서드는 다음의 단계를 거치게 된다. 그리고 이 메서드는 OnCancelListener로 등록한 객체가 있으면 호출해 준다.

  • mCancelMessage가 있으면 메시지를 전달한다.
  • dismiss()를 호출한다.

2. dismiss() 메서드

 
    /**
     * Dismiss this dialog, removing it from the screen. This method can be
     * invoked safely from any thread.  Note that you should not override this
     * method to do cleanup when the dialog is dismissed, instead implement
     * that in {@link #onStop}.
     */
    public void dismiss() {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(mDismissAction);
        } else {
            mHandler.removeCallbacks(mDismissAction);
            mDismissAction.run();
        }
    }

위 dismiss() 메서드는 화면에서 다이얼로그를 종료하는 역할을 하고 스레드 세이프 하다. 스레드 세이프 하다는 것으로 UI 스레드만이 다이얼로그를 종료시킨다는 것도 알 수 있다. 다이얼로그는 mDismissAction.run() 메서드가 종료시킨다. 이 메서드는 dismissDialog() 메서드를 호출하고, 이 메서드에서는 윈도 매니저를 사용해서 다이얼로그를 윈도에서 제거한다.

그리고 다이얼로그가 화면에 보이는 상황에서 백 키를 누르면 아래의 소스와 같이 동작한다. 즉 cancel() 메서드를 호출해서 다이얼로그를 종료시킨다. 따라서 백키의 이벤트로 다이얼로그가 종료하는 상황은 DialogInterface.OnCancelListener 인터페이스를 구현하면 되겠다.

    /**
     * Called when the dialog has detected the user's press of the back
     * key.  The default implementation simply cancels the dialog (only if
     * it is cancelable), but you can override this to do whatever you want.
     */
    public void onBackPressed() {
        if (mCancelable) {
            cancel();
        }
    }

위에서 살펴본 cancel()과 dismiss()와 같이 API 문서를 봐도 차이점이 잘 이해가 되지 않는 경우에는 API 소스를 보면서 확인하는게 매우 도움이 된다.

안드로이드 구글 분석툴(Google Analytics for Android)을 살펴보자.

안드로이드 구글 분석 툴(Google Analytics for Android)은 안드로이드 앱에서 발생하는 페이지 뷰, 액션등의 데이터를 수집하고 분석해 주는 서비스이다. 이 툴은  Analytics SDK for Android V3 에서 다운로드 받을 수 있다. 다운로드 받은 파일에서 libGoogleAnalytics.jar 파일을 프로젝트의 lib 폴더에 복사하고, 빌드 패스(Build Path)에 추가한 뒤에 사용하면 된다.

위 사이트에서 예제 코드를 보면, 버튼의 클릭은 trackEvent() 메서드, 액티비티(Activity)를 로딩해서 데이터를 가져오는 형태는 trackPageView() 메소드를 호출해서 데이터를 수집하는 것을 볼 수 있다. 그리고 코드는 일정시간이 지나면 크론(Cron) 잡처럼 디스패칭해서 수집된 데이터를 서버(Google Analytics)로 전송할 수 있다는 것도 알 수 있다.

그래서 이 라이브러리의 소스가 궁금해서 간략해서 분석해 봤다.

1. GoogleAnalyticsTracker는 static 인스턴스로 전형적인 싱클톤 패턴의 형태를 띠고 있다.
2. 예제의 startSession 메소드를 호출하게 되면
2.1 이벤트를 저장할 데이터베이스를 생성한다
2.2 네트웍으로 처리할 디스패처를 생성한다..
2.3 디스패처의 콜백을 받을넘을 생성한다..
2.4 네트웍 연결관리하는 매니저를 생성하고..
2.5 안드로이드의 Handler를 하나 생성한다.. 이넘의 용도는 Timer의 schedule메소드와 비슷하게 일정시간 뒤에 등록한 Handler의 메소드를 실행한다..
3. 예제의 trackEvent() 메서드를 호출하면..
3.1 이벤트 객체를 만들어서, 위 Handler객체의 postDelayed() 메소드를 사용해서 디스패처를 실행한다.
4. 예제의 stopSession을 호출하면..
4.1 디스패처를 종료한다. 디스패처가 종료되면 서버에 리포팅하는 스레드를 종료시킨다.

위 과정이 안드로이드 구글 분석 툴이 수집한 데이터를 서버에 전달하는 기본 과정이다. 이 과정에서 데이터를 서버로 전송하는 디스패처로 NetworkDispater 클래스(내부에서 아파치 HTTP 라이브러리 사용)를 사용하고 있고, 간단한 형태는 아래와 같다.

class NetworkDispatcher implements Dispatcher
{
	private static final String GOOGLE_ANALYTICS_HOST_NAME = "www.google-analytics.com";
	private static final int GOOGLE_ANALYTICS_HOST_PORT = 80;
	private static final int MAX_GET_LENGTH = 2036; // 2048도 아닌 2036의 사이즈는 왜?? 흠.. 궁금하다.. 
	private static final int MAX_POST_LENGTH = 8192;
	private static final String USER_AGENT_TEMPLATE = "%s/%s (Linux; U; Android %s; %s-%s; %s Build/%s)";
	private final String userAgent;
	private static final int MAX_EVENTS_PER_PIPELINE = 30;
	private static final int MAX_SEQUENTIAL_REQUESTS = 5;
	private static final long MIN_RETRY_INTERVAL = 2L;
	private final HttpHost googleAnalyticsHost;
	private DispatcherThread dispatcherThread;
	private boolean dryRun = false;  

	public NetworkDispatcher() {
		this("GoogleAnalytics", "1.4.2");
	}

	public NetworkDispatcher(String paramString1, String paramString2) {
		this(paramString1, paramString2, "www.google-analytics.com", 80);
	}

	NetworkDispatcher(String paramString1, String paramString2, String paramString3, int paramInt) {
		this.googleAnalyticsHost = new HttpHost(paramString3, paramInt);
		Locale localLocale = Locale.getDefault();
		this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s-%s; %s Build/%s)", new Object[] { paramString1, paramString2, Build.VERSION.RELEASE, localLocale.getLanguage() != null ? localLocale.getLanguage().toLowerCase() : "en", localLocale.getCountry() != null ? localLocale.getCountry().toLowerCase() : "", Build.MODEL, Build.ID });
	}
}

 

이제 구글 분석 툴(Google Analytics for Android)에 대해서 간략하게 살펴봤다. 이런 라이브러리를 사용하기전에 혹시 성능이슈가 발생하지 않을까? 또는 네트웍이 안 되면 문제가 생기지 않을까? 하는 의문은 역시 소스가 답을 해 준다.

한마디 더 덧붙이자면, 소스 코드 좋쿠나 ^^