Tr_Code/PWA_README.md

14 KiB

PWA (Progressive Web App) 가이드

이 문서는 Tr_Code 웹 애플리케이션을 안드로이드 앱으로 패키징하여 Google Play Store에 배포하는 방법을 설명합니다.

📋 목차

🌐 PWA란?

**Progressive Web App(PWA)**는 웹 기술로 만들어졌지만 네이티브 앱처럼 동작하는 애플리케이션입니다.

PWA의 장점

설치 가능 - 홈 화면에 아이콘 추가
오프라인 작동 - 네트워크 없이도 사용 가능
빠른 로딩 - 캐시를 통한 빠른 시작
푸시 알림 - 사용자에게 알림 전송
자동 업데이트 - 별도 앱 업데이트 불필요
크로스 플랫폼 - Android, iOS, Desktop 모두 지원

Tr_Code PWA 특징

  • 완전한 오프라인 지원: 한 번 방문 후 네트워크 없이도 사용 가능
  • 빠른 응답속도: HTMX 기반 부분 렌더링
  • 모바일 최적화: 반응형 디자인
  • 다크모드: 자동 다크모드 지원

📱 TWA (Trusted Web Activity)

TWA는 PWA를 안드로이드 네이티브 앱으로 패키징하는 기술입니다.

TWA vs WebView

특징 TWA WebView
성능 Chrome 브라우저 엔진 사용 (빠름) 앱 내장 (느림)
업데이트 자동 (서버만 업데이트) 앱 재배포 필요
보안 Chrome 보안 정책 적용 개발자 구현 필요
캐시 Service Worker 수동 구현
크기 작음 (~2MB) 큼 (WebView 포함)

🛠️ 준비사항

1. 도메인 및 HTTPS

PWA는 HTTPS가 필수입니다 (localhost 제외).

# 현재 구조
http://your-domain.com → NPM (Proxmox) → M1T nginx → App

NPM(Nginx Proxy Manager)에서 SSL 인증서를 설정하세요:

  • Let's Encrypt 자동 발급
  • 또는 기존 인증서 업로드

2. Android Studio

Android Studio 다운로드

# 시스템 요구사항
- OS: Windows 10/11, macOS, Linux
- RAM: 8GB 이상 권장
- 저장공간: 10GB 이상

3. Java Development Kit (JDK)

Android Studio에 포함되어 있지만, 별도 설치도 가능:

# Ubuntu/Linux
sudo apt install openjdk-17-jdk

# 확인
java -version

4. Google Play Console 계정

  • Google Play Console 가입
  • 일회성 등록 비용: $25 (평생 사용)
  • 개발자 계정 승인까지 1-2일 소요

🏗️ Android Studio 설정

1. 프로젝트 생성

이미 build.gradle 파일이 있으므로 다음 단계를 진행합니다.

2. 기존 프로젝트 구조

Tr_Code/
├── app/                  # Android 앱 소스
│   ├── src/
│   │   └── main/
│   │       ├── AndroidManifest.xml
│   │       ├── res/      # 리소스 (아이콘, 문자열 등)
│   │       └── java/     # Java/Kotlin 코드
├── build.gradle          # 프로젝트 빌드 설정
├── settings.gradle       # 프로젝트 설정
└── twa-manifest.json     # TWA 설정

3. twa-manifest.json 설정

{
  "packageId": "com.trcode.app",
  "host": "your-domain.com",
  "name": "1호선 고장코드",
  "launcherName": "고장코드",
  "display": "standalone",
  "themeColor": "#1f2937",
  "backgroundColor": "#111827",
  "enableNotifications": false,
  "startUrl": "/",
  "iconUrl": "https://your-domain.com/static/icon.png",
  "maskableIconUrl": "https://your-domain.com/static/icon-maskable.png",
  "splashScreenFadeOutDuration": 300,
  "signingKey": {
    "path": "android.keystore",
    "alias": "trcode"
  },
  "features": {
    "locationDelegation": {
      "enabled": false
    },
    "playBilling": {
      "enabled": false
    }
  }
}

4. AndroidManifest.xml 주요 설정

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.trcode.app">

    <!-- 권한 -->
    <uses-permission android:name="android.permission.INTERNET" />
    
    <application
        android:label="1호선 고장코드"
        android:icon="@mipmap/ic_launcher"
        android:theme="@style/Theme.TrCode">
        
        <!-- TWA Activity -->
        <activity
            android:name="com.google.androidbrowserhelper.trusted.LauncherActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <!-- Digital Asset Links (중요!) -->
        <meta-data
            android:name="asset_statements"
            android:resource="@string/asset_statements" />
            
    </application>
</manifest>

TWA가 작동하려면 도메인과 앱을 연결해야 합니다.

서버 측: .well-known/assetlinks.json

# M1T 서버에 파일 생성
sudo mkdir -p /home/ckh08045/Tr_Code/static/.well-known
sudo nano /home/ckh08045/Tr_Code/static/.well-known/assetlinks.json
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.trcode.app",
    "sha256_cert_fingerprints": [
      "YOUR_SHA256_FINGERPRINT_HERE"
    ]
  }
}]

SHA256 Fingerprint 얻는 방법:

# 키스토어에서 추출
keytool -list -v -keystore android.keystore -alias trcode

# 출력에서 SHA256 찾기
# 예: AA:BB:CC:DD:EE:FF:...
# 콜론(:) 제거하고 입력

nginx 설정 추가

# /etc/nginx/sites-available/tr_code에 추가
location /.well-known/assetlinks.json {
    alias /home/ckh08045/Tr_Code/static/.well-known/assetlinks.json;
    default_type application/json;
    add_header Access-Control-Allow-Origin *;
}
# nginx 재시작
sudo systemctl reload nginx

# 테스트
curl https://your-domain.com/.well-known/assetlinks.json

🔨 앱 빌드

1. 키스토어 생성 (처음 한 번만)

cd /home/ckh08045/Tr_Code

# 키스토어 생성
keytool -genkey -v -keystore android.keystore \
  -alias trcode \
  -keyalg RSA -keysize 2048 -validity 10000

# 정보 입력
# - 비밀번호 (잘 기억하세요!)
# - 이름, 조직, 도시, 국가 등

⚠️ 중요: android.keystore 파일과 비밀번호를 안전하게 백업하세요!

2. Gradle 빌드 설정

build.gradle 파일 확인:

android {
    compileSdkVersion 33
    
    defaultConfig {
        applicationId "com.trcode.app"
        minSdkVersion 21
        targetSdkVersion 33
        versionCode 1
        versionName "1.0"
    }
    
    signingConfigs {
        release {
            storeFile file('android.keystore')
            storePassword 'your_password'
            keyAlias 'trcode'
            keyPassword 'your_password'
        }
    }
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled false
        }
    }
}

3. 빌드 실행

Android Studio에서:

  1. BuildSelect Build Variantrelease 선택
  2. BuildBuild Bundle(s) / APK(s)Build Bundle(s)
  3. 완료되면: app/build/outputs/bundle/release/app-release.aab

명령줄에서:

cd /home/ckh08045/Tr_Code

# Clean
./gradlew clean

# AAB (Play Store용) 빌드
./gradlew bundleRelease

# 또는 APK (테스트용) 빌드
./gradlew assembleRelease

# 출력 위치
# AAB: app/build/outputs/bundle/release/app-release.aab
# APK: app/build/outputs/apk/release/app-release.apk

4. 테스트

# APK 설치 (USB 디버깅 활성화된 안드로이드 기기)
adb install app/build/outputs/apk/release/app-release.apk

# 또는 Android Studio에서 Run

🚀 Play Store 배포

1. Google Play Console 준비

  1. Google Play Console 로그인
  2. 모든 앱앱 만들기
  3. 앱 세부정보 입력:
    • 앱 이름: 1호선 고장코드
    • 기본 언어: 한국어
    • 앱/게임:
    • 무료/유료: 무료

2. 앱 콘텐츠 작성

개인정보처리방침

  • URL 제공 (필수)
  • 예: https://your-domain.com/privacy-policy

앱 카테고리

  • 카테고리: 비즈니스 또는 생산성
  • 콘텐츠 등급: 설문조사 진행

광고

  • 광고 포함 여부 선택

3. 프로덕션 릴리스 생성

  1. 프로덕션새 릴리스 만들기
  2. app-release.aab 업로드
  3. 출시 노트 작성:
첫 번째 릴리스
- 고장코드 조회
- TCMS 신호 조회
- MMI 코드 조회
- 오프라인 지원
  1. 검토 제출

4. 스토어 등록정보

스크린샷 (필수)

  • 휴대전화: 2개 이상 (16:9 비율)
  • 7인치 태블릿: 선택사항
  • 10인치 태블릿: 선택사항

그래픽 애셋

  • 앱 아이콘: 512x512 PNG
  • 기능 그래픽: 1024x500 PNG

설명

1호선 철도 차량 고장코드 및 TCMS 신호 조회 애플리케이션

주요 기능:
✓ 제조사별 고장코드 검색
✓ TCMS 신호 조회
✓ MMI 코드 데이터베이스
✓ 오프라인 지원
✓ 빠른 검색 및 필터링

철도 유지보수 및 정비 업무를 위한 필수 도구입니다.

5. 심사 및 배포

  • 제출 후 1-7일 심사 기간
  • 승인되면 자동으로 Play Store에 배포
  • 거부 시 피드백 확인 후 수정하여 재제출

💾 오프라인 기능

Service Worker

PWA의 오프라인 기능은 Service Worker로 구현됩니다.

static/sw.js 예시:

const CACHE_NAME = 'tr-code-v1';
const urlsToCache = [
  '/',
  '/static/css/main.css',
  '/static/js/main.js',
  '/static/icon.png',
];

// 설치
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
});

// 요청 가로채기
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );
});

// 업데이트
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== CACHE_NAME) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

manifest.json

static/manifest.json:

{
  "name": "1호선 고장코드",
  "short_name": "고장코드",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#111827",
  "theme_color": "#1f2937",
  "orientation": "portrait-primary",
  "icons": [
    {
      "src": "/static/icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/static/icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ]
}

HTML에서 등록

templates/base.html:

<head>
  <link rel="manifest" href="/static/manifest.json">
  <meta name="theme-color" content="#1f2937">
  <link rel="apple-touch-icon" href="/static/icon.png">
</head>
<body>
  <!-- ... -->
  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/static/sw.js')
        .then(reg => console.log('SW registered', reg))
        .catch(err => console.log('SW registration failed', err));
    }
  </script>
</body>

FAQ

Q1: localhost로 테스트할 수 있나요?

A: 네! PWA는 localhost에서 HTTPS 없이도 작동합니다. 하지만 TWA는 실제 도메인이 필요합니다.

Q2: 앱 업데이트는 어떻게 하나요?

A: 서버만 업데이트하면 됩니다! 앱을 다시 빌드/배포할 필요가 없습니다. 사용자가 앱을 열면 자동으로 최신 버전을 로드합니다.

단, 앱 아이콘이나 이름 변경 시에는 Play Store에 새 버전을 제출해야 합니다.

Q3: iOS는 지원되나요?

A: PWA 자체는 iOS에서도 작동합니다 (Safari). 하지만 TWA는 Android 전용이므로, iOS 앱을 만들려면:

  • 웹 앱 추가: Safari에서 "홈 화면에 추가"
  • App Store: Apple Developer Program ($99/년) 가입 후 네이티브 앱 개발 필요

Q4: 서버가 다운되면 앱도 작동하지 않나요?

A: Service Worker로 캐시를 구현하면 오프라인에서도 기본 기능은 사용할 수 있습니다. 단, 새 데이터는 서버 연결이 필요합니다.

Q5: 데이터는 어디에 저장되나요?

A:

  • 서버: Supabase (PostgreSQL)
  • 클라이언트: 브라우저 캐시 (Service Worker)
  • 앱 내부: 없음 (모든 데이터는 웹에서 로드)

Q6: 보안은 어떻게 되나요?

A:

  • HTTPS 필수 (데이터 암호화)
  • Digital Asset Links (앱-도메인 인증)
  • Chrome 브라우저의 보안 정책 적용
  • Supabase Row Level Security

Q7: 앱 크기는 얼마나 되나요?

A: TWA 앱은 매우 작습니다:

  • APK/AAB: 약 2-5MB
  • 설치 후: 10-20MB (캐시 포함)

비교: 일반 네이티브 앱은 20-100MB 이상

Q8: Play Store 심사에서 거부될 수 있나요?

A: 가능성 있는 거부 사유:

  • Digital Asset Links 미설정
  • 개인정보처리방침 누락
  • 앱 콘텐츠 설명 불충분
  • 스크린샷 부족

모두 수정 후 재제출 가능합니다.

Q9: 현재 서버 구조에서 PWA가 잘 작동하나요?

A: 네! 현재 구조는 PWA에 최적화되어 있습니다:

  • nginx 리버스 프록시
  • HTMX (빠른 로딩)
  • 반응형 디자인
  • localhost Supabase (빠른 응답)

NPM(Proxmox)에서 HTTPS만 설정하면 완벽합니다!

Q10: 비용은 얼마나 드나요?

A:

  • Google Play Console: $25 (평생)
  • 도메인: 연간 $10-20
  • SSL 인증서: 무료 (Let's Encrypt)
  • 서버: 기존 사용 중

총 초기 비용: 약 $25-50

📚 추가 자료

🆘 도움이 필요하신가요?

문제가 발생하거나 질문이 있으면 시스템 관리자에게 문의하세요.


작성일: 2025-10-13
버전: 1.0