안드로이드는 기본적으로 프로그래밍 모델이 랜더링과 별도로 데이터를 가져오는 쓰레드를 AsyncTask를 상속받아서 Asynchronous하게 처리하게 끔 가이드를 하고 있습니다.. 참고로, 아래내용은 안드로이드 2.2 기반에서의 경험입니다. ^^

그래고, 많은 데이터를 가지고 오기 위해서 페이징을 합니다. 안드로이드는 하단에 spinner와 Loading과 같은 메세지가 표준처럼 사용되고 있죠.

사진에 대한 Thumbnail이 매우 많고, 30개씩 가져온다고 가정을 합니다.. 보통 ArrayAdapter나 ListAdapter를 상속받아서 Adapter 객체를 만들고, Adapter 객체의 getView()에서 랜더링을 위한 작업을 구현하게 됩니다.
@Override
public View getView(int position, View cView, ViewGroup parent) {
}

Thumbnail을 가져와야 하니, 보통 위 메소드 안에서 Thumbnail을 가져오기 위해, AsyncTask를 상속받은 쓰레드로 처리를 하는 것이 일반적입니다. 이 상황은 쓰레드를 많이 생성하게 됩니다..

위의 경우에 RejectedExecutionException 이 발생할 수 있습니다.. 안드로이드는 내부적으로 java.util.concurrent 패키지의 ExecutorService를 Thread 디스패칭에 사용하고 있습니다. 따라서, 요 문제는 아래의 ExecutorService가 처리해야 하는 AsyncTask의 POOL_SIZE가 순간적으로 넘어서고, 이 경우에 AsyncTask가 풀에 들어갈 수 없어서 발생하는 익셉션입니다. 위 문제는 빈번하게 발생하지 않을 것 같지만, 게임이라던지 쓰레드가 많이 필요한 작업을 하다 보면 발생할 수 있습니다..

아래는 Android 플랫폼에서 기본적으로 가지고 있는 AsyncTask의 정책값입니다.
private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 10;
private static final int KEEP_ALIVE = 10;

따라서, 위 문제는 하나의 쓰레드로 30개의 이미지를 가져오는 형태로 살짝 바꾸면 해결이 됩니다. 당근 성능과 안전성의 트레이드 오프라고 할 수 있겠습니다..

위에서 간단하게 RejectedExecutionException을 피해갈 수 있는 방안에 대해서 살펴보았습니다..
위에서 고려한 사진의 Thumbnail에 대한 예로, 한번 더 고민을 해 보면, 만약에 30개를 가져오는 쓰레드를 getView()외에서 처리를 한다면, 지금 보고 있는 화면에서 사진이 안 보일 겁니다.. 왜냐하면, getView()에서 처리를 하지 않았기 때문에 빈 사진만 보이겠죠.

그래서, 위 가정대로, 사진이 많아서 페이징을 해야하고 Thumbnail을 보여준다면, 사진을 가져오는 첫 페이지에서는 getView()에서 AsyncTask를 불러서 사진을 가져오고, 두 번째 페이지 부터는 데이터를 가져와서 Adapter에 바인딩하는 쓰레드가 30개의 사진을 처리하는 쓰레드를 다시 부르는 형태로 구현을 하는 것이 좋을 것 같습니다..
저작자 표시
안드로이드에서는 메세지를 보여주고 사라지는 용도로 Toast를 빈번하게 사용하고 있습니다.
하지만, 아쉬운 점이 하나 있다면, duration을 설정할 수 없습니다.
그래서, 간단하게 duration을 변경하는 방법으로, Toast의 show()를 쓰레드로 원하는 시간 만큼 계속 보여주면 쉽게 해결할 수 있습니다.

간단하게, 안드로이드 Toast 클래스에서 제공하는 SHORT과 LONG의 기본 시간값입니다..
private static final int LONG_DELAY = 3500; // 3.5 seconds
private static final int SHORT_DELAY = 2000; // 2 seconds

아래는 CountDownTimer를 이용해서 총 4초동안, 1초씩 줄어들면서 Toast의 show() 메소드를 호출해서, 총 6초(Toast.LENGTH_SHORT이 duration 이니)동안 메세지를 보여주는 코드입니다..

final Toast toast = Toast.makeText(context(), getString(R.string.login_error),Toast.LENGTH_SHORT);
toast.show();
new CountDownTimer(4000, 1000) {
  public void onTick(long millisUntilFinished) {toast.show();}
  public void onFinish() {toast.show();}
}.start();

또 다른 방법으로 https://github.com/quiqueqs/Toast-Expander/blob/master/src/com/thirtymatches/toasted/ToastExpander.java 에서 쓰레드로 시간을 빼면서 루프를 돌리는 방법도 있습니다.

저작자 표시
Tag // Android, Toast
Facebook은 정말 플랫폼 회사라고 할 수 있는 다양한 SDK를 지원하고 있고, 그 중에 안드로이드에서 Facebook을 쉽게 연동할 수 있는 SDK가 있습니다.. 보통 SDK라고 하면, 전통적인 관념에서 보자면, 소트프웨어를 개발하가 위해서 지원하는 툴킷을 포함한 라이브러리(?)라고 정의할 수 있겠습니다..

그런데, Facebook SDK를 받아서 사용해 보면, 라이브러리는 아니지만, 그렇다고 해서 SDK라고 봐야 하는지도 약간은 의문인데.. 형태는 프레임웍의 모습을 띄고 있습니다.. 흠, 모습이 좀 애매해서, 그냥 SDK라고 봐야 하나 봅니다..
서두가 너무 길었네요.. ^^;;

제가 안드로이드에서 Facebook을 연동하면서 이슈가 될 만한 것중에 2가지를 골라봤습니다..

1. 인증창이 안뜨는 경우가 종종 발생한다..
제가 2.2와 2.3버전에서 테스트를 해 봤는데, 에뮬레이터는 잘 동작합니다..
하지만, 폰에 설치를 해보니, 되는 폰도 있고 아닌 것도 있습니다..
그래서, 인증창을 띄울때 기존의 예제에서 조금 더 추가해줘야 할 필요가 있습니다.  

- 예제코드(http://developers.facebook.com/docs/mobile/android/build/)는 아래의 코드입니다.. 
facebook.authorize(this, new String[] { "email", "publish_checkins" },
      new DialogListener() {
           @Override
           public void onComplete(Bundle values) {}

           @Override
           public void onFacebookError(FacebookError error) {}

           @Override
           public void onError(DialogError e) {}

           @Override
           public void onCancel() {}
      }
);

- 위의 코드로 인증창이 안뜨는 문제가 해결된 코드..
facebook.authorize(this, Facebook.FORCE_DIALOG_AUTH, new String[] { "email", "publish_checkins" },
      new DialogListener() {
           @Override
           public void onComplete(Bundle values) {}

           @Override
           public void onFacebookError(FacebookError error) {}

           @Override
           public void onError(DialogError e) {}

           @Override
           public void onCancel() {}
      }
);


2. Facebook accessToken의 캐싱타임..
- 요 문제는 기본적으로 accessToken의 캐싱 타임이 하루로 설정되어 있기 때문에 발생할 수 있습니다.
https://github.com/facebook/facebook-android-sdk/blob/master/facebook/src/com/facebook/android/Facebook.java 소스의 대략 91번째 줄이 accessToken의 캐싱타임입니다..
코드를 살짝 보니, 두둥 final private long REFRESH_TOKEN_BARRIER = 24L * 60L * 60L * 1000L; <-- 수정도 보지도 마라라는 코드.. ^^;;
흠, 그래서 입맛에 맞게 accessToken의 캐싱 타임을 줄이려면, Facebook SDK의 REFRESH_TOKEN_BARRIER 값을 변경해야 합니다..

위의 기본적인 2가지만 해결하면 사용하는데 이슈가 별로 없을 것 같습니다.. ^^
저작자 표시
보통 Base64는 특수문자나 바이너리를 인코딩해서 네트웍이나 파일처리를 위해서 사용합니다.
물론, 설정파일에 입력하기 어려운 " 이나 특수문자가 포함된 데이터는 설정파일에 넣게 되면 에러가 발생하게 됩니다..

그래서, 민감한 데이터를 암호화해서 로컬에 저장하기도 하는데, 암호화를 하다보면 특수문자 때문에 데이터가 로컬의 설정파일에 들어가지 않는 경우가 종종 발생합니다.. 그 때도 Base64로 인코딩해서 저장을 하면 쉽게 해결이 됩니다..

그래서, 간단한 Base64 클래스를 끄적여 봅니다..

import java.io.UnsupportedEncodingException;
import android.util.Base64;

/**
 * <pre>
 * net.sjava.android.util.Base64Util.java
 * </pre>
 *
 * @author : mcsong@gmail.com
 * @version :
 * @data : 2011. 10. 2. 오후 3:34:23
 *
 */
public class Base64Util {
    
    /**
     * Encode txt
     * @param txt
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String encode(String txt) throws UnsupportedEncodingException {
        byte[] data = txt.getBytes("UTF-8");
        return Base64.encodeToString(data, Base64.DEFAULT);
    }
    
    /**
     * Decode txt
     * @param txt
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String decode(String txt) throws UnsupportedEncodingException {
            return new String(Base64.decode(txt, Base64.DEFAULT), "UTF-8");
    }
}


저작자 표시
Tag // Android, BASE64
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같은 클래스를 만들어서, 좀 더 정확하게 의도한 바를 오버라이딩을 통해서 구현할 수 있을 것이다.

저작자 표시
이클립스에서 문법이 전혀 틀리지 않았는데..
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도 있구요..
흠... 아무리 해도 안되네요.. ^^;;

그래서, 이클립스를 다시 재시작을 했습니다.. 잘됩니다..
젠장, 그래서 결론은 문제가 없어 보이는데, 에러가 발생하면, 재시작.. 재시작이 중요한 해결책이 될 수 있겠네요.. ^^
저작자 표시
Tag // ADT, Android, XML
Dialog는 안드로이드에서 자주 사용되는 UI 컴퍼넌트이지요..
그런데, Dialog를 show()하고 종료하기 위해서, cancel(), dismiss()를 사용할 수 있는데요..
API 문서에서는 아래와 같이 설명을 하고 있습니다.. 제가 UI쪽은 거의 안해봐서 그런가? 동작하는게 똑 같아 보입니다..


void cancel()
Cancel the dialog.

void dismiss()
Dismiss this dialog, removing it from the screen.


흠.. 몬 차이가 있을까요??
소스에 자세한 설명이 있네요.. 젠장..^^;;

아래 canel() 메소드를 호출하면 mCancelMessage가 있으면 보내주고, dismiss()를 호출하는 구조이고, onCancelListener에서 선언한 메소드를 호출해 준다고..

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


아래 dismiss()메소드는 스크린에서 dialog를 제거하는데 쓰레드 세이프 하다네요..

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 onStop().
 
     public void dismiss() {
         if (Thread.currentThread() != ) {
             .post();
         } else {
             .run();
         }
     }


흠.. API 문서를 봐서 잘 이해가 안되는 메소드는 역시, 소스를 살펴봐야 됩니다..
역시 소스에 답이 있다는 진리는 변하지가 않는 것 같습니다.. ^^


저작자 표시
 Google Analytics for Android는 http://code.google.com/mobile/analytics/docs/android/ 에서 다운로드를 받고.. libGoogleAnalytics.jar 파일을 프로젝트의 lib 폴더에 복사를 하고, build path에 잡아서 사용하면 된다..

대충 예제 코드만 보면, 버튼의 클릭은 trackEvent, Activity를 로딩해서 데이터를 가져오는 형태는 trackPageView 메소드를 호출해서 통계자료로 사용하는 것으로 보인다.. 그리고, 코드는 일정시간이 지나면 Cron 잡처럼 디스패칭해서 데이터를 서버(Google Analytics)로 전송하는 형태라는 것을 알려주고 있다.

흠,, 그래도 도대체 어떻게 구현했는지가 궁금했다.. 그래서, 살짝 까본 결과는 아래와 같다.

* GoogleAnalyticsTracker는 static 인스턴스로 전형적인 싱클톤 패턴의 형태를 띠고 있다.

* 예제의 startSession 메소드를 호출하게 되면
 ** 이벤트를 저장할 데이터베이스를 생성한다
 ** 네트웍으로 처리할 디스패처를 생성한다..
 ** 디스패처의 콜백을 받을넘을 생성한다..
 ** 네트웍 연결관리하는 매니저를 생성하고..
 ** 안드로이드의 Handler를 하나 생성한다.. 이넘의 용도는 Timer의 schedule메소드와 비슷하게 일정시간 뒤에 등록한 Handler의 메소드를 실행한다..

* 예제의 trackEvent를 호출하면..
 ** 이벤트 객체를 만들어서, 위의 Handler객체의 postDelayed메소드를 사용해서 디스패처를 구동한다.

* 예제의 stopSession을 호출하면..
 ** 디스패처를 종료한다.. 결국 서버에 리포팅하는 쓰레드를 종료한다는 것이다..

흠, 위 과정이 Google Analytics for Android가 서버에 통계데이터를 리포팅하는 러프한 과정이다.
그 중에서도 디스패처로 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를 사용하기 전에, 혹시 앱에 성능이슈를 유발할까? 또는 네트웍이 안 되면 문제가 생기지 않을까? 하는 의문은 역시 코드가 답을 해 준다.. 한마디 더 덧붙이자면, 코드 잘짰넹.. ㅋㅋ
저작자 표시
Android 기반의 앱을 개발하다 보면, 해상도 문제가 골치 아프다..
특히, 태블릿을 지원하기 시작하는 허니콤이나, 모바일과 태블릿을 같이 지원하는 아이스크림까지 나온 상황에서는 말이다..

물론, 해상도에 적합하게 xml을 선언해서 개발하라고 하는 구글의 가이드(Supporting Multiple Screens)도 있지만, 다양한 해상도를 지원하기 위해서 /res/blahblah/layout.xml을 만들라고 하는것은.. 해상도에 맞춰 앱의 퀄리티를 유지하는 방향으로는 좋겠지만, 그 만큼 개발비용(iOS에 비해)을 증가시킬게 분명하다.. 물론, 이렇게 해서 태블릿도 하나의 앱에서 지원하면 좋겠지만, 개발해 보니, 이렇게 분리해서 태블릿의 장점을 녹여낼 수 있을까?라는 질문에는 회의적이다..

그래서, 개인적으로 도달한 답은 모바일 앱과 태블릿 앱을 분리해서 개발하는 게 좋을 듯 하다..
그럼, 각각의 앱을 개발하면서 각자(모바일, 태블릿)의 해상도는 어떻게 지원할 것인가? 에 대한 대답은 layout_weight으로 UI를 구성하는 것이다..

아래의 예제에서 빨간색으로 구성한 것들이 <LinearLayout>에서 각 UI가 차지하는 %를 숫자로 표현한 것이고, weightSum은 없으면 알아서 배열을 하지만, 아래의 예에서는 입력을 해 놓았다..

그리고, horizontal이든지 vertical이든지 비율로 크기를 맞추기 위해서는, 가로를 맞추기 위해서는 layout_width, 세로로 맞추기 위해서는 layout_height을 '0dp'로 입력을 해 두면 된다..

흠, 자 이렇게 하면, 다양한 해상도를 쉽게 지원할 수 있답니다...

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/linearLayout"
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:background="#568dfb"
        android:weightSum="10.0"
        android:layout_gravity="center_vertical"
        android:layout_height="40dp">
  
        <TextView android:id="@+id/textView"
            android:text=" "
            android:visibility="visible"
            android:paddingLeft="8dp"
            android:layout_width="0dp"
            android:layout_weight="6"
            android:textColor="@android:color/white"
            android:layout_height="40dp"
            android:textAppearance="?android:attr/textAppearanceMedium"/>
  
           <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/linear_container"
            android:orientation="horizontal"
            android:layout_width="0dp"
            android:layout_height="40dp"
            android:layout_weight="4"
            android:background="#568dfb"
            android:gravity="right"
            android:layout_gravity="center_vertical">
           
            <ProgressBar
                android:layout_gravity="right|center_vertical"
                android:background="#568dfb"
                android:layout_width="30dp"
                android:layout_height="30dp" />
           
            <TextView
                android:id="@+id/loadingTextView"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:layout_gravity="right"
                android:background="#568dfb"
                android:gravity="center_vertical"
                android:paddingLeft="4dp"
                android:paddingRight="4dp"
                android:text="Loading..."
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:textColor="@android:color/white"/>
        </LinearLayout>
    </LinearLayout>

저작자 표시
Android에서 Facebook SDK를 사용해서 인증하는 내용입니다.. 
기본적으로 Facebook Developer 페이지에서 자세한 내용을 설명하고 있습니다.. 
아래는 그 내용을 간략하게 정리한 내용입니다.. 

1.  https://github.com/facebook/facebook-android-sdk 다운로드 및 풀기
 1.1. 압축을 푼 프로젝트에서 facebook 폴더의 프로젝트를 eclipse에서 import 한다.. 

 

 1.2 기존의 프로젝트에 reference 걸기.. 
  - Project > Properties > Android > Library > Add로 com_facebook_sdk를 추가해 준다. 

  


2. OpenSSL 설치
 2.1. 리눅스 설치 : http://www.ibrtses.com/linux/openssl.html
 2.2. 윈도우즈 설치 : http://www.slproweb.com/products/Win32OpenSSL.html 에서 환경에 맞게 설치를 한다. 혹, bin 디렉토리가 PATH에 안 잡혀 있어서 Facebook에 등록해야 하는 해시키 생성에 실패할 수 있으니 확인이 필요함.. 

3. 해시키 만들고 등록하기..
 3.1 해시키 만들기 : 아래의 명령을 통해서 해시키를 만든다..  
  > keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64
 3.2 만들어진 해시키를 Facebook에 등록한다.. 
      Facebook Developer 페이지의 Step 5의 이미지를 보면 쉽게 확인할 수 있다. 

4. 테스트 하기..
테스트 코드도 역시 Facebook Developer 페이지에서 확인하면 좋다.. ^^
 
저작자 표시
안드로이드 2.3 부터인가? AsyncTask 기반의 쓰레드 프로그래밍을 해야 합니다.. 그러다가 자주는 아니지만 가끔 android.view.WindowLeaked 에러를 토하면서 앱이 죽을 때가 있는데, 이런 경우에는 별도 쓰레드(AsyncTask 같은)를 실행한 Activity가 onPuase나 onClose 상태가 되면서 finish되는 상태가 되고, 별도 쓰레드(UI 작업을 하는)가 아직 실행중인 상태에서 발생을 하게 됩니다.. 결국, 현재 Context를 AsyncTask에서 사용하고 있는데, Context가 사라저 버려서 발생을 합니다.. 

그래서, 간단한 해결책을 살펴보자..
Dialog류의 작업을 보통 AsyncTask로 많이 구동시키는데..

1. Dialog를 클래스 변수로 선언을 한다..
private ProgressDialog dialog;
private Dialog d; 
2. 별도 쓰레드에서 클래스 변수를 인스턴스화해서 사용하자..
dialog = ProgressDialogFactory.create(AAAActivity.this, false);
3. Activity의 onPause나 onClose같은 상황이 되서 Activity가 finish되는 상황에서 dialog를 종료해 주자..
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
dialog = null;
}
if(d != null && d.isShowing()) {
d.dismiss();
d = null;
}
finish(); 

위 방식으로 프로그래밍을 하면 android.view.WindowLeaked 문제는 쉽게 해결하겠다.. ^^
저작자 표시

Android Oftener 프로젝트..

from Android 2011/11/11 01:01
Android Oftener 프로젝트..
이 프로젝트는 간단하게 내가 자주 전화를 걸고, 문자를 보내는 사람들을 화면에 많이 보낸 순으로 정렬해 주고, 전화와 문자를 바로 보낼 수 있게 해 주는 유틸리티성 앱 프로젝트이다..

이 프로젝트는 간단하게 개발할 수 있어서 3~4일에 걸쳐서 개발을 했고, 디자이너(ideamplifier_at_gmail.com)인 존님의 도움을 받아서 나름 디자인을 입혔다..

개인적으로, 번들 Favorite이 좀 불편해서 개발을 했고, 많이 사용하는 앱이 되었으면 좋겠다..
아래는 캡쳐를 한 화면이다..

마켓 링크 : https://market.android.com/details?id=net.sjava.oftener&feature=search_result#?t=W251bGwsMSwyLDEsIm5ldC5zamF2YS5vZnRlbmVyIl0.

 


저작자 표시
모바일 애플리케이션들은 대체로 Http보다는 보안 이슈로 Https를 사용합니다..
안드로이드의 Http/Https 래퍼는 Apache HTTP Client와 Java의 HttpUrlConnection(HttpsURLConnection) 2 가지가 있습니다. 공식 블로그에서는 진저브레드(Gingerbread) 이상에서는 HttpURLConnection을 사용하기를 권장하고 있습니다. 하지만, 상당히 많은 앱에서 Apache HTTP Client를 사용하고 있을 것으로 생각이 듭니다.. 저 같은 경우에는, 양 쪽을 같이 혼용해서 사용하고 있습니다.. 

SSLSessionCache 클래스는 API 문서를 보시면 알 수 있습니다.. 
2개의 옵션을 제공하는데, 세션캐시를 특정위치에 저장할 수 있거나, 기본 위치에 저장하는 정도.. ^^;;

아래의 코드는 HttpClient를 생성해주는 메쏘드입니다. 
빨간색 부분이 SSL에 SSLSessionCache를 사용하게 해 주는 코드입니다.. 흠.. 

public static synchronized HttpClient createHttpClient(Context c, int cTimeout, int sTimeout) {
// Use a session cache for SSL sockets

    SSLSessionCache sessionCache = c == null ? null : new SSLSessionCache(c);

        
// sets up parameters
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, "utf-8");
HttpConnectionParams.setConnectionTimeout(params, cTimeout);
HttpConnectionParams.setSoTimeout(params, sTimeout);

params.setBooleanParameter("http.protocol.expect-continue", false);

// registers schemes for both http and https
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
final SSLSocketFactory sslSocketFactory = SSLCertificateSocketFactory.getHttpSocketFactory(sTimeout, sessionCache);
//sslSocketFactory.setHostnameVerifier(SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
sslSocketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
registry.register(new Scheme("https", sslSocketFactory, 443));
ThreadSafeClientConnManager manager = new ThreadSafeClientConnManager(params, registry);
return new DefaultHttpClient(manager, params);
}


위 내용은 http://hi-android.info/src/android/net/http/AndroidHttpClient.java.html와 http://na.isobar.com/2011/best-way-to-use-httpclient-in-android/에서 참고했습니다.. ^^
저작자 표시
Android에서 앱의 Activity Stack을 일정 숫자만큼 유지를 하는 클래스입니다..
이 클래스를 통해서 앱의 종료도 detect할 수 있습니다..

* ActivityManager클래스..
코드가 너무 간단해서 설명 생략..
import java.util.concurrent.LinkedBlockingQueue;
import android.app.Activity;
/**
 * Activity Manager Class
 * @author mcsong@gmail.com
 *
 */
public class ActivityManager {
    private static final int max = 13;
    private static final int size =12;
    private static LinkedBlockingQueue<Activity> queue = new LinkedBlockingQueue<Activity>(max);
    private static final String CNAME = ActivityManager.class.getSimpleName();
   
    public static void createActivity(Activity activity) {
        if(activity == null)
            return;

        queue.add(activity);
        if(queue.size() > size) {
            try {
                queue.take().finish();
            } catch (Exception e) {
                LogUtil.e(CNAME, LogUtil.TAG, ExceptionUtil.getException(e));               
            }
        }
    }
   
    public static boolean isRootActivity(Activity activity) {
        if(activity == null)
            return false;
       
        try {
            if(activity == queue.peek())
                return true;
        } catch (Exception e) {
            LogUtil.e(CNAME, LogUtil.TAG, ExceptionUtil.getException(e));               
        }
       
        return false;
    }

    public static void clear() throws InterruptedException {
        while(queue.size() > 0) {
            queue.take().finish();
        }
    }
}

* 사용방법
- Activity 생성(onCreate 메소드에서)시에 createActivity()를 호출한다. 자연스럽게 size만큼 Activity Stack이 관리가 된다.
- 앱 종료 detect는 isRootActivity()로 확인을 하면 된다.. ture인 경우 각 Activity 클래스에서.. onBackPressed()를 Override해서 확인하면 된다.. 참고로, AbstractActivity 클래스를 만들어서 상속을 사용하면 코드가 깔끔해 지겠다..
- 앱을 종료하면서 clear()를 호출해서 Activity stack에 있는 Activity들을 다 날려주셔야 Activity 관리가 명확해 집니다.. ^^

* 안드로이드 activity의 동작형태는 android:launchMode="standard" 에서 잘 동작합니다.. ^^

저작자 표시

ACCESS_CHECKIN_PROPERTIES      체크인데이터베이스의_속성테이블로_액세스
ACCESS_COARSE_LOCATION         코스_로케이션_액세스_(Cell-ID/WiFi)
ACCESS_FINE_LOCATION           파인로케이션_액세스(GPS)         
ACCESS_LOCATION_EXTRA_COMMANDS 로케이션_옵션_커맨드_액세스      
ACCESS_MOCK_LOCATION           목_로케이션_프로바이더_생성_(테스트용)
ACCESS_NETWORK_STATE           네트워크_상태_접근               
ACCESS_SURFACE_FLINGER         서피스_플링거_접근               
ACCESS_WIFI_STATE              WiFi상태_접근                    
ADD_SYSTEM_SERVICE             시스템서비스_추가                
BATTERY_STATS                  배터리_상태                      
BLUETOOTH                      블루투스                         
BLUETOOTH_ADMIN                블루투스_어드민                  
BRICK                          디바이스_실효성_지정             
BROADCAST_PACKAGE_REMOVED      제거된_패키지에_대한_notification_브로드캐스트
BROADCAST_SMS                  SMS에_대한_브로드캐스트          
BROADCAST_STICKY               인텐트_브로드캐스트              
CALL_PHONE                     통화                             
CALL_PRIVILEGED                통화(긴급전화_포함)              
CAMERA                         카메라                           
CHANGE_COMPONENT_ENABLED_STATE 컴포넌트의_실효성_변경           
CHANGE_CONFIGURATION           컨피그_변경                      
CHANGE_NETWORK_STATE           통신상태_변경                    
CHANGE_WIFI_STATE              WiFi상태_변경                    
CLEAR_APP_CACHE                어플리케이션_캐시_클리어         
CLEAR_APP_USER_DATA            어플리케이션의_유저데이터_클리어 
CONTROL_LOCATION_UPDATES       위치정보_갱신                    
DELETE_CACHE_FILES             캐시파일_제거                    
DELETE_PACKAGES                패키지_제거                      
DEVICE_POWER                   전원상태에_대한_로우레벨_접근    
DIAGNOSTIC                     진단리소스_읽고쓰기              
DISABLE_KEYGUARD               키_가드_끄기_DUMP_덤?           
EXPAND_STATUS_BAR              상태표시줄_확장                  
FACTORY_TEST                   팩토리_테스트                    
FLASHLIGHT                     플래시라이트                     
FORCE_BACK                     포스백                           
GET_ACCOUNTS                   어카운트_획득                    
GET_PACKAGE_SIZE               패키지_획득                      
GET_TASKS                      태스크_획득                      
HARDWARE_TEST                  하드웨어테스트                   
INJECT_EVENTS                  유저이벤트_키/트랙볼             
INSTALL_PACKAGES               패키지_인스톨                    
INTERNAL_SYSTEM_WINDOW         내부_시스템윈도_활용             
INTERNET                       인터넷                           
MANAGE_APP_TOKENS              어플리케이션_토큰관리            
MASTER_CLEAR                   마스터_클리어                    
MODIFY_AUDIO_SETTINGS          오디오설정_편집                  
MODIFY_PHONE_STATE             전화상태_편집                    
MOUNT_UNMOUNT_FILESYSTEMS      파일시스템_편집                  
PERSISTENT_ACTIVITY            액티비티_지속                    
PROCESS_OUTGOING_CALLS         전화_발신처리_접근               
READ_CALENDAR                  캘린더_읽어오기                  
READ_CONTACTS                  주소록_읽어오기                  
READ_FRAME_BUFFER              프레임버퍼_읽어오기              
READ_INPUT_STATE               입력상태_읽어오기                
READ_LOGS                      로그_읽어오기                    
READ_OWNER_DATA                owner_data읽어오기               
READ_PHONE_STATE               통화상태_읽어오기_READ_SMS_SMS읽어오기
READ_SYNC_SETTINGS             동기설정_읽어오기                
READ_SYNC_STATS                동기상태_읽어오기                
REBOOT                         reboot                           
RECEIVE_BOOT_COMPLETED         boot완료                         
RECEIVE_MMS                    MMS수신                          
RECEIVE_SMS                    SMS수신                          
RECEIVE_WAP_PUSH               WAP수신                          
RECORD_AUDIO                   오디오_수신                      
REORDER_TASKS                  태스크_Z오더                     
RESTART_PACKAGES               패키지_리스타트                  
SEND_SMS                       SMS송신                          
SET_ACTIVITY_WATCHER           액티비티_왓쳐지정                
SET_ALWAYS_FINISH              액티비티_전체_종료               
SET_ANIMATION_SCALE            스케일_애니메이션_지정           
SET_DEBUG_APP                  디버그어플리케이션_지정          
SET_ORIENTATION                스크린_로테이션지정              
SET_PREFERRED_APPLICATIONS     자주_사용하는_어플리케이션_지정  
SET_PROCESS_FOREGROUND         포어그라운드_처리지정            
SET_PROCESS_LIMIT              제한처리_지정                    
SET_TIME_ZONE                  타임존_지정                      
SET_WALLPAPER                  배경화면_지정                    
SET_WALLPAPER_HINTS            배경화면_힌트_지정               
SIGNAL_PERSISTENT_PROCESSES    지속처리_시그널_지정             
STATUS_BAR                     상태표시줄_지정                  
SUBSCRIBED_FEEDS_READ          서브스트립드_피즈_읽어오기       
SUBSCRIBED_FEEDS_WRITE         서브스트립드_피즈_쓰기           
SYSTEM_ALERT_WINDOW            알림_윈도우                      
VIBRATE                        진동                             
WAKE_LOCK                      알람                             
WRITE_APN_SETTINGS             APN설정_쓰기                     
WRITE_CALENDAR                 캘린더_쓰기                      
WRITE_CONTACTS                 주소록_쓰기                      
WRITE_GSERVICES                G서비스_쓰기                     
WRITE_OWNER_DATA               owner_data쓰기                   
WRITE_SETTINGS                 설정_쓰기
WRITE_SMS                      SMS쓰기
WRITE_SYNC_SETTINGS            동기설정_쓰기


위 내용은 http://javaexpert.tistory.com/329에서 발췌를 하였습니다.

저작자 표시
res/AndroidManifest.xml 파일에 아래 내용을 추가하시면 됩니다. ^^
<uses-permission android:name="android.permission.INTERNET" />