Files
2nd/10_Wiki/Topics/Coding/Android_ML_Kit_Health.md
T
2026-05-09 22:47:42 +09:00

11 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
android-ml-kit-health Android ML Kit / Health Connect / On-device AI Coding draft B conceptual 2026-05-09 2026-05-09
android
mlkit
health
on-device
vibe-coding
language applicable_to
Kotlin
Android
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

implementation("com.google.mlkit:text-recognition:16.0.0")
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

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

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

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

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)

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+)

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

implementation("androidx.health.connect:connect-client:1.1.0-alpha07")
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.health.READ_STEPS" />
<uses-permission android:name="android.permission.health.WRITE_STEPS" />
<uses-permission android:name="android.permission.health.READ_HEART_RATE" />

<queries>
    <package android:name="com.google.android.apps.healthdata" />
</queries>
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

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

healthConnectClient.insertRecords(listOf(
    StepsRecord(
        count = 5000,
        startTime = Instant.now().minusSeconds(3600),
        endTime = Instant.now(),
        startZoneOffset = ZoneOffset.UTC,
        endZoneOffset = ZoneOffset.UTC,
    )
))

Aggregation

val agg = healthConnectClient.aggregate(
    AggregateRequest(
        metrics = setOf(StepsRecord.COUNT_TOTAL),
        timeRangeFilter = TimeRangeFilter.between(start, end)
    )
)

val total = agg[StepsRecord.COUNT_TOTAL] ?: 0L

Background sync

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<HealthSyncWorker>(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

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 모델)

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

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)

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

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)

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.

🔗 관련 문서