Browse Source

1.区分debug和正式包同步人脸的方式

JaycePC 1 week ago
parent
commit
d1f71c2460

+ 1 - 1
app/build.gradle

@@ -19,7 +19,7 @@ android {
         minSdk 31
         targetSdk 35
         versionCode 2
-        versionName "2.12"
+        versionName "2.13"
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         room {

+ 317 - 0
app/src/main/java/xn/xxp/app/SyncFaceDeBugTool.java

@@ -0,0 +1,317 @@
+
+package xn.xxp.app;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.os.Environment;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
+import androidx.core.util.Pair;
+
+import com.blankj.utilcode.util.ActivityUtils;
+import com.blankj.utilcode.util.FileUtils;
+import com.blankj.utilcode.util.GsonUtils;
+import com.blankj.utilcode.util.LogUtils;
+import com.blankj.utilcode.util.ThreadUtils;
+import com.blankj.utilcode.util.Utils;
+import com.google.gson.reflect.TypeToken;
+import com.njlz.event.BuildModelEvent;
+import com.njlz.face.FaceUtils;
+import com.njlz.smartdata.FaceBuildBean;
+import com.njlz.smartdata.FaceConfig;
+
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import http.OkHttpUtils;
+import http.client.HttpTool;
+import okhttp3.Request;
+import okhttp3.Response;
+import okio.BufferedSink;
+import okio.Okio;
+import xn.xxp.home.auth.ChoiceAuthActivity;
+import xn.xxp.home.setting.FaceListActivity;
+import xn.xxp.room.RoomTool;
+import xn.xxp.room.bean.Face;
+import xn.xxp.room.dao.FaceDao;
+
+public enum SyncFaceDeBugTool {
+    INSTANCE;
+
+    private static final String TAG = "SyncFaceTool";
+    private static final int MAX_DOWNLOAD_THREADS = 4;
+    private volatile boolean isBuildFaceEnd = false;
+    private List<Face> faceList;
+    public volatile boolean isSyncFace = false;
+    private long lastBuildTime = 0;
+
+    @WorkerThread
+    public void doWork() {
+        if (ThreadUtils.isMainThread()) {
+            LogUtils.e("该方法必须在子线程调用");
+            return;
+        }
+
+        if (!checkActivityState()) return;
+
+
+        if (isSyncFace) {
+            LogUtils.d("正在同步人脸");
+            return;
+        }
+
+        isSyncFace = true;
+        try {
+            FaceUtils.getInstance().init(Utils.getApp());
+            FaceConfig faceConfig = FaceUtils.getInstance().getFaceConfig();
+            LogUtils.json("底库配置", faceConfig);
+
+            EventBus.getDefault().register(this);
+
+            List<Pair<String, String>> faceUrlList = fetchLabFaceList();
+            if (faceUrlList == null || faceUrlList.isEmpty()) {
+                LogUtils.d("不需要下载变更人脸");
+                return;
+            }
+
+            List<FaceBuildBean> faceBuildBeanList = downloadFaceImages(faceUrlList);
+            if (faceBuildBeanList != null && !faceBuildBeanList.isEmpty()) {
+                processFaceBuilding(faceBuildBeanList);
+            }
+
+            cleanRedundantFaces();
+            LogUtils.d("最终底库", FaceUtils.getInstance().getAllFaceId());
+        } catch (Exception e) {
+            LogUtils.e(Log.getStackTraceString(e));
+        } finally {
+            cleanupResources();
+            isSyncFace = false;
+        }
+    }
+
+    private boolean checkActivityState() {
+        Activity topActivity = ActivityUtils.getTopActivity();
+        if (topActivity instanceof ChoiceAuthActivity) {
+            LogUtils.d("人脸识别中,不去同步人脸!");
+            return false;
+        }
+        if (topActivity instanceof FaceListActivity) {
+            LogUtils.d("正在查阅底库,不去同步人脸!");
+            return false;
+        }
+        return true;
+    }
+
+    private List<Pair<String, String>> fetchLabFaceList() {
+        try (Response response = HttpTool.getLabFaceList()) {
+            if (response == null || !response.isSuccessful()) return null;
+
+            JSONObject jsonObject = new JSONObject(response.body().string());
+            if (jsonObject.getInt("code") != 200) return null;
+
+            Type faceListType = new TypeToken<List<Face>>() {
+            }.getType();
+            faceList = GsonUtils.fromJson(jsonObject.getJSONArray("data").toString(), faceListType);
+            if (faceList == null || faceList.isEmpty()) return Collections.emptyList();
+
+            return filterNeedDownloadFaces();
+        } catch (Exception e) {
+            LogUtils.e(Log.getStackTraceString(e));
+            return null;
+        }
+    }
+
+    private List<Pair<String, String>> filterNeedDownloadFaces() {
+        List<Pair<String, String>> results = new ArrayList<>();
+        FaceDao faceDao = RoomTool.getInstance().faceDao();
+        List<String> existingFaceIds = FaceUtils.getInstance().getAllFaceId();
+
+        for (Face face : faceList) {
+            Face localFace = faceDao.getById(face.getUserId());
+            if (shouldDownloadFace(localFace, face, existingFaceIds)) {
+                results.add(Pair.create(
+                        String.valueOf(face.getUserId()),
+                        HttpTool.checkUrl(face.getFaceUrl())
+                ));
+            }
+        }
+        return results;
+    }
+
+    private boolean shouldDownloadFace(Face localFace, Face remoteFace, List<String> existingFaceIds) {
+        if (localFace == null) return true;
+        if (remoteFace.getLastUpdated() <= localFace.getLastUpdated()) return false;
+        return !existingFaceIds.contains(String.valueOf(remoteFace.getUserId()));
+    }
+
+    private List<FaceBuildBean> downloadFaceImages(List<Pair<String, String>> faceUrlList) {
+        ExecutorService executor = Executors.newFixedThreadPool(MAX_DOWNLOAD_THREADS);
+        ConcurrentHashMap<String, File> downloadedFiles = new ConcurrentHashMap<>();
+        CountDownLatch latch = new CountDownLatch(faceUrlList.size());
+        AtomicInteger failureCount = new AtomicInteger(0);
+
+        try {
+            String downloadDir = createDownloadDirectory();
+
+            for (Pair<String, String> pair : faceUrlList) {
+                executor.submit(() -> {
+                    try {
+                        File outputFile = new File(downloadDir, pair.first + ".jpg");
+                        if (downloadImage(pair.second, outputFile)) {
+                            downloadedFiles.put(pair.first, outputFile);
+                        }
+                    } finally {
+                        latch.countDown();
+                    }
+                });
+            }
+
+            latch.wait();
+            executor.shutdown();
+
+            if (failureCount.get() > 0) {
+                LogUtils.w("部分人脸下载失败,失败数量:" + failureCount.get());
+            }
+
+            return buildFaceBeans(downloadedFiles);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            return null;
+        } finally {
+            executor.shutdownNow();
+        }
+    }
+
+    private boolean downloadImage(String url, File outputFile) {
+        Request request = new Request.Builder().url(url).build();
+        try (Response response = OkHttpUtils.client.newCall(request).execute()) {
+            if (!response.isSuccessful() || response.body() == null) {
+                LogUtils.w("下载失败: " + url);
+                return false;
+            }
+
+            try (InputStream is = response.body().byteStream();
+                 BufferedSink sink = Okio.buffer(Okio.sink(outputFile))) {
+                sink.writeAll(Okio.source(is));
+                return true;
+            }
+        } catch (IOException e) {
+            LogUtils.e("下载异常: " + url, e);
+            return false;
+        }
+    }
+
+    @NonNull
+    private String createDownloadDirectory() {
+        @SuppressLint("SdCardPath")
+        String dirPath = Environment.getExternalStorageDirectory() + "/Download/facePic/";
+        FileUtils.createOrExistsDir(dirPath);
+        FileUtils.deleteAllInDir(dirPath);
+        return dirPath;
+    }
+
+    private List<FaceBuildBean> buildFaceBeans(ConcurrentHashMap<String, File> downloadedFiles) {
+        List<FaceBuildBean> beans = new ArrayList<>();
+        downloadedFiles.forEach((id, file) -> {
+            FaceBuildBean bean = new FaceBuildBean();
+            bean.setPicPath(file.getAbsolutePath());
+            bean.setFaceUuid(id);
+            beans.add(bean);
+        });
+        return beans;
+    }
+
+    private void processFaceBuilding(List<FaceBuildBean> faceBuildBeanList)
+            throws InterruptedException {
+
+        FaceUtils.getInstance().faceBuild(faceBuildBeanList);
+        lastBuildTime = System.currentTimeMillis();
+        isBuildFaceEnd = false;
+
+        while (!isBuildFaceEnd) {
+            Thread.sleep(10);
+            if (System.currentTimeMillis() - lastBuildTime > 2000) {
+                isBuildFaceEnd = true;
+            }
+        }
+    }
+
+    @Subscribe(threadMode = ThreadMode.ASYNC)
+    public void onReceive(BuildModelEvent event) {
+        if (event.isDone()) {
+            processBuildResults();
+            isBuildFaceEnd = true;
+        }
+        lastBuildTime = System.currentTimeMillis();
+    }
+
+    private void processBuildResults() {
+        List<FaceBuildBean> results = FaceUtils.getInstance().getFaceResult();
+        if (results == null || results.isEmpty()) return;
+
+        FaceDao faceDao = RoomTool.getInstance().faceDao();
+        results.forEach(bean -> {
+            if (bean.getErrCode() != 0) {
+                LogUtils.e("建模失败", bean.getFaceUuid(), bean.getErrCode());
+            } else {
+                updateFaceDatabase(faceDao, bean.getFaceUuid());
+            }
+        });
+    }
+
+    private void updateFaceDatabase(FaceDao faceDao, String faceUuid) {
+        if (faceList == null) return;
+
+        long uuid = Long.parseLong(faceUuid);
+        for (Face face : faceList) {
+            if (face.getUserId() == uuid) {
+                faceDao.insertAll(face);
+                break;
+            }
+        }
+    }
+
+    private void cleanRedundantFaces() {
+        List<String> existingIds = FaceUtils.getInstance().getAllFaceId();
+        if (existingIds == null || existingIds.isEmpty()) return;
+
+        FaceDao faceDao = RoomTool.getInstance().faceDao();
+        existingIds.forEach(id -> {
+            if (!containsFace(id)) {
+                FaceUtils.getInstance().deleteFace(id);
+                faceDao.delete(Long.parseLong(id));
+            }
+        });
+    }
+
+    private boolean containsFace(String faceId) {
+        return faceList.stream().anyMatch(f ->
+                String.valueOf(f.getUserId()).equals(faceId));
+    }
+
+    private void cleanupResources() {
+        try {
+            EventBus.getDefault().unregister(this);
+            FaceUtils.getInstance().destroy();
+        } catch (Exception e) {
+            LogUtils.e(Log.getStackTraceString(e));
+        }
+    }
+}

+ 234 - 235
app/src/main/java/xn/xxp/app/SyncFaceTool.java

@@ -2,14 +2,20 @@ package xn.xxp.app;
 
 import android.annotation.SuppressLint;
 import android.app.Activity;
+import android.app.DownloadManager;
+import android.content.Context;
+import android.net.Uri;
 import android.os.Environment;
+import android.os.FileObserver;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 import androidx.core.util.Pair;
 
 import com.blankj.utilcode.util.ActivityUtils;
+import com.blankj.utilcode.util.AppUtils;
 import com.blankj.utilcode.util.FileUtils;
 import com.blankj.utilcode.util.GsonUtils;
 import com.blankj.utilcode.util.LogUtils;
@@ -27,27 +33,15 @@ import org.greenrobot.eventbus.ThreadMode;
 import org.json.JSONObject;
 
 import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
 import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.Map;
 
-import http.OkHttpUtils;
 import http.client.HttpTool;
-import okhttp3.Call;
-import okhttp3.Callback;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
 import okhttp3.Response;
-import okio.BufferedSink;
-import okio.Okio;
 import xn.xxp.home.auth.ChoiceAuthActivity;
 import xn.xxp.home.setting.FaceListActivity;
 import xn.xxp.room.RoomTool;
@@ -57,263 +51,268 @@ import xn.xxp.room.dao.FaceDao;
 public enum SyncFaceTool {
     INSTANCE;
 
-    private static final String TAG = "SyncFaceTool";
-    private static final int MAX_DOWNLOAD_THREADS = 4;
+    private DownloadManager downloadManager;
+    private long lastEventTime = 0;
+    private long lastBuildTime = 0;
     private volatile boolean isBuildFaceEnd = false;
     private List<Face> faceList;
     public volatile boolean isSyncFace = false;
-    private long lastBuildTime = 0;
-
-    @WorkerThread
-    public void doWork() {
-        if (ThreadUtils.isMainThread()) {
-            LogUtils.e("该方法必须在子线程调用");
-            return;
-        }
-
-        if (!checkActivityState()) return;
 
+    SyncFaceTool() {
 
-        if (isSyncFace) {
-            LogUtils.d("正在同步人脸");
-            return;
-        }
-
-        isSyncFace = true;
-        try {
-            FaceUtils.getInstance().init(Utils.getApp());
-            FaceConfig faceConfig = FaceUtils.getInstance().getFaceConfig();
-            LogUtils.json("底库配置", faceConfig);
-
-            EventBus.getDefault().register(this);
+    }
 
-            List<Pair<String, String>> faceUrlList = fetchLabFaceList();
-            if (faceUrlList == null || faceUrlList.isEmpty()) {
-                LogUtils.d("不需要下载变更人脸");
+    @WorkerThread
+    public void doWork() {
+        if (AppUtils.isAppDebug()) {
+            SyncFaceDeBugTool.INSTANCE.doWork();
+        } else {
+            if (ThreadUtils.isMainThread()) {
+                LogUtils.e("该方法必须在子线程调用");
                 return;
             }
-
-            List<FaceBuildBean> faceBuildBeanList = downloadFaceImages(faceUrlList);
-            if (faceBuildBeanList != null && !faceBuildBeanList.isEmpty()) {
-                processFaceBuilding(faceBuildBeanList);
+            Activity topActivity = ActivityUtils.getTopActivity();
+            if (topActivity instanceof ChoiceAuthActivity) {
+                LogUtils.d("人脸识别中,不去同步人脸!");
+                return;
             }
-
-            cleanRedundantFaces();
-            LogUtils.d("最终底库", FaceUtils.getInstance().getAllFaceId());
-        } catch (Exception e) {
-            LogUtils.e(Log.getStackTraceString(e));
-        } finally {
-            cleanupResources();
-            isSyncFace = false;
-        }
-    }
-
-    private boolean checkActivityState() {
-        Activity topActivity = ActivityUtils.getTopActivity();
-        if (topActivity instanceof ChoiceAuthActivity) {
-            LogUtils.d("人脸识别中,不去同步人脸!");
-            return false;
-        }
-        if (topActivity instanceof FaceListActivity) {
-            LogUtils.d("正在查阅底库,不去同步人脸!");
-            return false;
-        }
-        return true;
-    }
-
-    private List<Pair<String, String>> fetchLabFaceList() {
-        try (Response response = HttpTool.getLabFaceList()) {
-            if (response == null || !response.isSuccessful()) return null;
-
-            JSONObject jsonObject = new JSONObject(response.body().string());
-            if (jsonObject.getInt("code") != 200) return null;
-
-            Type faceListType = new TypeToken<List<Face>>() {
-            }.getType();
-            faceList = GsonUtils.fromJson(jsonObject.getJSONArray("data").toString(), faceListType);
-            if (faceList == null || faceList.isEmpty()) return Collections.emptyList();
-
-            return filterNeedDownloadFaces();
-        } catch (Exception e) {
-            LogUtils.e(Log.getStackTraceString(e));
-            return null;
-        }
-    }
-
-    private List<Pair<String, String>> filterNeedDownloadFaces() {
-        List<Pair<String, String>> results = new ArrayList<>();
-        FaceDao faceDao = RoomTool.getInstance().faceDao();
-        List<String> existingFaceIds = FaceUtils.getInstance().getAllFaceId();
-
-        for (Face face : faceList) {
-            Face localFace = faceDao.getById(face.getUserId());
-            if (shouldDownloadFace(localFace, face, existingFaceIds)) {
-                results.add(Pair.create(
-                        String.valueOf(face.getUserId()),
-                        HttpTool.checkUrl(face.getFaceUrl())
-                ));
+            if (topActivity instanceof FaceListActivity) {
+                LogUtils.d("正在查阅底库,不去同步人脸!");
+                return;
+            }
+            if (isSyncFace) {
+                LogUtils.d("正在同步人脸");
+                return;
+            }
+            if (null == downloadManager) {
+                downloadManager = (DownloadManager) Utils.getApp().getApplicationContext().getSystemService(Context.DOWNLOAD_SERVICE);
+            }
+            // 初始化
+            isSyncFace = true;
+            try {
+                // 同步人脸
+                FaceUtils.getInstance().init(Utils.getApp());
+                FaceConfig faceConfig = FaceUtils.getInstance().getFaceConfig();
+                LogUtils.json("底库配置", faceConfig);
+                EventBus.getDefault().register(this);
+                // http请求同步
+                List<Pair<String, String>> faceUrlList = getLabFaceList();
+                if (null != faceUrlList && !faceUrlList.isEmpty()) {
+                    // 下载人脸
+                    List<FaceBuildBean> faceBuildBeanList = downloadPicAndBuild(faceUrlList);
+                    LogUtils.json("需要变更的人脸", faceBuildBeanList);
+                    // 人脸建模
+                    if (null != faceBuildBeanList && !faceBuildBeanList.isEmpty()) {
+                        FaceUtils.getInstance().faceBuild(faceBuildBeanList);
+                        lastBuildTime = System.currentTimeMillis();
+                        // 等待人脸建模
+                        while (!isBuildFaceEnd) {
+                            Thread.sleep(10);
+                            if (System.currentTimeMillis() - lastBuildTime > 2000) {
+                                isBuildFaceEnd = true;
+                            }
+                        }
+                    }
+                    LogUtils.json(RoomTool.getInstance().faceDao().getAll());
+                } else {
+                    LogUtils.d("不需要下载变更人脸");
+                }
+                // 删除本地多余的人脸
+                delRedundantFace();
+                LogUtils.d("底库", FaceUtils.getInstance().getAllFaceId());
+            } catch (Exception e) {
+                LogUtils.e(Log.getStackTraceString(e));
+            } finally {
+                try {
+                    // 销毁
+                    EventBus.getDefault().unregister(this);
+                    FaceUtils.getInstance().destroy();
+                } catch (Exception e) {
+                    LogUtils.e(Log.getStackTraceString(e));
+                }
             }
+            isSyncFace = false;
         }
-        return results;
     }
 
-    private boolean shouldDownloadFace(Face localFace, Face remoteFace, List<String> existingFaceIds) {
-        if (localFace == null) return true;
-        if (remoteFace.getLastUpdated() <= localFace.getLastUpdated()) return false;
-        return !existingFaceIds.contains(String.valueOf(remoteFace.getUserId()));
-    }
-
-    private List<FaceBuildBean> downloadFaceImages(List<Pair<String, String>> faceUrlList) {
-        ExecutorService executor = Executors.newFixedThreadPool(MAX_DOWNLOAD_THREADS);
-        ConcurrentHashMap<String, File> downloadedFiles = new ConcurrentHashMap<>();
-        CountDownLatch latch = new CountDownLatch(faceUrlList.size());
-        AtomicInteger failureCount = new AtomicInteger(0);
-
+    private List<Pair<String, String>> getLabFaceList() {
         try {
-            String downloadDir = createDownloadDirectory();
-
-            for (Pair<String, String> pair : faceUrlList) {
-                executor.submit(() -> {
-                    try {
-                        File outputFile = new File(downloadDir, pair.first + ".jpg");
-                        if (downloadImage(pair.second, outputFile)) {
-                            downloadedFiles.put(pair.first, outputFile);
+            Response response = HttpTool.getLabFaceList();
+            if (null != response && response.isSuccessful()) {
+                String json = response.body().string();
+                JSONObject jsonObject = new JSONObject(json);
+                int code = jsonObject.getInt("code");
+                if (200 == code) {
+                    Type faceListType = new TypeToken<List<Face>>() {
+                    }.getType();
+                    faceList = GsonUtils.fromJson(jsonObject.getJSONArray("data").toString(), faceListType);
+
+                    if (null != faceList && !faceList.isEmpty()) {
+                        FaceDao faceDao = RoomTool.getInstance().faceDao();
+                        @SuppressLint("SdCardPath") final String savePath = "/sdcard/facePic/";
+                        FileUtils.createOrExistsDir(savePath);
+                        // 1.id 2.url
+                        List<Pair<String, String>> faceUrlList = new ArrayList<>();
+                        List<String> allFaceIdList = FaceUtils.getInstance().getAllFaceId();
+                        for (int i = 0; i < faceList.size(); i++) {
+                            Face face = faceList.get(i);
+                            Face sqlFace = faceDao.getById(face.getUserId());
+                            if (null != sqlFace) {
+                                boolean sqlInDb = false;
+                                for (int j = 0; j < allFaceIdList.size(); j++) {
+                                    if (allFaceIdList.get(j).equals(String.valueOf(sqlFace.getUserId()))) {
+                                        sqlInDb = true;
+                                    }
+                                }
+
+                                if (sqlInDb && face.getLastUpdated() <= sqlFace.getLastUpdated()) {
+                                    continue;
+                                }
+                            }
+                            faceUrlList.add(Pair.create(String.valueOf(face.getUserId()), HttpTool.checkUrl(face.getFaceUrl())));
                         }
-                    } finally {
-                        latch.countDown();
+                        return faceUrlList;
+                    } else {
+                        return new ArrayList<>();
                     }
-                });
-            }
-
-            latch.wait();
-            executor.shutdown();
-
-            if (failureCount.get() > 0) {
-                LogUtils.w("部分人脸下载失败,失败数量:" + failureCount.get());
+                }
             }
-
-            return buildFaceBeans(downloadedFiles);
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            return null;
-        } finally {
-            executor.shutdownNow();
+        } catch (Exception e) {
+            LogUtils.e(Log.getStackTraceString(e));
         }
+        return null;
     }
 
-    private boolean downloadImage(String url, File outputFile) {
-        Request request = new Request.Builder().url(url).build();
-        try (Response response = OkHttpUtils.client.newCall(request).execute()) {
-            if (!response.isSuccessful() || response.body() == null) {
-                LogUtils.w("下载失败: " + url);
-                return false;
+    private List<FaceBuildBean> downloadPicAndBuild(List<Pair<String, String>> faceUrlList) {
+        if (null == faceUrlList || faceUrlList.isEmpty()) {
+            return null;
+        }
+        try {
+            @SuppressLint("SdCardPath") final String filePathDir = "/sdcard/" + Environment.DIRECTORY_DOWNLOADS + "/facePic/";
+            FileUtils.createOrExistsDir(filePathDir);
+            FileUtils.deleteAllInDir(filePathDir);
+            List<FaceBuildBean> faceBuildBeanList = new ArrayList<>();
+            List<File> downloadList = new LinkedList<>();
+            for (int i = 0; i < faceUrlList.size(); i++) {
+                // 1.id 2.url
+                Pair<String, String> downloadPair = faceUrlList.get(i);
+                DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadPair.second));
+                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "facePic/" + downloadPair.first + ".jpg");
+                request.setMimeType("image/jpeg");
+                request.setVisibleInDownloadsUi(false);
+                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
+                downloadManager.enqueue(request);
+                downloadList.add(new File(filePathDir + downloadPair.first + ".jpg"));
             }
-
-            try (InputStream is = response.body().byteStream();
-                 BufferedSink sink = Okio.buffer(Okio.sink(outputFile))) {
-                sink.writeAll(Okio.source(is));
-                return true;
+            // 文件观察者
+            FileObserver fileObserver = getFileObserver(filePathDir, downloadList);
+            // 开始观察
+            fileObserver.startWatching();
+            lastEventTime = System.currentTimeMillis();
+            // 观察者无响应2秒后 认为下载结束
+            while (!downloadList.isEmpty()) {
+                Thread.sleep(1000);
+                if (System.currentTimeMillis() - lastEventTime > 2000) {
+                    downloadList.clear();
+                }
             }
-        } catch (IOException e) {
-            LogUtils.e("下载异常: " + url, e);
-            return false;
-        }
-    }
-
-    @NonNull
-    private String createDownloadDirectory() {
-        @SuppressLint("SdCardPath")
-        String dirPath = Environment.getExternalStorageDirectory() + "/Download/facePic/";
-        FileUtils.createOrExistsDir(dirPath);
-        FileUtils.deleteAllInDir(dirPath);
-        return dirPath;
-    }
-
-    private List<FaceBuildBean> buildFaceBeans(ConcurrentHashMap<String, File> downloadedFiles) {
-        List<FaceBuildBean> beans = new ArrayList<>();
-        downloadedFiles.forEach((id, file) -> {
-            FaceBuildBean bean = new FaceBuildBean();
-            bean.setPicPath(file.getAbsolutePath());
-            bean.setFaceUuid(id);
-            beans.add(bean);
-        });
-        return beans;
-    }
-
-    private void processFaceBuilding(List<FaceBuildBean> faceBuildBeanList)
-            throws InterruptedException {
-
-        FaceUtils.getInstance().faceBuild(faceBuildBeanList);
-        lastBuildTime = System.currentTimeMillis();
-        isBuildFaceEnd = false;
-
-        while (!isBuildFaceEnd) {
-            Thread.sleep(10);
-            if (System.currentTimeMillis() - lastBuildTime > 2000) {
-                isBuildFaceEnd = true;
+            // 停止观察
+            fileObserver.stopWatching();
+
+            // 同步人脸
+            List<File> facePicList = FileUtils.listFilesInDir(filePathDir);
+            if (!facePicList.isEmpty()) {
+                for (int i = 0; i < facePicList.size(); i++) {
+                    File file = facePicList.get(i);
+                    FaceBuildBean faceBuildBean = new FaceBuildBean();
+                    faceBuildBean.setPicPath(file.getAbsolutePath());
+                    faceBuildBean.setFaceUuid(file.getName().replace(".jpg", ""));
+                    faceBuildBeanList.add(faceBuildBean);
+                }
             }
+            return faceBuildBeanList;
+        } catch (Exception e) {
+            LogUtils.e(Log.getStackTraceString(e));
         }
+        return null;
     }
 
     @Subscribe(threadMode = ThreadMode.ASYNC)
-    public void onReceive(BuildModelEvent event) {
-        if (event.isDone()) {
-            processBuildResults();
+    public void onReceive(BuildModelEvent buildModelEvent) {
+        if (buildModelEvent.isDone()) {
+            List<FaceBuildBean> list = FaceUtils.getInstance().getFaceResult();
+            FaceDao faceDao = RoomTool.getInstance().faceDao();
+            if (list != null && !list.isEmpty()) {
+                for (FaceBuildBean faceBuildBean : list) {
+                    if (faceBuildBean.getErrCode() != 0) {
+                        LogUtils.e("建模失败", faceBuildBean.getFaceUuid(), faceBuildBean.getErrCode());
+                    } else {
+                        if (null != faceList && !faceList.isEmpty()) {
+                            long uuid = Long.parseLong(faceBuildBean.getFaceUuid());
+                            for (int i = 0; i < faceList.size(); i++) {
+                                Face face = faceList.get(i);
+                                if (face.getUserId() == uuid) {
+                                    faceDao.insertAll(face);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
             isBuildFaceEnd = true;
         }
         lastBuildTime = System.currentTimeMillis();
     }
 
-    private void processBuildResults() {
-        List<FaceBuildBean> results = FaceUtils.getInstance().getFaceResult();
-        if (results == null || results.isEmpty()) return;
-
-        FaceDao faceDao = RoomTool.getInstance().faceDao();
-        results.forEach(bean -> {
-            if (bean.getErrCode() != 0) {
-                LogUtils.e("建模失败", bean.getFaceUuid(), bean.getErrCode());
-            } else {
-                updateFaceDatabase(faceDao, bean.getFaceUuid());
-            }
-        });
-    }
-
-    private void updateFaceDatabase(FaceDao faceDao, String faceUuid) {
-        if (faceList == null) return;
-
-        long uuid = Long.parseLong(faceUuid);
-        for (Face face : faceList) {
-            if (face.getUserId() == uuid) {
-                faceDao.insertAll(face);
-                break;
+    private @NonNull FileObserver getFileObserver(String filePathDir, List<File> downloadList) {
+        return new FileObserver(filePathDir) {
+            final Map<String, Integer> fileStateMap = new LinkedHashMap<>();
+
+            @Override
+            public void onEvent(int event, @Nullable String path) {
+                lastEventTime = System.currentTimeMillis();
+                if (null != path) {
+                    if (event == CLOSE_WRITE) {
+                        Integer count = fileStateMap.get(path);
+                        if (null == count) {
+                            fileStateMap.put(path, 1);
+                        } else if (count >= 2) {
+                            downloadList.removeIf(file -> path.equals(file.getName()));
+                        } else {
+                            fileStateMap.put(path, count + 1);
+                        }
+                    }
+                }
             }
-        }
+        };
     }
 
-    private void cleanRedundantFaces() {
-        List<String> existingIds = FaceUtils.getInstance().getAllFaceId();
-        if (existingIds == null || existingIds.isEmpty()) return;
-
+    /**
+     * 删除本地多余的人脸
+     */
+    private void delRedundantFace() {
+        List<String> allFaceIdList = FaceUtils.getInstance().getAllFaceId();
         FaceDao faceDao = RoomTool.getInstance().faceDao();
-        existingIds.forEach(id -> {
-            if (!containsFace(id)) {
-                FaceUtils.getInstance().deleteFace(id);
-                faceDao.delete(Long.parseLong(id));
+        if (faceList != null && allFaceIdList != null && !allFaceIdList.isEmpty()) {
+            if (faceList.isEmpty()) {
+                FaceUtils.getInstance().deleteAllFace();
+                faceDao.clear();
+            } else {
+                for (int i = 0; i < allFaceIdList.size(); i++) {
+                    String uuid = allFaceIdList.get(i);
+                    boolean hasFace = false;
+                    for (int j = 0; j < faceList.size(); j++) {
+                        if (faceList.get(j).getUserId() == Long.parseLong(uuid)) {
+                            hasFace = true;
+                            break;
+                        }
+                    }
+                    if (!hasFace) {
+                        FaceUtils.getInstance().deleteFace(uuid);
+                        faceDao.delete(Long.parseLong(uuid));
+                    }
+                }
             }
-        });
-    }
-
-    private boolean containsFace(String faceId) {
-        return faceList.stream().anyMatch(f ->
-                String.valueOf(f.getUserId()).equals(faceId));
-    }
-
-    private void cleanupResources() {
-        try {
-            EventBus.getDefault().unregister(this);
-            FaceUtils.getInstance().destroy();
-        } catch (Exception e) {
-            LogUtils.e(Log.getStackTraceString(e));
         }
     }
 }

+ 0 - 313
app/src/main/java/xn/xxp/app/XZ.java

@@ -1,313 +0,0 @@
-//package xn.xxp.app;
-//
-//import android.annotation.SuppressLint;
-//import android.app.Activity;
-//import android.app.DownloadManager;
-//import android.content.Context;
-//import android.net.Uri;
-//import android.os.Environment;
-//import android.os.FileObserver;
-//import android.util.Log;
-//
-//import androidx.annotation.NonNull;
-//import androidx.annotation.Nullable;
-//import androidx.annotation.WorkerThread;
-//import androidx.core.util.Pair;
-//
-//import com.blankj.utilcode.util.ActivityUtils;
-//import com.blankj.utilcode.util.FileUtils;
-//import com.blankj.utilcode.util.GsonUtils;
-//import com.blankj.utilcode.util.LogUtils;
-//import com.blankj.utilcode.util.ThreadUtils;
-//import com.blankj.utilcode.util.Utils;
-//import com.google.gson.reflect.TypeToken;
-//import com.njlz.event.BuildModelEvent;
-//import com.njlz.face.FaceUtils;
-//import com.njlz.smartdata.FaceBuildBean;
-//import com.njlz.smartdata.FaceConfig;
-//
-//import org.greenrobot.eventbus.EventBus;
-//import org.greenrobot.eventbus.Subscribe;
-//import org.greenrobot.eventbus.ThreadMode;
-//import org.json.JSONObject;
-//
-//import java.io.File;
-//import java.lang.reflect.Type;
-//import java.util.ArrayList;
-//import java.util.LinkedHashMap;
-//import java.util.LinkedList;
-//import java.util.List;
-//import java.util.Map;
-//
-//import http.client.HttpTool;
-//import okhttp3.Response;
-//import xn.xxp.home.auth.ChoiceAuthActivity;
-//import xn.xxp.home.setting.FaceListActivity;
-//import xn.xxp.room.RoomTool;
-//import xn.xxp.room.bean.Face;
-//import xn.xxp.room.dao.FaceDao;
-//
-//public enum SyncFaceTool {
-//    INSTANCE;
-//
-//    private DownloadManager downloadManager;
-//    private long lastEventTime = 0;
-//    private long lastBuildTime = 0;
-//    private volatile boolean isBuildFaceEnd = false;
-//    private List<Face> faceList;
-//    public volatile boolean isSyncFace = false;
-//
-//    SyncFaceTool() {
-//
-//    }
-//
-//    @WorkerThread
-//    public void doWork() {
-//        if (ThreadUtils.isMainThread()) {
-//            LogUtils.e("该方法必须在子线程调用");
-//            return;
-//        }
-//        Activity topActivity = ActivityUtils.getTopActivity();
-//        if (topActivity instanceof ChoiceAuthActivity) {
-//            LogUtils.d("人脸识别中,不去同步人脸!");
-//            return;
-//        }
-//        if (topActivity instanceof FaceListActivity) {
-//            LogUtils.d("正在查阅底库,不去同步人脸!");
-//            return;
-//        }
-//        if (isSyncFace) {
-//            LogUtils.d("正在同步人脸");
-//            return;
-//        }
-//        if (null == downloadManager) {
-//            downloadManager = (DownloadManager) Utils.getApp().getApplicationContext().getSystemService(Context.DOWNLOAD_SERVICE);
-//        }
-//        // 初始化
-//        isSyncFace = true;
-//        try {
-//            // 同步人脸
-//            FaceUtils.getInstance().init(Utils.getApp());
-//            FaceConfig faceConfig = FaceUtils.getInstance().getFaceConfig();
-//            LogUtils.json("底库配置", faceConfig);
-//            EventBus.getDefault().register(this);
-//            // http请求同步
-//            List<Pair<String, String>> faceUrlList = getLabFaceList();
-//            if (null != faceUrlList && !faceUrlList.isEmpty()) {
-//                // 下载人脸
-//                List<FaceBuildBean> faceBuildBeanList = downloadPicAndBuild(faceUrlList);
-//                LogUtils.json("需要变更的人脸", faceBuildBeanList);
-//                // 人脸建模
-//                if (null != faceBuildBeanList && !faceBuildBeanList.isEmpty()) {
-//                    FaceUtils.getInstance().faceBuild(faceBuildBeanList);
-//                    lastBuildTime = System.currentTimeMillis();
-//                    // 等待人脸建模
-//                    while (!isBuildFaceEnd) {
-//                        Thread.sleep(10);
-//                        if (System.currentTimeMillis() - lastBuildTime > 2000) {
-//                            isBuildFaceEnd = true;
-//                        }
-//                    }
-//                }
-//                LogUtils.json(RoomTool.getInstance().faceDao().getAll());
-//            } else {
-//                LogUtils.d("不需要下载变更人脸");
-//            }
-//            // 删除本地多余的人脸
-//            delRedundantFace();
-//            LogUtils.d("底库", FaceUtils.getInstance().getAllFaceId());
-//        } catch (Exception e) {
-//            LogUtils.e(Log.getStackTraceString(e));
-//        } finally {
-//            try {
-//                // 销毁
-//                EventBus.getDefault().unregister(this);
-//                FaceUtils.getInstance().destroy();
-//            } catch (Exception e) {
-//                LogUtils.e(Log.getStackTraceString(e));
-//            }
-//        }
-//        isSyncFace = false;
-//    }
-//
-//    private List<Pair<String, String>> getLabFaceList() {
-//        try {
-//            Response response = HttpTool.getLabFaceList();
-//            if (null != response && response.isSuccessful()) {
-//                String json = response.body().string();
-//                JSONObject jsonObject = new JSONObject(json);
-//                int code = jsonObject.getInt("code");
-//                if (200 == code) {
-//                    Type faceListType = new TypeToken<List<Face>>() {
-//                    }.getType();
-//                    faceList = GsonUtils.fromJson(jsonObject.getJSONArray("data").toString(), faceListType);
-//
-//                    if (null != faceList && !faceList.isEmpty()) {
-//                        FaceDao faceDao = RoomTool.getInstance().faceDao();
-//                        @SuppressLint("SdCardPath") final String savePath = "/sdcard/facePic/";
-//                        FileUtils.createOrExistsDir(savePath);
-//                        // 1.id 2.url
-//                        List<Pair<String, String>> faceUrlList = new ArrayList<>();
-//                        List<String> allFaceIdList = FaceUtils.getInstance().getAllFaceId();
-//                        for (int i = 0; i < faceList.size(); i++) {
-//                            Face face = faceList.get(i);
-//                            Face sqlFace = faceDao.getById(face.getUserId());
-//                            if (null != sqlFace) {
-//                                boolean sqlInDb = false;
-//                                for (int j = 0; j < allFaceIdList.size(); j++) {
-//                                    if (allFaceIdList.get(j).equals(String.valueOf(sqlFace.getUserId()))) {
-//                                        sqlInDb = true;
-//                                    }
-//                                }
-//
-//                                if (sqlInDb && face.getLastUpdated() <= sqlFace.getLastUpdated()) {
-//                                    continue;
-//                                }
-//                            }
-//                            faceUrlList.add(Pair.create(String.valueOf(face.getUserId()), HttpTool.checkUrl(face.getFaceUrl())));
-//                        }
-//                        return faceUrlList;
-//                    } else {
-//                        return new ArrayList<>();
-//                    }
-//                }
-//            }
-//        } catch (Exception e) {
-//            LogUtils.e(Log.getStackTraceString(e));
-//        }
-//        return null;
-//    }
-//
-//    private List<FaceBuildBean> downloadPicAndBuild(List<Pair<String, String>> faceUrlList) {
-//        if (null == faceUrlList || faceUrlList.isEmpty()) {
-//            return null;
-//        }
-//        try {
-//            @SuppressLint("SdCardPath") final String filePathDir = "/sdcard/" + Environment.DIRECTORY_DOWNLOADS + "/facePic/";
-//            FileUtils.createOrExistsDir(filePathDir);
-//            FileUtils.deleteAllInDir(filePathDir);
-//            List<FaceBuildBean> faceBuildBeanList = new ArrayList<>();
-//            List<File> downloadList = new LinkedList<>();
-//            for (int i = 0; i < faceUrlList.size(); i++) {
-//                // 1.id 2.url
-//                Pair<String, String> downloadPair = faceUrlList.get(i);
-//                DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadPair.second));
-//                request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "facePic/" + downloadPair.first + ".jpg");
-//                request.setMimeType("image/jpeg");
-//                request.setVisibleInDownloadsUi(false);
-//                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
-//                downloadManager.enqueue(request);
-//                downloadList.add(new File(filePathDir + downloadPair.first + ".jpg"));
-//            }
-//            // 文件观察者
-//            FileObserver fileObserver = getFileObserver(filePathDir, downloadList);
-//            // 开始观察
-//            fileObserver.startWatching();
-//            lastEventTime = System.currentTimeMillis();
-//            // 观察者无响应2秒后 认为下载结束
-//            while (!downloadList.isEmpty()) {
-//                Thread.sleep(1000);
-//                if (System.currentTimeMillis() - lastEventTime > 2000) {
-//                    downloadList.clear();
-//                }
-//            }
-//            // 停止观察
-//            fileObserver.stopWatching();
-//
-//            // 同步人脸
-//            List<File> facePicList = FileUtils.listFilesInDir(filePathDir);
-//            if (!facePicList.isEmpty()) {
-//                for (int i = 0; i < facePicList.size(); i++) {
-//                    File file = facePicList.get(i);
-//                    FaceBuildBean faceBuildBean = new FaceBuildBean();
-//                    faceBuildBean.setPicPath(file.getAbsolutePath());
-//                    faceBuildBean.setFaceUuid(file.getName().replace(".jpg", ""));
-//                    faceBuildBeanList.add(faceBuildBean);
-//                }
-//            }
-//            return faceBuildBeanList;
-//        } catch (Exception e) {
-//            LogUtils.e(Log.getStackTraceString(e));
-//        }
-//        return null;
-//    }
-//
-//    @Subscribe(threadMode = ThreadMode.ASYNC)
-//    public void onReceive(BuildModelEvent buildModelEvent) {
-//        if (buildModelEvent.isDone()) {
-//            List<FaceBuildBean> list = FaceUtils.getInstance().getFaceResult();
-//            FaceDao faceDao = RoomTool.getInstance().faceDao();
-//            if (list != null && !list.isEmpty()) {
-//                for (FaceBuildBean faceBuildBean : list) {
-//                    if (faceBuildBean.getErrCode() != 0) {
-//                        LogUtils.e("建模失败", faceBuildBean.getFaceUuid(), faceBuildBean.getErrCode());
-//                    } else {
-//                        if (null != faceList && !faceList.isEmpty()) {
-//                            long uuid = Long.parseLong(faceBuildBean.getFaceUuid());
-//                            for (int i = 0; i < faceList.size(); i++) {
-//                                Face face = faceList.get(i);
-//                                if (face.getUserId() == uuid) {
-//                                    faceDao.insertAll(face);
-//                                }
-//                            }
-//                        }
-//                    }
-//                }
-//            }
-//            isBuildFaceEnd = true;
-//        }
-//        lastBuildTime = System.currentTimeMillis();
-//    }
-//
-//    private @NonNull FileObserver getFileObserver(String filePathDir, List<File> downloadList) {
-//        return new FileObserver(filePathDir) {
-//            final Map<String, Integer> fileStateMap = new LinkedHashMap<>();
-//
-//            @Override
-//            public void onEvent(int event, @Nullable String path) {
-//                lastEventTime = System.currentTimeMillis();
-//                if (null != path) {
-//                    if (event == CLOSE_WRITE) {
-//                        Integer count = fileStateMap.get(path);
-//                        if (null == count) {
-//                            fileStateMap.put(path, 1);
-//                        } else if (count >= 2) {
-//                            downloadList.removeIf(file -> path.equals(file.getName()));
-//                        } else {
-//                            fileStateMap.put(path, count + 1);
-//                        }
-//                    }
-//                }
-//            }
-//        };
-//    }
-//
-//    /**
-//     * 删除本地多余的人脸
-//     */
-//    private void delRedundantFace() {
-//        List<String> allFaceIdList = FaceUtils.getInstance().getAllFaceId();
-//        FaceDao faceDao = RoomTool.getInstance().faceDao();
-//        if (faceList != null && allFaceIdList != null && !allFaceIdList.isEmpty()) {
-//            if (faceList.isEmpty()) {
-//                FaceUtils.getInstance().deleteAllFace();
-//                faceDao.clear();
-//            } else {
-//                for (int i = 0; i < allFaceIdList.size(); i++) {
-//                    String uuid = allFaceIdList.get(i);
-//                    boolean hasFace = false;
-//                    for (int j = 0; j < faceList.size(); j++) {
-//                        if (faceList.get(j).getUserId() == Long.parseLong(uuid)) {
-//                            hasFace = true;
-//                            break;
-//                        }
-//                    }
-//                    if (!hasFace) {
-//                        FaceUtils.getInstance().deleteFace(uuid);
-//                        faceDao.delete(Long.parseLong(uuid));
-//                    }
-//                }
-//            }
-//        }
-//    }
-//}

+ 11 - 4
app/src/main/java/xn/xxp/home/leave/LeaveFaceMatchingFragment.kt

@@ -25,6 +25,8 @@ import xn.xxp.home.auth.SafetyCheckResultDialog
 import xn.xxp.mqtt.event.OnlineUserEvent
 import xn.xxp.utils.AudioPlayer
 import xn.xxp.utils.Tool
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
 
 /**
  * 离开人脸比对
@@ -78,13 +80,18 @@ open class LeaveFaceMatchingFragment :
             viewBinding.personName.text = data.singInUser.nickName
             // 学号
             viewBinding.schoolNumber.text = "学号:${data.singInUser.userName ?: ""}"
+
+            val isoDateTime = data.singInUser.expirationDate
+
+            // 1. 解析ISO格式的日期时间
+            val dateTime = LocalDateTime.parse(isoDateTime, DateTimeFormatter.ISO_DATE_TIME)
+            // 2. 格式化为中文日期
+            val chineseFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日")
+            val formattedDate = dateTime.format(chineseFormatter)
             // 准入资格到期时间
             viewBinding.expirationDate.text =
                 "准入资格到期时间:${
-                    Format.formatZonedDateTime(
-                        data.singInUser.expirationDate,
-                        "yyyy年MM月dd日"
-                    )
+                    formattedDate
                 }"
             updateViolationViews(data)
         }