데이터 바인딩에 임의의 POJO(Plain Old Java Object)를 사용 가능하지만, POJO를 수정하더라도 UI가 업데이트 되지는 않음. 데이터가 변경될 때 이를 알려주는 기능을 데이터 객체에 부여해야 데이터 바인딩을 제대로 활용할 수 있음. Observable 객체, 필드, 컬렉션이라는 세 가지 다른 데이터 변경 알림 매커니즘이 있음.
이러한 Observable 데이터 객체 중 하나가 UI에 바인딩되어 있고 데이터 객체 속성이 변경되면 UI가 업데이트 됨.
Observable 객체
Observable 인터페이스를 구현하는 클래스를 사용하면 바인딩이 바인딩된 객체 단일 리스너를 연결.
Observable 인터페이스에는 리스너를 추가/제거하는 매커니즘이 있지만, 알림은 개발자의 선택에 따라 결정됨.
더 쉽게 개발하도록 BaseObservable을 만들어 리스너 등록 매커니즘 구현함. 속성이 변할 때 이를 알릴 책임은 데이터 클래스 구현자에게 있으며, getter에 Bindable 주석을 할당하고 setter에서 이를 알림
View 가 View.OnAttachStateChangeListener에 대한 set 메서드를 사용하는 대신 리스너에 대한 add와 remove를 사용하기 때문에 위 예시는 좀 더 복잡함.
ListenerUtil 클래스는 Binding Adapter에서 이전의 리스너를 제거할 수 있도록 이들을 추적하는 데 도움이 됨.
인터페이스 OnViewDetachedFromWindow와 OnViewAttachedToWindow에 @TargetApi(VERSION_CODES.HONEYCOMB_MR1) 주석을 추가하면 데이터 바인딩 코드 생성기가 addOnAttachStateChangeListener(View.OnAttachStateChangeListener) 에 의해 지원되는 같은 버전인 HoneyComb MR1과 새 기기에서 실행 시 리스너만 생성되어야 한다는 점을 인식하게 됨.
변환기
객체 변환
바인딩 식에서 Object가 반환되면 자동 setter, 이름이 바뀐 setter, 맞춤 setter 중에서 setter가 선택됨.
android:text='@{map["firstName"]}'
or
android:text="@{map[`firstName`]}"
android:text="@{map['firstName']}"
리소스
일반적인 구문을 사용하는 식의 일부로 리소스에 액세스할 수 있음.
<!-- 일부로 리소스에 액세스할 수 있음 -->
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
<!-- 매개변수를 제공하여 형식 문자열과 복수형을 평가할 수 있음 -->
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
<!-- 복수형이 여러 매개변수를 취할 때는 모든 매개변수가 전달되어야 함 -->
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
변수 형식은 컴파일 시에 검사되므로, 변수가 android.databinding.Observable을 구현하거나 변수가 Observable 컬렉션인 경우 그 점이 형식에 반영되어야 함. 변수가 기본 클래스거나 Observable* 인터페이스를 구현하지 않은 경우 변수가 식별되지 않음.
다양한 구성(예: 가로/세로 모드)에 대한 다양한 레이아웃 파일이 있을 경우 변수들이 결합하므로 충동하는 변수 정의가 없어야 함.
생성되는 바인딩 클래스는 설명되어 있는 변수마다 각각 setter와 getter가 있음. 변수는 setter가 호출될 때까지 기본 Java 값을 취함. 즉, 참조 형식에는 null, int에는 0, boolean 에는 false 값을 취함.
필요에 따라 바인딩 식에 사용하기 위해 context 로 명명된 별도의 변수가 생성됨. context는 루트 View의 getContext()에서 가져온 값임. context 변수는 그 이름을 가진 명시적 변수 선언으로 재정의 됨.
사용자 지정 바인딩 클래스 이름
기본적인 Binding 클래스 : 레이아웃 파일명을 바탕으로 생성되며 파스칼 표기법으로 바꾼 후 "Binding"이 접미사로 붙음. 이 클래스는 모듈 패키지 아래 데이터 바인딩 패키지에 배치됨.
예를 들면
레이아웃 파일 : contact_item.xml
바인딩 클래스 : ContactItemBinding
모듈 패키지 : com.example.my.app
배치 위치 : com.example.my.app.databinding
data 요소의 class 특성을 조정하여 바인딩 클래스명을 바꾸거나 다른 패키지에 배치할 수 있음.
<!-- 바인딩클래스명 변경 -->
<dataclass="ContactItem">
...
</data>
or
<!-- 모듈 패키지 내에서 다른 패키지에 배치할 경우 -->
<dataclass=".ContactItem">
...
</data>
or
<!-- 전체 패키지가 제공될 경우 어떤 패키지든 사용 가능 -->
<dataclass="com.example.ContactItem">
...
</data>
Include
특성에 앱 네임스페이스와 변수 이름을 사용하여 포함하는 레이아웃에서 포함되는 레이아웃의 바인딩으로 변수를 전달할 수 있음.
애플리케이션 구성 요소가 시작되고, 앱에 실행 중인 다른 구성 요소가 없으면 하나의 실행 스레드로 앱의 Linux 프로세스 시작함. 기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 스레드에서 실행됨. 하지만 여러가지 구성 요소가 각자 별도의 프로세스에서 실행되도록 할 수도 있고, 어느 프로세스에든 추가 스레드를 만들 수 있음.
프로세스
기본적으로 같은 앱의 모든 구성 요소는 같은 프로세스와 스레드에서 실행되고 이를 바꾸면 안됨. 그러나 어느 프로세스가 특정 구성 요소에 속하는지 확인해야 할 경우 매니페스트 파일에서 확인 가능.(android:process 속성)
지원하는 요소 : <application>, <activity>, <service>, <receiver>, <provider>
안드로이드 시스템에서 어느 프로세스를 삭제할지 결정할 때, 이들의 상대적 중요성을 가늠함.
프로세스 수명 주기
안드로이드 시스템은 프로세스에서 실행되는 구성 요소와 해당 구성 요소의 상태에 기초하여 각 프로세스에 "중요 계층"을 부여함. 중요도가 낮은 프로세스가 먼저 제거됨.
중요 계층 5가지
포그라운드 프로세스 : 사용자가 현재 진행하는 작업에 필요한 프로세스 (Activity나 Foreground Service)
사용자가 상호작용하는 Activity를 호스팅할 경우
사용자와 상호작용하는 액티비티에 바인드된 Service를 호스팅할 경우
포그라운드에서 실행되는 Service를 호스팅할 경우
수명 주기 콜백 중 하나를 실행하는 Service를 호스팅할 경우 (onCreate(), onStart() 또는 onDestroy() )
가시적 프로세스 : 포그라운드 구성 요소는 없지만 사용자가 화면에서 보는 것에 영향을 미칠 수 있는 프로세스
맨 앞에 있지는 않지만 사용자에게 보이는 Activity 를 호스팅할 경우
눈에 보이는 액티비티에 바인딩된 Service 호스팅할 경우
서비스 프로세스 : startService() 메서드로 시작되었지만, 위 경우에 들어가지 않는 서비스를 실행 중인 프로세스일 경우
백그라운드 프로세스 : 현재 사용자에게 보이지 않는 액티비티를 보유한 프로세스. 이러한 프로세스는 LRU(최저 사용 빈도) 목록에 보관하여 사용자가 가장 최근에 본 액티비티가 있는 프로세스가 가장 마지막에 중단되도록 함.
빈 프로스세스 : 활성 앱 구성 요소를 보유하지 않은 프로세스.
프로세스의 등급은 다른 프로세스가 이에 의존할 경우 상승 가능.(의존 대상의 중요도가 의존할 대상과 같거나 높음)
스레드
앱이 시작되면 앱에 대한 실행의 스레드를 생성하며, 메인 스레드라 함. 이 스레드는 드로어블 이벤트를 포함한 UI 위젯에 이벤트를 발송하는 역할을 맡기도 하여 UI 스레드라고도 함.
시스템은 구성 요소의 각 인스턴스에 대해 별도의 스레드를 생성하지 않음. 같은 프로세스에서 실행되는 모든 구성 요소는 UI 스레드에서 시작되고, 구성 요소를 호출하는 시스템이 해당 스레드에서 발송됨.
시스템 콜백에 응답하는 메서드(onKeyDown()이나 생명주기 메서드 등)는 항상 프로세스의 UI 스레드에서 실행됨.
UI 스레드에서 DB 쿼리 등의 긴 작업을 수행하면 사용자가 보기에 앱이 중단된 것처럼 보이며, 약 5초이상 진행되면 ANR 대화상자가 표시됨.
어떤 경우에는 구현하는 메서드가 하나 이상의 스레드에서 호출되는 일이 있어 안전하게 작성해야 함.
주로 원격으로 호출할 수 있는 메서드이 있음. 바인드된 서비스 등.
IBinder에서 구현된 메서드가 IBinder가 실행되는 프로세스에서 호출될 경우, 해당 메서드는 호출자의 스레드에서 실행됨. 호출이 다른 프로세스에서 발생하면, 해당 메서드는 시스템이 IBinder 와 같은 프로세스에 유지하는 스레드 풀에서 선택된 스레드에서 실행됨.(프로세스의 UI 스레드에서 실행되지 않음)
위와 같은 이유로 스레드로부터 안전한 구현이 필요함.
프로세스 간 통신
안드로이드는 원격 프로시저 호출(RPC)을 사용한 프로세스 간 통신(IPC) 매커니즘을 제공함.
여기서 메서드는 액티비티나 다른 앱 구성 요소에 호출되지만 원격으로 실행되고, 결과는 모두 호출자에게 되돌려 보냄.
메서드 호출과 데이터는 운영 체제가 이해할 수 있는 수준으로 분해되고, 로컬 프로세스와 주소 공간에서 원격 프로세스와 주소 공간으로 전송되어 다시 결합되어 호출에 응답하게 됨. 그런 다음 반환 값이 반대 방향으로 전송됨.
안드로이드가 이와 같은 IPC 트랜잭션을 수행하는데 필요한 코드를 제공하므로, 개발자는 RPC 프로그래밍 인터페이스를 정의하고 구현하는 데만 집중하면 됨.
시작됨 : startService()를 호출하여 시작되며, 한번 시작되고 나면 백그라운드에서 무기한으로 실행 될 수 있음. 해당 서비스를 시작한 구성 요소가 소멸되었더라도 무관함. 작업을 수행하고 결과를 호출자에게 반환하지 않음
바인드 됨 : bindService()를 호출하여 바인드 되며, 클라이언트-서버 인터페이스를 제공하여 구성 요소가 서비스와 상호작용할 수 있도록 해줌. 바인드된 서비스는 또 다른 애플리케이션 구성 요소가 이를 바인드되어 있는 경우에만 실행됨. 여러 개의 구성 요소가 서비스에 한꺼번에 바인드될 수 있지만, 이 모든 것이 바인딩을 해제하면 서비스가 소멸됨.
서비스는 위 두 가지 방식 모두 취할 수 있음.
서비스는 자신의 호스팅 프로세스의 기본 스레드에서 실행됨.
자신의 스레드를 직접 생성하지 않으며, 별도의 프로세스에서 실행되지도 않음.
기본 스레드 외부에서 작업을 수행해야 하지만, 사용자가 애플리케이션과 상호작용 중인 동안에만 수행하면 되는 경우라면, 서비스가 아니라 그 대신 새 스레드를 생성해야 함.
onStartCommand() : 다른 구성요소가 서비스를 시작하도록 요청한 경우.(바인딩만 제공시 이 메서드는 구현하지 않아도 됨.)
onBind() : 다른 구성요소가 서비스에 바인드되고자 하는 경우. 클라이언트가 서비스와 통신을 주고받기 위해 사용할 인터페이스를 제공해야 함. IBinder를 반환함.
한 구성 요소가 startService()를 호출하여 서비스를 시작하면(onStartCommand() 호출 발생) 해당 서비스는 스스로 stopSelf()나 다른 구성요소가 stopService()를 호출하기 전까지 실행 중인 상태 유지함.
한 구성 요소가 bindService()를 호출하여 서비스를 시작하면(onStartCommand() 호출 안함) 해당 서비스는 해당 구성 요소가 바인딩된 경우에만 실행됨. 모든 클라이언트로 바인딩 해제되면 소멸됨.
서비스는 기본적으로 메인 스레드에서 실행되므로, 성능에 영향일 미치는 작업은 서비스 내에서 새 스레드를 시작해야 함.
IntentService는 Service의 서브클래스로 기본 작업자 스레드를 생성하여 모든 시작 요청을 처리하되 한번에 하나씩 처리함. 여러 개의 요청을 동시에 처리하지 않아도 되는 경우 최선의 옵션. onHandleIntent()를 구현하면 됨. 시작 요청이 모두 처리된 후 서비스를 중단하므로 stopSelf()를 호출할 필요 없음.
onStartCommand() 메서드의 반환 값
START_NOT_STICKY : 서비스 중단되면 재생성 안됨.
START_STICKY : 서비스 중단되면 재생성하고 onStartCommand() 호출됨. null 인텐트로 처리됨.
START_REDELIVER_INTENT : 서비스 중단되면 재생성하고 onStartCommand() 호출됨. 서비스에 마지막에 전달된 인텐트로 처리됨.
바인드된 서비스를 생성하려면 onBind() 콜백 메서드를 구현하여 서비스와 통신을 위한 인터페이스를 정의하는 IBinder를 반환하도록 해야 함.
바인드된 서비스는 onStartCommand()를 통해 시작된 서비스와 달리 중단시키지 않아도 됨.
여러 클라이언트가 서비스에 한꺼번에 바인딩 된 경우 서비스와 상호작용 완료시 unbindService()를 호출하여 바인딩 해제함. 바인딩된 클라이언트가 없을 경우 서비스는 소멸됨.
포그라운드 서비스 실행시 startForeground()를 호출하며 상태 표시줄에 대한 알림인 Notification과 매개변수로 알림을 식별하는 정수를 사용함.(정수는 0이면 안됨.)
언제인지는 정확히 모르겠지만... 기존에 알던 html 양식이 바뀌었습니다...;;;; (아마 2018.12월말부터??)
위 블로그에서는 2가지 방식을 권하고 있지요. jsoup을 사용하는 방식과 HttpURLConnection을 사용하는 법이요.
먼저 jsoup을 사용하는 경우 기존 아래 같은 부분이..
Elements Version = doc.select(".content");
for (Element mElement : Version) {
if (mElement.attr("itemprop").equals("softwareVersion")) {
return mElement.text().trim();
}
}
아래와 같이 바꾸시면 되고요.
Elements Version = doc.select(".htlgb").eq(3);
for (Element mElement : Version) {
return mElement.text().trim();
}