Launcher3 모듈화-컴포넌트화
Source: Dev.to
목차
1. Background
스마트 콕핏 데스크톱 시스템은 일반적으로 Android AOSP Launcher3 소스 코드를 기반으로 맞춤화됩니다. 그러나 Launcher3의 코드베이스는 지나치게 복잡하여 맞춤화 비용이 높습니다. 이러한 비용을 줄이기 위해 이 프로젝트는 Launcher3를 여러 개의 독립 모듈로 분리하여 개별적으로 맞춤화할 수 있도록 합니다.
활발히 진행 중인 Vibe Coding 트렌드와 같은 상황에서, 이 맞춤화 프로젝트는 불필요하거나 심지어 어리석게 보일 수도 있습니다. 저도 같은 생각을 가지고 있습니다. Launcher3를 Cursor에 가져와 구체적인 요구사항을 제공했음에도 불구하고, Cursor는 개발 난이도를 효과적으로 낮추거나 전체 프로젝트 맥락 및 특정 기능 포인트의 논리적 맥락을 이해하지 못했습니다.
따라서 프로그래밍 도구 TRAE의 도움을 받아 이 프로젝트의 초기 버전을 완성했습니다.
2. Development Environment
Hardware
- Allwinner H618 개발 보드
- 2 GB DDR3 메모리
- 16 GB eMMC 스토리지
Software
- Android 12.0
Development Tools
- Android Studio Otter 2 Feature Drop | 2025.2.2
- Mac Pro Tahoe
- Gradle 8.13
- JVM‑openjdk version “17.0.17” (2025‑10‑21 LTS)
PaginationScrollViewDemo on GitHub
3. Launcher3 검토
3.1 UI 아키텍처


모든 데스크톱 요소가 단일 뷰에 집계되어 있어, 특정 뷰 유형과 관련된 문제를 추적하려면 일반적으로 UI와 로직을 포함한 다른 유형들을 제외해야 합니다.
3.2 데이터 아키텍처

MVP 아키텍처 하에서 Model은 기본 아이템‑요소 데이터를 로드하고 아이콘 캐시와 같은 보조 데이터도 가져옵니다. 모든 데이터는 최종적으로 Launcher 클래스 또는 위젯 관련 뷰에 전달되어 처리됩니다.
4. 구조 분해
초기 아이디어는 Launcher3를 여러 모듈로 나누어 각각 특정 기능(예: 애플리케이션 아이콘, 위젯)을 담당하게 하는 것이었습니다. 하지만 실제로 분할 작업을 진행하면서 Launcher3의 코드 구조가 매우 상호 의존적이라는 것이 드러났습니다. 예를 들어, 데스크톱 아이콘과 위젯 모두 DragLayer 클래스를 사용합니다. 이를 하나의 모듈에 넣는다고 해서 전체 복잡성이 감소하지 않습니다.
그 결과 프로젝트는 다음과 같은 모듈로 나뉘었습니다:
| 모듈 | 책임 |
|---|---|
| item_foundation | 아이템 요소(아이콘, 텍스트 등)의 핵심 데이터 구조와 로직. DragLayer를 포함하며, 아이콘과 위젯을 드래그하기 위한 기본 뷰입니다. |
| PaginationScrollView | 페이지 전환, 위치 예측 및 관련 동작을 처리하는 페이지화 스크롤 뷰 구현. |
| pop_exchange | 위젯 뷰 생성, 하단 위젯 리스트 구현 및 관련 기능. |
| data_persistence | 애플리케이션 아이콘, 위젯 및 기타 영구 데이터를 저장·조회합니다. |
| router | 모듈 간 메서드 호출을 담당합니다. |
| utils | 로깅, 스레드 풀 등 공통 유틸리티 클래스. |
전체 논리 구조

(이미지가 깨진 경우 URL을 확인하거나 올바른 것으로 교체하십시오.)
4.1 기능 변환
4.1.1 데이터 영속성
원래 Launcher3에서는 ContentProvider를 이용해 데이터를 처리했습니다. 여기서도 동일하게 사용하지만, 기존 로직은 폐기하고 재구성했습니다. 데이터 CRUD 로직을 추상화하여 중앙에 배치함으로써 인지적 혼란을 방지합니다.
4.1.2 위젯 트리밍
위젯은 데이터 읽기, 미리보기 뷰 생성, 드래그 제외, 삭제 등 다양한 기능을 포함합니다. 여기서는 핵심 기능을 추출해 데스크톱 아이콘과 함께 처리하지 않도록 했습니다. 일반적인 콕핏 데스크톱 개발에서는 커스텀 위젯이 크게 필요하지 않으므로 제거했습니다.
4.1.3 폴더
지금까지 접한 콕핏 프로젝트에서는 폴더 기능이 거의 사용되지 않아 현재는 구현하지 않았습니다.
4.1.4 바로가기
바로가기는 애플리케이션 아이콘을 직접 사용하고, 실행할 애플리케이션의 Activity를 지정합니다.
4.1.5 애플리케이션 상태
제가 본 프로젝트들에서는 시스템 자체에 앱 스토어가 존재하고, 일반적으로 네이티브 설치 프로그램과 연동해 앱 상태(설치, 업데이트, 삭제 등)를 관리하지 않습니다. 따라서 여기서는 해당 부분을 제외했으며, 추가·업데이트·삭제는 제공된 데이터‑작업 클래스를 통해 직접 수행할 수 있습니다.
4.1.6 애플리케이션 아이콘
원래 Launcher3에서는 IconCache DB를 통해 아이콘을 캐시했습니다. 여기서는 단일 테이블에 저장해 처리합니다.
5. 할 수 있는 일
- 페이지네이션 스크롤 뷰
- 위젯 리스트
이 두 기능은 조종석 데스크톱 사용 사례 시나리오의 약 80 %를 차지합니다. 별도의 애플리케이션 리스트(좌우 페이지네이션 및 페이지 전환을 위한 드래그 구현)로 사용하거나 위젯 리스트(위젯 편집 또는 위젯 추가/삭제를 위한 드래그 구현)로 사용할 수 있습니다.
현재 프로젝트에서는 개발자가 데이터 레이어에 신경 쓰지 않고 데이터 조합 및 UI 구현에만 집중하면 됩니다.
6. 사용 방법
홈 페이지에서 다음과 같이 설정할 수 있습니다:
// Define the number of rows and columns for icons
final int row = 3;
final int column = 5;
// Get screen resolution
DisplayMetrics displayMetrics = new DisplayMetrics();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
} else {
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
}
// Get screen width and height, then divide by rows and columns respectively
int screenWidth = displayMetrics.widthPixels;
int screenHeight = displayMetrics.heightPixels;
LogUtils.d(TAG, "device screen size: " + screenWidth + "x" + screenHeight);
PaginationProfile paginationProfile = new PaginationProfile.Builder()
.setHeightPx(screenHeight) // view height = screen height
.setWidthPx(screenWidth) // view width = screen width
.setCellHeightPx(360) // icon height = 360 px (view height / rows)
.setCellWidthPx(384) // icon width = 384 px (view width / columns)
.setNumColumns(column) // number of columns = 5
.setNumRows(row) // number of rows = 3
.setIconDrawablePaddingPx(2) // padding between icon and text
.setDefaultPageSpacingPx(2) // default page spacing
.setEdgeMarginPx(2) // edge margin
.setIconSizePx(108) // icon size
.setIconTextSizePx(16) // text size
.setCellTextColor(getColor(R.color.black)) // text color
.setWorkspacePadding(new Rect(0, 0, 0, 0)) // workspace padding
.setSaveDataInDb(true) // persist data in DB
.build();
setContentView(R.layout.activity_main);
// Get the paginated scroll view instance
PaginationScrollView paginationScrollView = findViewById(R.id.pagination_scroll_view);
// Initialize the widget‑list popup view (comment out if not needed)
PopUpExchangeView.getInstance().init(this);
// Determine whether data has been saved to DB
SharedPreferences sp = getSharedPreferences("PaginationScrollView", MODE_PRIVATE);
boolean dataSaved = sp.getBoolean("data_saved", false);
if (dataSaved) {
paginationScrollView.bindItems(); // load from DB
} else {
paginationScrollView.bindItems(fruits); // bind sample data
sp.edit().putBoolean("data_saved", true).apply(); // remember that we saved it
}
// Set click listener for the widget‑list popup button
FloatingActionButton popupButton = findViewById(R.id.show_pop_window);
popupButton.setOnClickListener(v -> {
PopUpExchangeView.getInstance().showOrClosePopWindow();
});
전체 화면 모드 진입
전체 화면 모드 종료
7. 개선이 필요한 기능
- 아이템 및 위젯 데이터의 삭제와 업데이트
8. 기타
- 사용자 행동 분석을 위해 아이템 작업 기록을 기반으로 데이터 추적을 추가합니다.
- 행동 데이터를 기반으로 AI 추천 기능을 추가합니다.
- 지오펜싱을 기반으로 AI 스마트 추천 기능을 추가합니다.
- 데이터 준수를 보장합니다.
