Skip to content

小阳心健康测量 Android SDK

安装 SDK

导入 SDK

管理控制台/许可证管理/SDK许可证处添加最新版本 Android SDK 即可获得 SDK 下载链接。

将 SDK 压缩包解压并保持目录结构拷贝到您的 Android 项目中即可。

导入SDK文件

项目配置

  • build.gradle

    groovy
    android {
        defaultConfig {
            //...
            ndk {
                abiFilters "arm64-v8a", "armeabi-v7a", "armeabi"
            }
        }
        packagingOptions {
            jniLibs {
                pickFirsts += ['lib/x86/libc++_shared.so', 'lib/arm64-v8a/libc++_shared.so', 'lib/armeabi-v7a/libc++_shared.so', 'lib/x86_64/libc++_shared.so']
            }
            resources {
                excludes += ['META-INF/DEPENDENCIES', 'META-INF/library_release.kotlin_module']
            }
        }
    }
    
    implementation(files('libs/measurement_android_sdk_v2.0.0beta0.aar'))
    implementation 'com.squareup.okhttp3:okhttp:3.12.0'
    implementation 'com.google.code.gson:gson:2.6.2'
    //协议缓冲区
    implementation 'com.google.protobuf:protobuf-java:3.13.0'
    // bzmedia
    implementation 'io.github.bookzhan:bzyuv:1.1.17@aar'
    implementation 'io.github.bookzhan:bzcommon:1.1.14@aar'
    implementation 'io.github.bookzhan:bzcamera:1.0.22@aar'
    implementation 'io.github.bookzhan:bzmedia:1.0.8@aar'
    //websocket
    implementation 'com.microsoft.signalr:signalr:5.0.15'
    //messagepack
    implementation 'com.microsoft.signalr.messagepack:signalr-messagepack:5.0.15'
    implementation 'com.blankj:utilcodex:1.30.2'
    //阿里巴巴人脸识别
    implementation 'com.alibaba.android.mnnkit:facedetection:0.0.5'
    implementation ('com.google.mediapipe:tasks-vision:0.10.0'){
        exclude group: 'com.google.protobuf'
    }
    //libyuv图像分析库
    implementation 'com.github.GitLqr:LQRLibyuv:1.0.0'
    //google mediaPipe人脸识别库
    implementation ('com.google.mediapipe:tasks-vision:0.10.0'){
        exclude group: 'com.google.protobuf'
    }
  • manifest.xml

    xml
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    <uses-featureandroid:glEsVersion="0x00020000"android:required="true" />

    可根据自己设备硬件性能,在application节点或activity节点开启加速配置。

初始化

初始化测量对象以进行后续健康测量。

测量配置

初始化测量前需要先创建一个MeasurementConfig对象,用于配置测量的相关参数。

java
MeasurementConfig(Context context, String appId, String sdkKey)

IMPORTANT

建议将配置对象缓存以供每次测量共享。

参数说明

名称类型必需示例值描述
contextContext-Application上下文
appIdstr-APP_ID 从管理控制台获取
sdkKeystr-SDK_KEY 从管理控制台获取

高级配置

如果需要自定义测量相关其它高级参数,可以通过如下方式进行配置,如不确定,请勿修改

方法名称参数说明描述
setMeasurementCategory(Category.MeasurementCategory... measurementCategory)测量类型设置单次测量关注的所有测量类型指标
setMinChunkTimeSpan(int minChunkTimeSpan)阶段测量最小时长设置阶段测量最小时长(毫秒)
setMeasurementDuration(int measurementDuration)测量时长设置测量时长(毫秒)
setServer(@NonNull String measurementUrl, @NonNull String authUrl,@NonNull boolean isIntranet)测量服务URL,认证服务URL,是否为内网部署环境设置服务地址(仅供私有化网络部署使用)

测量类型说明

  • 指定单次测量关注的所有测量类型指标,缺省值为Category.MeasurementCategory.All

  • 默认提供“生理测量”和“情绪测量”两种预设类型。

  • 生理测量包括 心率报告(心率、心率变异率)、房颤报告(房颤)、血压报告(舒张压、收缩压)、血氧报告(血氧饱和度)、风险报告(心脏病风险、中风风险、心血管疾病风险、心脏压力、血管功能)、基础报告(性别、皮肤年龄、体重指数)、综合心健康风险报告(综合心健康风险)。

  • 情绪测量包含 心率报告(心率)、攻击性报告(攻击性)、焦虑度报告(焦虑度)、活力度报告(活力度)、抑郁度报告(抑郁度)、疲劳度报告(疲劳度)、压力度报告(压力度)、情绪综合分报告(情绪综合分)。

  • 如果预设类型不满足,您也可以根据MeasurementCategory类型传入关注的指标分类。

    测量类型(SDK内部包含)定义如下:

    java
    public enum MeasurementCategory {
        //心率房颤
        HeartRate(1),
        //血压
        BloodPressure(2),
        //血氧
        BloodOxygen(4),
        //健康风险
        Risk(8),
        //攻击性
        Aggressivity(16),
        //焦虑度
        Anxiety(32),
        //压抑度[已废弃]
        Depression(64),
        //活力度
        Vitality(128),
        //积极性[已废弃]
        Positivity(256),
        //抑郁度
        Suppression(512),
        //疲劳度
        Fatigue(1024),
    
        //生理测量
        Physiology(15),
        //情绪测量
        Emotion(1713),
        //全部指标
        All(1727)
    }

    WARNING

    测量类型参数将受限于您所购许可证绑定的测量类型,这意味着该参数应为许可证所授权测量类型的子集。

示例代码

如下示例中IMeasurementConfig对象在Application.onCreate中创建并缓存。

java
public class DemoApp extends Application {
    public static IMeasurementConfig config;

    @Override
    public void onCreate() {
        super.onCreate();
        config = new MeasurementConfig(getApplicationContext(), "APP_ID", "SDK_KEY");
    }
}
java
public class DemoApp extends Application {
    public static IMeasurementConfig config;

    @Override
    public void onCreate() {
        super.onCreate();
        config = new MeasurementConfig(getApplicationContext(), "APP_ID", "SDK_KEY");
        config.setMeasurementCategory(Category.MeasurementCategory.Physiology);
    }
}
java
public class DemoApp extends Application {
    public static IMeasurementConfig config;

    @Override
    public void onCreate() {
        super.onCreate();
        config = new MeasurementConfig(getApplicationContext(), "APP_ID", "SDK_KEY");
        config.setMeasurementCategory(Category.MeasurementCategory.Emotion);
    }
}
java
public class DemoApp extends Application {
    public static IMeasurementConfig config;

    @Override
    public void onCreate() {
        super.onCreate();
        config = new MeasurementConfig(getApplicationContext(), "APP_ID", "SDK_KEY");
        //仅关注 “血氧/焦虑” 指标
        config.setMeasurementCategory(Category.MeasurementCategory.BloodPressure, Category.MeasurementCategory.Anxiety);
    }
}

初始化测量

测量过程依赖特征提取器以进行人脸特征定位等,SDK 支持MediaPipeMNN作为特征提取器。前者在数据准确性方面更优,后者对老旧设备的兼容支持更好,需要特别注意的是,选择MNN作为特征提取器时仅能获得生理类型的测量指标。推荐选择MediaPipe作为特征提取器,在老旧设备或性能较差的设备上选择MNN

IMPORTANT

根据不同设备性能,MediaPipe初次初始化速度可能较慢,建议在APP初始化时预加载MediaPipe对象并缓存,以减少测量初始化时间。

方法

java
//使用MediaPipe作为特征提取器
Measurement(IMeasurementConfig measurementConfig, FeatureExtractor featureExtractor);

//使用MNN作为特征提取器
Measurement(IMeasurementConfig measurementConfig);

参数说明

名称类型必需示例值描述
measurementConfigIMeasurementConfig-必要测量配置
featureExtractorFeatureExtractor是(MediaPipe作为特征提取时)-特征提取器预加载对象

事件订阅

在测量过程中的关键步骤Measurement对象会通过不同的事件暴露相应数据给用户,用户可以按需订阅相应事件。

名称参数触发时机
onStarted测量ID开始测量成功
onStateUpdate测量异常状态集合测量状态更新
onFrameProcessed视频帧(处理后),时间戳,宽度,高度视频帧处理完成
onCollected-数据采集完毕
onChunkReportGenerated测量ID,阶段性报告阶段性报告生成
onWholeReportGenerated测量ID,完整报告完整报告生成
onInterrupted-测量已中断
onCrashed异常对象测量出现异常

具体事件详解参见后续章节。

示例代码

java
public class DemoApp extends Application {
    public static IMeasurementConfig config;
    public static FeatureExtractor featureExtractor = null;

    @Override
    public void onCreate() {
        super.onCreate();
        config = new MeasurementConfig(getApplicationContext(), "APP_ID", "SDK_KEY");
        DemoApp.featureExtractor = new FeatureExtractor(this, new   InitCallback() {
            @Override
            public void onInitialized() {}

            @Override
            public void onError(Exception e) {}
        });
    }
}

public class SwitchActivity extends AppCompatActivity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_switch);
        initMeasurement();
    }

    private void initMeasurement() {
        measurement = new Measurement(DemoApp.config, DemoApp.faceLandmarkerHelper);
        measurement.setMeasurementListener(new IMeasurementListener() {
            @Override
            public void onStarted(String measurementId) {
            }
            @Override
            public void onStateUpdated(Map<String, ExceptionBean> warnings) {
            }
            @Override
            public void onFrameProcessed(byte[] nv21, long timestamp, int width, int height) {
            }
            @Override
            public void onCollected() {
            }
            @Override
            public void onChunkReportGenerated(String measurementId, MeasurementReport.HrReport hrReport){
            }
            @Override
            public void onWholeReportGenerated(String measurementId, MeasurementReport.Report report) {
            }
            @Override
            public void onInterrupted() {
            }
            @Override
            public void onCrashed(ExceptionBean exception) {
            }
        });
    }
}

测量检测

建议在测量开始前通过validate方法对测量环境进行检测(非强制),用户可以根据返回的校验问题修正测量条件以提高测量成功率与准确性。 测量要求参阅 健康测量条件及要求

方法

java
Map<String, ExceptionBean> validate(byte[] frame, long timestamp, int width, int height, int displayOrientation)

参数说明

名称类型必需描述
framebyte[]视频帧
timestamplong时间戳(务必在获得视频帧的第一时间记录时间戳,精确到毫秒的UNIX时间戳
widthint视频帧宽度
heightint视频帧高度
displayOrientationint视频帧角度(使输入图像顺时针旋转的角度,旋转后人脸变为正向,正向为0)
  • 视频帧仅用于校验是否满足基本测量条件,测量服务不会保存用户数据。
  • 请勿频繁进行测量检测,测量过程中会自动进行测量检测,无需手动检测。

校验结果

校验结果所暴露对象的Level属性反映了错误级别,Msg/Msg_cn为详细错误消息,建议用户根据错误原因做出相应调整再次检测,通过后再启动测量。

java
Map<String, ExceptionBean> warnings = measurement.validate(frame, timestamp, width, height, mCameraOrientation);

while (warnings.entrySet().iterator().hasNext()){
    Map.Entry<String, ExceptionBean> bean = warnings.entrySet().iterator().next();
    MyLog.d(TAG, String.format("Exception Key: %s, Message: %s, Message CN: %s, Level: %s", 
                            entry.getKey(), bean.getMsg(), bean.getMsg_cn(), bean.getLevel()));
}

启动测量

启动测量,成功会触发onStarted事件并返回measurement_id(本次测量唯一标识),失败则会触发onCrashed事件。

方法

java
void start()

示例代码

java
@Override
public void onStarted(String measurementId) {
    MyLog.d(measurementId)
}

measurement.start();

采集数据

测量成功开始后,用户需要从摄像头、视频文件等采集视频帧并提供给 SDK 进行图像分析,视频帧需要保持连续,且帧率不能太低,建议15-30帧每秒。视频帧采集满足条件后会触发onCollected事件,此时可以停止采集数据。

方法

java
void enqueue(byte[] frame, long timestamp, int width, int height, int displayOrientation);

参数说明

名称类型必需描述
framebyte[]视频帧
timestamplong时间戳(务必在获得视频帧的第一时间记录时间戳,精确到毫秒的UNIX时间戳
widthint视频帧宽度
heightint视频帧高度
displayOrientationint视频帧角度(使输入图像顺时针旋转的角度,旋转后人脸变为正向,正向为0)

视频帧中必须包含清晰人脸,人脸需要保持正脸,光线明亮均匀。视频帧务必保持连续,切勿跳帧。

示例代码

java
@Override
public void onCollected() {
}

measurement.enqueue(frame, timestamp, width, height, mCameraOrientation);

状态监测

测量过程中 SDK 会自动检测数据质量并通过onStateUpdated事件发布,用户可以按需订阅此事件,以获得告警信息并处理。

IMPORTANT

未处理的告警累计可能会导致测量中断。

示例代码

java
@Override
public void onStateUpdated(Map<String, ExceptionBean> warnings) {
   while (warnings.entrySet().iterator().hasNext()){
    Map.Entry<String, ExceptionBean> bean = warnings.entrySet().iterator().next();
    MyLog.d(TAG, String.format("Exception Key: %s, Message: %s, Message CN: %s, Level: %s", 
                            entry.getKey(), bean.getMsg(), bean.getMsg_cn(), bean.getLevel()));
}

视频处理

测量过程中 SDK 会自动顺序处理队列中的视频帧,每成功处理一帧会通过onFrameProcessed事件发布,用户可以按需订阅此事件,以进行必要的数据留存(如录制视频文件等)。

示例代码

java
@Override
public void onFrameProcessed(byte[] nv21, long timestamp, int width, int height) {
    //录制视频等自定义操作
}

订阅报告

测量成功开始后,SDK 会在接收到足够数据的情况下请求测量服务生成健康报告。测量过程中会产生阶段性报告并通过onChunkReportGenerated事件发布,测量结束后会产生完整健康报告并通过onWholeReportGenerated事件发布。

用户可以按需订阅onChunkReportGeneratedonWholeReportGenerated事件以获得健康报告。

java
@Override
public void onChunkReportGenerated(String measurementId, MeasurementReport.HrReport hrReport) {
    Log.d(TAG, "chunkReportHandler " + measurementId + " ,hrReport " + hrReport.toString());
}

@Override
public void onWholeReportGenerated(String measurementId, MeasurementReport.Report report) {
    Log.d(TAG, "wholeReportHandler " + measurementId + " ,measurementReport " + measurementReport.toString());
}

报告解读

请参见 健康报告解读

中断测量

在测量过程中可以随时终止测量。测量中断后会触发onInterrupted事件。

方法

java
void interrupt()

示例代码

java
@Override
public void onInterrupted() {
}

measurement.interrupt();

异常处理

启动测量后,任意环节出现异常均会通过onCrashed事件发布,用户可以按需订阅此事件,以获得错误信息并处理。

示例代码

java
@Override
public void onCrashed(ExceptionBean exception) {
   Log.e(Constants.TAG, "onException: " + exception.getMsg_cn() + " level: " + exception.getLevel());
}

onCrashed事件处理函数中所暴露对象的Level属性反映了当前异常级别,如果异常级别为Error则 SDK 会自动终止本次测量,用户需根据错误原因做出相应调整后重新开始新的测量。

示例程序

示例程序代码已分享至GitHub,仅供参考。

Released under the MIT License.