조각 탐색 패턴에 대한 Android 개발자 가이드
게시 됨: 2022-03-11수년에 걸쳐 Android에서 다양한 탐색 패턴 구현을 보았습니다. 일부 앱은 활동만 사용하고 다른 활동은 프래그먼트 및/또는 사용자 정의 보기와 혼합되었습니다.
내가 가장 좋아하는 탐색 패턴 구현 중 하나는 "One-Activity-Multiple-Fragments" 철학 또는 단순히 Fragment Navigation Pattern을 기반으로 합니다. 여기서 애플리케이션의 모든 화면은 전체 화면 Fragment이고 이러한 프래그먼트의 전부 또는 대부분은 하나의 활동.
이 접근 방식은 탐색 구현 방법을 단순화할 뿐만 아니라 훨씬 더 나은 성능을 제공하므로 더 나은 사용자 경험을 제공합니다.
이 기사에서는 Android의 몇 가지 일반적인 탐색 패턴 구현을 살펴본 다음 Fragment 기반 탐색 패턴을 도입하여 다른 패턴과 비교하고 대조합니다. 이 패턴을 구현하는 데모 애플리케이션이 GitHub에 업로드되었습니다.
활동의 세계
활동만 사용하는 일반적인 Android 애플리케이션은 런처에 의해 루트 활동이 시작되는 트리와 같은 구조(더 정확하게는 방향 그래프)로 구성됩니다. 애플리케이션을 탐색할 때 OS에서 유지 관리하는 활동 백 스택이 있습니다.
간단한 예가 아래 다이어그램에 나와 있습니다.
활동 A1은 애플리케이션의 진입점이며(예: 시작 화면 또는 기본 메뉴를 나타냄) 사용자는 여기에서 A2 또는 A3으로 이동할 수 있습니다. 활동 간에 통신해야 하는 경우 startActivityForResult() 를 사용하거나 활동 간에 전역적으로 액세스 가능한 비즈니스 논리 개체를 공유할 수 있습니다.
새 활동을 추가해야 하는 경우 다음 단계를 수행해야 합니다.
- 새 활동 정의
- AndroidManifest.xml 에 등록하십시오.
- 다른 액티비티의 startActivity() 로 엽니다.
물론 이 탐색 다이어그램은 상당히 단순한 접근 방식입니다. 백 스택을 조작해야 하거나 동일한 활동을 여러 번 재사용해야 하는 경우, 예를 들어 일부 튜토리얼 화면을 통해 사용자를 탐색하고 싶지만 각 화면이 실제로 동일한 활동을 사용하는 경우와 같이 매우 복잡해질 수 있습니다. 베이스.
다행스럽게도 작업이라는 도구와 적절한 백 스택 탐색을 위한 몇 가지 지침이 있습니다.
그런 다음 API 레벨 11에서 조각이 나왔습니다.
파편의 세계
Android는 주로 태블릿과 같은 대형 화면에서 보다 동적이고 유연한 UI 디자인을 지원하기 위해 Android 3.0(API 레벨 11)에 프래그먼트를 도입했습니다. 태블릿의 화면은 핸드셋의 화면보다 훨씬 크기 때문에 UI 구성 요소를 결합하고 교환할 여지가 더 많습니다. 프래그먼트를 사용하면 보기 계층 구조에 대한 복잡한 변경 사항을 관리할 필요 없이 이러한 디자인을 할 수 있습니다. 활동의 레이아웃을 조각으로 나누면 런타임에 활동의 모양을 수정할 수 있고 활동에서 관리하는 백 스택에서 이러한 변경 사항을 보존할 수 있습니다. – Google의 Fragments API 가이드에서 인용했습니다.
이 새로운 장난감을 통해 개발자는 다중 창 UI를 구축하고 다른 활동에서 구성 요소를 재사용할 수 있습니다. 일부 개발자는 이것을 좋아하지만 다른 개발자는 그렇지 않습니다. 프래그먼트를 사용할지 여부는 대중적인 논쟁이지만, 프래그먼트가 추가 복잡성을 가져오고 개발자가 적절하게 사용하기 위해 실제로 이해해야 한다는 데 모두가 동의할 것이라고 생각합니다.
Android의 전체 화면 조각 악몽
나는 단편이 단지 화면의 일부를 나타내는 것이 아니라 실제로 전체 화면이 활동에 포함된 단편인 예를 점점 더 많이 보기 시작했습니다. 모든 활동에 정확히 하나의 전체 화면 조각이 있고 그 이상은 없으며 이러한 활동이 존재하는 유일한 이유는 이러한 조각을 호스팅하는 디자인을 본 적이 있습니다. 명백한 설계 결함 옆에 이 접근 방식에는 또 다른 문제가 있습니다. 아래에서 다이어그램을 살펴보십시오.
A1은 어떻게 F1과 통신할 수 있습니까? Well A1은 F1을 만든 이후로 F1을 완전히 제어합니다. A1은 예를 들어 F1을 생성할 때 번들을 전달하거나 해당 공용 메서드를 호출할 수 있습니다. F1은 A1과 어떻게 통신할 수 있습니까? 이것은 더 복잡하지만 A1이 F1을 구독하고 F1이 A1에 알리는 콜백/관찰자 패턴으로 해결할 수 있습니다.
그러나 A1과 A2는 어떻게 서로 통신할 수 있습니까? 이것은 예를 들어 startActivityForResult() 를 통해 이미 다뤘습니다.
이제 진짜 질문이 나옵니다. F1과 F2가 어떻게 서로 통신할 수 있습니까? 이 경우에도 전 세계적으로 사용 가능한 비즈니스 논리 구성 요소를 가질 수 있으므로 데이터를 전달하는 데 사용할 수 있습니다. 그러나 이것이 항상 우아한 디자인으로 이어지는 것은 아닙니다. F2가 좀 더 직접적인 방법으로 F1에 일부 데이터를 전달해야 하는 경우 어떻게 해야 합니까? 음, 콜백 패턴을 사용하여 F2는 A2에 알릴 수 있고, A2는 결과로 완료되고 이 결과는 F1에 알리는 A1에 의해 캡처됩니다.
이 접근 방식은 많은 상용구 코드가 필요하며 빠르게 버그, 고통 및 분노의 원인이 됩니다.
모든 활동을 제거하고 나머지 조각을 유지하는 활동 중 하나만 유지할 수 있다면 어떨까요?
프래그먼트 탐색 패턴
수년에 걸쳐 나는 대부분의 애플리케이션에서 "One-Activity-Multiple-Fragments" 패턴을 사용하기 시작했으며 여전히 사용하고 있습니다. 예를 들어 여기와 여기에서 이 접근 방식에 대해 많은 논의가 있습니다. 그러나 내가 놓친 것은 내가 직접 보고 테스트할 수 있는 구체적인 예입니다.
다음 다이어그램을 살펴보겠습니다.
이제 우리는 하나의 컨테이너 활동만 가지고 있으며 구조와 같은 트리를 다시 가진 여러 조각이 있습니다. 그들 사이의 탐색은 FragmentManager 에 의해 처리되며 백 스택이 있습니다.
이제 startActivityForResult() 가 없지만 콜백/관찰자 패턴을 구현할 수 있습니다. 이 접근 방식의 장단점을 살펴보겠습니다.
장점:
1. 더 깨끗하고 유지 관리하기 쉬운 AndroidManifest.xml
이제 Activity가 하나만 있으므로 새 화면을 추가할 때마다 매니페스트를 업데이트할 필요가 더 이상 없습니다. 액티비티와 달리 프래그먼트를 선언할 필요가 없습니다.
이것은 사소한 것처럼 보일 수 있지만 50개 이상의 활동이 있는 더 큰 애플리케이션의 경우 AndroidManifest.xml 파일의 가독성을 크게 향상시킬 수 있습니다.
여러 화면이 있는 예제 애플리케이션의 매니페스트 파일을 보십시오. 매니페스트 파일은 여전히 매우 간단합니다.
<?xml version="1.0" encoding="utf-8"?> package="com.exarlabs.android.fragmentnavigationdemo.ui" > <application android:name= ".FragmentNavigationDemoApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.exarlabs.android.fragmentnavigationdemo.ui.MainActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:theme="@style/AppTheme.NoActionBar" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
2. 중앙 집중식 탐색 관리
내 코드 예제에서 내 경우에는 모든 조각에 주입되는 NavigationManager 를 사용하는 것을 볼 수 있습니다. 이 관리자는 로깅, 백 스택 관리 등을 위한 중앙 집중식 장소로 사용할 수 있으므로 탐색 동작이 나머지 비즈니스 로직과 분리되고 다른 화면 구현에서 확산되지 않습니다.

사용자가 사람 목록에서 일부 항목을 선택할 수 있는 화면을 시작하려는 상황을 상상해 봅시다. 또한 나이, 직업 및 성별과 같은 필터링 인수를 전달하려고 합니다.
활동의 경우 다음과 같이 작성합니다.
Intent intent = new Intent(); intent.putExtra("age", 40); intent.putExtra("occupation", "developer"); intent.putExtra("gender", "female"); startActivityForResult(intent, 100);
그런 다음 아래 어딘가에 onActivityResult 를 정의하고 결과를 처리해야 합니다.
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); }
이 접근 방식에 대한 제 개인적인 문제는 이러한 인수가 "추가 항목"이고 필수 항목이 아니므로 수신 활동이 추가 항목이 없을 때 모든 다른 경우를 처리하는지 확인해야 한다는 것입니다. 나중에 일부 리팩토링이 수행되고 예를 들어 추가 "연령"이 더 이상 필요하지 않으면 이 활동을 시작하는 코드의 모든 곳을 검색하고 모든 추가 항목이 올바른지 확인해야 합니다.
게다가 결과(사람 목록)가 _List 형식으로 도착하면 더 좋지 않을까요?
프래그먼트 기반 탐색의 경우 모든 것이 더 간단합니다. 필요한 인수와 콜백 구현을 사용하여 startPersonSelectorFragment() 라는 메서드를 NavigationManager 에 작성하기만 하면 됩니다.
mNavigationManager.startPersonSelectorFragment(40, "developer", "female", new PersonSelectorFragment.OnPersonSelectedListener() { @Override public boolean onPersonsSelected(List<Person> selection) { [do something] return false; } });
또는 RetroLambda를 사용하여
mNavigationManager.startPersonSelectorFragment(40, "developer", "female", selection -> [do something]);
3. 화면 간 더 나은 통신 수단
활동 간에는 기본 또는 직렬화된 데이터를 보유할 수 있는 번들만 공유할 수 있습니다. 이제 프래그먼트를 사용하여 F1이 임의의 객체를 전달하는 F2를 수신할 수 있는 콜백 패턴을 구현할 수 있습니다. _List를 반환하는 이전 예제의 콜백 구현을 살펴보십시오.
4. 조각을 만드는 것이 활동을 만드는 것보다 비용이 적게 듭니다.
이것은 예를 들어 5개의 메뉴 항목이 있고 각 페이지에 서랍이 다시 표시되어야 하는 서랍을 사용할 때 분명해집니다.
순수 활동 탐색의 경우 각 페이지는 서랍을 팽창시키고 초기화해야 하는데, 이는 물론 비용이 많이 듭니다.
아래 다이어그램에서 서랍에서 직접 액세스할 수 있는 전체 화면 조각인 여러 루트 조각(FR*)을 볼 수 있으며 서랍은 이러한 조각이 표시될 때만 액세스할 수 있습니다. 다이어그램의 점선 오른쪽에 있는 모든 것은 임의 탐색 체계의 예입니다.
컨테이너 활동에는 서랍이 있으므로 서랍 인스턴스가 하나만 있으므로 서랍이 표시되어야 하는 모든 탐색 단계에서 서랍을 다시 확장하고 초기화할 필요가 없습니다. 이 모든 것이 어떻게 작동하는지 아직도 확신하지 못하셨습니까? 서랍 사용법을 보여주는 샘플 응용 프로그램을 살펴보십시오.
단점
가장 큰 두려움은 프로젝트에서 프래그먼트 기반 탐색 패턴을 사용하는 경우 경로 어딘가에서 프래그먼트, 타사 라이브러리 및 다양한 OS 버전의 추가 복잡성 주위에서 해결하기 어려운 예상치 못한 문제에 직면하게 될 것이라는 점이었습니다. 지금까지 한 모든 작업을 리팩토링해야 한다면 어떻게 될까요?
실제로 ShinobiControls, ViewPagers 및 FragmentStatePagerAdapters와 같은 조각을 사용하는 타사 라이브러리인 중첩 조각 문제를 해결해야 했습니다.
이러한 문제를 해결할 수 있는 조각에 대한 충분한 경험을 얻는 것은 다소 긴 과정이라는 것을 인정해야 합니다. 그러나 모든 경우에 문제는 철학이 나쁘다는 것이 아니라 파편을 충분히 이해하지 못했다는 것입니다. 아마도 나보다 조각을 더 잘 이해했다면 이러한 문제가 발생하지도 않았을 것입니다.
지금 언급할 수 있는 유일한 단점은 단편 기반 탐색을 사용하는 복잡한 응용 프로그램의 모든 복잡한 시나리오를 보여주는 성숙한 라이브러리가 없기 때문에 해결하기 쉽지 않은 문제가 여전히 발생할 수 있다는 것입니다.
결론
이 기사에서는 Android 애플리케이션에서 탐색을 구현하는 다른 방법을 보았습니다. 활동을 사용하는 기존의 탐색 철학과 비교했으며 기존 접근 방식보다 사용하는 것이 유리한 몇 가지 좋은 이유를 보았습니다.
아직 구현하지 않은 경우 GitHub에 업로드된 데모 애플리케이션을 확인하세요. 사용법을 더 잘 보여줄 수 있는 더 좋은 예제로 자유롭게 포크하거나 기여하십시오.