feat:添加下单日志

This commit is contained in:
dzc committed 2026-10-23 15:53:43 +08:00
1 parent e75b3bd58e
commit 531815517a
35 files changed
+2039 -220

No files matched your search

@@ -7,6 +7,7 @@ import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import androidx.annotation.NonNull;
@@ -388,7 +389,9 @@ public class FlutterFaceDiscernPlugin implements FlutterPlugin, MethodChannel.Me
result.success(busyStatus);
return;
}
long enqueuedAt = SystemClock.elapsedRealtime();
FACE_EXECUTOR.execute(() -> {
Log.i("FaceStatusPerf", "queueMs=" + (SystemClock.elapsedRealtime() - enqueuedAt));
try {
Map<String, Object> status = FlutterTool.getFaceStatus(context, appId, activeKey, sdkKey);
MAIN_HANDLER.post(() -> result.success(status));
@@ -8,6 +8,7 @@ import static io.flutter.plugins.facediscern.database.DataBaseUtils.getFaceProfi
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.SystemClock;
import io.flutter.Log;
@@ -88,6 +89,7 @@ public class FlutterTool {
}
static Map<String, Object> getFaceStatus(Context context, String appId, String activeKey, String sdkKey) {
long startedAt = SystemClock.elapsedRealtime();
updateConfig(appId, activeKey, sdkKey);
Map<String, Object> status = new HashMap<>();
ActiveFileInfo activeFileInfo = new ActiveFileInfo();
@@ -96,10 +98,12 @@ public class FlutterTool {
FaceServer faceServer = FaceServer.getInstance();
if (activated) faceServer.init(context);
long authInitCompletedAt = SystemClock.elapsedRealtime();
// 状态检查只需要数量与 sampleId 集合,走不含 faceModel 大字段的轻量查询,
// 避免整库特征 JSON 反序列化把调用线程(主线程)阻塞数秒。
// 查询已过滤空特征行:databaseCount 与一致性集合都只统计可注册样本。
List<FaceEntity> databaseFaces = getFaceProfiles(context, null);
long sqliteScanCompletedAt = SystemClock.elapsedRealtime();
int databaseCount = databaseFaces.size();
int memoryCount = activated && faceServer.isEngineReady() ? Math.max(faceServer.getTotalFaceCount(), 0) : 0;
boolean registrationInProgress = REGISTRATION_IN_PROGRESS.get();
@@ -122,8 +126,13 @@ public class FlutterTool {
status.put("memoryCount", memoryCount);
status.put("registrationInProgress", registrationInProgress);
status.put("libraryConsistent", !registrationInProgress && databaseSampleIds.equals(memorySampleIds));
long sdkCompareCompletedAt = SystemClock.elapsedRealtime();
status.put("registrationProgress", new HashMap<>(latestRegistrationProgress));
status.put("lastRegistrationResult", latestRegistrationResult == null ? null : new HashMap<>(latestRegistrationResult));
Log.i("FaceStatusPerf", "authInitMs=" + (authInitCompletedAt - startedAt)
+ ";sqliteScanMs=" + (sqliteScanCompletedAt - authInitCompletedAt)
+ ";sdkCompareMs=" + (sdkCompareCompletedAt - sqliteScanCompletedAt)
+ ";totalMs=" + (SystemClock.elapsedRealtime() - startedAt));
return status;
}
@@ -61,6 +61,7 @@ import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
/**
@@ -73,7 +74,7 @@ public class FaceHelper implements FaceListener {
/**
* 识别结果的回调
*/
private RecognizeCallback recognizeCallback;
private volatile RecognizeCallback recognizeCallback;
private volatile long sessionGeneration;
@@ -179,7 +180,8 @@ public class FaceHelper implements FaceListener {
/** 最近一帧达到相机面积占比的人脸;异步结果也必须仍在此集合内。 */
private volatile Set<Integer> areaEligibleTrackIds = Collections.emptySet();
private Disposable timerDisposable;
private final FaceNoticeDispatcher noticeDispatcher = new FaceNoticeDispatcher(
Schedulers.computation(), AndroidSchedulers.mainThread());
private FaceHelper(Builder builder) {
onlyDetectLiveness = builder.onlyDetectLiveness;
@@ -309,6 +311,7 @@ public class FaceHelper implements FaceListener {
public synchronized void stop() {
sessionGeneration++;
noticeDispatcher.cancel();
clearTrackingState();
}
@@ -343,7 +346,7 @@ public class FaceHelper implements FaceListener {
recognizeCallback = callback;
if (getFeatureDelayedDisposables != null) getFeatureDelayedDisposables.clear();
if (delayFaceTaskCompositeDisposable != null) delayFaceTaskCompositeDisposable.clear();
if (timerDisposable != null && !timerDisposable.isDisposed()) timerDisposable.dispose();
noticeDispatcher.cancel();
clearTrackingState();
}
@@ -799,19 +802,19 @@ public class FaceHelper implements FaceListener {
}
private void noticeCurrentStatus(String notice) {
if (recognizeCallback != null) {
recognizeCallback.onNoticeChanged(notice);
}
if (timerDisposable != null && !timerDisposable.isDisposed()) {
timerDisposable.dispose();
}
timerDisposable = Observable.timer(1500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(aLong -> {
if (recognizeCallback != null) {
recognizeCallback.onNoticeChanged("");
}
});
noticeCurrentStatus(notice, false);
}
private void noticeCurrentStatus(String notice, boolean success) {
final RecognizeCallback callback = recognizeCallback;
final long generation = sessionGeneration;
if (callback != null) callback.onNoticeChanged(notice);
noticeDispatcher.schedule(success, () -> {
// 主线程只消费原代次、原回调的结果;不持有 FaceHelper 锁再进入会话管理器。
if (generation == sessionGeneration && callback != null && callback == recognizeCallback) {
callback.onNoticeChanged("");
}
});
}
private void searchFace(final FaceFeature faceFeature, final Integer trackId,
@@ -842,8 +845,9 @@ public class FaceHelper implements FaceListener {
}
}
setName(trackId, msg);
noticeCurrentStatus(msg);
changeRecognizeStatus(trackId, RequestFeatureStatus.SUCCEED);
// 先写成功状态,再零延迟投递主线程,保留旧页面与会话的统一结果通知链。
noticeCurrentStatus(msg, true);
} else {
noticeCurrentStatus("未通过:NOT_REGISTERED");
retryRecognizeDelayed(trackId);
@@ -0,0 +1,41 @@
package io.flutter.plugins.facediscern.tool.face.help;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.disposables.Disposable;
/** 只调度提示清空:成功立即投递,失败提示仍保留 1.5 秒。 */
final class FaceNoticeDispatcher {
private final Scheduler timerScheduler;
private final Scheduler callbackScheduler;
private Disposable pending;
private volatile long revision;
FaceNoticeDispatcher(Scheduler timerScheduler, Scheduler callbackScheduler) {
this.timerScheduler = timerScheduler;
this.callbackScheduler = callbackScheduler;
}
synchronized void schedule(boolean success, Runnable clearNotice) {
cancel();
long expectedRevision = revision;
Observable<Long> clearSignal = success
? Observable.just(0L)
: Observable.timer(1500L, TimeUnit.MILLISECONDS, timerScheduler);
pending = clearSignal
.observeOn(callbackScheduler)
.subscribe(ignored -> {
if (revision == expectedRevision) clearNotice.run();
});
}
synchronized void cancel() {
revision++;
if (pending != null) {
pending.dispose();
pending = null;
}
}
}
@@ -21,7 +21,7 @@ public class RecognizeConfiguration {
public static final boolean DEFAULT_ENABLE_IMAGE_QUALITY = true;
public static final boolean DEFAULT_KEEP_MAX_FACE = true;
/** 原始 RGB 相机画面中允许开始识别的最小人脸面积百分比。 */
public static final double DEFAULT_MIN_FACE_AREA_PERCENT = 5.0;
public static final double DEFAULT_MIN_FACE_AREA_PERCENT = 3.0;
/**
* 产生特征提取失败示语的特征提取次数(小于该值不提示)
@@ -1,15 +1,18 @@
package io.flutter.plugins.facediscern.tool.face.filter;
import io.flutter.plugins.facediscern.tool.face.model.RecognizeConfiguration;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class FaceAreaRatioTest {
@Test public void defaultFivePercentIncludesBoundaryButRejectsSmallerFaces() {
assertFalse(FaceAreaRatio.meetsMinimum(0, 0, 199, 250, 1000, 1000, 5.0));
assertTrue(FaceAreaRatio.meetsMinimum(0, 0, 200, 250, 1000, 1000, 5.0));
assertTrue(FaceAreaRatio.meetsMinimum(0, 0, 201, 250, 1000, 1000, 5.0));
@Test public void defaultThreePercentIncludesBoundaryButRejectsSmallerFaces() {
double defaultPercent = RecognizeConfiguration.DEFAULT_MIN_FACE_AREA_PERCENT;
assertFalse(FaceAreaRatio.meetsMinimum(0, 0, 119, 250, 1000, 1000, defaultPercent));
assertTrue(FaceAreaRatio.meetsMinimum(0, 0, 120, 250, 1000, 1000, defaultPercent));
assertTrue(FaceAreaRatio.meetsMinimum(0, 0, 121, 250, 1000, 1000, defaultPercent));
}
@Test public void usesAreaOfFullRawFrameAcrossAspectRatiosAndRotations() {
@@ -0,0 +1,79 @@
package io.flutter.plugins.facediscern.tool.face.help;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import io.reactivex.schedulers.TestScheduler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class FaceNoticeDispatcherTest {
@Test public void successNeedsNoTimeAdvanceButStillUsesCallbackScheduler() {
TestScheduler timer = new TestScheduler();
TestScheduler callback = new TestScheduler();
FaceNoticeDispatcher dispatcher = new FaceNoticeDispatcher(timer, callback);
List<String> delivered = new ArrayList<>();
dispatcher.schedule(true, () -> delivered.add("success"));
assertTrue(delivered.isEmpty());
callback.triggerActions();
assertEquals(Arrays.asList("success"), delivered);
assertEquals(0L, timer.now(TimeUnit.MILLISECONDS));
}
@Test public void failureNoticeRetainsItsFullDisplayInterval() {
TestScheduler timer = new TestScheduler();
TestScheduler callback = new TestScheduler();
FaceNoticeDispatcher dispatcher = new FaceNoticeDispatcher(timer, callback);
List<String> delivered = new ArrayList<>();
dispatcher.schedule(false, () -> delivered.add("clear failure"));
timer.advanceTimeBy(1499, TimeUnit.MILLISECONDS);
callback.triggerActions();
assertTrue(delivered.isEmpty());
timer.advanceTimeBy(1, TimeUnit.MILLISECONDS);
assertTrue(delivered.isEmpty());
callback.triggerActions();
assertEquals(Arrays.asList("clear failure"), delivered);
}
@Test public void successReplacesAnOlderClearAlreadyQueuedForCallback() {
TestScheduler timer = new TestScheduler();
TestScheduler callback = new TestScheduler();
FaceNoticeDispatcher dispatcher = new FaceNoticeDispatcher(timer, callback);
List<String> delivered = new ArrayList<>();
dispatcher.schedule(false, () -> delivered.add("old failure"));
timer.advanceTimeBy(1500, TimeUnit.MILLISECONDS);
dispatcher.schedule(true, () -> delivered.add("success"));
timer.triggerActions();
callback.triggerActions();
assertEquals(Arrays.asList("success"), delivered);
timer.advanceTimeBy(10, TimeUnit.SECONDS);
callback.triggerActions();
assertEquals(Arrays.asList("success"), delivered);
}
@Test public void stoppingOrChangingSessionDiscardsAPendingSuccessCallback() {
TestScheduler timer = new TestScheduler();
TestScheduler callback = new TestScheduler();
FaceNoticeDispatcher dispatcher = new FaceNoticeDispatcher(timer, callback);
List<String> delivered = new ArrayList<>();
dispatcher.schedule(true, () -> delivered.add("old session"));
timer.triggerActions();
dispatcher.cancel();
callback.triggerActions();
assertTrue(delivered.isEmpty());
dispatcher.schedule(true, () -> delivered.add("new session"));
timer.triggerActions();
callback.triggerActions();
assertEquals(Arrays.asList("new session"), delivered);
}
}
@@ -14,15 +14,15 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
public class RecognizeConfigurationStoreTest {
@Test public void existingSettingsWithoutNewKeyUseFivePercent() {
@Test public void existingSettingsWithoutNewKeyUseThreePercent() {
Map<String, Object> values = new HashMap<>();
values.put("keepMaxFace", false);
values.put("similarThreshold", 0.91F);
RecognizeConfiguration configuration = RecognizeConfigurationStore.load(preferences(values));
assertEquals(5.0, configuration.getMinFaceAreaPercent(), 0.0);
assertEquals(3.0, configuration.getMinFaceAreaPercent(), 0.0);
assertFalse(configuration.isKeepMaxFace());
assertEquals(0.91F, configuration.getSimilarThreshold(), 0.0F);
assertEquals(5.0, (Double) RecognizeConfigurationStore.toMap(configuration).get("minFaceAreaPercent"), 0.0);
assertEquals(3.0, (Double) RecognizeConfigurationStore.toMap(configuration).get("minFaceAreaPercent"), 0.0);
}
@Test public void saveLoadPartialUpdateAndResetPreserveContract() {
@@ -36,7 +36,7 @@ public class RecognizeConfigurationStoreTest {
}
assertEquals(100.0, RecognizeConfigurationStore.save(preferences,
Collections.singletonMap("enableLiveness", true)).getMinFaceAreaPercent(), 0.0);
assertEquals(5.0, RecognizeConfigurationStore.reset(preferences).getMinFaceAreaPercent(), 0.0);
assertEquals(3.0, RecognizeConfigurationStore.reset(preferences).getMinFaceAreaPercent(), 0.0);
assertEquals(0, values.size());
}
@@ -55,11 +55,11 @@ public class RecognizeConfigurationStoreTest {
}
}
@Test public void malformedPersistedValueFallsBackToFivePercent() {
@Test public void malformedPersistedValueFallsBackToThreePercent() {
for (Object invalid : new Object[]{Float.NaN, Float.POSITIVE_INFINITY, -1F, 0F, 101F, "5", false}) {
Map<String, Object> values = new HashMap<>();
values.put("minFaceAreaPercent", invalid);
assertEquals(5.0, RecognizeConfigurationStore.load(preferences(values)).getMinFaceAreaPercent(), 0.0);
assertEquals(3.0, RecognizeConfigurationStore.load(preferences(values)).getMinFaceAreaPercent(), 0.0);
}
}
@@ -67,7 +67,7 @@ public class RecognizeConfigurationStoreTest {
RecognizeConfiguration original = new RecognizeConfiguration.Builder().minFaceAreaPercent(17).build();
assertEquals(17.0, original.withImageQualityEnabled(false).getMinFaceAreaPercent(), 0.0);
assertFalse(original.withImageQualityEnabled(false).isEnableImageQuality());
assertEquals(5.0, new RecognizeConfiguration.Builder().build().getMinFaceAreaPercent(), 0.0);
assertEquals(3.0, new RecognizeConfiguration.Builder().build().getMinFaceAreaPercent(), 0.0);
}
/** Small in-memory implementation of the Android interface, without an Android runtime. */