TV 앱
4d73951 기준입니다.
free-my-tv는 즐겨 쓰는 스트리밍 앱, 작품 추천, 날씨와 시세를 배치하는 TV용 홈 화면입니다. 이 저장소의 학습 가치는 화면을 빠르게 그리는 코드와, 그 화면이 실패해도 TV를 계속 쓸 수 있게 하는 코드가 함께 있다는 데 있습니다.
홈 화면에서 Netflix 같은 앱을 고르면 해당 앱을 실행합니다. 영화 자체를 전송하거나 재생하는 서비스는 아닙니다. 작품 정보는 TMDB에서 받고, Android에서는 선택적으로 Watchmode의 링크를 이용해 작품 페이지를 엽니다. 시청에는 각 스트리밍 앱과 해당 서비스의 이용 조건이 그대로 적용됩니다.
HOME으로 등록되는 화면과, 앱 목록에 나타나는 실행 아이콘은 서로 다른 진입점일 수 있습니다.Netflix와 YouTube는 각각 방송국에 가깝습니다. free-my-tv는 그곳으로 가는 버튼을 보기 좋게 정리하는 보관함입니다. 보관함이 고장 났다고 TV의 원래 조작 방법까지 사라져서는 안 됩니다.
그래서 이 프로젝트에는 보기 좋은 선반만큼이나 기존 화면을 기억하고 복원하는 장치가 중요합니다. UI 개발과 시스템 운영이 한 제품 안에서 만나는 지점입니다.
| 대상 | 실행 형태 | 기존 홈 화면과의 관계 |
|---|---|---|
| Google TV / Android TV | Java 기반 Android 앱, FreeMyTV.apk | Free My TV 모드에서 HOME 역할을 바꿉니다. 일부 앱은 삭제 대신 비활성화합니다. |
| LG webOS | web/를 묶은 .ipk 웹 앱 | 기본 홈은 유지합니다. README는 숫자 키 길게 누르기로 빠른 실행을 안내합니다. |
| Samsung Tizen | 같은 웹 화면을 묶은 서명된 .wgt | 기본 홈은 유지합니다. README는 마지막 앱 자동 실행 설정을 안내합니다. |
Android 앱은 app/AndroidManifest.xml에서 HomeActivity를 HOME으로,
SettingsActivity를 일반 앱 목록의 진입점으로 선언합니다.
LG·Samsung의 앱 실행 기능은 web/tv.js가 각 운영체제의 API에 연결합니다.
HomeView는 현재 선택 위치, 작품 목록, 날씨를 화면으로 바꿉니다.HomeMode와 Watchdog가 원래 화면으로 가는 경로를 관리합니다.“날씨 기능이 있다”에서 멈추지 말고, 요청 → 캐시 → 알림 → 다시 그리기의 순서를 설명해 보세요. 이 흐름을 이해하면 뉴스 위젯이나 환율 카드 같은 다른 기능에도 같은 사고방식을 적용할 수 있습니다.
README는 광고성 추천 행, 광고 배너, 자동 재생 홍보가 없는 홈 화면을 약속합니다. 앱 선반을 직접 편집하고, 추천은 설치된 스트리밍 앱에 맞추며, 낮과 밤에 따라 색을 바꿉니다. 기능 설명을 읽기 전에 “TV 첫 화면을 내 취향으로 정리한다”는 목적을 이해할 수 있습니다.
다만 “광고 없음”의 범위는 이 홈 화면입니다. 실행한 외부 앱의 광고나 TV 펌웨어 전체의 통신을 없애는 기능으로
확대 해석하면 안 됩니다. 소스의 HomeMode.DEBLOAT도 알려진 패키지 목록과 보호 조건을 이용합니다.
| 관찰 | 2026-10-23 GitHub 기준 | 학습자가 살펴볼 점 |
|---|---|---|
| 관심 | 스타 3,629 / 포크 519 | 관심과 변형 수요를 보여 줍니다. 안정성이나 실제 설치 대수의 증명은 아닙니다. |
| 개발 시점 | 2026-10-19 생성 / 2026-10-23 최근 push | 아주 초기 프로젝트입니다. 오래 축적된 운영 경험과 구분해야 합니다. |
| 배포 | v1.1.0 / 2026-10-22 | 릴리스와 분석 대상 커밋의 관계를 확인하며 읽어야 합니다. |
| 선정 | TrendShift Weekly #24 | 분석일 2026-10-23의 선정 근거입니다. |
저장소 소개와 README는 Claude Code로 만들었다고 밝힙니다.
AGENTS.md와 CLAUDE.md에는 설치 안내, 기기별 예외, 복구 원칙이 적혀 있습니다.
AI 도구로 만든 결과물을 실제 하드웨어 제약과 연결한 사례라는 점이 관심을 끌 만합니다.
다만 이것은 제품과 소스에서 읽어 낸 해석이며, 스타 증가 원인을 검증한 조사 결과는 아닙니다.
대체 런처 자체는 새로운 발명이 아닙니다. Projectivy도 광고 없는 화면, 카테고리·채널 구성, 배경화면과 플러그인, 기본 런처 대체 기능을 소개합니다. 비교 기준은 Projectivy 공식 README입니다.
| 비교 축 | free-my-tv | Projectivy 공식 소개 |
|---|---|---|
| 공통 목적 | 광고 없는 홈 화면과 앱 실행 | 광고 없는 Android TV 런처와 개인화 |
| 눈에 띄는 구성 | 앱 선반, 시간대별 작품 선택, 날씨·시세, 일몰 테마 | 카테고리·채널, 아이콘·바로가기, 배경화면 플러그인 |
| 공부하기 좋은 점 | 이 저장소에서 Java UI·ADB·복원 경로를 함께 읽을 수 있음 | 공식 저장소는 기기 설정과 이슈 추적 용도도 겸한다고 설명함 |
| 플랫폼 접근 | Android 네이티브 + LG·Samsung 공유 웹 구현 | 공식 소개의 중심은 Android TV와 프로젝터·셋톱박스 |
| 선택의 기준 | 구현을 직접 바꾸며 시스템 설계까지 배우고 싶을 때 | 풍부한 화면 구성 옵션을 먼저 살펴보고 싶을 때 |
free-my-tv의 차별점은 광고 없는 화면이라는 약속에 더해, 그 화면과 전환·복구 과정을 소스 수준에서 함께 공부할 수 있다는 점입니다. 이 비교표는 각 앱의 소개와 구현을 바탕으로 하며, 같은 TV에서 성능이나 호환성의 우열을 검증한 결과는 아닙니다.
README는 Android 14를 쓰는 98인치 TCL TV에서 홈 화면 메모리 96 MB 대 242 MB, 약 60% 감소와 60 fps를 제시합니다. 이는 README의 특정 기기 측정 주장입니다. 모든 TV의 실측 결과나 최소 메모리 요구량으로 사용할 수 없습니다.
소스에는 그 주장을 뒷받침할 설계 흔적이 있습니다. HomeView.doFrame()은 바뀐 레이어만 갱신하고,
HomeMode.measureMemory()와 measureHomeTime()은 기기에서 측정하는 경로를 갖습니다.
최적화 코드가 있다는 사실과 특정 숫자를 재현했다는 사실은 구분해서 읽으세요.
이 저장소에는 package.json, build.gradle, Cargo.toml,
pyproject.toml, go.mod, Dockerfile이 없습니다.
웹 서비스의 전형적인 프론트엔드·백엔드 구성을 억지로 대입하기보다, 화면·데이터 처리·패키징으로 나눠야 정확합니다.
| 층 | 실제 기술·버전 | 확인할 파일 |
|---|---|---|
| Android 언어 | Java 8 문법·바이트코드 수준, 실행할 컴파일러는 JDK 17 이상 | build.sh의 -source 8 -target 8, CI의 Java 17 |
| 화면 | Android ViewGroup, Canvas, Paint, Choreographer | HomeView.java, DreamView.java |
| 그래픽 호환 | Android 10 이상 RenderNode, 이전 버전 Picture; AGSL은 Android 13 이상 경로 | Recording.java, Scene.java, Shaders.java |
| LG·Samsung 화면 | ES5 JavaScript, HTML, CSS. React·Vue·번들러 없음 | web/home.js, web/home.css |
| 표시 언어 | 한국어 포함 JSON 카탈로그 25개, Android ICU 복수형 처리 | app/assets/i18n/, Ui.java, web/strings.js |
| 폰트·기호 | Inter 가변 폰트, Material Symbols 자산. 패키지 버전 번호는 명시되지 않음 | app/assets/fonts/, app/assets/symbols/ |
위 표의 Java 파일은 모두 app/src/io/freemytv/ 아래입니다.
JDK 버전, Java 언어 수준, Android 최소 실행 버전은 서로 다른 숫자이며 각각 다른 문제를 결정합니다.
minSdk 26은 설치 가능한 최소 OS,
targetSdk 33은 앱이 대상으로 삼는 Android 동작 규칙입니다.
빌드에 사용하는 android-34는 컴파일할 때 참고하는 기능 목록입니다.자체 운영 API 서버나 SQL 데이터베이스는 없습니다. HomeData와 Recs가 TV 안에서 외부 API에
직접 요청하고, SharedPreferences와 파일에 결과를 보관합니다.
Android JSON 파서는 SDK의 org.json, HTTP 클라이언트는 HttpURLConnection입니다.
| 데이터 | 출처와 조건 | 실제 동작 |
|---|---|---|
| 날씨·일출·일몰 | Open-Meteo. 좌표 사용, API 키 없음 | HomeData.refreshIfStale(), 30분 기준. 도시 검색은 별도 지오코딩 API |
| 암호화폐 시세 | Binance 공개 API. 시세 표시가 켜져 있고 선택한 코인이 있을 때 | 5분 기준. 지원하지 않는 통화의 HTTP 400에는 달러 기준으로 재시도 |
| 작품 추천 | TMDB API v3. 사용자 TMDB 키 필요 | Recs.fetchList(), 국가·설치 앱·서비스 목록을 함께 사용 |
| 작품 바로가기 | Watchmode v1. 선택적 키 필요 | Recs.findLink(). 링크를 못 찾거나 앱이 처리하지 못하면 앱 실행으로 이어짐 |
| 설정 | 컴파일 시 기본값 + TV에서 바꾼 값 | Config 기본값보다 Prefs 저장값 우선 |
“계정·분석 추적 없음”은 README의 개인정보 설명입니다. 네트워크를 전혀 사용하지 않는다는 뜻은 아닙니다. 선택한 기능에 따라 위치 좌표, 작품 정보 요청, 시세 요청이 각 외부 서비스에 전달됩니다.
build.sh는 SDK 폴더에서 설치된 가장 높은 build-tools와 android.jar를 고릅니다.
반면 CI는 별도 SDK 경로에 지정 버전만 설치합니다. 로컬에서 최신 API를 잘못 호출하면 통과하고 CI에서는 실패할 수 있습니다.
| 근거 | 버전·설정값 | 배울 점 |
|---|---|---|
build.sh | --min-sdk-version 26 --target-sdk-version 33, d8 --min-api 26 | 리소스 패키징과 DEX 변환의 최소 버전을 함께 맞춥니다. |
.github/workflows/check.yml | Temurin JDK 17, build-tools 34.0.0, platform android-34 | 검증 환경의 변동을 줄입니다. |
| 같은 CI 파일 | actions/checkout@v7, actions/setup-java@v5 | 앱 의존성과 CI 액션 버전을 구별합니다. |
tools/check.sh | acorn@8.14.0 --ecma5 | 런타임 라이브러리가 아니라 오래된 TV용 구문 검사 도구입니다. |
| 웹 패키지 | Python 3, 정확한 부 버전 고정 없음. Tizen 최소 3.0 | 표준 라이브러리로 .ipk·.wgt를 구성합니다. |
pages.yml | configure-pages@v6, upload-pages-artifact@v5, deploy-pages@v5 | docs/ 정적 사이트의 배포이며 TV 앱 배포와는 별도입니다. |
Gradle은 여러 공정을 묶어 주는 생산 관리 시스템에 가깝습니다. 여기서는 build.sh가 각 공정의 기계를 직접 부릅니다.
공정이 눈에 잘 보이는 대신, 도구 탐색·Windows 경로·서명 키 관리도 스크립트가 직접 책임집니다.
Android 런타임에는 별도 서드파티 라이브러리를 연결하지 않습니다. 그렇다고 프로젝트 전체에 외부 의존성이 없는 것은 아닙니다. Android SDK, 외부 API, CI 도구, Samsung 서명 도구, 폰트와 아이콘 자산에는 각각 별도의 수명과 이용 조건이 있습니다.
이 도식에서 프로세스 경계는 중요합니다. Prefs의 static 필드는 프로세스마다 따로 존재합니다.
HomeData.get() 같은 싱글턴도 기기 전체에 하나라는 뜻이 아니라, 해당 프로세스 안에서 하나입니다.
app/src/io/freemytv/HomeView.java는 ViewGroup을 상속하며,
내부 Layer extends View가 하늘·작품·버튼·선반·서랍 등을 나눠 그립니다.
“Canvas 하나로 모든 화면을 그린다”는 요약보다 여러 사용자 정의 레이어를 직접 그리는 구조가 정확합니다.
onKey()는 방향키를 move()로 보냅니다. 상태가 바뀌면 스프링의 목표값을 조정하고,
doFrame()이 위치·속도를 진행시킵니다. Layer.onDraw()는 레이어 종류에 맞는 그리기 함수로 연결합니다.
// HomeView.java — doFrame()의 일부, 원문 발췌
float dt = lastNanos == 0 ? 1 / 60f
: Math.min(0.05f, (frameTimeNanos - lastNanos) / 1e9f);
lastNanos = frameTimeNanos;
if (tilesMoving || tilesDirty) tiles.invalidate();
// busy는 움직임과 열린 패널 등 현재 상태를 합친 값
if (busy) Choreographer.getInstance().postFrameCallback(this);
else Choreographer.getInstance().postFrameCallbackDelayed(this, 1000);
활동 중에는 프레임 콜백을 이어 받고, 유휴 상태에는 1초 뒤에 다시 확인합니다. “가만히 있으면 아무 코드도 실행하지 않는다”는 뜻은 아닙니다. 시계 같은 변화도 확인해야 하기 때문입니다. Choreographer 공식 문서에서 프레임 타이밍과 콜백의 관계를 더 살펴볼 수 있습니다.
Spring.step()은 한 번의 시간 간격을 최대 약 4 ms 단위로 나눠 적분합니다.
프레임이 늦어졌을 때 물리 계산이 크게 튀는 일을 줄이려는 장치입니다.
HomeView의 서랍 스프링은 new Spring(0.46f, 0.92f, 0)입니다.
// Spring.java — 핵심 계산, 원문 발췌
float omega = (float) (2 * Math.PI / response);
float stiffness = omega * omega, friction = 2 * damping * omega;
int steps = Math.max(1, (int) Math.ceil(dt / 0.004f));
float h = dt / steps;
for (int i = 0; i < steps; i++) {
float accel = -stiffness * (value - target) - friction * velocity;
velocity += accel * h;
value += velocity * h;
}
tilesDirty가 그 예입니다. 매번 모든 것을 계산하지 않고, 이전 결과를 재사용할 수 있는지 판단합니다.updateMotion()은 서랍을 여는 동안 이미 그린 레이어의 위치와 투명도를 바꿉니다.
비싼 유리 효과를 매 프레임 다시 만드는 비용을 줄입니다.
Recording.create()는 API 29 이상이면 RenderNode, 이전이면 Picture를 선택합니다.
HomeData 생성자는 저장된 날씨·시세를 먼저 복원합니다.
refreshIfStale()은 유효 기간이 지난 데이터만 단일 작업 스레드에 맡깁니다.
notifyListeners()는 메인 스레드로 돌아와 화면의 onData()를 호출합니다.
| 방어 장치 | 실제 소스 | 막으려는 문제 |
|---|---|---|
| 중복 요청 차단 | fetchingPrices, fetchingWeather | 동일한 새로고침이 연달아 요청되는 상황 |
| 실패 뒤 대기 | failedRecently(), 2분 | 네트워크 장애 중 API를 계속 두드리는 상황 |
| 시간 역행 처리 | now < pricesAt, now < weatherAt | TV 시계가 뒤로 바뀌어 캐시가 계속 새것으로 보이는 상황 |
| 도시별 캐시 식별 | weatherPlace()와 저장된 좌표 비교 | 도시를 바꾼 뒤 이전 도시의 날씨를 그대로 쓰는 상황 |
| 요청 시간 제한 | HomeData.get(String url)의 연결·읽기 각각 6,000 ms | 응답하지 않는 서버를 무한히 기다리는 상황 |
Recs도 Listener와 별도 실행기를 사용합니다.
README의 “매시간 새 추천 세 개”는 매시간 전체 목록을 다운로드한다는 뜻이 아닙니다.
now(String kind)는 시간 값을 이용해 세 작품을 고르고, refreshIfStale()은
6시간 경과, 오전·오후·밤의 전환, 언어·키·설치 앱 변화 등으로 목록 재수집을 판단합니다.
fetchList()는 지역의 제공 사업자와 설치된 앱을 맞추고, 주간 인기 작품과 최근 공개 작품을 섞습니다.
설치된 앱을 안다고 유료 구독 여부까지 아는 것은 아닙니다.
기본 탑재 앱을 구독하지 않는 사용자는 hide_services로 추천 대상을 줄일 수 있습니다.
HomeMode.request()는 ModeJob.schedule()로 작업을 넘깁니다.
ModeJob.onStartJob()은 작업 스레드에서 HomeMode.applyNow() 등을 실행하고,
기본 프로세스에서 전환을 진행합니다. 화면 프로세스가 죽어도 별도의 복구 경로를 둘 수 있습니다.
applyFree()가 원래 홈·화면보호기를 조사해 home-state.json에 기록합니다.wifiOwners()로 Wi-Fi를 저장한 앱을 확인합니다. 끄려는 앱과 겹치거나 확인이 불가능하면 조건에 따라 중단합니다.pm disable-user --user 0 성공을 확인할 때마다 disabled 목록을 저장합니다.applyOriginal()은 이 기록과 keptOff를 이용해 원복을 시도합니다.save()는 임시 파일에 쓰고 FileDescriptor.sync() 후 이름을 바꿉니다.
잘리다 만 JSON을 읽을 가능성을 줄이는 방식입니다.
다만 여러 시스템 명령 전체가 하나의 DB 트랜잭션처럼 원자적으로 처리되는 구조는 아닙니다.
되돌리기 버튼은 반대 명령 하나로 완성되지 않습니다. 변경 전 상태, 실제 성공한 변경, 복원 순서를 함께 기억해야 합니다.
Watchdog는 5분 안에 충돌이 3번 기록되면 requestRevertHome()을 요청합니다.
revertHomeNow()은 홈과 화면보호기 중심으로 복구하고, 나머지 비활성 앱은 목록에 남깁니다.
전체 Original 전환과 같은 동작이 아닙니다.
둘 다 TV 내부 ADB 연결이 가능해야 합니다. 또한 applyFree()의 예외 처리는 오류를 알리며 끝나므로,
모든 중간 실패가 즉시 자동 원복된다고 읽으면 안 됩니다.
targets()는 Google TV 초기 설정 앱을 원래 런처보다 앞에 둡니다.
복원 시에는 원래 런처를 먼저 살립니다. 코드 주석은 설정 앱의 복구 화면이 HOME을 가져가는 기기 문제를 설명합니다.
Build.DEVICE가 R3_GTV이면 전환을 거부하는 방어 조건도 있습니다.
web/tv.js는 TV.apps(), launch(), inputs(),
settings(), back() 같은 공통 인터페이스를 제공합니다.
LG에서는 Luna 서비스, Samsung에서는 tizen.application 등을 호출합니다.
web/home.js는 운영체제별 세부 API를 매번 알 필요가 줄어듭니다.
// web/tv.js — launch() 내부 분기, 원문 발췌
if (samsung) {
if (id.indexOf('source:') === 0) samsungSource(id, done);
else samsungLaunch(id, done);
return;
}
if (!webos) {
if (done) setTimeout(function () { done(true); }, 0);
return;
}
luna('luna://com.webos.applicationManager/launch',
params ? {id: id, params: params} : {id: id}, function (res) {
if (done) done(!!res);
});
일반 브라우저 분기는 실제 TV 앱을 실행하는 대신 성공 콜백을 돌려주는 모의 동작입니다. 웹 화면이 브라우저에 잘 보인다는 사실만으로 HDMI 전환이나 TV 앱 실행까지 검증되는 것은 아닙니다.
호텔 객실의 “조명 켜기” 버튼은 같아도 내부 배선은 건물마다 다릅니다. 화면이 버튼의 의미를 정하고, 어댑터가 해당 TV의 배선으로 연결합니다. 새 플랫폼을 붙일 때 바뀌는 부분을 한곳에 모으는 것이 핵심입니다.
공유 범위에도 경계가 있습니다. Android의 HomeView.java와 웹의 home.js는 별도 구현입니다.
Python의 tools/webapp.py가 공유하는 것은 설정·번역·폰트·기호와 LG·Samsung 웹 파일입니다.
화면 로직까지 한 번 작성해 세 플랫폼에서 그대로 실행하는 구조는 아닙니다.
저장소는 Android 앱, 공유 웹 앱, 플랫폼별 포장 도구, 소개 사이트로 나뉩니다.
같은 이름의 build.py가 두 개 있지만, 만드는 패키지 형식과 서명 절차가 다릅니다.
루트 기준 주요 구조는 다음과 같습니다.
| 부분 | 물리적 줄 수 | 읽을 때의 의미 |
|---|---|---|
| Java 전체 | 31개 / 11,131줄 | Android 네이티브 앱이 중심입니다. |
HomeView.java | 3,051줄 | 여러 UI 책임이 모여 있어 처음부터 끝까지 읽기 어렵습니다. |
HomeMode.java | 961줄 | 전환·복구의 예외 처리가 상당한 비중을 차지합니다. |
| TV 웹 JavaScript | 5개 / 2,220줄 | 짧게 압축된 zones.js도 포함합니다. |
| 사이트 JavaScript | 1개 / 281줄 | TV 앱과 별도인 소개 사이트 코드입니다. |
| Python 전체 | 9개 / 1,239줄 | 빌드·설치·자산 생성·번역 도구입니다. |
공백과 주석을 포함한 텍스트 줄 수입니다. 생성 결과와 Git 내부 데이터는 제외했습니다. 한 줄에 많은 데이터가 든 파일도 있어 줄 수를 기능 복잡도나 개발 시간으로 그대로 환산할 수는 없습니다.
Spring.java 41줄: 입력·상태·계산·종료 조건을 한 번에 이해합니다.HomeActivity.java 80줄: 화면이 언제 생기고 멈추는지 봅니다.HomeData.java 452줄: HTTP 요청과 캐시·Listener를 연결합니다.HomeView.onData() → doFrame() → Layer.onDraw(): 큰 파일을 호출 경로로 나눕니다.ModeJob.java 58줄 → Watchdog.java 84줄 → HomeMode: 복구 흐름을 따라갑니다.web/tv.js → tools/webapp.py: 운영체제 연결과 패키징의 차이를 봅니다.“OK 버튼을 놓았을 때 무엇이 실행될까?”를 정하고 onKey()에서 activate()까지 추적하세요.
이후에 포커스 이동, 서랍, 앱 재정렬처럼 질문을 한 개씩 늘리는 편이 구조를 이해하기 쉽습니다.
각 기술을 따로 외우기보다 사용자 입력이 상태를 바꾸고, 상태 변화가 저장·통신·화면 갱신으로 이어지는 연결을 설명해 보세요.
Spring은 value, velocity, target 세 상태를 가집니다.
snap()은 즉시 이동하면서 속도를 0으로 만들고, step()은 시간에 따라 조금씩 이동합니다.
settled()는 거리와 속도가 충분히 작을 때 종료를 결정합니다.
미니 실습: 종이에 초기값 0, 목표값 1을 놓고 response와 damping이
각각 어떤 경험을 바꿀지 예상해 보세요. 목표만 바꾸는 것과 snap()의 차이를 표로 적으면 좋습니다.
HomeActivity.onResume()은 home.resume()으로 연결되고,
onPause()는 home.pause()로 연결됩니다.
화면이 사라질 때 Listener·콜백·TV 입력 콜백을 해제하는 코드를 짝으로 찾으세요.
미니 실습: resume()의 등록 작업과 pause()의 해제 작업을 두 열에 적습니다.
해제가 없을 때 오래된 화면을 붙잡거나 불필요한 알림을 받을 수 있는 부분을 설명해 보세요.
이미 시작된 네트워크 작업을 모두 취소하는 것과 Listener 제거도 서로 다른 동작입니다.
Layer.onDraw()는 직접 그리기를 맡고, updateMotion()은 레이어의 위치·투명도 등을 바꿉니다.
Scene은 배경을 축소해 그리는 경로를 두고, Frost는 48×27 픽셀의 작은 이미지를 만듭니다.
작은 이미지를 확대·보간해 흐린 유리 배경에 활용하는 방식입니다.
미니 실습: 서랍을 10픽셀 옮길 때 글자와 배경을 다시 그려야 하는지,
이미 그린 결과를 이동해도 되는지 나눠 보세요.
Android 직접 그리기 문서의
Canvas와 Paint 역할을 이 코드에 대응시키면 좋습니다.
HomeData는 단일 ExecutorService로 I/O를 처리하고,
Handler(Looper.getMainLooper())로 UI에 결과를 알립니다.
volatile은 스레드 사이에서 값의 변경을 보이게 하는 도구지만, 여러 필드의 변경 전체를 원자적으로 묶어 주지는 않습니다.
Listener 인터페이스와 addListener()·removeListener()가 그 역할을 합니다.미니 실습: 시세와 날씨 요청이 같은 단일 실행기를 쓰면 한쪽의 지연이 다른 쪽에 어떤 영향을 주는지 타임라인을 그려 보세요. 무조건 스레드를 늘리기 전에 순서·중복·서버 부하를 함께 따져 봅니다.
Ui.tr()는 영어 키에 대응하는 번역을 찾고, trf()는 이름 있는 자리표시자를 채웁니다.
Ui.plural()은 ICU가 고른 복수형 범주를 이용합니다.
한국어에는 하나로 보이는 형태가 다른 언어에서는 수에 따라 여러 개 필요할 수 있습니다.
// Ui.java의 사용 규칙을 보여 주는 호출 예시
Ui.trf("{n} min ago", "n", 12);
Ui.plural("plural|one:{n} season|other:{n} seasons", 3);
// 번역은 문장 순서를 바꿀 수 있어도 {n}은 보존해야 합니다.
// 언어 변경 시 Ui.generation이 증가하여 화면의 글자 캐시를 갱신합니다.
미니 실습: app/assets/i18n/ko.json과 en.json에서 같은 키를 찾아 비교합니다.
tools/translate.py의 PLACEHOLDER와 check()가 왜 필요한지 설명해 보세요.
공유 웹 앱은 tools/webapp.py가 실제 사용하는 번역 키만 묶습니다.
Adb.connect()는 TV 자신의 127.0.0.1:5555에 접속합니다.
CNXN·AUTH로 연결과 인증을 진행하고, shell()은
OPEN·WRTE·OKAY·CLSE 메시지를 처리합니다.
미니 실습: Adb.read()가 읽는 24바이트 헤더를 필드별로 분해해 적습니다.
“문자열 명령을 보낸다” 아래에 길이·바이트 순서·인증·종료 신호가 있다는 점을 찾는 것이 목표입니다.
명령을 실제 TV로 보내지 않아도 프로토콜 구조는 공부할 수 있습니다.
Canvas로 그린 버튼은 화면 낭독기가 자동으로 의미를 알기 어렵습니다.
Spoken extends AccessibilityNodeProvider는 가상 항목의 설명·경계·동작을 제공합니다.
리모컨 버튼을 가로채는 RemoteKeyService와는 목적이 다른 코드입니다.
웹 쪽은 오래된 TV를 위해 ES5 문법과 콜백·XMLHttpRequest를 사용합니다.
tools/check.sh는 구문 검사 외에 fetch, Promise, Array.from 등
구형 환경에 없는 호출도 찾습니다. 구문이 해석된다고 API가 존재하는 것은 아닙니다.
미니 실습: 화려한 새 문법 하나를 도입할 때 “파싱 가능 여부”와 “런타임 기능 존재 여부”를 별도로 적습니다. 사용자 정의 버튼에는 시각적 포커스뿐 아니라 낭독 이름과 클릭 동작도 함께 정의해 보세요.
| 용도 | 요구 조건 | 근거와 주의점 |
|---|---|---|
| Android 실행 | Android 8 / API 26 이상 TV·셋톱박스 | build.sh의 minSdk. 기기 펌웨어별 HOME 전환 호환성은 별개입니다. |
| Android 개발 | JDK 17 이상, Android SDK, adb, Bash 환경 | CI 기준 build-tools 34.0.0, platform android-34. Windows는 README의 Git Bash 또는 WSL 경로 |
| LG 실행 | webOS 3 이상, Developer Mode | 최소 버전은 README 기준. 기존 LG 홈은 유지합니다. |
| LG 개발·설치 | Python 3, OpenSSH 도구, 같은 네트워크의 컴퓨터 | webos/build.py·install.py. 개발자 모드의 세션 유지 조건도 확인해야 합니다. |
| Samsung 실행 | 2017년 이후 TV, Tizen 3 이상 | README와 tizen/config.xml의 required_version="3.0" |
| Samsung 개발 | Python 3, 개발자 모드, 설치할 기기에 맞는 서명 | 자체 서명 빌드는 Tizen 도구 필요. 2023년 이후 기기 인증서 조건은 README·제조사 문서 확인 |
| 온라인 기능 | 인터넷, 선택 기능별 API 접근 | TMDB 키 없이도 기본 홈은 동작하지만 작품 추천은 비어 있습니다. |
| RAM·CPU·저장공간 | 정량적 최소 사양 명시 없음 | 96 MB는 README의 앱 측정치이며 TV 전체 RAM 요구량이 아닙니다. |
최소 Android 버전을 충족해도 GPU, 메모리, 제조사 서비스, 디버깅 방식에 따라 동작과 복구 가능성이 달라집니다.
Ui.LEGACY는 API 31 미만, 즉 Android 8~11에서 비용이 낮은 그리기 경로를 선택합니다.Recording.create()는 Android 10 이상에서 RenderNode, 그 이전에는 Picture를 씁니다.Scene은 RuntimeShader 생성 실패를 잡고 그라데이션·반투명 표현으로 대체합니다.
공식 API 문서상 이 기능은 API 33에 추가됐습니다.ScreensaverAlone을 기본 프로세스에서 실행하도록
app/res/values-v30/bools.xml에서 분기합니다. 다른 버전의 화면보호기는 :home 경로입니다.성능을 생각할 때는 패널의 “4K” 표기와 실제 UI 렌더링 해상도를 구분하세요.
HomeView.onMeasure()는 너비를 1920 기준으로 환산하며,
Backdrop는 UI 크기를 고려해 이미지 크기를 결정합니다.
1920×1080 RGBA 이미지는 픽셀당 4바이트로 계산하면 약 7.9 MiB입니다. 3840×2160이면 약 31.6 MiB로 네 배가 됩니다. 이는 압축 파일 크기가 아니라 픽셀 버퍼의 단순 계산입니다. 원본·흐림·낮·밤용 이미지를 각각 보관하면 작은 TV에서 메모리 사용량이 빠르게 늘어납니다.
Adb.java는 디버깅이 허용된 TV의 로컬 ADB를 이용합니다.
연결 시 사용자 키 승인이 필요할 수 있고, 무선 디버깅의 TLS 요청을 받으면 USB 디버깅 방식을 안내하며 중단합니다.
“루트 불필요”와 “권한 승인 불필요”를 같은 뜻으로 읽으면 안 됩니다.
Wi-Fi를 저장한 시스템 앱을 끄면 연결 정보가 사라질 수 있어 wifiOwners()가 이를 방어합니다.
일부 TCL 펌웨어는 백그라운드 작업을 제한하고, R3_GTV는 홈 전환 자체가 차단돼 있습니다.
README도 Free My TV 모드에서 곧바로 제거하면 비활성 앱이 그대로 남는다고 설명합니다. 제거 전 Original 복원과 단순 APK 삭제는 서로 다른 작업입니다.
제조사 개발자 모드 조건은 변할 수 있습니다. 기기 설치를 계획할 때는 LG 공식 개발자 모드 문서와 Samsung 공식 TV 연결 문서를 확인하세요.
다음 과제는 기기 변경 없이 시작할 수 있습니다. 첫 두 과제는 텍스트만 읽어도 완성할 수 있고, 이후 과제는 별도 학습용 사본에서 구현하는 확장 과제입니다. 과제를 끝냈다는 기준을 “코드를 바꿨다”가 아니라 “입력·실패 조건·예상 결과를 설명할 수 있다”로 정하세요.
출발점: HomeData.active(), quote(), pricesPath().
할 일: currency=USD, BRL, 지원되지 않는 통화에서의 달러 대체 상황을 표로 적습니다.
네 코인이 모두 선택됐다고 가정하고 요청할 거래쌍을 써 보세요.
완료 기준: USD에서는 BTCUSDT·ETHUSDT·SOLUSDT만,
BRL에서는 USDTBRL까지 포함되는 이유를 설명합니다.
USDTUSDT라는 잘못된 쌍이 전체 요청을 실패시킬 수 있다는 점을 연결하세요.
더 생각하기: 선택 상태뿐 아니라 Prefs.version과 inDollars가
activeCoins 캐시의 유효성에 필요한 이유는 무엇일까요?
출발점: HomeData.refreshIfStale(), failedRecently().
할 일: 시세가 4분 전·6분 전일 때, 요청 중일 때, 30초 전에 실패했을 때, TV 시계가 뒤로 이동했을 때를 나눠 요청 여부를 판단합니다.
완료 기준: “오래됨”, “중복 요청 방지”, “실패 대기”가 각각 별도 조건임을 설명하고, 각 사례에서 어떤 조건이 요청을 막는지 표시합니다. 실제 API 키나 통신은 필요하지 않습니다.
출발점: HomeData.staleness(), HomeView.onData(),
Ui.trf(), app/assets/i18n/ko.json.
할 일: 마지막 성공 시각과 마지막 실패 시각의 역할을 구분한 뒤, 데이터 없음·정상·오래된 캐시 세 상태의 안내 문구와 표시 위치를 설계합니다. 확장 구현에서는 기존 값은 유지하고 안내만 바꾸는 방법을 찾아봅니다.
완료 기준: 네트워크 장애가 가격 0원으로 표시되지 않고, 오래된 값을 최신값처럼 오해하지 않으며,
문구가 번역 키와 자리표시자 규칙을 지킵니다. Double.NaN의 의미도 적으세요.
출발점: web/tv.js의 일반 브라우저 분기와 launch(id, params, done).
할 일: 앱 세 개와 HDMI 입력 두 개를 반환하는 모의 데이터를 정의하고, 실행 성공·실패·지연 시 화면이 어떻게 반응할지 설계합니다. 실제 TV 주소나 서비스 호출은 사용하지 않습니다.
완료 기준: 공통 화면 코드를 운영체제 조건으로 채우지 않고도 실패를 표현할 수 있으며,
done이 한 요청에서 두 번 호출되지 않는지 확인할 사례를 제시합니다.
출발점: HomeMode.applyFree(), applyOriginal(),
revertHomeNow(), save().
할 일: 실제 명령을 보내는 대신 “명령 → 예정된 문자열 결과”를 반환하는 가짜 실행기를 설계합니다. HOME 지정 성공 후 앱 비활성화 실패, 상태 저장 실패, ADB 연결 실패를 각각 주입한다고 가정하세요.
완료 기준: 성공한 변경만 복원 목록에 남는지, 원래 꺼 둔 앱은 보존되는지,
초기 설정 앱과 원래 런처의 복원 순서가 맞는지 검사 항목을 적습니다.
기존 코드의 RUN 잠금이 제공하는 범위와 영속 기록의 역할도 구별하세요.
처음부터 ADB 인증이나 셰이더 수식을 전부 이해할 필요는 없습니다. 값 하나가 화면으로 가는 경로를 익힌 뒤, 수명·성능·실패·플랫폼 차이를 차례로 추가합니다. 일정은 학습 계획의 예시이며 공식 교육 과정이나 숙련 시간을 보장하는 기준은 아닙니다.
| 주차 | 주제·읽을 소스 | 직접 남길 결과물 |
|---|---|---|
| 1주차 | Java 객체·인터페이스·상태. Spring, HomeData.Listener | 상태 변수 뜻과 snap()·step() 차이, 시세 거래쌍 표 |
| 2주차 | Android Activity·Manifest·생명주기. HomeActivity, HomeView.resume()·pause() | 진입점과 프로세스 표, 등록·해제 작업의 대응표 |
| 3주차 | HTTP·JSON·캐시·스레드. HomeData, Recs.refreshIfStale() | 성공·실패·시간 역행을 포함한 새로고침 판단표 |
| 4주차 | Canvas·프레임·이미지 메모리. Layer.onDraw(), doFrame(), Recording | 한 번의 방향키 입력에서 재계산할 레이어 목록, 이미지 메모리 계산 |
| 5주차 | 영속 상태·작업·복구. ModeJob, HomeMode, Watchdog, Adb | 전환 중 실패 위치별 복구 시나리오와 가짜 명령 실행기 설계 |
| 6주차 | 플랫폼 어댑터·국제화·배포. web/tv.js, Ui, tools/webapp.py, CI | 모의 TV API 명세, 한국어 문구 설계, 빌드 단계 지도 |
로드맵의 마지막 목표는 기능을 하나 더 넣는 것보다, 그 기능이 느려지거나 실패했을 때의 동작까지 설계하는 것입니다.
Spring과 Listener가 작은 예제입니다.HomeActivity·ModeJob·HomeReceiver에 하나씩 대응시킵니다.Canvas를 도화지, Paint를 붓 설정으로 생각한 뒤,
좌표 변환·그리기 결과 저장·합성 순서로 넘어갑니다.Backdrop의 이미지 크기·흐림 결과 재사용 → Scene의 배경 기록 →
Shaders의 AGSL 수식 순서로 들어갑니다.
“GPU를 쓴다”가 아니라 어떤 계산을 언제 반복하는지 설명하는 것이 목표입니다.
HomeMode의 보호 조건 → 상태 파일 갱신 → verifyNow()의 부팅 후 재확인 →
Adb 인증 순서로 들어갑니다.
프로세스 분리가 제공하는 격리와, 동일한 기기의 ADB에 의존한다는 한계를 함께 평가합니다.
“캐시는 언제 버리는가”, “화면을 떠나면 무엇을 해제하는가”, “구형 OS에서는 무엇으로 대체하는가”, “중간 실패 후 무엇이 남는가”, “웹과 Android가 실제로 공유하는 것은 무엇인가”를 소스 이름과 함께 답할 수 있으면 이 프로젝트의 큰 흐름을 이해한 것입니다.
| 용어 | 쉬운 설명 |
|---|---|
| 런처 / HOME 역할 | 다른 앱을 실행하는 출발 화면과 그 화면을 담당하는 시스템 역할. HomeActivity가 맡습니다. |
| Activity | Android의 화면 진입 단위. 이 앱은 홈과 설정에 서로 다른 Activity를 둡니다. |
| Intent | “이 앱을 열어라”처럼 운영체제에 행동을 요청하는 메시지입니다. |
| Manifest | 화면·서비스·권한·프로세스를 운영체제에 알리는 앱의 구성표입니다. |
| Canvas / Paint | 도형·글자·이미지를 그리는 도구와 그릴 때의 색·두께·폰트 설정입니다. |
| 레이어 / 합성 | 화면을 여러 조각으로 그려 두고 위치·투명도와 함께 최종 화면으로 합치는 방식입니다. |
| 더티 플래그 | 그린 결과를 다시 만들어야 한다는 표시. tilesDirty처럼 쓰입니다. |
| Choreographer | 디스플레이의 프레임 타이밍에 맞춰 계산할 기회를 주는 Android 도구입니다. |
| 감쇠 스프링 | 목표를 향한 힘과 움직임을 줄이는 저항으로 부드러운 변화를 만드는 모델입니다. |
| RenderNode / Picture | 그리기 명령을 기록하고 재사용하는 도구. Recording이 OS별 차이를 감쌉니다. |
| AGSL / 셰이더 | 픽셀의 색을 계산하는 프로그램과 그 언어. 여기서는 하늘·유리 효과를 만듭니다. |
| ExecutorService | 작업을 맡길 스레드를 관리하는 도구. HTTP와 파싱을 UI 작업과 분리합니다. |
| Handler / Looper | 지정한 스레드의 작업 순서에 함수를 넣는 통로와 작업 처리 루프입니다. |
| Listener / 옵서버 | 데이터 변경을 구독하고 통지를 받는 객체입니다. |
| 캐시 / 유효 기간 | 이전 응답을 재사용하고 언제 다시 가져올지 판단하는 규칙입니다. |
| SharedPreferences | 설정처럼 작은 키·값을 기기에 저장하는 Android 기능. 프로세스 간 최신값 공유를 자동 보장하지 않습니다. |
| 프로세스 격리 | 화면과 작업을 별도 실행 공간에 두는 방식. 화면 충돌이 복구 작업까지 바로 죽이지 않게 합니다. |
| JobScheduler | 앱의 백그라운드 작업을 운영체제에 예약하는 도구. ModeJob의 실행 기반입니다. |
| 워치독 | 문제를 감지하고 복구를 요청하는 감시 장치. 여기서는 짧은 시간의 반복 충돌을 셉니다. |
| ADB / adbd | Android 디버깅 통신 도구와 기기 쪽 서비스. 이 앱은 자체 ADB 클라이언트를 구현합니다. |
| 루프백 주소 | 127.0.0.1처럼 자기 기기를 가리키는 주소. TV가 자기 TV의 ADB에 접속합니다. |
| little-endian | 여러 바이트 숫자의 낮은 자리 바이트를 먼저 저장하는 순서. ADB 헤더 해석에 사용합니다. |
| 원자적 교체 | 임시 파일을 완성한 뒤 이름을 바꿔 기존 파일을 대체하는 방식. 시스템 변경 전체의 원자성과는 다릅니다. |
| 딥 링크 | 앱의 첫 화면 대신 특정 작품 같은 내부 목적지로 가는 주소입니다. |
| 플랫폼 어댑터 | 공통 요청을 운영체제마다 다른 API로 바꾸는 연결부. web/tv.js가 예입니다. |
| ES5 | 오래된 JavaScript 표준. 구형 TV가 이해할 문법과 사용할 API 범위를 제한하는 기준입니다. |
| 국제화 / 자리표시자 | 언어별 표현을 준비하는 구조와 문장에 넣을 값의 이름. {n} 같은 표시를 보존합니다. |
| 가상 접근성 노드 | 직접 그린 버튼을 낭독기가 탐색하도록 제공하는 논리적 항목. Spoken이 만듭니다. |
| APK / DEX | Android 설치 패키지와 그 안에 들어가는 실행 코드 형식입니다. |
| IPK / WGT | 이 프로젝트가 LG와 Samsung 앱을 각각 포장하는 파일 형식입니다. |
| 코드 서명 | 배포자의 키로 패키지를 서명하는 절차. 업데이트할 때 같은 서명 키를 유지해야 합니다. |
| CI | 코드 변경 때 자동으로 검사하는 절차. 여기서는 빌드·번역·웹 호환성 등을 확인합니다. |
| 항목 | 상태 |
|---|---|
| 프로젝트 | simbasjungles/free-my-tv — 광고 없는 Google TV 홈 화면, Claude Code로 제작했다고 소개 |
| 선정 근거 | TrendShift Weekly #24 · 분석일 2026-10-23 |
| 스타 / 포크 | 3,629 / 519 — 2026-10-23 GitHub 기준 |
| 생성일 | 2026-10-19 |
| 최근 push | 2026-10-23 — 저장소 메타데이터의 갱신일 |
| 최신 릴리스 | v1.1.0 · 2026-10-22 |
| 분석 대상 최근 커밋 | 4d73951 · 2026-10-22 (UTC−03:00) · PR #188 병합: 달러 기준에서의 달러 카드 관련 수정 |
| 버전 파일 | Android versionName="1.1", versionCode="2".
webOS·Tizen은 1.1.0. 릴리스 이름과 앱별 버전 표기는 다릅니다. |
| 주 언어·핵심 규모 | Java 31개 / 11,131줄. JavaScript 6개 / 2,501줄. Python 9개 / 1,239줄 |
| 기타 텍스트 규모 | CSS 2개 / 1,903줄, HTML 3개 / 708줄, Shell 2개 / 174줄, XML 13개 / 274줄, JSON 26개 / 4,865줄, YAML 2개 / 75줄 |
| 집계 범위 | 커밋 기준 추적 파일 172개. 위 줄 수는 공백·주석을 포함하며 Git 내부 데이터·빌드 결과·이미지·폰트는 제외 |
| 번역 | app/assets/i18n/의 JSON 25개, 한국어 포함 |
| 라이선스 | 앱 코드 MIT. Inter는 SIL OFL 1.1, Material Symbols는 Apache 2.0 등 자산별 표기 별도 확인 |
스타·포크·생성일·push·릴리스는 2026-10-23 GitHub 기준이며,
파일·줄 수·함수 동작은 커밋 4d73951 기준입니다.
이후 변경 사항과 전체 커밋 수를 이 수치에서 추정하지 않습니다.
성능 수치는 README의 특정 TCL 기기 측정입니다. 모든 기기에서의 60 fps·메모리 절감, 모든 제조사 펌웨어의 전환·복원 성공을 보장하지 않습니다. CI의 빌드·번역·ES5 검사는 실기기 검증을 대신하지 않습니다.
자동 충돌 복구는 홈·화면보호기를 우선 복원하고, Original은 더 넓은 원복을 시도합니다. 비활성화로 인한 모든 부작용까지 복원하는 만능 백업은 아니며, 복구 자체도 로컬 ADB가 동작해야 합니다.
설치된 앱을 기준으로 추천하므로 구독권·지역별 재생 가능 여부를 모두 판별하지는 못합니다. 외부 앱의 광고도 제거하지 않습니다. 외부 API 장애·요금제·지역 제한은 각 서비스 조건에 따릅니다.
config.properties의 키는 Android의 생성된 상수나 웹의 config.js에 들어갑니다.
Git에서 제외하는 것과 배포 파일에서 비밀로 숨기는 것은 다릅니다. 개인 키를 넣은 패키지를 공개 배포하지 않도록 관리해야 합니다.
LG·Samsung은 기본 홈 교체가 아닌 별도 앱입니다. 또한 Android UI와 웹 UI는 별도 구현이라 기능·디자인 변경 시 양쪽을 함께 살펴야 합니다. MIT 코드와 별개로 폰트·아이콘·API 데이터의 조건도 확인하세요.