feat:添加下单日志
This commit is contained in:
1 parent
e75b3bd58e
commit
531815517a
35 files changed
+2039
-220
No files matched your search
+3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+21
-17
@@ -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);
|
||||
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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;
|
||||
|
||||
/**
|
||||
* 产生特征提取失败示语的特征提取次数(小于该值不提示)
|
||||
|
||||
+7
-4
@@ -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() {
|
||||
|
||||
+79
@@ -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);
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -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. */
|
||||
|
||||
Reference in new issue
Block a user