반응형

[240605] 

 

현재 헬스커넥트가 내장된 안드로이드 14 ? 일듯한 샤오미 태블릿에서 
어플을 작동시켜봤을 때, 
업데이트를 하고, 권한을 주어도 동작하지 않고, 무한 로딩에 걸림

 

  권한 허용 권한 제한 
업데이트 함 X X
업데이트 안함 X X

 

걍 어떻게 해도 안돼서 그냥 주석처리했다는 사람.. 
아무튼 아래 코드는 healthconnect app은 android 12 버전을 기준으로 합니다. 


안녕하세요.. 

헬스커넥트에 대해 아시나요

삼성과 협업해서 만든 운동 건강 기록을 관리하는 AOS의 기본 어플 포지션으로 가는 데요.

 

근데 이 헬커

okky, 오픈채팅방이든 자료를 찾아서 구현하려고 했는데

어디든지 물어봐도 답이 없어서 힘들었다는 점.

 

암튼 어플은 현재 나오는 최신 14버전? 들은 탑재가 된다는데..

테스트 기기인 S10은 android 12 라서 기본으로 탑재도 안됐었거든요? 

그래서 설치한 후에 급하게 그래프에 넣을 30분당 걸음수를 위해 구현을 열심히 돌려봤는데

 

결과는 실패

공기계라서 데이터가 없어서 그런건가? 싶어서
내가 출퇴근할 때마다 들고 다녔는데도몰?루 ..  

 

암튼 저에게 생긴 이슈는 

집계데이터가 개 엉터리로 가져와짐. 24시간동안 30분단위로 걸음 수를 가져오는 함수를 동작했을 때
6, 6, 6, 6, 6,6 ,6 , 6, 13, 6, 6, 6, .. 
아니면 
73,73,73,73,73,73,42,131,73... 
이러고 있음 


암튼 개열받고 어이없는데 뭐 오류가 난 건 아니니까
내가 뭐 핸드폰 센서 뜯어서 할수도 없는데 어케하누?

 

각설하고 그래도 총 걸음수랑 소모 칼로리는 잘 가져와지니 이거라도 포스팅 해보겠읍니다

원시 데이터 - 총 걸음 수  O
원시 데이터 - 총 소모 칼로리  O
집계 데이터 - 시간당, 30분당 걸음 수  X

> 0 세팅

# 0 - 1 의존성 추가 

implementation("androidx.health.connect:connect-client:1.1.0-alpha07")

 

2024.05.19 기준 현재 alpha07 버전 

https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata&hl=ko&gl=US&pli=1

 

헬스 커넥트 - Google Play 앱

건강, 피트니스, 웰빙 앱 간에 간편하게 데이터를 공유하세요.

play.google.com

# 0 - 2 기기에서 앱이 필요한데 링크가 필요함

// ------! 앱이 설치 안됐을 때 연결해주는 주소 !------
val providerPackageName = "com.google.android.apps.healthdata"
val uriString = "market://details?id=$providerPackageName&url=healthconnect%3A%2F%2Fonboarding"

 

#  0 - 3 manifest 추가 필요 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
...
    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
    <uses-permission android:name="android.permission.health.READ_HEART_RATE"/>
    <uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/>
    <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_TOTAL_CALORIES_BURNED"/>
    <uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED"/>
    <application
...
        <activity
            ...
            <intent-filter>
            ...
                <action android:name="android.intent.action.MAIN" />
                <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
             ...
            </intent-filter>
            
            ...
            
    </application>
    ... 
    // 어플리케이션 태그 밖에 queries 추가
    <queries>
        <package android:name="com.google.android.apps.healthdata" />
    </queries>
</manifest>

 

세팅 완료? 

 

추가적으로
저렇게 필요한 것들(걸음수, 칼로리 등)만 추가했는데, 본인이 원하는 데이터에 맞게, 헬스커넥트 어플에 들어가셔서 권한을 추가하면 됨다

 

 

# 1 - 1 권한 설정 

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding
    val mViewModel : MeasureViewModel by viewModels()
    
    // ------! 추가해야하는 곳 !------
    lateinit var requestPermissions : ActivityResultLauncher<Set<String>>
    lateinit var  healthConnectClient : HealthConnectClient
    val endTime = LocalDateTime.now()
    val startTime = LocalDateTime.now().minusDays(1)
    val PERMISSIONS =
        setOf(
            HealthPermission.getReadPermission(HeartRateRecord::class),
            HealthPermission.getWritePermission(HeartRateRecord::class),
            HealthPermission.getReadPermission(StepsRecord::class),
            HealthPermission.getWritePermission(StepsRecord::class),
            HealthPermission.getWritePermission(TotalCaloriesBurnedRecord::class),
            HealthPermission.getReadPermission(TotalCaloriesBurnedRecord::class),
        )
  • 일단 해당 Activity에서 권한 설정을 얻을 수 있게 PERMISSIONS를 추가
  • 데이터를 가져올 시간대 범위를 설정합니다. 

# 1 - 2 앱 설치 여부 확인 

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ...
        
        val providerPackageName = "com.google.android.apps.healthdata"
        val availabilityStatus = HealthConnectClient.getSdkStatus(this, providerPackageName )
        if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) {
            return // 실행 가능한 통합이 없기 때문에 조기 복귀
        }
        if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) {
            // 선택적으로 패키지 설치 프로그램으로 리디렉션하여 공급자를 찾음.
            val uriString = "market://details?id=$providerPackageName&url=healthconnect%3A%2F%2Fonboarding"
            this.startActivity(
                Intent(Intent.ACTION_VIEW).apply {
                    setPackage("com.android.vending")
                    data = Uri.parse(uriString)
                    putExtra("overlay", true)
                    putExtra("callerId", this)
                }
            )
            return
        }
        healthConnectClient = HealthConnectClient.getOrCreate(this)
        Log.v("현재 시간", "endTime: $endTime, startTime: $startTime")
        
        // Create the permissions launcher
        val requestPermissionActivityContract = PermissionController.createRequestPermissionResultContract()
        requestPermissions = registerForActivityResult(requestPermissionActivityContract) { granted ->
            lifecycleScope.launch {
                if (granted.containsAll(PERMISSIONS)) {
                    Log.v("PermissionO", "$healthConnectClient")
                    aggregateData(healthConnectClient)
                } else {
                    Log.v("PermissionX", "$healthConnectClient")
                    checkPermissionsAndRun(healthConnectClient)
                }
            }
        }

SdkStatus를 체크해서 앱이 미설치 상태일 경우, 플레이 스토어로 자동 연결됩니다. 

 

그리고, 설치가 됐을 경우 헬스커넥트 앱에서 권한을 확인하고 데이터를 가져올 함수가 필요합니다

 

# 1 - 3 외부 앱 권한 설정 확인 

private suspend fun checkPermissionsAndRun(healthConnectClient: HealthConnectClient) {
        val granted = healthConnectClient.permissionController.getGrantedPermissions()
        if (granted.containsAll(PERMISSIONS)) {
			// 권한 확인 후 aggregateData 함수 시작 
            aggregateData(healthConnectClient)
        } else {
            requestPermissions.launch(PERMISSIONS)
        }
    }

 

 

# 1 - 4 권한 확인 후 , HealthConnectClient에서 사용할 기능을 담은 함수 만들기

private suspend fun aggregateData(healthConnectClient: HealthConnectClient) {
        // (선택) Instant로 변환해서 시간 범위 설정 
        val startTimeInstant = startTime.atZone(ZoneId.of("Asia/Seoul")).toInstant()
        val endTimeInstant = endTime.atZone(ZoneId.of("Asia/Seoul")).toInstant()
       
    	// 30분단위 걸음 수 집계 (엉터리로 가져와 짐..), 총 걸음수 , 총 칼로리
        aggregateStepsInto3oMins(healthConnectClient, startTime, endTime)
        readStepsByTimeRange(healthConnectClient, startTimeInstant, endTimeInstant)
        readCaloryByTimeRange(healthConnectClient, startTimeInstant, endTimeInstant)
    }

 

> 2 걸음수, 칼로리 가져오기 

private suspend fun readCaloryByTimeRange(
        healthConnectClient: HealthConnectClient,
        startTime: Instant,
        endTime: Instant
    ) {
        try {
            val response = healthConnectClient.readRecords(
                ReadRecordsRequest(
                    TotalCaloriesBurnedRecord::class,
                    timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
                )
            )
            val energy = response.records[0].energy.toString()
            Log.v("Total Calory", energy)
            mViewModel.calory.value = Math.round(energy.split(" ")[0].toDouble()).toString() + " Kcal"

        } catch (e: Exception) {
            Log.v("Calory Exception", "$e")
        }
    }
	private suspend fun readStepsByTimeRange(
        healthConnectClient: HealthConnectClient,
        startTime: Instant,
        endTime: Instant
    ) { try {
        val response = healthConnectClient.readRecords(
            ReadRecordsRequest(
                StepsRecord::class,
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
            )
        )
        mViewModel.totalSteps.value = response.records[0].count.toString()

        Log.v("Total Steps", "${mViewModel.totalSteps.value}")
    } catch (e: Exception) {
        Log.v("Total Steps Exception", "$e")
    }
    }

 

쫌 비슷하지 않나요? 
healthConnectClient에서 readRecords 해서 기록을 가져오는 형식. 
앱을 킬 때 이 healthConnectClient를 init 하지 않으면 어떤 값이든 가져와지지 않습니다.

마치 ble의 BluetoothAdapter와 유사한 사용법인 것 같아요 ( 블루투스는 GATT에,, 뭐에 아효) 

아무튼 저는 현재시간부터 minusDays(1)을 통해서 하루 동안의 기록을 가져오고 있어요.

그리고 시간 범위에서는 between 뿐만 아니라 시간 이전, 이후 까지 전부 가능합니다.

 

 

그리고 이 백그라운드 작업 함수들을 다 넣어놨으면 ? 

override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
            checkPermissionsAndRun(healthConnectClient)
        }
checkPermissionsAndRun 라는 함수를 실행하는건 lifecycleScope로 상태를 관찰해서 바로 동작하게끔 연결해놨구요.

이를 구현하지 않으면 앱을 최초 설치 시, 권한 허용 후에 바로 데이터를 가져와지지 않음.

아무튼 여기까지는 잘 동작하는 ( 제 프로젝트에서는..?) 코드인데요..


30분 단위의 집계함수는 절대 안돌아간다는 것.. 30분단위로 24시간이면 48개의 listOf가 나오긴하는데 
30분 동안 70보 걷는게 말이 되는 건지.. 

private suspend fun aggregateStepsInto3oMins(
        healthConnectClient: HealthConnectClient,
        startTime: LocalDateTime,
        endTime: LocalDateTime
    ) { try {
        val response = healthConnectClient.aggregateGroupByDuration(
            AggregateGroupByDurationRequest(
                metrics = setOf(StepsRecord.COUNT_TOTAL),
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
                timeRangeSlicer = Duration.ofMinutes(30L)
            )
        )
        val stepsList = mutableListOf<Long>()
        var previousSteps : Long? = null
        for (durationResult in response) {

            val totalSteps = durationResult.result[StepsRecord.COUNT_TOTAL]
            if (totalSteps != null) {
                if (previousSteps == null) {
                    stepsList.add(totalSteps)
                } else {
                    stepsList.add(totalSteps - previousSteps)
                }
                previousSteps = totalSteps
            } else {
                stepsList[0]
            }
            Log.v("30minSteps", "$totalSteps")
        }
        mViewModel.steps.value = stepsList
        Log.v("30minSteps list", "$stepsList")
        Log.v("hour응답", "${response.size}")
    } catch (e: Exception) {
        Log.v("30minSteps Exception", "$e")
    }
    }

 

개 열받아 혹시 

집계데이터 가져오는 방법 제가 놓친 부분 있으면 꼭 알려주시면 감사하겠읍니다.. 

 


모든 코드는 https://developer.android.com/health-and-fitness/guides/health-connect?hl=ko

 

헬스 커넥트  |  Android health & fitness  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 주요 장점 고객 평가 이 페이지에 나와 있는 콘텐츠와

developer.android.com

에서 왔다는 거 참고 부ㅡ탁

반응형