ProFocus 4/7/2026 · 5 phút đọc

Chạy trên điện thoại đời 2013: techstack low-end đằng sau ProFocus

ProFocus biến một chiếc điện thoại dư thành đồng hồ để bàn, đồng hồ tập trung và khung ảnh WiFi. Để làm được thế, nó phải chạy trên Android 4.3 và iOS 12, không Firebase và không Play Services. Đây là cách chúng tôi làm.

Ảnh chụp ProFocus

Hầu hết các app lặng lẽ bỏ hỗ trợ điện thoại cũ vì như thế dễ hơn. ProFocus làm ngược lại, bởi hỗ trợ điện thoại cũ chính là sản phẩm. Nó lấy chiếc điện thoại nằm trong ngăn kéo của bạn, cái máy từ hai đời nâng cấp trước, và biến nó thành đồng hồ để bàn, đồng hồ tập trung, và khung ảnh WiFi. Điều đó chỉ chạy được nếu app thực sự cài và chạy trên phần cứng mà mọi người khác đã bỏ cuộc. Đây là techstack giữ đúng lời hứa đó.

Thấp tới đâu

  • Android: minSdk 18, tức là Android 4.3 Jelly Bean, từ năm 2013. Chúng tôi test trên một thiết bị 4.3 thật, không chỉ trên máy ảo.
  • iOS: deployment target 12.2, nên một chiếc iPhone 6s đời 2015 vẫn được hỗ trợ đầy đủ.
  • TV: một bản Android TV và một bản tvOS 12, cùng một app đồng hành Wear OS.

Chạm được những mức sàn đó không hề miễn phí. Nó định hình gần như mọi quyết định kỹ thuật bên dưới.

Không Firebase, không Play Services, một cách có chủ đích

Lựa chọn lớn nhất: ProFocus không kèm Firebase SDK và không phụ thuộc Google Play Services. Những thư viện đó nặng, không đảm bảo chạy trên máy đời cũ hoặc máy đã gỡ Google, và kéo theo rất nhiều bề mặt phức tạp cho một app chỉ cần hiển thị đồng hồ.

Thay vào đó, dữ liệu phân tích đi ra dưới dạng lời gọi GA4 Measurement Protocol thuần tới máy chủ của chính chúng tôi. Máy chủ đó, tracking-server, là một proxy Fastify (Node 20, TypeScript) không trạng thái và không có database: nó nhận một lô sự kiện, ánh xạ API key sang một project, và chuyển tiếp tới đúng luồng GA4. Chỉ một máy chủ nhỏ là thứ duy nhất từng nói chuyện với Google, nên app vẫn gọn và chạy được cả nơi không có Play Services.

Đường chướng ngại vật của iOS 12

Swift trên iOS 12 là một bãi mìn của những thứ biên dịch ngon lành rồi crash khi khởi động:

  • Swift Concurrency (async/await) crash trên iOS 12 vì runtime concurrency không tồn tại. Nên cả app viết theo API completion-handler; ngay cả lớp thanh toán RevenueCat cũng dùng API callback, không bao giờ async.
  • SwiftProtobuf cũng crash khi khởi động trên iOS 12. Chúng tôi chia sẻ một schema remote.proto giữa các nền tảng, nhưng trên iOS không dùng các kiểu protobuf sinh ra chút nào. Thay vào đó có một struct RemoteProtocol.swift viết tay phản chiếu schema, và mọi thứ truyền đi dưới dạng JSON thuần trên đường truyền.
  • Một lỗ hổng linker cho ___chkstk_darwin trên thiết bị được xử lý bằng cách force-load libclang_rt.ios.a.

Phía Android có phiên bản riêng của điệu nhảy này: những tính năng thực sự cần API mới hơn, như CameraX để quét QR hay Google Play Billing, được canh giữ sau kiểm tra API-level lúc chạy. App vẫn khai báo minSdk 18, và những tính năng đó chỉ sáng lên trên máy đủ mới để có chúng, còn một chiếc điện thoại 4.3 chạy mọi thứ còn lại.

Một SDK dùng chung, không phải keo dán riêng từng app

Phần thanh toán và phân tích không được viết lại cho từng app. Chúng nằm trong một SDK dùng chung điều khiển bằng cấu hình, dùng xuyên suốt các sản phẩm FightTech:

ModuleNền tảngNhiệm vụ
fight-payment-*iOS 12+, Android 18+Mua trong app RevenueCat + paywall
fight-tracking-ga-*iOS 12+, Android 18+Client GA4 Measurement Protocol
protocả haiSchema điều khiển từ xa dùng chung (JSON trên đường truyền)

Mọi thứ được tiêm vào lúc init: API key, id quyền lợi, theme, các hook phân tích. Không gì bị hardcode theo từng sản phẩm, và chỉ những SDK key công khai mới được commit vào repo. Nối một app mới vào thanh toán và phân tích là chuyện cấu hình, không phải code.

Người dùng nhận được gì

Tất cả phần đường ống đó cộng lại thành một thứ đơn giản nhìn từ ngoài: một app không tài khoản và không theo dõi bên thứ ba, chia sẻ ảnh giữa các thiết bị qua WiFi nội bộ (điện thoại tự mở một máy chủ HTTP nhỏ trên cổng 8080), hiển thị đồng hồ kiểu Flip, Circle hay Analog, và chạy đồng hồ tập trung, trên một chiếc điện thoại mà các app hiện đại từ chối cài.

Bài học kỹ thuật là “hỗ trợ máy cũ” không phải một ô tick bạn đánh dấu ở cuối. Nó là một ràng buộc bạn mang theo từ dòng đầu tiên: không async, không SDK nặng, kiểm tra lúc chạy ở khắp nơi, và máy chủ nhỏ của chính bạn đứng thay cho những dịch vụ nền tảng bạn đã chọn không mang theo.

Bạn có thể thử nó từ App Store — lý tưởng nhất là trên chính chiếc điện thoại đang nằm trong ngăn kéo.

App Store ↗
← Tất cả bài viết