Regular Motion

개발자가 상팔자

Category: android (page 2 of 23)

[Android] Activity Transition.

Activity 전환시의 Transition은 매우 중요하다.

신부감을 고르는 것만큼은 아니지만 오늘 점심에 김치찌개를 먹을지 돈까스를 먹을지 보다는 중요하다.

중요도에 비해 적용은 매우 쉽다. 거의 공짜다.

간단한 효과의 경우 4개의 .xml 파일과 2줄의 코드면 끝이다. OMG!!!

2015년 S/S에 유행하고 있는 현재 Activity를 Step Back 시키면서 새로운 Activity를 우측에서 꺼내오는 효과는

아래 4개의 .xml 파일로 처리한다.

(참고로 UI/UX의 트렌드 세터 Airbnb도 위의 효과를 여러군데서 적용하고 있다)

slide_in.xmlslide_out.xml은 호출되는 Activity에 적용될 효과고,

step_back.xmlstep_in.xml은 호출하는 Activity에 적용할 효과다.

 

4개의 .xml 파일을 res/anim 폴더에 만들었다면 이제 8부 능선을 넘었다.

이제 만들어 놓은 .xml 파일들을 Activity를 실행하는 시점과 종료하는 시점에 적용만 하면 된다.

 

실행하는 시점의 코드는 아래와 같다.

overridePendingTransition의 첫번째 파라미터는 enter animation이고, 두번째 파라미터는 leave animation이다.

즉, 실행되는 Activity는 slide-in으로 들어오고, 현재 Activity는 step-back으로 나가는 거다.

 

실행되는것 만큼 Activity가 종료될때의 효과도 중요한데, Activity가 실행될때의 효과를 반대로 적용해주는게 좋다.

 

Activity가 종료될 때, 기존의 Activity는 step-in 하면서, 현재 Activity는 slide-out 시키는 효과다.

 

이제 당신의 앱도 섹시하게 화면전환이 되는걸 확인 할 수 있다.

아무렴 그렇고 말고.

[Android] App Indexing Introduction.

App Indexing은 짧게 요약하면 Google Search의 검색 결과에 어플리케이션의 컨텐츠를 노출시키는 방법이다.

어플리케이션에서 App Indexing을 구성하는 방법은 아래 4단계를 거치면 된다.

 

1. App Indexing을 Project의 dependencies에 추가한다. (시작은 쉽다)

 

2.  Google Search의 검색 결과에서 앱이 바로 실행되기 위해서는 intent-filter가 필요하다. (너와 나의 연결고리 말이다.)

검색 결과에서 바로 실행 될 Activity에 Deep Linking을 위한 intent-filter를 추가한다.

아래 intent-filter는 http://radioinn.com/podcast로 시작하는 broadcast를 처리하겠다는 의미다.

 

3. 연결고리까지 만들었으니, 이제 어플리케이션이 어떤 컨텐츠를 갖고 있는지 증명해야한다.

어플리케이션에서 Google Search의 검색 결과에 추가하고 싶은 컨텐츠를 아래와 같이 추가한다.

Title의 경우 Google Search의 자동 완성에 표시될 이름이고,

ContentId의 경우 Google Search의 겸색 결과에서 앱을 실행시에 dataString에 포함될 값이다.

* 실행된 Activity에서는 ContentId를 통해 원하는 컨테츠를 보여줄 수 있어야 한다.

 

3번까지 정상적으로 성공했으면, Google앱의 Search에서 Title에 포함된 내용을 검색했을 때 아래와 같이 나와야 한다.

아래 이미지에서는 ‘윤종신 허지웅의 어수선한 영화이야기’가 App Indexing을 통해 Google Search에 포함된 경우다.

우오오!!!! 이렇게 간단하다니!!!!

2015-06-02 09.59.55

 

4. 이제 Google Search에서 검색 결과를 클릭했을 때의 처리만 남아있다.

이미 2번에서 Google Search의 검색 결과에서 클릭했을 때 실행을 위한 intent-filter는 추가했었다.

intent-filter가 정상적으로 추가되어 있다면, Google Search에서 검색 결과를 클릭했을 때,

앱이 실행중이면 onNewIntent(), 앱이 실행중이 아니면 onCreate()로 intent를 넘겨준다.

이제 넘겨받은 intent에서 ContentId를 파싱해서 컨테츠를 보여주면 된다.

끝. (이제 앱이 히트를 칠 일만 남았다)

 

* 참고자료

https://developers.google.com/app-indexing/introduction

– http://io2015codelabs.appspot.com/codelabs/app-indexing#1

[Android] RecyclerView

Android Tutorial을 보던 중 RecyclerView라는 매우 활용도가 높은 박지성과 같은 Widget을 발견하여 공유한다.

Support Library v7에 혜성처럼 등장한 RecyclerView는 기존의 List/GridView를 개선시킨 버전으로 유연하게 리스트 타입을 변경할 수 있도록 설계되어 있다.

기존의 ListView가 ListView – Adapter – DataSet의 구조를 갖고 있었다면, RecyclerView는 이에 LayoutManager를 추가하여 리스트 타입을 쉽게 변경할 수 있도록 설계되어 있다.

또한, 평균 이상의 추리능력을 갖고 있는 사람은 유추했겠지만, 기존에 권장사항이었던 ViewHolder 패턴을 강제하여 재활용을 적극 권장하는 구조로 설계되어 있다.

RecyclerView.Adapter를 보면 기존의 getView() 대신 onCreateViewHolder()onBindViewHolder() 함수를 통해 제한된 수의 Child view를 생성한 뒤, 재활용하는 구조다. 로그를 통해 확인해보면 Child view 생성이 필요한 시점에만 onCreateViewHolder()를 통해 Child view를 생성하고 이후에는 onBindViewHolder()를 통해 View는 재활용하고 데이터만 갱신하는 구조다.

전체적인 구조는 아래와 같다.

기본적으로 제공하는 리스트 타입은 List, Grid, StaggeredGrid를 제공하고 있고 각각의 타입을 vertical, horizontal로 설정 가능하다. 각각의 LayoutManager는 아래와 같다.

ListLayoutManager : https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html

GridLayoutManager : https://developer.android.com/reference/android/support/v7/widget/GridLayoutManager.html

StaggeredGridLayoutManager : https://developer.android.com/reference/android/support/v7/widget/StaggeredGridLayoutManager.html

 

RecyclerView의 첫번째 미덕은 적용이 쉽다는 점이다.

  1. 1. support-library-v7에 숨쉬고 있는 android-support-v7-recyclerview.jar를 프로젝트에 추가한 뒤.
  2. 2. xml에 RecyclerView를 추가한 뒤,
  3. 3. 기존의 ListView와 비슷하게 사용하면 된다. 한가지 다른점이 있다면 LayoutManager를 통해 리스트 타입을 설정해줘야 한다는 점이다.
  4. 4. RecyclerAdapter는 RecyclerView.Adapter를 상속해야 한다.

 

RecyclerView의 두번째 미덕은 Entry의 추가, 삭제, 이동시에 기본적으로 제공하는 애니메이션이 훌륭하다는 점이다.

물론 추가, 삭제, 이동은 아래와 같이 쉽게 구현할 수 있다.

 

 

 

One more thing,

support-library-v4에 포함되어있는 SwipeRefreshLayout와의 궁합은 심히 대단해서 MBC 우결측에서 섭외를 시도하고 있다는 풍문도 들릴 정도다.

 

[Android] Fragment Transactions & Activity State Loss

이 글은 http://www.androiddesignpatterns.com에 소개된 글을 저자의 동의를 얻고 번역한 것임을 알려드립니다.

원문 : http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html

 

Honeycomb이 최초로 배포된 이후로 아래의 Stack trace와 Exception message는 Stack Overflow에 지속적으로 올라왔다.

이 글은 위의 Exception이 언제 그리고 왜 발생하는지 알아보고 몇가지 해결 방안을 제시할 것이다.

 

Why was the exception thrown?

위의 Exception은 Activity의 상태가 저장된 이후 FragmentTransaction을 commit하려고 할 때, Activity State Loss 현상이 발생하고 그에 따른 결과로 발생한다. Activity State Loss가 정확히 뭘 의미하는지 알아보기 전에, onSaveInstanceState()가 호출될 때 어떠한 작업이 내부에서 이루어지는지 알아보자.

지난번 Binders & Death Recipients 포스팅에서 언급했듯이, 안드로이드 실행 환경에서 어플리케이션은 자신의 운명에 대해 약간의 권한만을 갖고 있다. 안드로이드 시스템은 메모리를 회수하기 위해 아무때나 백그라운드 액티비티를 종료시킬 수 있는 권한을 갖고 있다.

이런 불규칙한 일을 사용자가 눈치채지 못하도록 하기 위해, 안드로이드 프레임워크는 Activity를 종료시키기 전에 자신의 상태를 저장할 수 있도록 onSaveInstanceState() 메소드를 호출해준다. 그리고 이후 Activity가 Restore될 때 저장한 상태 값들을 통해 상태를 복원한다.

프레임워크가 onSaveInstanceState()를 호출할 때, dialog, fragment, view들의 상태를 저장할 수 있도록 Bundle object를 전달한다. 이후 Activity가 메소드를 반환할 때 시스템은 Bundle object를 System server process에 안전하게 저장한다.

이후 시스템이 Activity를 재생성 할 때, Bundle을 Activity에게 돌려주고 돌려받은 Bundle 통해 Activity의 마지막 상태를 복원한다.

근데 왜 Exception이 발생하는가? Bundle object는 Activity의 onSaveInstanceState()가 호출될 때의 snapshot과 같다. 그런데 onSaveInstanceState()가 호출된 이후에 FragmentTransaction#commit()을 호출한다면 해당 transaction은 snapshop에 저장되지 않을 것이다.

사용자의 관점에서보면, 해당 transaction이 유실되어 UI State가 유실된 것처럼 보여질 것이다.  Android는 User Experience를 지키지 위해 State Loss가 발생 할 때마다 IllegalStateException을 발생시켜 이를 방지한다.

 

When is the exception thrown?

이미 위의 Exception을 경험했다면, Platform의 버전에 따라 Exception의 발생 시점이 불규칙하다는 사실을 눈치 챘을수도 있다.

예를 들어 오래된 단말기에서는 자주 발생하지 않는다던가, 정식 Framework Class를 사용할 때보다 support library를 사용할 때 더 자주 발생한다던가. 위와 같은 불규칙으로 인해 많은 사람들은 support library에 버그가 많은것 아니냐? 믿을 수 없다. 라는 등의 의심을 해왔다. 하지만 이는 사실이 아니다.

위와 같은 불규칙이 발생하게된 원인은 Honeycomb때 생긴 Acitivty Lifecycle의 중요한 변화와 관련이 있다. Honeycomb 이전에 Activity는 onPause() 함수 이후에 killable 상태로 변경되었다. 하지만 Honeycomb 부터 Activity는 onStop() 이후에 killable 상태로 변경되었다. 그리고 이는 onSaveInstanceState() 메소드가 onPause() 전이 아닌 onStop() 전에 호출되도록 변경됐다는 것을 의미한다. 아래 표에 차이점이 정리되어 있다.

Screen shot 2015-03-15 at 오후 11.47.34

Activity Lifecycle과 관련된 변화로 인해, support library는 Platform 버전에 따라 다르게 동작해야 할 필요가 있다.

예를 들어, Honeycomb 이후의 기기에 대해서는 onSaveInstanceState() 이후의 시도하는 모든 commit()에 대해서 exception을 발생시킬 필요가 있다.  그러나 Honeycomb 이전의 기기에 대해 동일하게 Exception을 발생시키는 것은 지나치게 제한적인 측면이 있다. 따라서 Android 팀은 아래와 같이 정리했다.

Screen shot 2015-03-15 at 오후 11.55.55

 

How to avoid the exception?

실제로 어떠한 작업들이 이루어 지는지 이해하고나면 Activity State Loss를 방지하는 일은 훨씬 수월하다.  만약 여기까지 이해했다면 support library가 어떻게 동작하는지 그리고 어플리케이션을 개발할 때 State Loss를 예방하는게 얼마나 중요한지 이해할 수 있을 것이다.

만약 이 글을 Quick fix 용도로 활용한다면 FragmentTransaction을 사용할 때의 유의사항이 아래와 같다.

  • – Be careful when committing transactions inside Activity lifecycle methods. 대부분의 어플리케이션에서 transaction commit은 onCreate() 메소드 또는 사용자의 입력에 대한 response로 이루어지고 이는 아무런 문제를 발생시키지 않을 것이다. 하지만 onStart(), onResume()과 같은 Activity의 다른 lifecycle 메소드에서 transaction commit이 이루어진다면 조금 까다로워진다. 예를 들어 FragmentActivity#onResume() 메소드는 Activity state가 복원되기 전에 호출될 수 있기 때문에 transaction commit을 하지 말아야 한다. 만약 어플리케이션이 onCreate()가 아닌 다른 lifecycle 메소드에서 transaction을 commit 해야 한다면 onResumeActivity() 또는 onPostResume() 메소드를 활용해라. 위의 두개의 메소드는 Activity의 상태가 복원된 이후에 호출됨을 보장하기 때문에 Activity State Loss를 방지할 수 있다.
  • – Avoid performing transactions inside asynchronous callback methods. 일반적으로 많이 사용하는 AsyncTask#onPostExecute()LoaderManager.LoaderCallbacks#onLoadFinished()를 포함한 Asynchronous의 callback에서는 Activity lifecycle과 관련한 정보를 갖고 있지 않기 때문에 transaction을 commit하는 것은 위험할 수 있다.
    구글 개발자를 포함한 모두가 Asynchronous callback에서 transaction을 commit하지 않는게 가장 바람직하다는 사실에는 동의한다.
    Asynchronous callback은 onSaveInstanceState() 메소드 이후에 호출되지 않는걸 보장하지 않기 때문에 commitAllowingStateLoss()를 사용해야만 할지도 모른다. (commitAllowingStateLoss() 함수는 State Loss가 발생할 수 있는 상황에서 예외를 발생시키지 않는다.)
  • – Use commitAllowingStateLoss() only as a last resort. commit() 메소드와 commitAllowingStateLoss() 메소드의 유일한 차이점은 commitAllowingStateLoss() 메소드의 경우 State Loss가 발생해도 Exception을 발생시키지 않는다는 것이다. commitAllowingStateLoss() 메소드는 state loss가 발생할 수 있음을 함축하고 있기 때문에 일반적으로는 사용하지 않는다. 최선책은 Activity의 상태가 저장되기 전에 commit() 메소드를 사용하는 것이다.

 

 

 

[Android] DownloadManager 사용의 장점 및 단점.

Project를 진행하다 Download Module을 만들게 됐다. 이전에 사용해보려다 말았던 DownloadManager를 사용하기 위해, Project에서 필요한 기능들을 제공하는지 알아봤다.

Download Module에서 필요한 최소한의 기능은 아래와 같다.

 

1. DownloadFailed시에 Callback/Notify 받을 수 있어야 한다.

Download Complete는 BroadcastReceiver를 통해 Callback을 받을 수 있지만 Failed에 대한 Callback을 받을 수 있는 방법은 없는 것 같다. 다만 Download 정보를 Query하면 Download Status를 얻을 수 있어서 Failed 됐다는 사실은 알 수 있다.

즉, 주기적으로 Download 정보를 확인하는 방법을 통해 Download Status를 추적할 수 있다.

 

2. 파일의 저장경로를 지정할 수 있어야 한다.

이건 사실 당연한거라;;; Request Class의 아래 함수들을 이용해서 저장 경로를 설정할 수 있다.

setDestinationInExternalFilesDir() : Application의 하위 경로에 파일을 저장할 때 사용.

setDestinationInExternalPublicDir() : Public 경로에 파일을 저장할 때 사용.

setDestinationUri() : Uri 타입으로 경로를 설정할 때 사용.

 

3. Download progress를 0.5초 단위로 갱신할 수 있어야 한다.

다운로드 정보를 Query 하면 COLUMN_BYTES_DOWNLOADED_SO_FAR 값을 얻을 수 있는데 현재까지 Download한 용량을 byte 단위로 제공한다.

 

4. Download 작업을 취소할 수 있어야 한다.

DownloadManager의 remove(id) 함수를 통해 취소할 수 있다.

(* id는 enqueue의 return 값이다.)

 

5. 이어받기,  Pause/Resume이 가능해야 한다.

나의 경우는 이어받기 때문에 DownloadManager 사용을 망설이게 됐다.

DownloadManager의 설명을 읽어보면, 아래와 같은 부분이 나온다.

The download manager will conduct the download in the background, taking care of HTTP interactions and retrying downloads after failures or across connectivity changes and system reboots.

Download가 완료되지 못한 작업들을 DownloadManager가 기억하고 있다가 이후에 자동으로 재시도하는 구조로 되어 있는데 재시도하는 시점이나 방법에 대해서는 3rd party가 제어할 수 있는 부분이 별로 없다. Network에 연결되면 자동으로 다시 시작한다.

 

6. Notification을 제어할 수 있어야 한다.

DownloadManager에서 기본으로 제공하는 Notification을 사용하면, Notification의 Title / Description은 제어할 수 있고, Notification의 등록/제거를 알아서 해주기 때문에 편하다. 하지만 Notification이 클릭됐을때의 PendingIntent라던가 Notification의 Icon등을 제어할 수 없는 부분은 아쉽다.

(* 기본으로 제공하는 Notification은 setNotificationVisibility() 함수를 통해 사용할 수 있다.)

Notification의 PendingIntent나 Icon까지 제어하려면 DownloadManager가 기본으로 제공하는 Notification을 비활성화 한 뒤, 직접 Notification을 생성/등록/제거 하면 된다.

Older posts Newer posts

© 2017 Regular Motion

Theme by Anders NorenUp ↑