Google for Mobile I/O RECAP 2018 (01)

현장


전리품

기본 : 출입태그, 티셔츠, 텀블러 (얼리버드 이벤트로 못받음 ㅠㅠ), #IMakeApps 스티커

그리고 도시락


01. Keynote. 환영사

  • 발표자 : 민경환 (Head of Korea Android Apps and Games Business Development, Google)
  • 세션설명 :

환영함.

데이터 바인딩 라이브러리 3

참고링크 : https://developer.android.com/topic/libraries/data-binding/?hl=ko

데이터 객체

데이터 바인딩에 임의의 POJO(Plain Old Java Object)를 사용 가능하지만, POJO를 수정하더라도 UI가 업데이트 되지는 않음. 데이터가 변경될 때 이를 알려주는 기능을 데이터 객체에 부여해야 데이터 바인딩을 제대로 활용할 수 있음. Observable 객체, 필드, 컬렉션이라는 세 가지 다른 데이터 변경 알림 매커니즘이 있음.

이러한 Observable 데이터 객체 중 하나가 UI에 바인딩되어 있고 데이터 객체 속성이 변경되면 UI가 업데이트 됨.

Observable 객체

Observable 인터페이스를 구현하는 클래스를 사용하면 바인딩이 바인딩된 객체 단일 리스너를 연결.

Observable 인터페이스에는 리스너를 추가/제거하는 매커니즘이 있지만, 알림은 개발자의 선택에 따라 결정됨.

더 쉽게 개발하도록 BaseObservable을 만들어 리스너 등록 매커니즘 구현함. 속성이 변할 때 이를 알릴 책임은 데이터 클래스 구현자에게 있으며, getter에 Bindable 주석을 할당하고 setter에서 이를 알림

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

Bindable 주석은 컴파일 중에 BR 클래스 파일에 항목을 생성함. BR 클래스 파일은 모듈 패키지에 생성됨.

데이터 클래스에 대한 기본 클래스를 변경할 수 없는 경우, 리스너를 효율적으로 저장하고 알리기 편한 PropertyChangeRegistry를 사용하여 Observable 인터페이스를 구현 가능.

Observable Field

ObservableFields는 단일필드를 가진 Observable 객체.데이터 클래스에 public final 필드를 생성함.

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

ObservableField 와 형제들

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable

값 액세스시 set 및 get 접근자 메서드 사용

user.firstName.set("Google");
int age = user.age.get();

Observable 컬렉션

데이터 객체에 키 입력 방식으로 액세스 가능. 키가 String과 같은 참조 형식일 경우 ObservableArrayMap이 유용함.

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

레이아웃에서 String키를 통해 맵에 액세스시

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</data>
…
<TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

키가 정수일 때는 ObservableArrayList가 유용

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);ㅔ]

레이아웃에서 인덱스를 통해 액세스시

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</data>
…
<TextView
   android:text='@{user[Fields.LAST_NAME]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

생성되는 바인딩

생성되는 바인딩 클래스는 레이아웃 내에서 레이아웃 변수를 View와 연결함.

Binding의 이름과 패키지는 사용자 지정 가능하며, 바인딩 클래스는 모두 ViewDataBinding을 확장함.

생성

일반적으로 정적 메서드 사용하여 생성.

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

또는 레이아웃을 확장시킨 후 따로 바인딩

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

바인딩을 미리 알 수 없을 때는 DataBindingUtil 클래스를 사용하여 바인딩 생성

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

###View 적용

레이아웃에서 ID가 있는 각각의 View에 대한 공용 최종 필드 생성됨.

바인딩은 ID가 있는 View를 추출함.

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
   android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
  android:id="@+id/lastName"/>
   </LinearLayout>
</layout>

위 코드는 아래 바인딩 클래스를 생성함.

public final TextView firstName;
public final TextView lastName;

ID는 데이터 바인딩이 없으면 거의 필요 없지만, 코드에서 View에 액세스해야 할 경우가 있음.

###변수

각 변수에는 접근자 메서드가 주어짐

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

위 코드는 바인딩에 setter와 getter를 생성함

public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);

###ViewStub

ViewStub은 보이지 않게 시작되어 보이게 되거나 명시적으로 확장 명령이 있을때 다른 레이아웃을 확장하여 레이아웃에서 스스로를 대체함.

ViewStubProxy 객체가 ViewStub을 대신하여 ViewStub 확장시 확장된 View 계층 구조에 액세스하기도 할때 개발자에게 ViewStub에 대한 액세스 권한을 부여함.

다른 레이아웃을 확장할 때는 새 레이아웃에 대해 바인딩이 설정되어 있어야 함. ViewStubProxy는 하나만 존재할 수 있으며, 개발자는 ViewStubProxy에 대한 OnInflateListener를 설정하여 바인딩 설정 후에 호출하도록 함.

###고급 바인딩

####동적 변수

임의의 레이아웃에 대해 작동하는 RecyclerView.Adapter가 특정 바인딩 클래스를 알지 못하는 경우 onBindViewHolder(VH, int) 중에 바인딩 값을 할당해야 함.

BindingHolder에는 ViewDataBinding 베이스를 반환하는 getBinding 메서드가 있음.

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

####즉각적인 바인딩

변수나 Observable이 변경되면 바인딩이 다음 프레임 전에 변경되도록 예약됨.

바인딩을 즉시 실행해야 할 경우 ViewDataBinding#executePendingBindings() 메서드를 사용.

####백그라운드 스레드

데이터 모델이 컬렉션이 아닌 한, 백그라운드 스레드에서 데이터 모델을 변경할 수 있음.

데이터 바인딩은 동시 실행 문제 방지를 위해 각각의 변수/필드를 지역 범위로 설정함.

특성 Setter

바인딩된 값이 변경시마다 바인딩 클래스는 바인딩 식과 함께 View에 대한 setter 메서드를 호출해야 함.

데이터 바인딩 프레임워크에는 값을 설정하기 위해 호출할 메서드를 사용자 지정하는 방법이 있음.

###자동 Setter

데이터 바인딩은 메서드 setAttribute 찾기를 시도. 특성 이름 자체만 찾으면 됨.

TextView의 android:text와 연관된 식은 setText(String)을 찾음.

식에서 int가 반환되는 경우 데이터 바인딩은 setText(int) 메서드를 검색함.

참고로 주어진 이름을 가진 특성이 존재하지 않더라도 데이터 바인딩은 작동함.

예를 들어 DrawerLayout에 특성이 없지만 setter는 많이 있다고 가정해보면 자동 setter를 사용하면 이런 setter 중 하나를 사용할 수 있음.

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"/>

이름이 바뀐 Setter

setter의 이름이 일치하지 않는 특성은 BindingMethods 주석을 통해 특성을 setter와 연결할 수 있음.

메서드는 클래스와 연결되어야 하며 이름이 바뀐 메서드마다 BindingMethod 주석이 하나씩 포함됨.

예를 들어 android:tint 특성은 실제로 setTint가 아니라 setImageTintList(ColorStateList)와 연결됨.

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

Android 프레임워크 특성이 이미 구현되었으므로, 개발자가 setter의 이름을 바꿔야 할 일은 거의 없음.

사용자 지정 Setter

일부 특성은 사용자 지정 바인딩 로직이 필요함.

예를 들어 android:paddingLeft 특성에 대해 연결되 setter가 없고 setPadding(left, top, right, bottom)이 존재함.

개발자는 BindingAdapter 주석이 있는 정적 바인딩 어댑터 메서드를 사용하여 특성의 setter를 호출도록 사용자 지정이 가능함.

Android 특성에 이미 BindingAdapter가 생성되어 있음.

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}

바인딩 어댑터는 다른 형식의 사용자 지정에 유용함. 예를 들어 이미지 로드를 위해 오프스레드 상태로 맞춤 로더 호출 가능함.

개발자가 만든 바인딩 어댑터는 충돌이 있을 경우 데이터 바인딩 기본 어댑터를 무시함.

여러 매개변수를 받는 어댑터를 사용할 수도 있음.

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>

imageUrl과 error가 둘 다 ImageView에 사용되고 imageUrl이 문자열이고 error가 드로어블인 경우에 이 어댑터가 호출됨.

  • 맞춤 네임스페이스는 일치 확인 중에 무시됨.
  • Android 네임스페이스용 어댑터를 작성할 수도 있음.

바인딩 어댑터 메서드는 선택적으로 핸들러의 기존 값을 취할 수도 있음.

기존 값과 새 값을 취하는 메서드는 특성의 모든 기존 값을 먼저 가진 후 새 값을 가져야 함.

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
   if (oldPadding != newPadding) {
       view.setPadding(newPadding,
                       view.getPaddingTop(),
                       view.getPaddingRight(),
                       view.getPaddingBottom());
   }
}

이벤트 핸드러는 한 개의 추상 메서드를 가진 추상 클래스나 인터페이스와 함께 사용할 수 있을 뿐임.

@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
       View.OnLayoutChangeListener newValue) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        if (oldValue != null) {
            view.removeOnLayoutChangeListener(oldValue);
        }
        if (newValue != null) {
            view.addOnLayoutChangeListener(newValue);
        }
    }
}

리스너에 여러 개의 메서드가 있을 때는 여러 개의 리스너로 분할해야 함.

View.OnAttachStateChangeListener에는 onViewAttachedToWindow()와 onViewDetachedFromWindow()의 두 메서드가 있음.

이들 메서드의 특성과 핸들러를 구분하기 위해 두 개의 인터페이스를 만들어야 함.

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
    void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
    void onViewAttachedToWindow(View v);
}

한 리스너를 변경하면 다른 리스너에도 영향을 미치기 때문에 세 가지의 각기 다른 바인딩 어댑터가 있어야 함.

즉 각각의 특성을 위한 바인딩 어댑터 하나와 두 리스너 모두에 하나씩 있어야 함.

@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
    setListener(view, null, attached);
}

@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
    setListener(view, detached, null);
}

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
        final OnViewAttachedToWindow attach) {
    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
        final OnAttachStateChangeListener newListener;
        if (detach == null && attach == null) {
            newListener = null;
        } else {
            newListener = new OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    if (attach != null) {
                        attach.onViewAttachedToWindow(v);
                    }
                }

                @Override
                public void onViewDetachedFromWindow(View v) {
                    if (detach != null) {
                        detach.onViewDetachedFromWindow(v);
                    }
                }
            };
        }
        final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
                newListener, R.id.onAttachStateChangeListener);
        if (oldListener != null) {
            view.removeOnAttachStateChangeListener(oldListener);
        }
        if (newListener != null) {
            view.addOnAttachStateChangeListener(newListener);
        }
    }
}

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가 선택됨.

Object는 선택된 setter의 매개변수 형식으로 형변환됨.

이는 ObservableMaps를 사용하여 데이터를 유지하는 경우에 편리함.

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

userMap은 Object를 반환하고 그 Object는 setter setText(CharSequence)에서 찾은 매개변수 형식으로 자동 형변환됨.

매개변수 형식에 대한 혼돈이 있을 수 있을 때는 개발자가 식을 통해 형변환해야 함.

사용자 지정변환

특정 형식 간 자동 변환이 이루어져야 할 경우. 예를 들어 배경 설정시.

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

배경은 Drawable을 취하지만 색은 정수 형식임.

Drawable이 실행되고 정수가 반환될 때마다 int가 ColorDrawable로 변환되어야 함.

이 변환은 BindingConversion 주석과 함께 정적 메서드를 사용하여 수행됨.

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

변환은 setter 레벨에서만 이루어지므로 다음과 같이 형식을 혼합할 수 없음

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Android Studio의 데이터 바인딩 지원

데이터 바인딩 식에 대해 다음과 같은 기능 지원함

  • 구문 강조표시
  • 식 언어 구문 오류의 플래그 지정
  • XML 코드 완성
  • (선언 탐색 등의) 탐색과 빠른 문서화를 포함한 참조

배열과 제너릭 형식은 오류가 없을 때 오류를 표시할 수도 있음.

Preview 창에 데이터 바인딩 식의 기본값이 표시됨(제공되는 경우).

Preview 창은 TextView에 PLACEHOLDER 기본 텍스트 값을 표시하는 레이아웃 XML 파일의 한 예시.

<TextView android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@{user.firstName, default=PLACEHOLDER}"/>


데이터 바인딩 라이브러리 2

참고링크 : https://developer.android.com/topic/libraries/data-binding/?hl=ko

식 언어

공통

  • 수학 + - / * %
  • 문자열 연결 +
  • 논리 && ||
  • 이항 & | ^
  • 단항 **+ - ! ~**
  • 시프트 >> >>> <<
  • 비교 == > < >= <=
  • instanceof
  • 그룹화 ()
  • 리터럴 - 문자, 문자열, 숫자, null
  • 형변환
  • 메서드 호출
  • 필드 액세스
  • 배열 액세스 []
  • 삼항 연산자 ?:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

연산 누락

Java에서 사용 가능한 식 구문에서 몇 가지 연산 누락됨

  • this
  • super
  • new
  • 명시적 일반 호출

null 병합 연산자

null 병합 연산자는 왼쪽 피연산자가 null 이 아니면 왼쪽, null이면 오른쪽 피연산자를 선택함.

android:text="@{user.displayName ?? user.lastName}"

아래와 동일함

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

속성 참조

식이 클래스의 한 속성을 참조할 떄, 필드, getter, ObservableField 에 똑같은 형식이 사용됨.

android:text="@{user.lastName}"

NullPointerException 방지

생성되는 데이터 바인딩 코드는 자동으로 null 검사하여 null 포인터 예외를 피함.

예를 들면

  • user가 null 인 경우
  • user.name은 기본값(null)
  • user.age는 기본값(0) (여기서 age는 int)

컬렉션

편의상 [] 연산자를 사용하여 배열, 목록, 희소 목록, 맵 등의 공통 컬렉션에 액세스할 수 있음.

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

문자열 리터럴

작은 따옴표로 묶어서 사용하면 식에서 큰 따움표를 사용하기 쉬움.

특성 값을 큰 따움표로 묶을 경우 문자열 리터럴에는 ' 또는 ` 중 하나를 사용해야 함.

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)}"

plurals 는 단수/복수형에 대한 처리시 사용.

https://developer.android.com/guide/topics/resources/string-resource?hl=ko#Plurals

형식일반적인 참조식 참조
String[]@array@stringArray
int[]@array@intArray
TypedArray@array@typedArray
Animator@animator@animator
StateListAnimator@animator@stateListAnimator
color int@color@color
ColorStateList@color@colorStateList


데이터 바인딩 라이브러리 1

참고링크 : https://developer.android.com/topic/libraries/data-binding/?hl=ko

데이터 바인딩 라이브러리는 지원 라이브러리로 Android 2.1(API 7) 이상의 모든 버전에서 사용 가능.

데이터 바인딩을 사용하려면 Android Plugin for Gradle 1.5.0-alpha1 이상이 필요.

빌드 환경

  • Android SDK Manager의 Support 저장소에 라이브러리 다운로드.

  • build.gradle 파일에 dataBinding 요소 추가

    android { ... dataBinding { enabled = true
    } }

데이터 바인딩 레이아웃 파일

데이터 바인딩 레이아웃 파일은 layout 루트 태그로 시작되고 그 뒤에 data 요소와 view 루트 요소가 나옴.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

data 내에 있는 user 변수는 레이아웃 내에서 사용할 수 있는 속성에 대한 설명임

   <data>
       <variable name="user" type="com.example.User"/>
   </data>

"@{}" 구문을 사용하여 특성 속성에 기록됨.

       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>

데이터 객체

User에 대한 POJO(plain-old java project) 예시

(결코 변경되지 않는 데이터가 있는 경우)

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

JavaBeans 객체 예시

public class User {
   private final String firstName;
   private final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
   public String getFirstName() {
       return this.firstName;
   }
   public String getLastName() {
       return this.lastName;
   }
}

데이터 바인딩 관점에서 위 두 클래스는 동일함.

TextView의 android:text 특성에 사용된 @{user.firstName} 은 전자의 firstName 필드와 후자의 getFirstName() 메서드에 액세스함. 또는 firstName() 메서드가 존재할 경우 메서드로 해석하기도 함.

데이터 바인딩

BInding 클래스는 레이아웃 파일명 기준으로 생성되어 파일 이름을 파스칼 표기법(Pascal Case : 합성어 첫 글자를 대문자로 표기)으로 변환하고 그 뒤에 "Binding"을 접미사로 붙임.

main_activity.xml일 경우 생성되는 클래스는 MainActivityBinding 임.

이 클래스는 레이아웃 속성(예: user 변수)에서 레이아웃 View까지 모든 바인딩을 유지하고 바인딩 식에 대해 값을 할당하는 방법을 알고 있음.

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}

다음을 통해 뷰를 얻을 수도 있음

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

ListView 어댑터나 RecyclerView 어댑터 내에서 데이터 바인딩 항목 사용시 다음을 선호하기도 함.

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

이벤트 처리

데이터 바인딩을 사용하여 뷰에서 발소오디는 이벤트 처리식을 작성할 수 있으며, 이벤트 특성 이름은 리스너 메서드명에 따라 결정됨.

예를 들면 View.OnLongClickListener 의 onLongClick() 이벤트의 특성은 android:onLongClick

  • 메서드 참조 : 식에서 리스너 메서드의 서명을 준수하는 메서드를 참조함.
  • 리스너 바인딩 : 이벤트 발생 시 계산되는 람다 식. 뷰에서 설정하는 리스너를 항상 생성하며, 이벤트가 발송될 때 람다 식을 계산함.

메서드 참조

이벤트를 핸들러 메서드에 직접 바인딩함.

컴파일 시 처리되므로, 메서드가 존재하지 않거나 메서드 서명이 올바르지 않을 경우 컴파일 시 오류 발생

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

바인딩 식이 View 에 대해 click 리스너를 할당할 수 있음.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.Handlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

리스너 바인딩

이벤트 발생시 실행되는 바인딩 식.

임의의 데이터 바인딩 식을 실행할 수 있으며, Android Gradle Plugin for Gradle 2.0 이상에서 사용 가능.

반환 값만 리스너의 예상 반환 값과 일치해야 함.

public class Presenter {
    public void onSaveClick(Task task){}
}

click 이벤트를 클래스에 바인딩

  <?xml version="1.0" encoding="utf-8"?>
  <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
          <variable name="task" type="com.android.example.Task" />
          <variable name="presenter" type="com.android.example.Presenter" />
      </data>
      <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
          <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
          android:onClick="@{() -> presenter.onSaveClick(task)}" />
      </LinearLayout>
  </layout>

리스너는 식의 루트 요소로만 허용되는 람다 식으로 표현됨.

식에서 콜백이 사용될 때 데이터 바인딩이 필요한 리스너를 자동으로 생성하고 이벤트에 등록함.

뷰가 이벤트를 발생시키면 데이터 바인딩이 주어진 식을 계산.

정규 바인딩 식에서처럼 리스너 식이 평가되는 동안 여전히 데이터 바인딩의 null 및 스레드의 안전이 보장됨.

위의 예시에서는 onClick(android.view.View)로 전달되는 view 매개변수를 정의 안함.

리스너 바인딩에서 리스너 매개변수로 두 가지 중 선택 가능.

메서드에 대한 모든 매개변수를 무시하거나 모든 매개변수의 이름을 지정하는 것

매개변수의 이름을 지정하기로 선택하면 매개변수를 사용할 수 있음.

  android:onClick="@{(view) -> presenter.onSaveClick(task)}"

또는 식에 매개변수를 사용할 경우

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
  android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

두 개 이상의 매개변수를 포함한 람다 식 사용 가능

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
  <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

수신 중인 이벤트가 void 형식이 아닌 값을 반환하는 경우

식도 그와 동일한 형식의 값을 반환해야 함

public class Presenter {
    public boolean onLongClick(View view, Task task){}
}
  android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

null 객체로 인해 식의 계산이 불가능할 경우, 데이터 바인딩은 그 형식에 대한 기본 java 값을 반환.

예를 들어, 참조 형식에는 null, int 에는 0, boolean 에는 false 등을 반환함.

조건자(예:ternary)가 있는 식을 사용해야 할 경우 void를 기호로 사용 가능.

  android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

복잡한 리스너 방지

리스너 식은 UI에서 콜백 메서드로 사용 가능한 데이터를 전달하는 것만큼이나 단순해야 함.

비즈니스 로직은 리스너 식에서 호출한 콜백 메서드 내에 구현해야 함.

몇 가지 특화된 click 이벤트 핸들러 중 충돌 방지를 위해 android:onClick 이외의 특성이 필요함.

클래스리스너 Setter특성
SearchViewsetOnSearchClickListener(View.OnClickListener)android:onSearchClick
ZoomControlssetOnZoomInClickListener(View.OnClickListener)android:onZoomIn
ZoomControlssetOnZoomOutClickListener(View.OnClickListener)android:onZoomOut

레이아웃 세부정보

가져오기

data 요소 내에서 import 요소가 사용될 수도 있음.

Java에서처럼 이러한 요소를 사용하여 레이아웃 파일 내에 클래스를 쉽게 참조할 수 있음.

<data>
    <import type="android.view.View"/>
</data>
...
<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

클래스 이름 간에 충돌이 발생할 때 해당 클래스 중 하나의 이름을 alias 속성으로 바꿀 수 있음

그리고 import 요소로 가져온 타입을 variable 요소에서 사용할 수 있음.

<data>
    <import type="android.view.View"/>
    <import type="com.example.real.estate.View" alias="Vista"/>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User&gt;"/>
</data>

Android Studio는 아직 가져오기를 처리하지 않으므로 자동 완선 기능이 작동하지 않을 수 있음.

그래도 컴파일은 제대로 되므로, 변수 정의에서 정규화된 이름을 사용하여 IDE 문제를 해결할 수 있음.

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

식에서 정적 필드와 메서드를 참조할 때 가져온 형식을 사용 가능

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data>
…
<TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Java에서처럼 java.lang.* 을 자동으로 가져오게 됨.

변수

data 요소 내에서 사용할 수 있는 variable 요소의 개수 제한은 없음.

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

변수 형식은 컴파일 시에 검사되므로, 변수가 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 특성을 조정하여 바인딩 클래스명을 바꾸거나 다른 패키지에 배치할 수 있음.

<!-- 바인딩클래스명 변경 -->
<data class="ContactItem">
    ...
</data>

or

<!-- 모듈 패키지 내에서 다른 패키지에 배치할 경우 -->
<data class=".ContactItem">
    ...
</data>

or

<!-- 전체 패키지가 제공될 경우 어떤 패키지든 사용 가능 -->
<data class="com.example.ContactItem">
    ...
</data>

Include

특성에 앱 네임스페이스와 변수 이름을 사용하여 포함하는 레이아웃에서 포함되는 레이아웃의 바인딩으로 변수를 전달할 수 있음.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name" bind:user="@{user}"/>
       <include layout="@layout/contact" bind:user="@{user}"/>
   </LinearLayout>
</layout>

name.xml 및 contract.xml 모두 user 변수가 있어야 함.

데이터 바인딩은 include를 병합 요소의 직접 하위 요소로 지원하지 않음.

지원하지 않는 레이아웃의 예

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <merge>
       <include layout="@layout/name" bind:user="@{user}"/>
       <include layout="@layout/contact" bind:user="@{user}"/>
   </merge>
</layout>


Google I/O Extended Seoul 2018

링크 : https://io-extended-seoul-18.firebaseapp.com/

일시 : 2018.06.10 11:00 ~ 17:25

장소 : 세종대학교 광개토관 컨벤션홀 (B2)

간단하게 들었던 섹션 정리입니다.

01. KeyNote

  • 발표자 : David MacLaunghim(Google/Global Programs Lead)
  • 발표자료 :

GDG 현황 및 활성화를 위한 노력?

번역 자막이나 동시 통역 없이 영어로 진행되서 내용은 잘 모름.

걍 들리거나 눈에 보이는 대로 정리하자면은...

구글에서도 Open Source의 중요성이 높아지고 Third-Party Storage 도 마찬가지

그리고 개발자 커뮤니티의 활성화를 장려한다는 듯

기존 GDG의 경우 Local / Tech / Special 로 주제를 가지고 진행한다고 함.

그리고 GDE(Google Developer Experts)도 있고 Women Techmakers도 있음.

GDG Korea 조은 님 추가

GDG Korea에서는 주로 Meet up을 이용하며, slack.gdg.kr로 GDG Korea의 정보를 얻을 수 있음.

  • Track A : Android / Design
  • Track B : Machine Learning
  • Track C : Flutter, Web

02. Track A. What's new in Android P

  • 발표자 : 전병권 (Riot Games)
  • 발표자료 :

Android P 이야기. Compiler 관련 D8/R8 변화. App Bundle 추가. Jetpack은 중요하므로 직접 봐야할 것.

Android P 릴리즈 일정

  • 현재 DP3 (API 28) 까지 나왔으며, API 추가는 이제 없음.

주요 기능

  • Battery

    App Stanby Buckets (사용빈도 대비 전력사용 고려하여 관리)

  • Background Limit

    Voice Recoding, Camera Capture, Sensor 등 제한됨.

  • Location : 구글 맵 실내측위 지원, Wi-Fi RTT 이용.

  • Accessibility : View 속성 추가

    • android:accessibilityPaneTitle
    • android:accessibilityHeading
    • android:screenReaderFocusable
  • Security : 시스템이 앱을 대신해 지문 인증 대화상자 제공 등

  • Display cut (notch) : 개발자 옵션 - Drawing 섹션 -> Simulate a display with a cutout 메뉴로 확인 가능.

  • Actions : deep links

  • Notifications

    • 이미지 지원
    • 단순화된 대화 참가자 지원 (Notification.Person 클래스)
    • 회신을 초안으로 저장
    • 그룹대화 여부 식별
    • 인텐트의 문맥적 액션 설정
    • SmartReply (제안 회신 지원)
    • 채널 그룹 차단
    • 알림 채널과 채널 그룹의 차단 상태 변경시 브로드캐스트 인텐트 보냄.
    • 새로운 알림 일시중지 우선순위 범주 추가
  • Deprecation Policy (targetApiVersion 등)

별첨

  • FLAG_ACTIVITY_NEW_TASK : 비 액티비티 컨텍스트에서 액티비티 시작시 인텐트 플래그에 전달해야 함.

Android P Runtime

  • Kotlin 지원 향상
  • Dex 구조 변경(dex 내 data 공유 형식으로)
  • Profile Guided Optimizations
    • Cloud Profiles 형식으로 개인 정보 제외하여 Play Store에 전달됨. 보통 15명 정보면 최적화됨.
  • 기존 1 : Start -> Javac/Kotlinc -> Proguard -> dex -> APK
  • 기존 2 : Start -> Javac/Kotlinc -> Proguard -> class별 dex -> dex merge -> APK
  • D8 : Start -> Javac/Kotlinc -> Proguard -> D8(gradle 3.2부터 지원) -> APK
  • R8(개발 중) : Start -> Javac/Kotlinc -> R8 -> APK

Android App Bundle

기존 Multiple APK에 대한 지원으로 하나의 APK로 생성됨

Android Studio 의 Build Bundles/APK 메뉴에서 사용 가능 (결과물은 aab 확장자 형식)

앱 모듈 부분 설치 등 가능(Instant App 같이 처리 가능)

Jetpack (직접 확인 필요)


03. Track A. 새로운 안드로이드 개발 툴

  • 발표자 : 정승욱 (Grab)
  • 발표자료 :

Android Studio 3.2 Canary 이야기 + Jetpack 이야기. Navigation, Work Manager 사용 이야기.

Android Studio 3.2 Canary

  • Navigation (단일 액티비티, 다중 프래그먼트 지원)
  • Android X Refactoring 지원
    • Android Support Library 를 Android extension library (AndroidX) 네임스페이스로 리펙터링
    • Refactor -> Refactor to AndroidX 메뉴 추가
  • Sample Data : 레이아웃 편집기의 샘플 데이터로 미리 뷰를 채워볼 수 있음.
  • Slice : Android OS System의 UI에 앱 콘텐츠의 일부를 삽입하는 방법.
  • App Bundle : Google Play에서 특정기기에 필요한 APK 만 제공하는 동적 전송 플랫폼용.
  • D8 (기존 컴파일러 대체)
  • R8 (컴파일러 + Proguard)
    • gradle에서 android.enableR8 속성으로 사용 가능하나, R8은 아직 테스트 단계임.
  • CMakeList : C/C++ 코드를 위해 CMake Build Scripts 지원
  • Profiler
    • 에너지 프로파일
    • 시스템 트레이스 (기존 DDMS 대체)
    • 프로파일 세션
    • 코드 레벨 CPU 동작 기록
    • JNI 참조 추적

Jetpack

Android 앱을 빠르고 쉽게 만들 수 있도록 도와주는 라이브러리, 도구, 아키텍처 지침 모음.

공통 인프라 코드를 제공함.

  • AppCompack
  • Android KTX
  • Paging
  • Room
  • ViewModel

Navigation

1개 액티비티에 다중 프래그먼트 사용하기 위함.

  • Up and Back 지원
  • Bundle 참조 지원
  • Navigation 리소스에서 프래그먼트 액션 정의.
  • Activity 리소스에서 NavHostFragment 사용
  • AppCompactActivity의 onSupportNavigationUp() 사용
  • Navigation Controller 에서 네비게이션 리소스 파싱 및 관리
  • 모든 동작은 Runtime에 동작하므로 빌드 걱정 X

Work Manager

Android 백그라운드 작업을 관리.

  • intentService, JobScheduler, JobDispatcher, AlarmManager 대체
  • 디바이스 상태나 충전 상태 등에 대한 옵션 처리 가능
  • Status는 LiveData 형식이며 Room 사용
  • WorkService, WorkManager 클래스 사용
  • Background(스케쥴러) -> Worker -> Success -> sqlite

04. Track B. Something about TPU

  • 발표자 : 이진원 (삼성전자)
  • 발표 자료 :

Tensor Processing Unit 이야기

S/W 이야기 X

Micro-processor 방향성도 Domain 기반으로 변화해감.

y = f(wx + b)

보통 머신 러닝에 쓰이는 공식을 보면 결국 곱하기와 더하기 연산이 중요함.

  • Matrix Multiply Unit
  • Accumulators

TPU 처리속도 진화

TPU v1 (92 tera ops) -> Cloud TPU -> TPU Pod -> TPU v2 Pod -> TPU v3 (100 peta flops) Pod

모바일 용은 보통 1 tera ops 정도 필요

TPU 구성을 보면 Control 로직 비중은 2%

Systolic Array 연산이 중요함.

TPU의 원래 목표는 GPU의 10배의 성능이었으나 실제 대부분 지표에서 10배 이상의 성능이 나오고 있음.

Google Cloud TPU 서비스 베타 상태임.

아래 것들 사용 가능한 듯 (Tensor Board)... 아닐수도 있음;;

  • Image Recognition & Object Detection
  • Transformer
  • Speech Recognition

05. Track B. ML Kit 모바일 개발자를 위한 머신러닝

  • 발표자 : 남상균 (NBT)
  • 발표 자료 :

Firebase ML Kit 이야기. 결국은 GCP Vision API 사용. 결론은 유료.

Google I/O Keynote 에서도 AI에 대한 중요성 강조함.

  • 2017년 : Mobile first to AI first
  • 2018년 : Solving problems with AI for everyone
    • Mobile에는 ML Kit, TF Lite for Mobile

캐시슬라이드의 경우 광고 타켓에 대해서 visioning 사용 (구글 API 사용)

기존에는 client에서 data를 server에 전달 후 연산하는 구조가 주로 쓰임.

on-device ML 도 가능해짐

  • 장점

    • 오프라인 가능
    • 데이터 디바이스 보관
  • 단점

    • 메모리 사용
    • 배터리 소모
    • 낮은 컴퓨팅 파워

Tensor Flow Lite는 아직 실사용에는 어려운 점이 많음. 그래서 ML Kit이 나온 듯.

Google의 ML Kit & AR Core 와

Apple의 Core ML & ARKit 의

네이밍이 가져다 주는 개발자의 작명센스란..

ML Kit은 파이어베이스에서 지원함. Google Cloud Platform API로 지원됨.

  • 기본 API : 주로 이미지 관련
    • 이미지 라벨
    • 텍스트
    • 얼굴 감지
    • 바코드 스캔
    • 랜드마크 인식
    • 스마트 회신(자동완성) 제공 예정
  • 커스텀 모델
    • 동적 모델 다운로드
    • 파이어베이스 원격설정을 통한 A/B 테스트 지원
    • 모델 변환과 압축 제공 예정

Google Codelabs에서 관련 튜토리얼 확인 가능 : https://codelabs.developers.google.com/

Firebase ML Kit

  • Cloud/on-device(기기별) 지원
  • Firebase는 브릿지 역할이라 GCP API를 실제 사용하게 됨.(유료임)
  • on-device일 경우 앱 설치 후 모델 다운로드를 권장함
  • Text Detector 등의 경우 Cloud/on-device에 따라 처리 갯수 차이가 남
  • 커스텀 모델은 model interpreter 사용
  • Firebase 모델 매니저에서 Local/Cloud 모델 소스 관리

visioning API 사용시 주의점 : 이미지 Rotation 값을 잘 설정해야 함.

ML Kit 사용시 고려할만한 서비스들

  • Firebase A/B Testing
  • Cloud Firestore
  • Firebase Performance
  • Google Analytics

Google Cloud Vision API 가격


06. Track A. New Google News & Material Design

  • 발표자 : 안태완 (Google)
  • 발표 자료 :

앱 프로덕트 디자인에 대한 내용 + Material Design Theming 이야기

개발 이야기 X

Google AI 모토는 Human Intelligence 를 support 하는 것

Fake New가 많아 Trust에 대한 부분 필요.

AI 를 통해 paragraph를 뽑아 리스트 배치도 함.

Google Material Theme

Open source 기반 높은 수준의 디지털 경험을 제공하는 것이 목적

Material Theming

기존 Material 은 Product 차별점이 없음.

좀 더 flexible하게 확장된 개념.

주요 개념

  • Centerable
  • more white space
  • product (brand) color
  • 컨텐츠 중심

적용 사례

  • Gmail
  • Google News
  • Google Play
  • Google Home

전리품

기본 : 출입태그, 구글백(신발주머니 같은..), 티셔츠, 선글라스

GDG : 스피너

레진코믹스, 오픈서베이 : 스티커?

참고자료

https://io-extended-seoul-18.firebaseapp.com

Droid Knights 2018

링크 : https://droidknights.github.io/2018/

일시 : 2018.04.22 09:00 ~ 18:00

장소 : 코엑스 컨퍼런스홀

간단하게 들었던 섹션 정리 및 자료 링크 취합입니다.

01. Track 1. No More Fragment, No More Activity

  • 발표자 : 정승욱 (GrabTaxi 안드로이드 개발자)
  • 발표자료 :

동남아 서비스로 국내보다 단말 저사양 및 파편화 현상이 심함.

해상도 대응이나 안드로이드 버전별 대응 등의 어려움이 있음.

각 국가별 개발팀이 분산되어 있으며, 30여명의 개발자로 팀 구성됨.

Activity나 Fragment 등의 사용없이 단일 Activity로 앱 구성.

서비스 모듈 별 관리를 위해 Router, Node 형태 도입.

Back키 처리나 스킴 처리 등의 대응 필요.

02. Track 1. Next Step Architecture

  • 발표자 : 남상균, 정현지 (NBT/캐시슬라이드 Chief Architect & 안드로이드 개발자)
  • 발표자료 :

오래된 앱을 유지하면서 시스템을 개선하기 위한 노력.

도메인 베이스의 의사결정이 개발 환경 등에도 영향을 미침.

안드로이드 API 최소버전도 최근에 들어서 상향 조정함.

Kotlin 전환 등의 노력들을 함.

03. Track 2. Best Practice on Android Instant Apps

  • 발표자 : 김종식 (원티드 안드로이드 개발자)
  • 발표자료 :

설치 없이 네이티브 앱 사용경험을 제공하는 인스턴트 앱 개발과정

Android Instant App FAQ (https://developer.android.com/topic/instant-apps/faqs.html?hl=ko)

  • 호환되는 기기 : Android 4.1(API 16) 이상 & Google Play서비스가 설치된 기기

  • 두 가지 다른 앱을 빌드해야 하나?

    하나의 프로젝트만 유지하면 가능. 두 가지 빌드 아티팩트인 설치 가능 APK와 인스턴트 버전을 생성하도록 프로젝트 구성하면 됨.

  • 어떤 Android API와 기능을 사용할 수 있나?

    Android Instant Apps 기능이 기존 Android 앱을 보완하기는 하나, 대체하지는 않음.

    동일한 Android API, 동일한 프로젝트, 동일한 소스코드를 사용하나 설치되지 않은 앱에 대한 사용자의 기대 사항에 맞지 않을 수 있는 일부 기능은 제한함. 예를 들어 백그라운드 서비스 사용이나 백그라운드 알림, 고유 기기 식별자에 액세스하지 못할 수 있음.

  • 사용자가 앱을 영구적으로 설치하도록 선택할 수 있나?

    Google Play 스토어에서 앱 다운로드하도록 허용 가능함. 다운로드 후에는 사용자가 그 환경을 떠난 후에도 앱이 휴대폰에 남아있음.

  • Permission은 어떤식으로 작동?

    Android 6.0 (API 23)에 새로 추가된 런타임 권한 모델을 사용함.

  • Instant Apps 게시는?

    기존의 Android 앱과 유사하게 Google Play Developer Console을 통해 게시 가능

참조 링크 : https://tech.wanted.co.kr/android/2018/01/30/android-instant-app.html

gradle 이나 AndroidManifest 설정은 위 링크를 참조하면 됨.

인스턴트 앱을 위해 InstantApps 라는 클래스가 추가되었으며, isInstantApp(Context context) 메서드로 현재 프로세스를 확인할 수 있음.

배포시 Holdback 설정을 통해 0과 1 사이의 값을 설정하여 인스턴트 앱 출시 비중을 정함.(0일 경우 전체를 인스턴트 앱으로 출시)

프로덕션 출시가 아니라면 10MB 까지 업로드가 가능했으나, 프로덕션 출시시에는 4MB를 넘기면 안됨.

기본 support 라이브러리들도 용량을 먹기 때문에 실제 사용 가능한 용량은 적을 수 있음.

04. Track 2. Kotlin 코루틴은 어떻게 동작하는가?

일반 subroutine

  • 단일 지점에서 시작 -> 특정 지점에서 종료

Coroutine

  • 단일 지점에서 시작 -> 임의 지점에서 멈춤 -> 해당 지점에서 재개 -> 특정 지점에서 종료

Sequencial code를 통해 non-blocking 코드를 작성하기 위한 수단으로 Coroutine 제공

suspend는 중단/재개 지점을 위한 continuation을 매핑하기 위한 키워드

호출한 대상/어디까지 실행했는지/어떤 값들을 가지고 있는지 등에 대한 상태를 Continuation Passing Style로 처리됨.

각 코드 라인은 Labeling되어 switch문을 통해 처리되며, Statemachine, Continuation, 상태 저장/복원 등으로 동작함.

UI 쓰레드에서 실행됨.

05. Track 2. Google Mobile Vision과 OpenCV로 card.io를 확장한 범용 카드번호인식 개발

LINE에서 사용 중인 신용카드 인식 오픈소스인 card.io가 양각되어 있는 신용카드만 인식하는 문제 해결 과정.

LINE 캐릭터 카드 같은 경우들을 인식을 못함.

Google Mobile Vision과 OpenCV를 활용하여 일반 프린팅된 카드 번호 및 멤버쉽 카드 그리고 바코드를 인식하는 라이브러리를 개발함.

card.io 관련 여러가지 이슈들

  • 작은 해상도(바코드 인식에 맞는 사이즈가 안나옴)
  • 양각 문자만 인식(Google Mobile Vision을 활용해보았으나, 너무 많은 문자열이 인지됨.)
  • 카드 스캔시 영역 설정시 퍼포먼스 이슈
  • 문자열 인지 후 카드번호 얻는 방식(여러 문자열이 나열되는 경우들. 예를들어 자동차 번호판 인식같이)
  • Java와 NDK 간 속도차이는 최소 5배 이상은 차이났음.

##06. Track 2. Travis-ci를 이용한 CI/CD와 도커를 이용한 Jenkins for Android 구성하기

CI(Continuous Integration) : Build, Test를 실시하는 프로세스를 상시로 실시해줌. 지속적 통합.

CD(Continuous Delivery or Continuous Deploy) : 소프트웨어를 빠르고 주기적으로 빌드하고 테스트하고 출시하는 것이 목표. 지속적 배포.

이를 수동으로 하는 것은 자동차 스틱을 운전하는 것이나 돌로 탑쌓기와 유사함.

CI툴들을 사용해 피드백을 받고 스크립트로 자동화까지 처리한다면 자율주행같이 사용할 수 있음.

Travis CI를 이용한 CI/CD

Travis-CI : Github에서 진행되는 오픈소스 프로젝트. Private repository는 유료.

  • 장점
    • 손쉬운 프로젝트 설정 및 서비스 연동(Github와 seamless한 통합)
    • 오픈소스 프로젝트시 무료
    • 전용 CI/CD 서버 필요하지 않음.
    • 모든 Job이 독립적 동작
    • 빌드 메트릭스 제공
  • 단점
    • 제한된 옵션 제공 (Site GUI 제한적)
    • 느린 속도
    • Private Repository는 유료

Jenkins for Android using Docker

Jenkins : 다양한 시스템에서 사용 가능. 다양한 플러그인. 다양한 작업 모드 및 조건의 빌드 가능

  • 장점
    • 무료
    • 사용자 정의 옵션
    • 방대한 양의 플러그인
    • 다양한 적용사례 및 풍부한 레퍼런스
    • Remote access API 제공
  • 단점
    • 별도 서버 필요
    • 시스템 구성 및 사용자 정의 시간이 오래 걸릴 수 있음

07. Track 1. Android Test

  • 발표자 : 정경호 (에그번 안드로이드/서버 개발)
  • 발표자료 :

개발자들은 테스트를 피할 수 없음.

유닛 테스트도 작성이 어려운 경우가 많음.

서비스 테스트부터라도 작성을 해야 함.

UI 테스트도 할 수 있음.

패턴도 적용해야 함.

아무튼 해야 함!!!!

08. Track 1. 내가 안드로이드 개발자가 되었을 때 아무도 알려주지 않은 것들

안드로이드 개발자가 걷는 길

초보 모바일 개발자일 때 어려움들

  • Device/version fragmentation
  • 비동기
  • Life Cycle
  • 구글의 신기능

안드로이드 개발자가 되었을때 아무도 알려주지 않았던 것들

  • Clean Architecture
    • 하라는 건 많지만 제대로 쓰는 사람은 별로 안보임.
  • MVVM
    • 쓰면 좋다는데 복잡하고 예제들이 제 각각임.
  • MVP
    • MVVM 보다는 쉬운 것 같지만 진행하다보니 또 다른 컨트롤러를 만드는 기분임.
  • RxJava
    • 어렵고 헷갈리고, 자매품 LiveData는 좀 쉽지만 애매함.
  • Dependency Injection
    • 처음엔 쉬운 것 같은데 끝없는 decision making의 연속. 특정 인스턴스에 대한 injection을 어떻게 해야 효과적인지 헷갈림.
  • TDD (Test-Driven Development)
    • 유닛 테스트 튜토리얼들 중 실제 상용 프로젝트 수준의 유닛 테스트를 만드는 법을 알려주는 건 없음.

위의 것들을 통해 나타나는 전형적 증상

  • 비관주의 : 이 또한 지나가리
  • 팔랑귀 증후군 : 당장 해보자
  • 툴 만능주의 : 툴과 라이브러리 사용능력이 실력이다?
  • Low-hanging fruit : 쉬운 문제에만 집착

중요한 스킬

  • Solution Provider

    • 알고리즘에 대한 깊은 이해
    • 개발 언어의 semantics의 깊은 원리와 언어 설계 철학에 대한 이해
    • Dalvik의 동작 원리 이해
    • Activity/Fragment 중요 동작을 플랫폼 코드 레벨에서의 이해
    • 안드로이드가 화면을 렌더링 하는 매커니즘의 이해
    • 등등등
  • Architect

    • 내 프로젝트를 간단한 README.md 파일 외에 다른 개발자에게 인수인계할 수 있음.

    • 코드를 1년 뒤에 봤을때 바로 이해 가능

    • 큰 규모의 구조 수정도 1~2일 내에 해낼 수 있을 정도로 유연한 구조

    • Jr 엔지니어에게 후속 코딩을 안심하고 몽땅 떠넘겨도 될 수준으로 기본 구조가 견고하게 작성.

      (잘못된 방식으로 구현하기가 매우 어렵도록 구조를 만듬)

중급 개발자를 위한 안내

  • 시작이 반
  • 공유의 힘 (주관이 들어가면 스스로에게도 독자에게도 도움이 됨)
  • 일본의 경우
    • 블로그를 통한 공유가 일상화
    • 주로 지역 단위의 세미나인 공부회
    • Moku-moku meeting(묵묵한 미팅?) : 그냥 각자 코딩 모임. 국내 모각코 같은...
    • Network Party : 자기가 구현한 아키텍처, 테스트 등 구체적인 부분에 대한 지식 교환.

패턴 사용에 따른 경험들

MVP & MVVM

  • MVC is not evil

    • 모바일 환경의 문제 : 비동기 처리, 라이프 사이클 처리
    • 안드로이드의 View-Controller 분리가 애매
    • 그로 인한 총체적 난국 : 최악의 가독성, 각 클래스들의 너무 많은 역할들.
  • MVP

    • 안드로이드 초기부터 구전으로 전해져 왔던 아키텍처
    • 장점
      • Fat activity/fragment 방지
      • 역할 분리로 인해 소스 가독성 및 품질 상승
      • 플랫폼 의존적인 UI 처리는 View쪽으로 분리하여 Presenter는 쉽게 테스트 작성 가능
      • Clean Architecture와 1:1 mapping 가능. CA 그대로 채용하면 설계 관련 노력도 최소화.
  • What is not MVVM?

    • MVVM의 특징이 Life Cycle은 아님.

    • Data Binding : 필수가 아님. 다만 사용하면 생산성에 확실한 도움이 됨.

    • RxMVVM : 역시 필수는 아니지만 없이는 매우 불편함.

    • ViewModel 용어에서 오는 혼란

      • Model과 같이 작은 단위로 쪼개져있지 않음. 통신에다 별게 다 들어간 형태임.
      • View를 위한 Model (Entity) 라는 개념 & 단방향 Presenter라는 두개의 개념임.
      • Presenter와 매우 유사하나 ViewModel은 View를 제어하지 않음.
      • View는 ViewModel을 호출하되, 결과는 Callback/Observable 형태로 받음.

MVVM vs MVP

둘 다 훌륭한 구조이며, 압도적 우위는 없음. 프로젝트 성격에 따라 다르게 받아들여질 수 있음.

MVP의 비교우위

  • 시작이 쉬우며, Legacy Code의 분량이 많거나 구조 개선에 주어진 시간이 짧은 경우 MVP가 유리
  • RxJava를 안 써도 훌륭한 구조를 만들 수 있음.

MVVM

  • 초기 진입장벽은 있음.
  • View 상태 관리가 복잡한 경우 ViewModel에서의 처리가 자연스러움
  • AAC (Android Architecture Component 후광 효과)
  • Cross Platform 구현에 좀 더 유리 (Kotlin을 Swift로 옮기는 것만으로도 View 이외의 소스의 70% 이상을 재사용 가능)

MVVM - MVP 공통의 장점

  • 개발 속도가 몇 배는 빨라짐 (단 개인차가 있으며, 초기 2~3주간 개발 속도는 몇 배는 느려질 수 있음)
  • 코드 변경에 대한 두려움이 거의 없어짐
  • 기획의 급격한 사양 변경에도 대응 가능
  • 1년 뒤에 코드를 다시 봐도 즉시 어디를 고쳐야할지 알 수 있음.
  • 다른 엔지니어에게 코드를 보여줘도 쪽팔리지 않음!!!

MVP/MVVM at Scale

프로젝트 사이즈가 점점 커지면서 위의 기대와는 달리 지저분해짐.

  • Fat Presenter
  • 테스트 케이스 만들기가 어려워짐
  • 코드가 지저분함. Activity Manager 같은 게 하나 늘어나기만 한 느낌
  • 주된 원인 중 하나는 Presenter를 View와 명확히 분리하지 않은 것

해결책

  • View와 Presenter를 잘못 구현하기 어렵도록 하는 장치 필요.
  • View는 이벤트를 발생시키고, UI를 그려주는 역할만 담당, 비즈니스 로직이나 모델이 끼어들 여지를 차단.
  • Presenter가 View의 세부 사항을 알 수 없도록 P는 View를 추상화 시킨 형태로만 참조하도록 만듦

MVP: Contract Pattern

  • View Interface를 통한 분리
  • 필요한 경우 Presenter까지 Interface화 시킴

MVVM Best Practice: Passive View

  • View는 (플랫폼 의존적인) 그리기 이외에는 아무것도 알 수 없도록 함
  • Lighter View, Fatter ViewModel이 되나 더 나은 구조로 가기 위한 중요한 가교

MVVM is not an architecture.

비즈니스 로직의 비대화

  • Presenter나 ViewModel의 도메인 로직이 감당할 수 없는 크기가 되는 상황

  • Single Responsibility Principle 적용

    • 모범 답안 : Clean Architecture!
    • Presenter에서 도메인 로직을 분리 가능
    • Data 계층의 세분화를 통해 더욱 추상화된 형태의 구조 가능

Clean Architecture

기승전CA?

장점

  • Presenter/Domain/Data 계층의 분리로 인해 전체 로직의 흐름이 일목요연해짐. 가독성 급상승.
  • 특정 클래스가 비대해지는 것을 방지
  • 각 Layer 간 구분이 명확하므로, 영향 범위도 명확
  • 단 클래스 량이 급격히 증가 -> 한번에 구현하려 하면 개발속도 저하와 버그 양산 야기
    • CA가 익숙하지 않거나 경험이 없는 경우 모든 걸 따라할 필요는 없음
    • 프리젠테이션 로직의 합리화부터 시작
      • View - ViewModel/Presenter - Repository - Model (Entity)
    • 도메인/데이터 로직은 필요에 의해 점진적으로 하는 것이 합리적
  • 자동 생성 플러그인 : https://github.com/kiuchikeisuke/Android-Studio-CleanArchitecture-template-forKotlin
    • RxJava, Dagger, Retrofit, AAC, Presenter/ViewModel 코드까지 몽땅 만들어 줌
    • Swagger 지원으로 REST API call 인터페이스도 자동으로 만들어 줌

몇 가지 난제들...

구조의 철학적 문제 : Presenter의 비즈니스 로직과 Use Case의 비즈니스 로직의 차이점?

  • 도메인 로직이 Use Case에 들어감
  • 사업부 사람들도 알고 있어야 하는 로직이라면 도메인 로직

RecyclerView in MVP

Presenter로 RecyclerView - Adapter 대하는 것은 까다로운 문제임

  • Activity/Fragment가 (Presenter의 명령을 받아) Adapter를 제어 - 단순한 패턴의 리스트에는 효과적
  • Adapter와 매핑되는 Presenter를 구현
  • 개별 item과 각각 1:1 매핑되는 작은 Presenter를 구현

RecyclerView in MVVM

Presenter에 비해선 단순한 문제. Data binding으로 그대로 ViewModel 연결 가능

  • 복잡한 경우가 아니라면 Adapter에 ViewModel 연결

  • 복잡하다면 각 Item과 매핑되는 작은 ViewModel 구현

    • 이 경우 ViewModel은 View를 위해 가공된 Model이기 때문에 어떤 의미에서 진정한 ViewModel
    • AAC를 이용해 small ViewModel을 생성시 provider에 파라미터를 별도 지정해야 함. 차라리 상위 ViewModel이 생성하는 게 더 좋을 수 있음.

경험

서비스 중인 앱을 단기간 내에 리뉴얼 개발하면서 얻은 결과

  • 비즈니스 관점에선 앱 구조 개선은 큰 가치가 없음. 새로운 기능들을 얼마나 빠른 속도로 넣느냐..
급한 일급하지 않은 일
중요한 일신 기능 구현
버그 수정 + 앱 안정화
Java -> Kotlin 변환
ViewModel or Presenter 전환
오래된 라이브러리 갈아타기
중요하지 않은 일각종 회의 참석이상한 변수명, 메소드명 변경
들여쓰기 변경
기타 스타일 수정

전리품

기본 : 출입태그, 티셔츠, 밀스 그레인, 그래놀라바, 에코백, 뱃지, 물, 스티커

네이버 : 노트 or 펜

쿠팡 : 수첩 커버 및 수첩 세트

하이퍼커넥트 : 티셔츠, 스티커

리멤버 : 샤오미 보조 배터리

참고자료

https://yands11.github.io/droid-knights/

https://blog.naver.com/PostView.nhn?blogId=cenodim&logNo=221258932085&redirect=Dlog&widgetTypeCall=true&directAccess=false

[Android] 프로세스(process) 및 스레드(thread)

참고링크 : https://developer.android.com/guide/components/processes-and-threads?hl=ko

애플리케이션 구성 요소가 시작되고, 앱에 실행 중인 다른 구성 요소가 없으면 하나의 실행 스레드로 앱의 Linux 프로세스 시작함. 기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 스레드에서 실행됨. 하지만 여러가지 구성 요소가 각자 별도의 프로세스에서 실행되도록 할 수도 있고, 어느 프로세스에든 추가 스레드를 만들 수 있음.


프로세스

기본적으로 같은 앱의 모든 구성 요소는 같은 프로세스와 스레드에서 실행되고 이를 바꾸면 안됨. 그러나 어느 프로세스가 특정 구성 요소에 속하는지 확인해야 할 경우 매니페스트 파일에서 확인 가능.(android:process 속성)

  • 지원하는 요소 : <application>, <activity>, <service>, <receiver>, <provider>

안드로이드 시스템에서 어느 프로세스를 삭제할지 결정할 때, 이들의 상대적 중요성을 가늠함.


프로세스 수명 주기

안드로이드 시스템은 프로세스에서 실행되는 구성 요소와 해당 구성 요소의 상태에 기초하여 각 프로세스에 "중요 계층"을 부여함. 중요도가 낮은 프로세스가 먼저 제거됨.


  • 중요 계층 5가지
    1. 포그라운드 프로세스 : 사용자가 현재 진행하는 작업에 필요한 프로세스 (Activity나 Foreground Service)
      • 사용자가 상호작용하는 Activity를 호스팅할 경우
      • 사용자와 상호작용하는 액티비티에 바인드된 Service를 호스팅할 경우
      • 포그라운드에서 실행되는 Service를 호스팅할 경우
      • 수명 주기 콜백 중 하나를 실행하는 Service를 호스팅할 경우 (onCreate(), onStart() 또는 onDestroy() )
    2. 가시적 프로세스 : 포그라운드 구성 요소는 없지만 사용자가 화면에서 보는 것에 영향을 미칠 수 있는 프로세스
      • 맨 앞에 있지는 않지만 사용자에게 보이는 Activity 를 호스팅할 경우
      • 눈에 보이는 액티비티에 바인딩된 Service 호스팅할 경우
    3. 서비스 프로세스 : startService() 메서드로 시작되었지만, 위 경우에 들어가지 않는 서비스를 실행 중인 프로세스일 경우
    4. 백그라운드 프로세스 : 현재 사용자에게 보이지 않는 액티비티를 보유한 프로세스. 이러한 프로세스는 LRU(최저 사용 빈도) 목록에 보관하여 사용자가 가장 최근에 본 액티비티가 있는 프로세스가 가장 마지막에 중단되도록 함.
    5. 빈 프로스세스 : 활성 앱 구성 요소를 보유하지 않은 프로세스.

프로세스의 등급은 다른 프로세스가 이에 의존할 경우 상승 가능.(의존 대상의 중요도가 의존할 대상과 같거나 높음)


스레드

앱이 시작되면 앱에 대한 실행의 스레드를 생성하며, 메인 스레드라 함. 이 스레드는 드로어블 이벤트를 포함한 UI 위젯에 이벤트를 발송하는 역할을 맡기도 하여 UI 스레드라고도 함.


시스템은 구성 요소의 각 인스턴스에 대해 별도의 스레드를 생성하지 않음. 같은 프로세스에서 실행되는 모든 구성 요소는 UI 스레드에서 시작되고, 구성 요소를 호출하는 시스템이 해당 스레드에서 발송됨.


시스템 콜백에 응답하는 메서드(onKeyDown()이나 생명주기 메서드 등)는 항상 프로세스의 UI 스레드에서 실행됨.


UI 스레드에서 DB 쿼리 등의 긴 작업을 수행하면 사용자가 보기에 앱이 중단된 것처럼 보이며, 약 5초이상 진행되면 ANR 대화상자가 표시됨.


안드로이드 UI 도구 킷은 스레드로부터 안전하지 않아 worker 스레드에서 처리하면 안됨.


Worker 스레드

단일 스레드 모델로 인해 앱 UI의 반응성을 위해서는 UI 스레드를 차단하지 않는 것이 중요함. 이를 위해 시간이 걸리는 작업들은 별도의 스레드에서 수행하며 이를 백그라운드 또는 워커 스레드라 함.


  • 안드로이드가 다른 스레드에서 UI 스레드에 액세스하기 위해 제공하는 방식
    • Activity.runOnUiThread(Runnable)
    • View.post(Runnable)
    • View.postDelayed(Runnable, long)

작업이 복잡해질수록 위 방식은 더 복잡해지므로, 더 복잡한 인터렉션을 처리하려면 worker 스레드에서 Handler를 사용하여 UI 스레드에 전달받은 메시지를 처리하는 방안을 고려해야 함. 최선의 해결책은 AsyncTask 클래스를 확장하여 사용하는 것.


AsyncTask

AsyncTask를 사용하면 UI에서 비동기식 작업을 수행할 수 있게 해줌. woker 스레드에서 작업을 수행하고 그 결과를 UI 스레드에 게시하므로 직접 스레드 및 핸들러를 처리할 필요가 없음.


AsyncTask를 서브 클래스로 지정한 다음, 백그라운드 스레드 풀에서 실행되는 doInBackground() 콜백 메서드를 구현하고, UI 업데이트를 위해 onPostExecute() 를 구현해야 함.


  • 매개 변수의 유형, 진행률 값과 최종 값을 제네릭을 사용하여 지정할 수 있음
  • doInBackground()는 worker 스레드에서 실행됨.
  • onPreExecute(), onPostExecute() 및 onProgressUpdate()는 UI 스레드에서 호출됨.
  • doInBackground()가 반환한 값이 onPostExecute() 로 전달됨.
  • publishProgress()를 doInBackground()에서 호출하여 UI 스레드에서 onProgressUpdate()를 실행함.
  • 모든 스레드에서 언제든 작업을 취소할 수 있음.

Thread-safe 메서드

어떤 경우에는 구현하는 메서드가 하나 이상의 스레드에서 호출되는 일이 있어 안전하게 작성해야 함.


주로 원격으로 호출할 수 있는 메서드이 있음. 바인드된 서비스 등.


IBinder에서 구현된 메서드가 IBinder가 실행되는 프로세스에서 호출될 경우, 해당 메서드는 호출자의 스레드에서 실행됨. 호출이 다른 프로세스에서 발생하면, 해당 메서드는 시스템이 IBinder 와 같은 프로세스에 유지하는 스레드 풀에서 선택된 스레드에서 실행됨.(프로세스의 UI 스레드에서 실행되지 않음)


위와 같은 이유로 스레드로부터 안전한 구현이 필요함.


프로세스 간 통신

안드로이드는 원격 프로시저 호출(RPC)을 사용한 프로세스 간 통신(IPC) 매커니즘을 제공함.


여기서 메서드는 액티비티나 다른 앱 구성 요소에 호출되지만 원격으로 실행되고, 결과는 모두 호출자에게 되돌려 보냄.


메서드 호출과 데이터는 운영 체제가 이해할 수 있는 수준으로 분해되고, 로컬 프로세스와 주소 공간에서 원격 프로세스와 주소 공간으로 전송되어 다시 결합되어 호출에 응답하게 됨. 그런 다음 반환 값이 반대 방향으로 전송됨.


안드로이드가 이와 같은 IPC 트랜잭션을 수행하는데 필요한 코드를 제공하므로, 개발자는 RPC 프로그래밍 인터페이스를 정의하고 구현하는 데만 집중하면 됨.


IPC 수행시 bindService()를 사용하여 앱이 서비스에 바인드 되어야 함.

[Android] 서비스 (Service)

참고링크 : https://developer.android.com/guide/components/services?hl=ko


서비스는 백그라운드에서 오래 실행되는 작업을 수행할 수 있는 애플리케이션 구성요소.

사용자 인터페이스 제공안함.


서비스는 본질적으로 두 가지 형식을 취함

  • 시작됨 : 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이면 안됨.)

  • Unbounded Service
    • Call to startService()
    • onCreate()
    • onStartCommand()
    • Service running
    • The Service is stopped by itself or a client
    • onDestroy()
    • Service shut down
  • Bounded Service
    • Call to bindService()
    • onCreate()
    • onBind()
    • Clients are bound to service
    • All clients unbind by calling unbindService()
    • onUnbind()
    • onDestroy()
    • Service shut down


우려하던 대로 2019.01.02일자로 또 바뀌었네요. 내용 추가합니다.




앱에서 최신 앱 버전을 체크하기 위해 구글플레이의 링크를 이용해 따시는 경우들이 종종 있습니다. 


서버가 있어 서버에 등록하고 쓴다면 좋겠지만, 앱 업데이트시 마다 변경해야 하고 서버 자체가 없는 경우도 있다보니..


편의상 아래 링크처럼 구글플레이의 앱 링크를 열어 html을 긁어서 사용하는 모양새들인데요.


http://gun0912.tistory.com/8


언제인지는 정확히 모르겠지만... 기존에 알던 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();
	}


HttpURLConnection을 사용하는 방식으로 하셨을 경우는 아래의 부분을..

	String startToken = "softwareVersion\">";
	String endToken = "<";


아래와 같이 바꾸시면 됩니다. (2018.04.17 일자 내용 추가)


	String startToken = "<div class="BgcNfc">Current Version</div><span class="htlgb"><div><span class="htlgb">";
        // 2018.04.17일자 이전
	// String startToken = "<div class=\"BgcNfc\">Current Version</div><div><span class=\"htlgb\">";
	String endToken = "</span></div>";


또 바겼습니다. 아래와 같이 바꾸시면 됩니다. (2019.01.04 일자 내용 추가)


	String startToken = "<div class=\"BgcNfc\">Current Version</div><span class=\"htlgb\"><div class=\"IQ1z0d\"><span class=\"htlgb\">";
// 2019.01.04일자 이전 // String startToken = "<div class="BgcNfc">Current Version</div><span class="htlgb"><div><span class="htlgb">"; // 2018.04.17일자 이전 // String startToken = "<div class=\"BgcNfc\">Current Version</div><div><span class=\"htlgb\">"; String endToken = "</span></div>";

다만 위와 같은 방법은 구글플레이 html 양식이 바뀐다면 또 문제가 되겠지요.


가능하시다면 번거럽더라도 다른 방법을 찾는게 맞을듯 싶습니다.


아 추가로 기존에 HttpURLConnection의 메소드 타입을 GET, POST 상관없이 구글플레이 html을 받아올 수 있었는데요.


오늘 확인해보니 POST로 할 경우 html을 받아올 수 없더군요. GET으로 하셔야 합니다.


이상 구글플레이에서 앱 버전 따기 간단히 남겨봅니다.

Docker 정리

Docker 란?

2013년에 등장한 새로운 컨테이너 기반 가상화 도구

계층화된 파일 시스템을 사용해 가상화된 컨테이너의 변경사항을 추적 및 관리

컨테이너 특정 상태를 항상 보존해두고, 필요할 때 언제 어디서나 실행할 수 있도록 도와주는 도구


기존 가상머신

가상 머신은 격리된 환경을 구축해준다는 데서 매력적

실제 배포용으로 쓰기에는 성능 면에서 불리한 도구

운영체제 위에서 또 다른 운영체제를 통째로 돌리는 구조라 리소스를 비효율적으로 활용하게 됨

Docker

어느 플랫폼에서나 재현가능한 어플리케이션 컨테이너를 만들어주는 것을 목표함

LXC(리눅스 컨테이너)라는 개념에서 출발함으로써 특정 리눅스 배포판에서 사용 가능

가상 머신이라고 하기보다는 격리된 환경을 만들어주는 도구

컨테이너와 이미지의 차이와 Dockerfile 만든 법 등을 익히는게 중요함

Docker에서 말하는 장점들

  • 유연성 (Flexible) : 가장 복잡한 애플리케이션조차도 컨테이너화할 수 있습니다.
  • 경량 (Lightweight) : 컨테이너는 호스트 커널을 활용하고 공유합니다.
  • 교환 가능 (Interchangeable) : 업데이트 및 업그레이드를 즉시 배포 할 수 있습니다.
  • 휴대성 (Portable) : 로컬로 구축하고, 클라우드에 배치하고, 어디서나 실행할 수 있습니다.
  • 확장성 (Scalable) : 컨테이너 복제본을 늘리고 자동으로 배포 할 수 있습니다.
  • 스태킹 (Stackable) : 서비스를 세로 및 가로로 쌓을 수 있습니다.


Docker 제품군

Community Edition (CE)

Docker를 시작하고 컨테이너 기반 앱을 실험하려는 개발자 및 소규모 팀에 이상적 (무료)

두 가지 업데이트 채널을 가짐

  • Stable : 분기마다 안정적인 업데이트 제공
  • Edge : 매월 새 기능 제공

Enterprise Edition (EE)

업무용 응용 프로그램 제작, 배송 및 실행하는 엔터프라이즈 개발 및 IT팀을 위한 설계 (유료)

Docker CE와 EE 간 비교 이미지 (출처: Docker Docs, https://docs.docker.com/install/)

지원 플랫폼

Desktop

Cloud

Server

Docker EE

Docker CE

Docker 설치하기

Ubuntu의 경우 Docker CE설치시

OS requirements (18년 3월 22일 기준)

Docker CE는 아래 버전 중 하나의 64비트 버전이 필요함

  • Artful 17.10 (Docker CE 17.11 Edge and higher only)
  • Xenial 16.04 (LTS)
  • Trusty 14.04 (LTS)

Docker CE는 Ubuntu에서 x86_64armhfs390x (IBM Z), and ppc64le (IBM Power) 아키텍쳐를 지원함

(ppc64le and s390x limitations: IBM Z 및 Power 아키텍쳐 패키지는 Ubuntu Xenial 이상만 사용 가능)

Docker CE 설치

  • Docker repositories를 설정 후 설치
  • DEB 패키지 다운로드 후 수동 설치
  • 스크립트를 사용하여 설치

Docker Storage를 설정 후 설치 방법

## 패키지 업데이트
    $ sudo apt-get update
    ...
    ## HTTPS를 통해 repository를 사용할 수 있도록 패키지 설치
    $ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
    ...
    ## Docker 공식 GPG 키 추가
    ## Key fingerprint 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 확인
    $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
    $ sudo apt-key fingerprint 0EBFCD88
    pub   4096R/0EBFCD88 2017-02-22
    Key fingerprint = 9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
    uid                  Docker Release (CE deb) <docker@docker.com>
    sub   4096R/F273FCD8 2017-02-22
    ...
    ## stable repository 사용시 아래 커맨드 추가
    ## arch 값을 amd64, armhf, ppc64el, s390x 등으로 맞춰서 사용
    $ sudo add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
       $(lsb_release -cs) \
       stable"
    ...
    ## 패키지 업데이트
    $ sudo apt-get update
    ...
    ## 옵션 1. Docker CE 최신 버전 설치
    $ sudo apt-get install docker-ce
    ...
    ## 옵션 2. Docker CE 특정 버전 설치
    $ apt-cache madison docker-ce
    docker-ce | 17.12.0~ce-0~ubuntu | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages
    $ sudo apt-get install docker-ce=<VERSION>
    ...
    ## hello-world 이미지를 실행하여 Docker CE가 올바르게 설치되었는지 확인
    $ sudo docker run hello-world

다른 OS에서 Docker 설치

Docker 제거

Docker CE 제거

Docker CE 패키지 제거

$ sudo apt-get purge docker-ce

Image, Container, Volume, 사용자 정의 구성파일 등은 자동으로 제거 안됨.

모든 데이터를 삭제하려면 아래처럼 실행

$ sudo rm -rf /var/lib/docker

Docker Image

이미지는 추상적인 개념이며, 이미지를 기반으로 생성된 컨테이너가 실행됨

이미지는 코드, 런타임, 도서관, 환경 변수 및 구성 파일 등 응용 프로그램을 실행하는 데 필요한 모든 것을 포함하는 실행 가능한 패키지

Docker Image 관련 명령어

docker images

현재 시스템에서 사용 가능한 이미지 리스트

$ docker images
    REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
    hello-world         latest              f2a91732366c        4 months ago        1.85kB

docker pull 

이 명령어를 사용하면 docker.io의 공식 저장소에서 이미지를 다운로드

공식 저장소에 있는 이미지 정보들은 https://hub.docker.com/explore/에서 확인 가능

$ docker pull ubuntu
    Pulling repository ubuntu
    04180f9bd8a6: Download complete
    1e548c932d40: Download complete
    ...

Docker Container

컨테이너는 기본적으로 리눅스에서 실행되며, 다른 컨테이너와 호스트 시스템의 커널을 공유

더 많은 메모리를 사용하지 않고 개별 프로세스를 실행하여 가볍게 만듬

Docker Container 관련 명령어

docker run

특정 이미지로 컨테이너 생성 및 실행

$ docker run -i -t ubuntu:12.04 /bin/bash
    root@8bfd70fe7392:/#

docker ps

실행 중인 컨테이너들을 출력 (전체 볼때는 docker ps -a)

$ docker ps
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS
    0fae5858c9c0        ubuntu:12.04        /bin/bash           8 seconds ago       Up 7 seconds

    PORTS               NAMES
    sleepy_nobel

docker start <Container name/id>

생성되어 있는 컨테이너를 실행

$ docker start a37f1348c4c4
    a37f1348c4c4
    $ docker ps
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS
    a37f1348c4c4        hello-world         "/hello"            25 minutes ago      Up 7 seconds

    PORTS               NAMES
    sleepy_nobel

docker stop <Container name/id>

실행 중인 컨테이너를 정지

$ docker stop a37f1348c4c4
    a37f1348c4c4
    $ docker ps
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS
    a37f1348c4c4        hello-world         "/hello"            25 minutes ago      Exited(0) 25 minutes ago

    PORTS               NAMES
    sleepy_nobel

docker exec -it <Container name/id>

실행 중인 컨테이너에 진입시

쉘이 안떠있는 컨테이너일 경우 : docker exec -it <Container name/id> /bin/bash

$ docker attach e2af613
    root@e2af61348652:/#

docker stop $(docker ps -a -q)

실행 중인 컨테이너들 전체 정지시

$ docker stop $(docker ps -a -q)
    a37f1348c4c4
    e2af6134a1s2
    ...

참고 자료

Docker 공식 문서 : https://docs.docker.com/

nacyout님의 "도커(Docker) 튜토리얼:깐 김에 배포까지" : http://blog.nacyot.com/articles/2014-01-27-easy-deploy-with-docker/

(18년 3월 22일 기준 정리)


+ Recent posts