Android2014. 1. 24. 15:01

화면꺼짐 상태에서 GCM 메시지 수신 안되는 문제



갑자기 잘 되던 GCM 메시지 수신이 화면이 잠긴(꺼진, Screen Off)상태에서 수신이 안되는 현상이 발생했다.

화면을 켜면 그때 바로 수신이 된다. 정확히 말하면 화면을 켜고 잠금화면을 풀면 그때 바로 수신이 된다.

또한, 다른 스마트폰에서는 잘되기도 한다. 이유가 뭘까?


그 이유는 GCM을 구글쪽에 발송하는 중계 서버에 문제였는데 GCM을 구글 GCM서버로 발송할 때 "delay_while_idle" 이라는 옵션이 있다. 이 옵션의 Default 값이 안드로이드4.0 이전에는 false였다가 4.0부터 true로 바뀌었기 때문이다. (문제가 발생한 단말기의 버전은 4.1.2 입니다.)


관련해서 안드로이드 공식페이지에서 내용을 찾아봤다.


If the device is connected but idle, the message will still be delivered right away unless the delay_while_idle flag is set to true. Otherwise, it will be stored in the GCM servers until the device is awake.  


dealy_while_idle 옵션은 "true" 또는 "false" 값에 따라 수신되는 동작을 변경할 수 있는데, 만약, false로 설정하면 화면이 꺼진 후 스마트폰이 idle상태가 된 상태에서도 GCM은 바로 수신이 되지만, true로 설정하면 GCM 서버에 저장되었다가 스마트폰이 켜질 때(awake) 수신이 된다.


조금 더 내용을 살펴보면...


If the device is not connected to GCM, the message will be stored until a connection is established (again respecting the collapse key rules). When a connection is established, GCM will deliver all pending messages to the device, regardless of the delay_while_idle flag. If the device never gets connected again (for instance, if it was factory reset), the message will eventually time out and be discarded from GCM storage. The default timeout is 4 weeks, unless the time_to_live flag is set.


스마트폰이 GCM서버에 연결되어 있지 않으면 메시지는 연결이 될때까지 저장되고, 이후에 연결이 완료되면 GCM서버는 그때 미루고 있었던 메시지를 스마트폰으로 전달하게 된다. 중요한 것은 이 과정에서 dealy_while_idle 옵션이 어떻게 되어 있는지 상관없이 동작을 한단다. 스크린을 끈 상태에서 테스트했을 때, 계속 수신이 안되다가도 한 번씩 수신이 되기도 했는데 OS 내부에서 주기적으로 GCM서버에 연결을 하는 동작이 있는 것으로 추측만 했는데 바로 이 내용인가 싶다. 이건 그저 추측이다. (혹시 아시는 분 계시면 명쾌한 답 부탁드립니다. ^^) 


만약, 스마트폰이 공장초기화 등으로 인해 GCM 서버에 더이상 연결을 하지 않으면 4주간 유지했다가 이후에 "time out"이 되어 소멸됩니다. 이 time out 시간은 "time_to_live"옵션으로 변경이 가능하다. (테스트까지는 안했음)


그런데, delay_while_idle이란 옵션이 왜 존재하고 왜 default값을 false에서 true로 변경을 한걸까?


GCM will usually deliver messages immediately after they are sent. However, this might not always be possible. For example, the device could be turned off, offline, or otherwise unavailable. In other cases, the sender itself might request that messages not be delivered until the device becomes active by using the delay_while_idle flag. Finally, GCM might intentionally delay messages to prevent an application from consuming excessive resources and negatively impacting battery life.


이 내용으로 짐작해보면, 결국 사용자가 스마트폰을 사용하지 않을 때 GCM을 수신받아서 뭔가 작업을 하는 것은 결과적으로 더 많은 리소스와 배터리를 소비하게되는데 이를 방지하기 위해 제공하는 옵션이다. 결국, 사용자가 스마트폰을 보고있지 않은 상황에서 꼭 하지 않아도 되는 기능이라면 잠시 미뤄두라는 거다.


결론

GCM을 전송할 때는 중요한 기능은 delay_while_idle 옵션을 false로, 그렇지 않다면 true로 전송을 하자. 또한, OS별로 Default 값이 다를 수 있으니, GCM 중계 서버(구글꺼 말고 우리꺼)에서 별도의 default 값을 반드시 정하고 서비스하는 것이 맞다.




[참고문서]




Posted by 데브로망스
Android2014. 1. 22. 10:45

프레그먼트 다루기 (Managing Fragments)


액티비티에서 프레그먼트를 다루기 위해서는  FragmentManager를 이용해야한다. FragmentManager의 객체를 얻으려면 액티비티에서 제공하는 getFragmentManager()를 호출하면 된다.


FragmentManager에서 제공하는 기능으로 할 수 있는 것을 나열해 보면...


  • findFragmentById() 또는 findFragmentByTag() 함수로 액티비티가 갖고 있는 프레그먼트들에 접근할 수 있다. UI를 갖는 프레그먼트의 경우 findFragmentById(), findFragmentByTag() 둘 중 하나를 사용하면되는데, UI가 없는 프레그먼트의 경우에는 findFragmentByTag()함수만을 사용할 수 있다.
  • popBackStack() 함수를 사용해서 백스택으로부터 프레그먼트를 꺼낼 수 있다. (사용자가 Back 명령을 했을때)
  • 백스택의 변경 사항을 처리하기 위한 리스너는 addOnBackStackChangedListener()함수로 등록할 수 있다.


이러한 함수들의  좀 더 자세한 내용은 FragmentManager 클래스에 대한 문서를 참고하자.


이전 글에서 거론한대로 우리는 FragmentManager 클래스를 통해 프레그먼트 트랜잭션(추가, 삭제 등)을 하는데 필요한 FragmentTransaction 객체를 생성할 수 있다.




프레그먼트 트랜잭션 수행하기 (Performing Fragment Transactions)


액티비티에서 프레그먼트를 사용하는 것의 가장 큰 특징은 사용자의 동작에 따라 추가, 삭제, 교체 그리고 그 외 다른 동작들을 수행할 수 있다는 것이다. 액티비티에 적용한 각각의 변경 셋트를 Transaction(이하 트랜잭션)이라 부르는데, FragmentTransaction 클래스가 제공하는 API를 사용해서 이러한 트랜잭션을 수행할 수 있다. 또한, 각각의 트랜잭션에 대한 이력을 액티비티가 관리하는 백스택에 저장한 후 프레그먼트를 조작해서 사용자에게 뒤로가기 기능을 제공할 수 있다.


FragmentTransaction의 인스턴스는 FragmentManager 클래스로부터 가져올 수 있다.


FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();


각각의 트랜잭션은 우리가 한 번에 수행하고자하는 변경 내용의 셋트라고 할 수 있다. 우리에게 주어진 하나의 트랜잭션 동안 add(), remove(), replace()와 같은 함수를 사용해서 원하는 변경 내용을 모두 모아 셋트를 구성할 수 있다. 그 다음에 반드시 commit() 함수를 호출해서 실제 액티비티에 이 트랜잭션을 반영하면 된다.


이번 변경사항을 백스택에 추가하기 위해 commit() 함수를 호출하기 전에 addToBackStack()을 호출할 수도 있다. 백스택은 액티비티가 관리하는데, 백버튼을 눌렀을 때 사용자에게 이전 프레그먼트 상태로 되돌려줄 수 있도록 해준다.


예를 들어 아래 코드는 하나의 프레그먼트를 다른 녀석으로 교체해주면서 백스택에 이전 상태를 저장하고 있다.


// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction
.replace(R.id.fragment_container, newFragment);
transaction
.addToBackStack(null);

// Commit the transaction
transaction
.commit();


이 코드에서 newFragment는 R.id.fragment_container ID로 식별되는 레이아웃 컨테이너에 포함된 다른 프레그먼트를 대체하게 된다. addToBackStack()함수를 호출해서 교체하는 트랜잭션은 백스택에 저장되고 사용자는 이 트랜잭션을 되돌릴 수 있게 되어 결과적으로 백버튼을 눌러 이전 프레그먼트로 돌아올 수 있다.


만약 우리가 add(), remove()와 같은 여러 변경 사항을 한 트랜잭션에 추가하고 addToBackStack()을 호출한다면, commit()을 호출하기 전의 모든 변경 내용은 개별 트랜잭션 단위로 백스택에 추가되고 백버튼은 그 모든 변경 내용을 되돌려줄 것이다.


FragmentTransaction 클래스에 변경 내용을 추가할때는 호출 순서가 크게 상관없지만, 아래 내용은 주의해야한다.

  • commit()함수는 맨 마지막에 호출해야한다.
  • 동일한 컨테이너에 다수의 프레그먼트를 추가할 때, 그 추가하는 순서는 뷰 계층에서 화면에 보여지는 순서를 결정한다.


프레그먼트를 삭제하는 트랜잭션을 수행할 때, addToBackStack()을 호출하지 않으면 해당 프레그먼트는 트랜잭션이 최종반영(commit)될 때 완전 제거(destroy)되어 사용자는 그 프레그먼트로 돌아갈 수 없게된다. 반면, addToBackStack()함수를 호출했을 때는 프레그먼트는 잠시 정지(stop)되고 사용자가 이전으로 되돌아오려고 할 때 재개(resume)된다.


주의 : 각각의 프레그먼트 트랜잭션이 수행되는 동안, 우리는 최종반영(commit) 이전에 setTransitiojn()을 호출해서 애니메이션을 적용할 수 있다.



commit()을 호출한다고 해서 트랜잭션이 바로 실행되지 않고 액티비티의 UI 쓰레드(메인쓰레드)의 스케쥴에 따르게되고 이 쓰레드가 수행 가능할 때에 맞춰서 실행된다. 하지만, 우리는 executePendingTransactions()을 호출해서 UI쓰레드가 commit()호출되는 즉시 트랜잭션이 실행되도록 할 수 있다. 하지만, 이렇게 하는 것은 해당 트랜잭션이 다른 쓰레드와 의존적이지 않은 경우라면 딱히 필요하지 않다.


주의 : 우리는 액티비티가 현재 상태를 저장하기 전(사용자가 액티비티를 떠날 때)에만 commit()함수를 사용할 수 있다. 이 시점 이후에 commit을 시도하면 예외가 발생한다. 이렇게 한 이유는 액티비티가 원복될 때 commit 이전의 상태가 저장되지 않을 수 있기 때문이다. commit에 대한 내용이 날라가도 괜찮은 상황이라면 commitAllowingStateLoss()함수를 사용하면 된다.




[원문보기]



Posted by 데브로망스
Android2014. 1. 14. 19:09

프레그먼트 만들기(Creating a Fragment)



프레그먼트를 만들기 위해서는 반드시 Fragment 클래스를 상속받아야 한다. (또는 이미 생성한 서브클래스를 상속받아도 됨) 프레그먼트 클래스는 액티비티와 매우 유사한 코드로 작성되어있는데, onCreate(), onStart(), onPause(), onStop()과 같은 액티비티에서 제공하던 유사한 콜백함수를 제공한다. 사실, 기존에 개발했던 앱을 프레그먼트를 이용하도록 변경한다면 액비티비 콜백 함수들에서 구현했던 코드를 각각 프레그먼트의 콜백함수로 그대로 옮겨와도 된다.


일반적으로 우리는 최소한 아래 나열된 생명주기(lifecycle) 함수를 구현해야한다.


onCreate()

프레그먼트가 생성될 때, 시스템이 호출한다. 구현 시, 프레그먼트가 pause 또는 stop 되었다가 다시 resume이 되는 상황에 영향받지 않도록 유지해야하는 필수적인 콤포넌트를 초기화해야 한다.


onCreateView()

프레그먼트가 처음으로 UI를 그리는 시점에 시스템이 호출한다. 프레그먼트에 UI를 넣으려면 프레그먼트 레이아웃의 루트격인 이 함수에서 VIew를 리턴해야한다. 만약 UI가 필요없는 프레그먼트인 경우 null을 반환하면 된다. 


onPause()

사용자가 프레그먼트를 떠나는 순간 시스템에서 호출한다. (프레그먼트가 반드시 destroy되는 상황은 아니다) 이 함수는 보통 해당 사용자의 세션에서 유지되어야 하는 모든 변경 사항을 저장하는 곳이다. (당장에는 사용자가 해당 프레그먼트로 돌아오지 않을 수 있기 때문이다)


대부분의 앱은 프레그먼트를 작성할 때 적어도 위의 3개 함수를 구현해야 한다. 하지만, 프레그먼트의 생명주기의 다양한 상황을 다루기 위해서는 위 3개의 콜백함수 외에도 다른 콜백함수들을 작성해야한다. 모든 콜백 함수에 대한 더 자세한 내용은 다른 글에서 다두도록한다.


안드로이드는 기본 Fragment 클래스 대신, 쓸만한 몇 개의 서브 클래스를 제공한다.


DialogFragment

둥실둥실(?) 떠있는 다이얼로그를 띄울 때 사용한다. 액티비티 클래스에서 제공하던 Dialog Helper 함수 대신 사용하기 적절한데, 이유는 프레그먼트 다이얼로그를 액티비티에서 관리하는 프레그먼트들의 back stack 속에 포함시킬 수 있어서 사용자를 이전 프레그먼트로 돌아갈 수 있도록 할 수 있기 때문이다.


ListFragment

(SimpleCursorAdapter와 같은) 어댑터에 의해 관리되는 리스트 항목을 표출할 수 있는 ListActivity와  유사한 클래스다. 사용자 클릭을 처리하는 onListItemClick() 콜백 함수와 같이 리스트뷰를 관리할 수 있는 다양한 함수를 제공한다.


PreferencesFragment

ReferencesActivity와 유사하게 리스트 형태로 환경설정 계층을 표출할 수 있다. 우리의 앱에서 환경설정 액티비티를 제공할 때 유용하다.






액티비티가 동작중일 때의 프레그먼트의 생명주기

출처 : http://developer.android.com/guide/components/fragments.html




UI 추가하기 (Adding a user interface)


프레그먼트는 보통 액티비티의 한 부분을 차지하면서 액티비티에 자신의 레이아웃을 제공한다.


프레그먼트를 위한 레이아웃을 반영하려면 onCreateView() 콜백함수를 구현해야하는데, 위에서 설명한대로 이 함수는 프레그먼트가 레이아웃을 그리는 시점에 시스템에서 호출하는 콜백함수다. 이 함수를 구현할 때는 프레그먼트 레이아웃의 루트가 될 View를 리턴해야 한다.


주의사항 : ListFragment의 서브클래스인 경우, onCreateView()에서 디폴트로 ListView를 리턴하기 때문에 별도의 추가 구현이 필요없다.


onCreateView()에서 레이아웃을 리턴하려면, XML로 정의해둔 레이아웃 리소스를 이용하여 inflate 할 수 있다. 이 과정을 쉽게 할 수 있도록 onCreateView()가 호출될 때 LayoutInflater 오브젝트가 전달된다.


아래 코드는 example_fragment.xml 파일을 로드하는 프레그먼트의 서브클래스의 사용 예이다.


public static class ExampleFragment extends Fragment {
   
@Override
   
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             
Bundle savedInstanceState) {
       
// Inflate the layout for this fragment
       
return inflater.inflate(R.layout.example_fragment, container, false);
   
}
}


onCreateView()로 전달된 container 파라메터는 액티비티가 소유한 부모 ViewGroup인데, 이 그룹에 프레그먼트가 속하게 되는 것이다. 또다른 파라메터 savedInstanceState는 Bundle 오브젝트인데, 프레그먼트가 resume될 때, 프레그먼트의 이전 인스턴스 데이터가 담겨있다. (이에 대한 내용은 Handling the Fragment Lifecycle 섹션에서 자세히 다루도록 하자)


inflate() 함수를 호출하려면 3개의 매개변수가 필요하다.

  • inflate 하려는 레이아웃의 리소스 ID
  • inflate된 레이아웃의 부모가 될 ViewGroup. container(ViewGroup)객체를 전달하는 것은 매우 중요한데, 이는 시스템이 레이아웃 파라메터가 inflate되었을 때 이 녀석의 루트뷰(레이아웃이 속할 부모뷰에 의해 정의된)가 되도록 적용시켜야하기 때문이다. 
  • inflate 작업이 진행되는 동안, inflate된 레이아웃이 ViewGroup(두 번째 파라메터)에 적용(attatched)되어야 하는지 여부를 판단하는 boolean값. (여기서는 시스템이 이미 inflate된 레이아웃을 container에 집어넣었기 때문에 false값을 부여했다. 만약, 이 값을 true로 하면, 맨 마지막 레이아웃에서 불필요한 뷰 그룹을 생성하게 된다.)

여기까지 우리는 레이아웃을 갖는 프레그먼트를 만드는 법을 배웠다. 이제는 액티비티에 프레그먼트를 집어넣는 방법에 대해 알아보자.




액티비티에 프레그먼트 넣기(Adding a fragment to an activity)


일반적으로, 프레그먼트는 액티비티의 전체 뷰 계층의 일부로 포함되어있는 상위 액티비티에 UI 요소를 제공한다. 액티비티 레이아웃에 프레그먼트를 추가하는 방법에는 2가지가 있다.


  • 액티비티 레이아웃 파일에 fragment 선언하는 방법

이 경우, view에서 했던 것 처럼 레이아웃 속성을 작성할 수 있다. 아래는 두 개의 프레그먼트를 갖는 액티비티의 레이아웃 파일의 예시이다.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   
android:orientation="horizontal"
   
android:layout_width="match_parent"
   
android:layout_height="match_parent">
   
<fragment android:name="com.example.news.ArticleListFragment"
           
android:id="@+id/list"
           
android:layout_weight="1"
           
android:layout_width="0dp"
           
android:layout_height="match_parent" />
   
<fragment android:name="com.example.news.ArticleReaderFragment"
           
android:id="@+id/viewer"
           
android:layout_weight="2"
           
android:layout_width="0dp"
           
android:layout_height="match_parent" />
</LinearLayout>


<fragment> 안에 있는 "android:name" 속성은 Fragment 클래스가 레이아웃에서 인스턴스화되는 것을 의미한다. 시스템이 액티비티 레이아웃을 생성할 때, 레이아웃안에 정의된 각각의 프레그먼트의 인스턴스를 만들고 프레그먼트 레이아웃을 반환하도록 각 프레그먼트의 onCreateView()함수를 호출한다. 시스템은 프레그먼트에서 직접 반환한 View를 <fragment> 요소를 대체하여 추가한다.



주의 : 액티비티가 재실행되는 경우 프레그먼트를 복구하기 위해 시스템에서 사용하는 프레그먼트의 유니크한 ID가 필요하다. (또한, 프레그먼트 삭제와 같은 조작을 할 때도 프레그먼트를 구분하는데 사용할 수도 있다.) 프레그먼트를 위한 ID를 부여하는데는 3가지 방법이 있다.

    • "android:id" 속성을 통해 유니크한 ID를 부여한다.
    • "android:tag" 속성을  통해 유니크한 문자열를 부여한다.
    • 위의 두 개를 모두 부여하지 않는 경우, 시스템은 컨테이너뷰의 ID를 사용한다.

  • 직접 구현을 통해 이미 생성되어 있는 ViewGroup에 프레그먼트를 추가하는 방법

액티비티가 실행되는 동안에 우리는 액티비티 레이아웃에 프레그먼트를 추가할 수 있다. 이를 위해 간단하게 프레그먼트가 위치할 ViewGroup을 지정하면 된다.

프레그먼트 추가, 삭제, 교체 등과 같은 액비티비 내에서 프레그먼트를 조작하려면 반드시 FragmentTransaction에서 제공하는 API들을 사용해야한다. FragmentTransaction의 인스턴스는 Activity로 부터 얻을 수 있는데, 아래처럼 구현하면 된다.


FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();


이제, 추가할 프레그먼트와 이를 포함할 뷰를 지정해주는 add()함수를 통해 프레그먼트를 추가할 수 있다.

아래 코드를 보자.


ExampleFragment fragment = new ExampleFragment();
fragmentTransaction
.add(R.id.fragment_container, fragment);
fragmentTransaction
.commit();


add()함수로 전달하는 첫번째 인자는 프레그먼트가 위치해야할 ViewGroup의 리소스 ID이고, 두번째 파라메터는 추가할 프레그먼트 객체다.

FragmentTransaction으로 변경할 내용을 완료했으면 실제 적용을 시키기 위해 commit()함수를 반드시 호출해야한다.



UI없는 프레그먼트 추가하기 (Adding a fragment without a UI)


위에서는 액티비티에 UI를 제공할 목적으로 프레그먼트를 만드는 법에 대해 설명했다. 하지만, 별도의 UI를 표출하지 않고 액티비티의 백그라운드 작업만을 수행하도록  만들 수도 있다.


UI없는 프레그먼트를 추가하려면 액티비티내에서 add(Fragment, String) 함수를 이용하여 추가하면 된다. (View ID보다는 유니크한 Tag 문자열을 부여하는 것이 더 좋다) 이 함수는 프레그먼트를 추가하긴 하지만 액티비티 레이아웃에 있는 뷰와는 연결되지 않기 때문에 onCreateView()함수가 호출되지 않는다. 즉, 이 콜백함수를 구현하지 않아도 된다.


프레그먼트에 문자열 Tag를 부여하는 것은 UI없는 프레그먼트만을 위한 것은 아니다. (UI를 갖는 프레그먼트에도 문자열 Tag를 부여할 수 있다.) 하지만, UI가 없는 프레그먼트의 경우라면 이 문자열 Tag는 해당 프레그먼트를 식별할 수 있는 유일한 정보다. 만약, 구동 중에 액티비티로부터 해당 프레그먼트 객체를 받아오고 싶다면 findFragmentByTag()함수를 사용하면 된다.


UI없이 백그라운드 작업을 수행하는 프레그먼트가 포함된 액티비티 클래스를 참고하려면 FragmentRetainInstance.java 샘플 코드를 참고하자.




[원문보기]




Posted by 데브로망스