--- id: android-ml-kit-health title: Android ML Kit / Health Connect / On-device AI category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [android, mlkit, health, on-device, vibe-coding] tech_stack: { language: "Kotlin", applicable_to: ["Android"] } applied_in: [] aliases: [ML Kit, Health Connect, MediaPipe, on-device ML, AICore, Gemini Nano] --- # Android ML Kit / Health Connect / On-device AI > Google ML Kit (built-in ML), Health Connect (cross-app health), MediaPipe (advanced ML), Gemini Nano (on-device LLM, Pixel 9+). ## πŸ“– 핡심 κ°œλ… - ML Kit: 일반 ML task λΉ λ₯Έ μ‚¬μš©. - Health Connect: data 톡합 + permissions. - MediaPipe: vision / LLM 자체 λͺ¨λΈ. - Gemini Nano: AICore β€” on-device LLM. ## πŸ’» μ½”λ“œ νŒ¨ν„΄ ### ML Kit β€” Text recognition ```kotlin implementation("com.google.mlkit:text-recognition:16.0.0") ``` ```kotlin import com.google.mlkit.vision.text.TextRecognition import com.google.mlkit.vision.text.latin.TextRecognizerOptions val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) val image = InputImage.fromBitmap(bitmap, rotation) val result = recognizer.process(image).await() // suspend extension for (block in result.textBlocks) { for (line in block.lines) { println(line.text) } } ``` β†’ OCR. 영수증 / λͺ…함. ### ML Kit β€” Barcode ```kotlin implementation("com.google.mlkit:barcode-scanning:17.2.0") val scanner = BarcodeScanning.getClient() val barcodes = scanner.process(image).await() for (barcode in barcodes) { when (barcode.valueType) { Barcode.TYPE_URL -> println("URL: ${barcode.url?.url}") Barcode.TYPE_WIFI -> println("WiFi: ${barcode.wifi?.ssid}") Barcode.TYPE_TEXT -> println(barcode.rawValue) } } ``` ### ML Kit β€” Face detection ```kotlin val options = FaceDetectorOptions.Builder() .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST) .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL) .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL) .build() val detector = FaceDetection.getClient(options) val faces = detector.process(image).await() for (face in faces) { val bounds = face.boundingBox val smilingProb = face.smilingProbability ?: 0f val leftEye = face.getLandmark(FaceLandmark.LEFT_EYE)?.position } ``` ### ML Kit β€” Translation ```kotlin implementation("com.google.mlkit:translate:17.0.2") val options = TranslatorOptions.Builder() .setSourceLanguage(TranslateLanguage.KOREAN) .setTargetLanguage(TranslateLanguage.ENGLISH) .build() val translator = Translation.getClient(options) translator.downloadModelIfNeeded().await() val translation = translator.translate("μ•ˆλ…•").await() // β†’ "Hello" ``` ### ML Kit β€” Pose / Body ```kotlin implementation("com.google.mlkit:pose-detection:18.0.0-beta3") val options = PoseDetectorOptions.Builder() .setDetectorMode(PoseDetectorOptions.STREAM_MODE) .build() val detector = PoseDetection.getClient(options) val pose = detector.process(image).await() val nose = pose.getPoseLandmark(PoseLandmark.NOSE)?.position val leftWrist = pose.getPoseLandmark(PoseLandmark.LEFT_WRIST)?.position ``` β†’ Fitness app. ### MediaPipe (advanced) ```kotlin implementation("com.google.mediapipe:tasks-vision:0.10.20") val options = ImageClassifier.ImageClassifierOptions.builder() .setBaseOptions(BaseOptions.builder().setModelAssetPath("model.tflite").build()) .setMaxResults(5) .build() val classifier = ImageClassifier.createFromOptions(context, options) val result = classifier.classify(MPImage.fromBitmap(bitmap)) for (cat in result.classifications()[0].categories()) { println("${cat.categoryName()}: ${cat.score()}") } ``` β†’ Custom TFLite λͺ¨λΈ. ### Gemini Nano (on-device, Pixel 9+) ```kotlin implementation("com.google.ai.edge.aicore:aicore:0.0.1-exp01") val options = generationConfig { context = context // Activity / Application temperature = 0.2f topK = 16 maxOutputTokens = 256 } val generativeModel = GenerativeModel(generationConfig = options) val response = generativeModel.generateContent("Summarize this article: ...").text ``` β†’ Cloud 호좜 없이. Privacy + offline + free. β†’ ⚠️ Pixel 9 / 일뢀 device 만. Compatibility check. ### Health Connect setup ```kotlin implementation("androidx.health.connect:connect-client:1.1.0-alpha07") ``` ```xml ``` ```kotlin import androidx.health.connect.client.HealthConnectClient import androidx.health.connect.client.records.StepsRecord import androidx.health.connect.client.permission.HealthPermission val healthConnectClient = HealthConnectClient.getOrCreate(context) val permissions = setOf( HealthPermission.getReadPermission(StepsRecord::class), HealthPermission.getWritePermission(StepsRecord::class), ) // Request val launcher = registerForActivityResult( PermissionController.createRequestPermissionResultContract() ) { granted -> /* ... */ } launcher.launch(permissions) ``` ### Read steps ```kotlin val response = healthConnectClient.readRecords( ReadRecordsRequest( recordType = StepsRecord::class, timeRangeFilter = TimeRangeFilter.between( Instant.now().minusSeconds(3600 * 24), Instant.now() ) ) ) val totalSteps = response.records.sumOf { it.count } ``` ### Write steps ```kotlin healthConnectClient.insertRecords(listOf( StepsRecord( count = 5000, startTime = Instant.now().minusSeconds(3600), endTime = Instant.now(), startZoneOffset = ZoneOffset.UTC, endZoneOffset = ZoneOffset.UTC, ) )) ``` ### Aggregation ```kotlin val agg = healthConnectClient.aggregate( AggregateRequest( metrics = setOf(StepsRecord.COUNT_TOTAL), timeRangeFilter = TimeRangeFilter.between(start, end) ) ) val total = agg[StepsRecord.COUNT_TOTAL] ?: 0L ``` ### Background sync ```kotlin class HealthSyncWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) { override suspend fun doWork(): Result { val steps = readSteps() syncToServer(steps) return Result.success() } } // Schedule val request = PeriodicWorkRequestBuilder(15, TimeUnit.MINUTES).build() WorkManager.getInstance(ctx).enqueueUniquePeriodicWork("health-sync", KEEP, request) ``` ### Privacy ``` - λͺ…μ‹œμ  μ‚¬μš©μž consent - Data minimization (read only what needed) - μ‚¬μš©μž κ°€ access / delete κ°€λŠ₯ - GDPR / HIPAA compliance (US) ``` ### CameraX + ML Kit ```kotlin val analyzer = ImageAnalysis.Analyzer { imageProxy -> val mediaImage = imageProxy.image ?: return@Analyzer val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) barcodeScanner.process(image) .addOnSuccessListener { barcodes -> /* ... */ } .addOnCompleteListener { imageProxy.close() } } ``` β†’ Real-time camera + ML. ### TFLite (custom λͺ¨λΈ) ```kotlin implementation("org.tensorflow:tensorflow-lite:2.16.1") val interpreter = Interpreter(loadModelFile()) val input = ByteBuffer.allocateDirect(...) val output = ByteBuffer.allocateDirect(...) interpreter.run(input, output) ``` β†’ 자체 λͺ¨λΈ (TF / PyTorch β†’ TFLite). ### Audio classification ```kotlin implementation("com.google.mediapipe:tasks-audio:0.10.20") val options = AudioClassifier.AudioClassifierOptions.builder() .setBaseOptions(BaseOptions.builder().setModelAssetPath("yamnet.tflite").build()) .build() val classifier = AudioClassifier.createFromOptions(context, options) val result = classifier.classify(MPAudioData.create(buffer, sampleRate)) for (cat in result.classifications()[0].categories()) { println("${cat.categoryName()}: ${cat.score()}") // "Music", "Speech", "Bark", ... } ``` ### Subject segmentation (BG removal) ```kotlin implementation("com.google.mlkit:subject-segmentation:16.0.0-beta1") val segmenter = SubjectSegmentation.getClient() val result = segmenter.process(image).await() val foregroundBitmap = result.foregroundBitmap // λ°°κ²½ X β€” μ‚¬μš©μž / μ‚¬λžŒ 만 ``` ### Document scanner ```kotlin implementation("com.google.android.gms:play-services-mlkit-document-scanner:16.0.0-beta1") val options = GmsDocumentScannerOptions.Builder() .setGalleryImportAllowed(true) .setPageLimit(5) .setResultFormats(RESULT_FORMAT_PDF, RESULT_FORMAT_JPEG) .build() GmsDocumentScanning.getClient(options) .getStartScanIntent(activity) .addOnSuccessListener { intent -> startActivityForResult(intent, ...) } ``` β†’ Document scan + auto crop + PDF. ### Smart reply (chat) ```kotlin implementation("com.google.mlkit:smart-reply:17.0.4") val smartReply = SmartReply.getClient() val conversation = listOf( TextMessage.createForRemoteUser("Hi", System.currentTimeMillis(), "user_1"), ) val result = smartReply.suggestReplies(conversation).await() for (suggestion in result.suggestions) { println(suggestion.text) // "Hi!", "Hello", "Hey there" } ``` ### Battery / performance ``` On-device ML = 빠름 + free + private. But: - Battery μ‚¬μš© - Memory - Model size (10-100 MB) β†’ μΈ‘μ • + throttle. ``` ### Cloud vs on-device ``` On-device: + Free (no API cost) + Private (no upload) + Offline + Low latency - Limited model size - Battery / memory Cloud (Vertex AI / Gemini API): + Bigger / better model + Always updated - Cost - Privacy - Latency ``` β†’ 일반 task = on-device. Complex / accuracy critical = cloud. ### Edge AI (modern stack) ``` 1. Quick task: ML Kit (built-in) 2. Custom: MediaPipe + TFLite 3. LLM: Gemini Nano (Pixel 9+) 4. Cloud fallback: Gemini API ``` ## πŸ€” μ˜μ‚¬κ²°μ • κΈ°μ€€ | μž‘μ—… | μΆ”μ²œ | |---|---| | OCR / barcode / face | ML Kit | | 자체 λͺ¨λΈ | MediaPipe / TFLite | | On-device LLM | Gemini Nano (Pixel 9+) | | Health data | Health Connect | | 일반 LLM | Cloud (Gemini API) | | Real-time | CameraX + ML Kit | ## ❌ μ•ˆν‹°νŒ¨ν„΄ - **λͺ¨λ“  κ±° cloud LLM**: cost / privacy. - **Health Connect κΆŒν•œ ν•œ 번 + λͺ¨λ“  κ±°**: minimum access. - **PII model ν•™μŠ΅ μ™ΈλΆ€ send**: privacy violation. - **Gemini Nano + λͺ¨λ“  device**: compatibility check. - **Battery λ¬΄μ‹œ**: μ‚¬μš©μž 끄기. - **λͺ¨λΈ download 큰 + first launch**: progressive. ## πŸ€– LLM ν™œμš© 힌트 - ML Kit = κ°€μž₯ λ‹¨μˆœ + λΉ λ₯Έ μ‹œμž‘. - Health Connect = cross-app data. - MediaPipe = custom + advanced. - Gemini Nano = privacy-friendly LLM. ## πŸ”— κ΄€λ ¨ λ¬Έμ„œ - [[Android_CameraX_Patterns]] - [[AI_Local_LLM_Inference]] - [[Mobile_Push_Deep]]