2019년 4월초 AndroidX의 많은 부분이 업데이트 되었습니다.
그 중에서 Activity#1.0.0-alpha06
의 변경 중에서 OnBackPressedCallback
의 변경에 대해서 알아보겠습니다.
먼저 소개할 내용은 ComponentActivity
클래스입니다.
ComponentActivity는 Activity#1.0.0-alpha01
부터 추가되었으며 기존에 있던 SupportActivity
의 새로운 이름으로 변경한 형태로 보입니다. 이 클래스는 FragmentActivity
와 AppComatActivity
의 상위 클래스입니다.
이번에 이야기하는 ComponentActivity는 androidx.activity
에 있는 클래스이며, androidx.core.app에 동일한 이름의 ComponentActivity는 아닙니다.
public class ComponentActivity extends androidx.core.app.ComponentActivity
implements LifecycleOwner, ViewModelStoreOwner,SavedStateRegistryOwner {
Activity 및 Fragment의 Back Key 처리시 내부적으로 호출 및 작업되는 클래스가 바로 ComponentActivity라고 보시면 됩니다.
Activity#1.0.0-alpha01 Release Note
1.0.0-alpha01
부터 onBackPressed
호출 시 추가적인 BackPressed 판단 가능한 기능이 추가되었습니다. addOnBackPressedCallback
라는 메소드 입니다. 덕분에 Fragment
에서도 손쉽게 OnBackPressedCallback
인터페이스를 이용해 BackKey 처리가 가능해졌습니다.
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
class MainFragment : Fragment() {
// OnBackPressedCallback 정의
private val callback = object : OnBackPressedCallback {
override fun handleOnBackPressed(): Boolean {
return if (isHandled) {
doAction()
true
} else {
false
}
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// Activity에 BackPressedCallback 등록
activity?.addOnBackPressedCallback(callback)
...
}
override fun onDestroy() {
super.onDestroy()
// Activity에 BackPressedCallback 제거
activity?.removeOnBackPressedCallback(callback)
...
}
}
기본 형태와 다른 부분은 addOnBackPressedCallback
를 호출 시 파라매터로 LifecycleOwner
를 전달하는 것입니다.
class MainFragment : Fragment() {
...
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
activity?.addOnBackPressedCallback(viewLifecycleOwner, callback)
...
}
...
}
좀 더 ComponentActivity
의 내부를 확인해보면 동작을 쉽게 이해할 수 있습니다.
Link : ComponentActivity
public class ComponentActivity extends androidx.core.app.ComponentActivity
implements LifecycleOwner, ViewModelStoreOwner {
...
// OnBackPressedCallback Listener를 보관할 Cache
final CopyOnWriteArrayList<LifecycleAwareOnBackPressedCallback> mOnBackPressedCallbacks = new CopyOnWriteArrayList<>();
...
@Override
public void onBackPressed() {
// 등록한 순차적으로 OnBackPressedCallback#handleOnBackPressed() 를 실행
for (OnBackPressedCallback onBackPressedCallback : mOnBackPressedCallbacks) {
if (onBackPressedCallback.handleOnBackPressed()) {
return;
}
}
...
}
...
// Add, OnBackPressedCallback & 현재 Activity의 LifecycleOwner 사용
public void addOnBackPressedCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
addOnBackPressedCallback(this, onBackPressedCallback);
}
// Add, OnBackPressedCallback & LifecycleOwner 사용
public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
@NonNull OnBackPressedCallback onBackPressedCallback) {
Lifecycle lifecycle = owner.getLifecycle();
// LifecycleOwner의 Lifecycle을 체크
if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
return;
}
// 최근에 추가한 Callback이 우선 순위를 갖도록 가장 앞에 추가
mOnBackPressedCallbacks.add(0, new LifecycleAwareOnBackPressedCallback(
lifecycle, onBackPressedCallback));
}
// Remove, OnBackPressedCallback
public void removeOnBackPressedCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
Iterator<LifecycleAwareOnBackPressedCallback> iterator =
mOnBackPressedCallbacks.iterator();
LifecycleAwareOnBackPressedCallback callbackToRemove = null;
while (iterator.hasNext()) {
LifecycleAwareOnBackPressedCallback callback = iterator.next();
// Callback 비교
if (callback.getOnBackPressedCallback().equals(onBackPressedCallback)) {
callbackToRemove = callback;
break;
}
}
if (callbackToRemove != null) {
// Lifecycle 구독 해지
callbackToRemove.onRemoved();
// Cache 목록에서 Callback 제거
mOnBackPressedCallbacks.remove(callbackToRemove);
}
}
...
}
Activity#1.0.0-alpha06
이전까지 OnBackPressedCallback은 ComponentActivity
내부의 Add/Remove를 이용해서 관리했습니다.
Activity#1.0.0-alpha06 Release Note
1.0.0-alpha01
에 추가되었던 ComponentActivity#addOnBackPressedCallback
의 기능이 deprecated
가 되었습니다.
그 대신 OnBackPressedDispatcher
를 이용하는 형태로 변경되었습니다.
import androidx.activity.OnBackPressedCallback
import androidx.arch.core.util.Cancellable
import androidx.fragment.app.Fragment
class MainFragment : Fragment() {
// 취소 가능한 Interface
private var backPressedCancellable: Cancellable? = null
// OnBackPressedCallback 정의
private val callback = object : OnBackPressedCallback {
override fun handleOnBackPressed(): Boolean {
return if (isHandled) {
doAction()
true
} else {
false
}
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// onBackPressedDispatcher#addCallback 호출 시
// Cancellable 인터페이스를 구현한 OnBackPressedCancellable 가 반환
backPressedCancellable = activity?.onBackPressedDispatcher?.addCallback(callback)
}
override fun onDestroy() {
super.onDestroy()
backPressedCancellable?.cancel()
}
...
}
기본 형태와 다른 부분은 addOnBackPressedCallback
를 호출 시 파라매터로 LifecycleOwner
를 전달하는 것과 Cancellable
Callback을 이용해 BackPressedDispatcher 구독을 취소하는 것입니다.
class MainFragment : Fragment() {
...
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// onBackPressedDispatcher#addCallback 호출 시
// Cancellable 인터페이스를 구현한 LifecycleOnBackPressedCancellable 가 반환
backPressedCancellable = activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, callback)
...
}
...
}
위쪽에서 언급한 OnBackPressedDispatcher의 내부를 한 번 보겠습니다.
public final class OnBackPressedDispatcher {
@SuppressWarnings("WeakerAccess") /* synthetic access */
final ArrayDeque<OnBackPressedCallback> mOnBackPressedCallbacks = new ArrayDeque<>();
OnBackPressedDispatcher() {
}
// 기본 형태로 Callback 추가시 호출 됨
@NonNull
public Cancellable addCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
synchronized (mOnBackPressedCallbacks) {
mOnBackPressedCallbacks.add(onBackPressedCallback);
}
return new OnBackPressedCancellable(onBackPressedCallback);
}
// Lifecycle을 사용하는 형태로 Callback 추가시 호출 됨
@NonNull
public Cancellable addCallback(@NonNull LifecycleOwner owner,
@NonNull OnBackPressedCallback onBackPressedCallback) {
Lifecycle lifecycle = owner.getLifecycle();
if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
return Cancellable.CANCELLED;
}
return new LifecycleOnBackPressedCancellable(lifecycle, onBackPressedCallback);
}
public boolean onBackPressed() {
synchronized (mOnBackPressedCallbacks) {
// descendingIterator 형태의 iterator를 이용해서 handleOnBackPressed 체크
Iterator<OnBackPressedCallback> iterator =
mOnBackPressedCallbacks.descendingIterator();
while (iterator.hasNext()) {
if (iterator.next().handleOnBackPressed()) {
return true;
}
}
return false;
}
}
// 단순 형태의 OnBackPressedCancellable
private class OnBackPressedCancellable implements Cancellable {
private final OnBackPressedCallback mOnBackPressedCallback;
private boolean mCancelled;
OnBackPressedCancellable(OnBackPressedCallback onBackPressedCallback) {
mOnBackPressedCallback = onBackPressedCallback;
}
@Override
public void cancel() {
synchronized (mOnBackPressedCallbacks) {
// OnBackPressedDispatcher의 mOnBackPressedCallbacks에서 제거
mOnBackPressedCallbacks.remove(mOnBackPressedCallback);
mCancelled = true;
}
}
@Override
public boolean isCancelled() {
return mCancelled;
}
}
// Lifecycle을 참조하는 OnBackPressedCancellable
private class LifecycleOnBackPressedCancellable implements GenericLifecycleObserver,
Cancellable {
private final Lifecycle mLifecycle;
private final OnBackPressedCallback mOnBackPressedCallback;
@Nullable
private Cancellable mCurrentCancellable;
private boolean mCancelled = false;
LifecycleOnBackPressedCancellable(@NonNull Lifecycle lifecycle,
@NonNull OnBackPressedCallback onBackPressedCallback) {
mLifecycle = lifecycle;
mOnBackPressedCallback = onBackPressedCallback;
lifecycle.addObserver(this);
}
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_START) {
// ON_START, OnBackPressedDispatcher의 mOnBackPressedCallbacks에 추가
mCurrentCancellable = addCallback(mOnBackPressedCallback);
} else if (event == Lifecycle.Event.ON_STOP) {
// ON_STOP, 현재 Cancellable 객체가 유효한 경우 cancel 처리
if (mCurrentCancellable != null) {
// 최종적으로 OnBackPressedCancellable#cancel 이 호출
mCurrentCancellable.cancel();
}
} else if (event == Lifecycle.Event.ON_DESTROY) {
// ON_DESTROY, cancel 처리
cancel();
}
}
@Override
public void cancel() {
// Lifecycle을 미참조
mLifecycle.removeObserver(this);
if (mCurrentCancellable != null) {
// Cancel 처리
// 최종적으로 OnBackPressedCancellable#cancel 이 호출
mCurrentCancellable.cancel();
mCurrentCancellable = null;
}
mCancelled = true;
}
@Override
public boolean isCancelled() {
return mCancelled;
}
}
}
새롭게 추가된 OnBackPressedDispatcher
클래스는 기존 ComponentActivity에서 관리하던 OnBackPressedCallback 을 가져와서 관리하는 형태로 변경되습니다.
그리고, 기본 형태인 OnBackPressedCancellable와 Lifecycle을 참조하는 LifecycleOnBackPressedCancellable 로 좀 더 명확히 분리되었습니다.
다음으로 1.0.0-alpha06부터 변경된 ComponentActivity의 내부를 먼저 살펴보겠습니다.
public class ComponentActivity extends androidx.core.app.ComponentActivity
implements LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {
...
private final OnBackPressedDispatcher mOnBackPressedDispatcher = new OnBackPressedDispatcher();
// deprecated
private final WeakHashMap<OnBackPressedCallback, Cancellable>
mOnBackPressedCallbackCancellables = new WeakHashMap<>();
...
@Override
public void onBackPressed() {
// BackPressedDispatcher에서
if (mOnBackPressedDispatcher.onBackPressed()) {
return;
}
...
}
@NonNull
public final OnBackPressedDispatcher getOnBackPressedDispatcher() {
return mOnBackPressedDispatcher;
}
@Deprecated
public void addOnBackPressedCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
// OnBackPressedDispatcher에서 생성된 Cancellable을 추가
mOnBackPressedCallbackCancellables.put(onBackPressedCallback,
getOnBackPressedDispatcher()
.addCallback(this, onBackPressedCallback));
}
@Deprecated
public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
@NonNull OnBackPressedCallback onBackPressedCallback) {
// OnBackPressedDispatcher에서 생성된 Cancellable을 추가
mOnBackPressedCallbackCancellables.put(onBackPressedCallback,
getOnBackPressedDispatcher()
.addCallback(owner, onBackPressedCallback));
}
@Deprecated
public void removeOnBackPressedCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
// onBackPressedCallback을 Cache Map에서 가져와서 cancel 처리 수행
Cancellable cancellable = mOnBackPressedCallbackCancellables.remove(onBackPressedCallback);
if (cancellable != null) {
cancellable.cancel();
}
}
...
}
기존 버전과 호환을 위해 addOnBackPressedCallback
/removeOnBackPressedCallback
는 그대로 유지하고 있습니다.
가장 큰 차이점은 OnBackPressedDispatcher
가 OnBackPressedCallback
의 동작을 관리하는 변화입니다.
추가적인 차이점은 아래와 같습니다.
mOnBackPressedCallbackCancellables
OnBackPressedCallback
가 각자 가지는 동작을 분리좀 더 자세한 차이점은 아래의 Review 링크를 참고해주세요/
Separate onBackPressed handling from ComponentActivity
이번 글을 통해서 Back Key 선택 시의 동작과 새로운 변경 점을 알아보았습니다.
1.0.0-alpha06 과 1.0.0-alpha01의 차이는 더 명확하게 구현을 분리한 형태가 된듯 합니다.
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024