Ver código fonte

Add 添加扫码服务

Yue 1 mês atrás
pai
commit
a2616f8cee
17 arquivos alterados com 1691 adições e 332 exclusões
  1. 1 0
      UI/CF.APP/chicken_farm/.gitignore
  2. 1 0
      UI/CF.APP/chicken_farm/android/app/build.gradle
  3. BIN
      UI/CF.APP/chicken_farm/android/app/libs/ScanManager_V202105081630.jar
  4. 10 0
      UI/CF.APP/chicken_farm/android/app/src/main/AndroidManifest.xml
  5. 78 0
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/MainActivity.java
  6. 21 0
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanBinder.java
  7. 18 0
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanCallback.java
  8. 29 0
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanConstants.java
  9. 176 0
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanMethodCallHandler.java
  10. 503 0
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanService.java
  11. 576 0
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/services/RFIDService.java
  12. 97 0
      UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/services/SoundService.java
  13. 119 0
      UI/CF.APP/chicken_farm/lib/core/services/pda/pda_scan_service.dart
  14. 0 42
      UI/CF.APP/chicken_farm/lib/core/services/pda/pda_scanner_service.dart
  15. 62 35
      UI/CF.APP/chicken_farm/lib/pages/breeding/cage_change_page.dart
  16. 0 82
      UI/CF.APP/chicken_farm/lib/services/pda_service.dart
  17. 0 173
      UI/CF.APP/chicken_farm/lib/widgets/pda_demo_page.dart

+ 1 - 0
UI/CF.APP/chicken_farm/.gitignore

@@ -6,6 +6,7 @@
 *.lock
 .DS_Store
 .atom/
+.demo/
 .build/
 .buildlog/
 .history

+ 1 - 0
UI/CF.APP/chicken_farm/android/app/build.gradle

@@ -56,6 +56,7 @@ android {
 dependencies {
     // implementation fileTree(include: ['*.aar'], dir: 'libs')
     implementation files('libs\\lcrrgxmodule-release.aar')
+    implementation files('libs\\ScanManager_V202105081630.jar')
 }
 
 flutter {

BIN
UI/CF.APP/chicken_farm/android/app/libs/ScanManager_V202105081630.jar


+ 10 - 0
UI/CF.APP/chicken_farm/android/app/src/main/AndroidManifest.xml

@@ -1,5 +1,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <uses-permission android:name="android.permission.CAMERA" />
+     <!-- 唤醒锁权限 -->
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
     <application
         android:label="chicken_farm"
         android:name="${applicationName}"
@@ -28,6 +31,13 @@
         </activity>
         <!-- Don't delete the meta-data below.
              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+
+        <!-- 注册扫码服务 -->
+        <service
+            android:name=".ScanService"
+            android:exported="true" 
+            android:foregroundServiceType="dataSync" /> <!--exported 允许外部应用调用(如需) --><!--foregroundServiceType 前台服务类型(可选) -->
+
         <meta-data
             android:name="flutterEmbedding"
             android:value="2" />

+ 78 - 0
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/MainActivity.java

@@ -1,7 +1,85 @@
 package com.vber.chicken_farm;
 
+import android.os.Bundle;
+import androidx.annotation.NonNull;
 import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import com.vber.chicken_farm.scan.ScanMethodCallHandler;
+import com.rfid.InventoryTagMap;
+import com.rfid.trans.TagCallback;
+
+import java.util.List;
+
 
 public class MainActivity extends FlutterActivity {
 
+    private static final String TAG = "MainActivity";
+
+    private ScanMethodCallHandler scanMethodCallHandler;
+    
+    @Override
+    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
+        super.configureFlutterEngine(flutterEngine);
+        // 初始化并注册扫码MethodChannel处理器
+        scanMethodCallHandler = new ScanMethodCallHandler(
+            getApplicationContext(), // 全局Context,避免Activity生命周期影响
+            flutterEngine.getDartExecutor().getBinaryMessenger() // Flutter通信信使
+        );
+    }
+
+    @Override
+    public void cleanUpFlutterEngine(FlutterEngine flutterEngine) {
+        super.cleanUpFlutterEngine(flutterEngine);
+        // 释放资源:避免内存泄漏
+        if (scanMethodCallHandler != null) {
+            scanMethodCallHandler.dispose();
+            scanMethodCallHandler = null;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        
+        Log.d(TAG, "MainActivity创建完成");
+    }
+
+    
+    @Override
+    protected void onPause() {
+        super.onPause();
+        
+        Log.d(TAG, "MainActivity暂停");
+
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        
+        Log.d(TAG, "MainActivity恢复");
+
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+      
+       
+        Log.d(TAG, "MainActivity销毁,资源已释放");
+    }
+
+     @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        Log.d(TAG, "按键按下: " + keyCode);
+      
+        
+        // 其他按键处理
+        return super.onKeyDown(keyCode, event);
+    }
+
 }

+ 21 - 0
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanBinder.java

@@ -0,0 +1,21 @@
+package com.vber.chicken_farm.scan;
+
+import android.os.Binder;
+
+/**
+ * 扫码服务Binder通信类
+ */
+public class ScanBinder extends Binder {
+    private final ScanService scanService;
+
+    public ScanBinder(ScanService service) {
+        this.scanService = service;
+    }
+
+    /**
+     * 获取扫码服务实例
+     */
+    public ScanService getScanService() {
+        return scanService;
+    }
+}

+ 18 - 0
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanCallback.java

@@ -0,0 +1,18 @@
+package com.vber.chicken_farm.scan;
+
+/**
+ * 扫描结果回调接口
+ */
+public interface ScanCallback {
+    /**
+     * 扫描结果回调
+     * @param result 扫码结果字符串
+     */
+    void onScanResult(String result);
+
+    /**
+     * 扫描错误回调
+     * @param errorMsg 错误信息
+     */
+    void onScanError(String errorMsg);
+}

+ 29 - 0
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanConstants.java

@@ -0,0 +1,29 @@
+package com.vber.chicken_farm.scan;
+
+/**
+ * 扫码服务常量定义
+ */
+public final class ScanConstants {
+    // 扫描结果广播Action
+    public static final String SCAN_ACTION = "scan.rcv.message";
+    
+    // 唤醒锁标识
+    public static final String WAKE_LOCK_TAG = "ScanService:WakeLock";
+    
+    // 灯光模式常量
+    public static final int LIGHT_MODE_OFF = 0;         // 关闭
+    public static final int LIGHT_MODE_AIM_ONLY = 1;    // 仅瞄准
+    public static final int LIGHT_MODE_ILLUM_ONLY = 2;  // 仅照明
+    public static final int LIGHT_MODE_ALTERNATE = 3;   // 交替
+    public static final int LIGHT_MODE_BOTH = 4;        // 同时开启(默认)
+    
+    // 唤醒锁超时时间(10分钟)
+    public static final long WAKE_LOCK_TIMEOUT = 10 * 60 * 1000L;
+    
+    // 扫描模式:广播模式
+    public static final int SCAN_OP_MODE_BROADCAST = 0;
+    
+    private ScanConstants() {
+        // 禁止实例化
+    }
+}

+ 176 - 0
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanMethodCallHandler.java

@@ -0,0 +1,176 @@
+package com.vber.chicken_farm.scan;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
+import io.flutter.plugin.common.MethodChannel.Result;
+
+/**
+ * Flutter与Android扫码服务的通信桥接类
+ */
+public class ScanMethodCallHandler implements MethodCallHandler, ScanCallback {
+    private static final String TAG = "ScanMethodCallHandler";
+    // Flutter MethodChannel 名称(需与Flutter端一致)
+    public static final String CHANNEL_NAME = "com.vber.chicken_farm/scan";
+
+    private final Context context;
+    private final MethodChannel channel;
+    private ScanService scanService;
+    private boolean isServiceBound = false;
+
+    // 服务连接回调
+    private final ServiceConnection serviceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Log.d(TAG, "扫码服务绑定成功");
+            ScanBinder binder = (ScanBinder) service;
+            scanService = binder.getScanService();
+            scanService.setScanCallback(ScanMethodCallHandler.this);
+            isServiceBound = true;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.d(TAG, "扫码服务解绑");
+            scanService = null;
+            isServiceBound = false;
+        }
+    };
+
+    // 构造方法
+    public ScanMethodCallHandler(Context context, BinaryMessenger messenger) {
+        this.context = context;
+        this.channel = new MethodChannel(messenger, CHANNEL_NAME);
+        this.channel.setMethodCallHandler(this);
+        // 初始化时绑定服务
+        bindScanService();
+    }
+
+    /**
+     * 绑定扫码服务
+     */
+    private void bindScanService() {
+        try {
+            Intent intent = new Intent(context, ScanService.class);
+            context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+        } catch (Exception e) {
+            Log.e(TAG, "绑定扫码服务失败", e);
+        }
+    }
+
+    /**
+     * 处理Flutter端的方法调用
+     */
+    @Override
+    public void onMethodCall(MethodCall call, Result result) {
+        Log.d(TAG, "收到Flutter调用方法:" + call.method);
+        if (!isServiceBound || scanService == null) {
+            result.error("SERVICE_NOT_BOUND", "扫码服务未绑定", null);
+            return;
+        }
+
+        switch (call.method) {
+            // 扫描头管理API
+            case "openScanHead":
+                boolean openSuccess = scanService.openScanHead();
+                result.success(openSuccess);
+                break;
+            case "closeScanHead":
+                scanService.closeScanHead();
+                result.success(true);
+                break;
+            case "resetIdleTimeout":
+                scanService.resetIdleTimeout();
+                result.success(true);
+                break;
+                
+            // 扫码解码API
+            case "startScan":
+                boolean scanSuccess = scanService.startScan();
+                result.success(scanSuccess);
+                break;
+            case "stopSingleScan":
+                scanService.stopSingleScan();
+                result.success(true);
+                break;
+                
+            // 状态查询API
+            case "isScanning":
+                result.success(scanService.isScanning());
+                break;
+            case "isScanHeadOpened":
+                result.success(scanService.isScanHeadOpened());
+                break;
+                
+            // 灯光设置API
+            case "setLightingMode":
+                int mode = call.argument("mode");
+                scanService.setLightingMode(mode);
+                result.success("灯光模式已设置为:" + mode);
+                break;
+                
+            default:
+                result.notImplemented();
+                break;
+        }
+    }
+
+    /**
+     * 获取扫描状态
+     */
+    private boolean scanServiceIsScanning() {
+        try {
+            // 通过反射获取ScanService的isScanning字段(因为是private)
+            java.lang.reflect.Field field = ScanService.class.getDeclaredField("isScanning");
+            field.setAccessible(true);
+            return (boolean) field.get(scanService);
+        } catch (Exception e) {
+            Log.e(TAG, "获取扫描状态失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 扫描结果回调(转发给Flutter)
+     */
+    @Override
+    public void onScanResult(String result) {
+        // 向Flutter发送扫描结果事件
+        channel.invokeMethod("onScanResult", result);
+    }
+
+    /**
+     * 扫描错误回调(转发给Flutter)
+     */
+    @Override
+    public void onScanError(String errorMsg) {
+        // 向Flutter发送错误事件
+        channel.invokeMethod("onScanError", errorMsg);
+    }
+
+    /**
+     * 释放资源(Flutter插件销毁时调用)
+     */
+    public void dispose() {
+        Log.d(TAG, "释放扫码桥接资源");
+        // 解绑服务
+        if (isServiceBound) {
+            if (scanService != null) {
+                scanService.stopSingleScan();
+            }
+            context.unbindService(serviceConnection);
+            isServiceBound = false;
+        }
+        // 清空MethodChannel
+        channel.setMethodCallHandler(null);
+    }
+}

+ 503 - 0
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/scan/ScanService.java

@@ -0,0 +1,503 @@
+package com.vber.chicken_farm.scan;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.bld.ScanManager;
+import android.bld.scan.configuration.DecoderConfigValues;
+import android.util.Log;
+
+/**
+ * 扫码核心服务(扫描头独立管理+双超时+低功耗)
+ * 适配场景:
+ * 1. 提前打开扫描头(进入场景时),扫码时直接复用,提升响应速度;
+ * 2. 单次扫码超时(5秒):停止解码,保留扫描头;
+ * 3. 闲置超时(5分钟):无新扫码请求则关闭扫描头+注销广播;
+ * 4. 离开场景可手动关闭扫描头,立即释放资源;
+ * 5. 默认仅开启瞄准灯,降低功耗。
+ */
+public class ScanService extends Service {
+    private static final String TAG = "ScanService";
+    
+    // -------------------------- 可配置常量 --------------------------
+    /** 单次扫码超时时间(毫秒):无结果则停止解码,保留扫描头 */
+    public static final long SCAN_SINGLE_TIMEOUT_MS = 5000;
+    /** 扫描头闲置超时时间(毫秒):打开后无扫码请求则关闭 */
+    public static final long SCAN_IDLE_TIMEOUT_MS = 5 * 60 * 1000L; // 5分钟
+    /** 默认灯光模式:仅开启瞄准 */
+    public static final int DEFAULT_LIGHT_MODE = ScanConstants.LIGHT_MODE_AIM_ONLY;
+    
+    // -------------------------- 核心变量 --------------------------
+    private ScanManager scanManager;
+    private ScanCallback scanCallback;
+    private PowerManager.WakeLock wakeLock;
+    private boolean isScanning = false; // 是否正在解码
+    private boolean isScanHeadOpened = false; // 扫描头是否打开
+    private Handler scanHandler; // 统一处理延迟任务
+    private Runnable singleTimeoutRunnable; // 单次扫码超时任务
+    private Runnable idleTimeoutRunnable; // 扫描头闲置超时任务
+    
+    // 广播接收器(仅扫描头打开时注册)
+    private final BroadcastReceiver screenReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent == null || intent.getAction() == null || !isScanHeadOpened) return;
+            
+            switch (intent.getAction()) {
+                case Intent.ACTION_SCREEN_ON:
+                    // 屏幕亮起 - 若处于扫描中,恢复解码并重置单次超时
+                    if (isScanning) {
+                        resumeScan();
+                        resetSingleTimeout();
+                    }
+                    break;
+                case Intent.ACTION_SCREEN_OFF:
+                    // 屏幕熄灭 - 暂停解码,保留扫描头
+                    if (isScanning) {
+                        pauseScan();
+                    }
+                    break;
+            }
+        }
+    };
+    
+    private final BroadcastReceiver scanReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent == null || scanCallback == null || !isScanning) return;
+            
+            try {
+                byte[] barcode = intent.getByteArrayExtra("barocode");
+                int barcodeLen = intent.getIntExtra("length", 0);
+                
+                if (barcode != null && barcodeLen > 0) {
+                    String barcodeStr = new String(barcode, 0, barcodeLen);
+                    scanCallback.onScanResult(barcodeStr);
+                    Log.d(TAG, "扫描成功:" + barcodeStr);
+                    stopSingleScan(); // 单次扫码完成,停止解码(保留扫描头)
+                } else {
+                    scanCallback.onScanError("扫描结果为空");
+                }
+            } catch (Exception e) {
+                scanCallback.onScanError("解析扫描结果失败:" + e.getMessage());
+                Log.e(TAG, "解析扫描结果失败", e);
+            }
+        }
+    };
+    
+    // Binder实例
+    private final ScanBinder binder = new ScanBinder(this);
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return binder;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Log.d(TAG, "扫码服务创建");
+        scanHandler = new Handler(getMainLooper());
+        acquireWakeLock(); // 仅轻量级唤醒锁,不初始化扫描硬件
+    }
+
+    // -------------------------- 懒加载初始化 --------------------------
+    private void lazyInitScanManager() {
+        if (scanManager == null) {
+            scanManager = ScanManager.getDefaultInstance(this);
+            if (scanManager == null) {
+                Log.e(TAG, "获取ScanManager实例失败");
+                if (scanCallback != null) {
+                    scanCallback.onScanError("扫描硬件初始化失败");
+                }
+                return;
+            }
+            Log.d(TAG, "扫描管理器懒加载初始化成功");
+        }
+    }
+
+    private void registerScanReceivers() {
+        try {
+            IntentFilter screenFilter = new IntentFilter();
+            screenFilter.addAction(Intent.ACTION_SCREEN_ON);
+            screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
+            registerReceiver(screenReceiver, screenFilter);
+            
+            IntentFilter scanFilter = new IntentFilter(ScanConstants.SCAN_ACTION);
+            registerReceiver(scanReceiver, scanFilter);
+            
+            Log.d(TAG, "广播接收器注册完成");
+        } catch (Exception e) {
+            Log.e(TAG, "注册广播接收器失败", e);
+            if (scanCallback != null) {
+                scanCallback.onScanError("注册扫描广播失败:" + e.getMessage());
+            }
+        }
+    }
+
+    private void unregisterScanReceivers() {
+        try {
+            unregisterReceiver(screenReceiver);
+            unregisterReceiver(scanReceiver);
+            Log.d(TAG, "广播接收器已注销");
+        } catch (Exception e) {
+            Log.e(TAG, "注销广播接收器失败", e);
+        }
+    }
+
+    // -------------------------- 扫描头独立管理API(给Flutter调用) --------------------------
+    /**
+     * 提前打开扫描头(进入场景时调用)
+     * 逻辑:初始化扫描硬件+注册广播+启动闲置超时+默认开启瞄准灯
+     */
+    public boolean openScanHead() {
+        if (isScanHeadOpened) {
+            Log.w(TAG, "扫描头已打开,无需重复调用");
+            resetIdleTimeout(); // 重置闲置超时
+            return true;
+        }
+
+        // 1. 懒加载初始化扫描管理器
+        lazyInitScanManager();
+        if (scanManager == null) {
+            return false;
+        }
+
+        try {
+            // 2. 打开扫描头硬件
+            scanManager.openScanner();
+            // 3. 设置默认灯光(仅瞄准)
+            setLightingMode(DEFAULT_LIGHT_MODE);
+            // 4. 设置广播模式
+            scanManager.setOPMode(ScanConstants.SCAN_OP_MODE_BROADCAST);
+            // 5. 注册广播接收器
+            registerScanReceivers();
+            
+            isScanHeadOpened = true;
+            // 6. 启动扫描头闲置超时(核心调整:打开扫描头就启动)
+            startIdleTimeout();
+            Log.d(TAG, "扫描头已提前打开(闲置超时:" + SCAN_IDLE_TIMEOUT_MS + "ms)");
+            return true;
+        } catch (Exception e) {
+            Log.e(TAG, "打开扫描头失败", e);
+            if (scanCallback != null) {
+                scanCallback.onScanError("打开扫描头失败:" + e.getMessage());
+            }
+            return false;
+        }
+    }
+
+    /**
+     * 手动关闭扫描头(离开场景时调用)
+     * 逻辑:停止解码+关闭硬件+注销广播+取消所有超时
+     */
+    public void closeScanHead() {
+        // 1. 取消所有超时任务
+        cancelAllTimeouts();
+
+        // 2. 停止解码(若正在扫码)
+        if (scanManager != null && isScanning) {
+            try {
+                scanManager.stopDecode();
+            } catch (Exception e) {
+                Log.e(TAG, "停止解码失败", e);
+            }
+        }
+
+        // 3. 关闭扫描头硬件
+        if (scanManager != null && isScanHeadOpened) {
+            try {
+                scanManager.closeScanner();
+                Log.d(TAG, "扫描头已手动关闭");
+            } catch (Exception e) {
+                Log.e(TAG, "关闭扫描头失败", e);
+            }
+        }
+
+        // 4. 注销广播接收器
+        unregisterScanReceivers();
+
+        // 5. 更新状态
+        isScanning = false;
+        isScanHeadOpened = false;
+    }
+
+    // -------------------------- 扫码解码API(给Flutter调用) --------------------------
+    /**
+     * 开始单次扫码(扫描头已打开时直接复用,未打开则自动打开)
+     */
+    public boolean startScan() {
+        if (isScanning) {
+            Log.w(TAG, "扫码已在进行中,无需重复调用");
+            resetSingleTimeout(); // 重置单次超时
+            return true;
+        }
+
+        // 1. 若扫描头未打开,自动打开(兼容未提前打开的场景)
+        if (!isScanHeadOpened) {
+            boolean openSuccess = openScanHead();
+            if (!openSuccess) {
+                return false;
+            }
+        }
+
+        try {
+            // 2. 开始解码(复用已打开的扫描头)
+            scanManager.startDecode();
+            isScanning = true;
+            // 3. 启动单次扫码超时
+            startSingleTimeout();
+            // 4. 重置闲置超时(扫码行为刷新闲置计时)
+            resetIdleTimeout();
+            Log.d(TAG, "开始单次扫码(超时:" + SCAN_SINGLE_TIMEOUT_MS + "ms)");
+            return true;
+        } catch (Exception e) {
+            Log.e(TAG, "开始扫码失败", e);
+            isScanning = false;
+            if (scanCallback != null) {
+                scanCallback.onScanError("开始扫码失败:" + e.getMessage());
+            }
+            return false;
+        }
+    }
+
+    /**
+     * 停止单次扫码(仅停止解码,保留扫描头)
+     */
+    public void stopSingleScan() {
+        // 1. 取消单次扫码超时
+        cancelSingleTimeout();
+
+        // 2. 停止解码
+        if (scanManager != null && isScanning) {
+            try {
+                scanManager.stopDecode();
+                Log.d(TAG, "单次扫码停止(保留扫描头)");
+            } catch (Exception e) {
+                Log.e(TAG, "停止解码失败", e);
+            }
+        }
+
+        isScanning = false;
+        // 闲置超时仍在运行,到期自动关闭扫描头
+    }
+
+    // -------------------------- 超时任务管理(核心调整) --------------------------
+    /**
+     * 启动扫描头闲置超时(打开扫描头时调用)
+     */
+    private void startIdleTimeout() {
+        cancelIdleTimeout(); // 先取消旧任务
+        idleTimeoutRunnable = () -> {
+            if (isScanHeadOpened && !isScanning) {
+                Log.d(TAG, "扫描头闲置超时(" + SCAN_IDLE_TIMEOUT_MS + "ms),自动关闭");
+                closeScanHead(); // 关闭扫描头+注销广播
+                if (scanCallback != null) {
+                    scanCallback.onScanError("扫描头闲置超时已自动关闭");
+                }
+            }
+        };
+        scanHandler.postDelayed(idleTimeoutRunnable, SCAN_IDLE_TIMEOUT_MS);
+    }
+
+    /**
+     * 重置扫描头闲置超时(扫码/手动刷新时调用)
+     */
+    public void resetIdleTimeout() {
+        if (isScanHeadOpened && idleTimeoutRunnable != null) {
+            scanHandler.removeCallbacks(idleTimeoutRunnable);
+            scanHandler.postDelayed(idleTimeoutRunnable, SCAN_IDLE_TIMEOUT_MS);
+            Log.d(TAG, "扫描头闲置超时已重置");
+        }
+    }
+
+    /**
+     * 启动单次扫码超时(扫码时调用)
+     */
+    private void startSingleTimeout() {
+        cancelSingleTimeout(); // 先取消旧任务
+        singleTimeoutRunnable = () -> {
+            if (isScanning) {
+                Log.d(TAG, "单次扫码超时(" + SCAN_SINGLE_TIMEOUT_MS + "ms),停止解码");
+                stopSingleScan();
+                if (scanCallback != null) {
+                    scanCallback.onScanError("单次扫码超时,未检测到条码");
+                }
+            }
+        };
+        scanHandler.postDelayed(singleTimeoutRunnable, SCAN_SINGLE_TIMEOUT_MS);
+    }
+
+    /**
+     * 重置单次扫码超时(屏幕亮起时调用)
+     */
+    private void resetSingleTimeout() {
+        if (singleTimeoutRunnable != null) {
+            scanHandler.removeCallbacks(singleTimeoutRunnable);
+            scanHandler.postDelayed(singleTimeoutRunnable, SCAN_SINGLE_TIMEOUT_MS);
+            Log.d(TAG, "单次扫码超时已重置");
+        }
+    }
+
+    /**
+     * 取消单次扫码超时
+     */
+    private void cancelSingleTimeout() {
+        if (singleTimeoutRunnable != null) {
+            scanHandler.removeCallbacks(singleTimeoutRunnable);
+            singleTimeoutRunnable = null;
+        }
+    }
+
+    /**
+     * 取消闲置超时
+     */
+    private void cancelIdleTimeout() {
+        if (idleTimeoutRunnable != null) {
+            scanHandler.removeCallbacks(idleTimeoutRunnable);
+            idleTimeoutRunnable = null;
+        }
+    }
+
+    /**
+     * 取消所有超时任务
+     */
+    private void cancelAllTimeouts() {
+        cancelSingleTimeout();
+        cancelIdleTimeout();
+    }
+
+    // -------------------------- 辅助方法 --------------------------
+    /**
+     * 暂停扫码(屏幕熄灭时):仅停止解码,保留扫描头
+     */
+    private void pauseScan() {
+        if (scanManager != null && isScanning) {
+            try {
+                scanManager.stopDecode();
+                Log.d(TAG, "扫码已暂停(屏幕熄灭)");
+            } catch (Exception e) {
+                Log.e(TAG, "暂停扫码失败", e);
+            }
+        }
+    }
+
+    /**
+     * 恢复扫码(屏幕亮起时):重新解码+重置单次超时
+     */
+    private void resumeScan() {
+        if (scanManager != null && isScanHeadOpened) {
+            try {
+                scanManager.startDecode();
+                Log.d(TAG, "扫码已恢复(屏幕亮起)");
+            } catch (Exception e) {
+                Log.e(TAG, "恢复扫码失败", e);
+            }
+        }
+    }
+
+    /**
+     * 设置灯光模式(外部调用)
+     */
+    public void setLightingMode(int mode) {
+        if (scanManager != null && isScanHeadOpened) {
+            try {
+                int[] paramIDList = {DecoderConfigValues.ParamID.LIGHTS_MODE};
+                int[] symValue = {mode};
+                scanManager.setSYMValueInts(paramIDList, symValue);
+                Log.d(TAG, "灯光模式设置为:" + getLightModeDesc(mode));
+            } catch (Exception e) {
+                Log.e(TAG, "设置灯光模式失败", e);
+                if (scanCallback != null) {
+                    scanCallback.onScanError("设置灯光模式失败:" + e.getMessage());
+                }
+            }
+        } else {
+            if (scanCallback != null) {
+                scanCallback.onScanError("扫描头未打开,无法设置灯光");
+            }
+        }
+    }
+
+    private String getLightModeDesc(int mode) {
+        switch (mode) {
+            case ScanConstants.LIGHT_MODE_OFF: return "关闭";
+            case ScanConstants.LIGHT_MODE_AIM_ONLY: return "仅瞄准";
+            case ScanConstants.LIGHT_MODE_ILLUM_ONLY: return "仅照明";
+            case ScanConstants.LIGHT_MODE_ALTERNATE: return "交替";
+            case ScanConstants.LIGHT_MODE_BOTH: return "瞄准+照明";
+            default: return "未知模式(" + mode + ")";
+        }
+    }
+
+    // -------------------------- 基础方法 --------------------------
+    private void acquireWakeLock() {
+        try {
+            PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+            if (powerManager != null) {
+                wakeLock = powerManager.newWakeLock(
+                        PowerManager.PARTIAL_WAKE_LOCK,
+                        ScanConstants.WAKE_LOCK_TAG
+                );
+                // 唤醒锁时长适配最长闲置时间+单次扫码时间
+                wakeLock.acquire(SCAN_IDLE_TIMEOUT_MS + SCAN_SINGLE_TIMEOUT_MS);
+                Log.d(TAG, "唤醒锁已获取(适配闲置超时)");
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "获取唤醒锁失败", e);
+            if (scanCallback != null) {
+                scanCallback.onScanError("获取唤醒锁失败:" + e.getMessage());
+            }
+        }
+    }
+
+    // -------------------------- 对外暴露的状态/回调 --------------------------
+    public void setScanCallback(ScanCallback callback) {
+        this.scanCallback = callback;
+    }
+
+    public boolean isScanning() {
+        return isScanning;
+    }
+
+    public boolean isScanHeadOpened() {
+        return isScanHeadOpened;
+    }
+
+    // -------------------------- 生命周期 --------------------------
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        Log.d(TAG, "扫码服务销毁");
+        
+        // 彻底关闭扫描头
+        closeScanHead();
+        
+        // 释放Handler
+        if (scanHandler != null) {
+            scanHandler.removeCallbacksAndMessages(null);
+            scanHandler = null;
+        }
+        
+        // 释放唤醒锁
+        if (wakeLock != null && wakeLock.isHeld()) {
+            wakeLock.release();
+            Log.d(TAG, "唤醒锁已释放");
+        }
+        
+        scanCallback = null;
+        scanManager = null;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        // 解绑时关闭扫描头(可选,根据业务调整)
+        closeScanHead();
+        return true;
+    }
+}

+ 576 - 0
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/services/RFIDService.java

@@ -0,0 +1,576 @@
+package com.vber.chicken_farm.service;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.util.Log;
+
+import com.uhf.lcrrgxmodule.factory.ILcUhfProduct;
+import com.uhf.lcrrgxmodule.factory.LcModule;
+import com.rfid.InventoryTagMap;
+import com.rfid.PowerUtil;
+import com.rfid.trans.ReaderParameter;
+import com.rfid.trans.ReadTag;
+import com.rfid.trans.TagCallback;
+
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * RFID服务类 - 完善版
+ * 包含:连接管理、读写操作、自动断开、息屏/亮屏处理
+ */
+public class RFIDService {
+    private static final String TAG = "RFIDService";
+    private static final String DEVICE_PORT1 = "/dev/ttyS3";
+    private static final String DEVICE_PORT2 = "/dev/ttyS2";
+    private static final int[] BAUD_RATE_LIST = {115200, 460800, 57600};
+    
+    // 自动断开阈值(默认5分钟,单位:毫秒)
+    private static final long DEFAULT_IDLE_DISCONNECT_TIME = 5 * 60 * 1000;
+    // 心跳检测间隔(默认30秒)
+    private static final long HEARTBEAT_INTERVAL = 30 * 1000;
+
+    private Context mContext;
+    private ILcUhfProduct rrlib;
+    private boolean isConnected = false;
+    private boolean isReading = false;
+    private ReaderParameter readerParameter;
+    
+    // 自动断开相关
+    private Timer idleTimer;
+    private Handler mainHandler;
+    private long idleDisconnectTime = DEFAULT_IDLE_DISCONNECT_TIME;
+    private PowerManager.WakeLock wakeLock; // 息屏保活锁
+    
+    // 外部回调
+    private RFIDStatusCallback statusCallback;
+    private TagReadCallback tagReadCallback;
+
+    // 构造函数
+    public RFIDService(Context context) {
+        this.mContext = context.getApplicationContext(); // 避免内存泄漏
+        this.mainHandler = new Handler(Looper.getMainLooper());
+        initReader();
+        initWakeLock();
+    }
+
+    /**
+     * 初始化RFID阅读器
+     */
+    private void initReader() {
+        try {
+            rrlib = new LcModule(mContext).createProduct();
+        } catch (Throwable e) {
+            Log.e(TAG, "初始化RFID阅读器失败", e);
+            notifyStatusChange(RFIDStatus.STATUS_INIT_FAILED);
+        }
+    }
+
+    /**
+     * 初始化息屏唤醒锁
+     */
+    private void initWakeLock() {
+        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        if (powerManager != null) {
+            wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "RFIDService:WakeLock");
+            wakeLock.setReferenceCounted(false);
+        }
+    }
+
+    // ======================== 连接管理核心API ========================
+    /**
+     * 连接RFID设备
+     * @param retryTimes 重试次数
+     * @return 连接结果
+     */
+    public synchronized boolean connect(int retryTimes) {
+        if (isConnected) {
+            resetIdleTimer(); // 重置闲置计时器
+            return true;
+        }
+
+        Log.d(TAG, "开始连接RFID设备,重试次数:" + retryTimes);
+        notifyStatusChange(RFIDStatus.STATUS_CONNECTING);
+        
+        PowerUtil.power("1"); // 设备上电
+        boolean connected = false;
+
+        // 多端口+多波特率重试连接
+        for (int i = 0; i < retryTimes && !connected; i++) {
+            for (int baud : BAUD_RATE_LIST) {
+                if (tryConnect(DEVICE_PORT1, baud) || tryConnect(DEVICE_PORT2, baud)) {
+                    connected = true;
+                    break;
+                }
+            }
+        }
+
+        if (connected) {
+            isConnected = true;
+            readerParameter = rrlib.GetInventoryParameter();
+            startIdleTimer(); // 启动闲置计时器
+            notifyStatusChange(RFIDStatus.STATUS_CONNECTED);
+            Log.d(TAG, "RFID设备连接成功");
+        } else {
+            PowerUtil.power("0"); // 连接失败下电
+            notifyStatusChange(RFIDStatus.STATUS_CONNECT_FAILED);
+            Log.e(TAG, "RFID设备连接失败");
+        }
+        return connected;
+    }
+
+    /**
+     * 尝试单个端口+波特率连接
+     */
+    private boolean tryConnect(String port, int baud) {
+        try {
+            int result = rrlib.Connect(port, baud);
+            Log.d(TAG, "尝试连接 - 端口:" + port + " 波特率:" + baud + " 结果:" + result);
+            return result == 0;
+        } catch (Exception e) {
+            Log.e(TAG, "端口连接异常", e);
+            return false;
+        }
+    }
+
+    /**
+     * 断开RFID连接
+     */
+    public synchronized void disconnect() {
+        if (!isConnected) return;
+
+        Log.d(TAG, "断开RFID设备连接");
+        notifyStatusChange(RFIDStatus.STATUS_DISCONNECTING);
+        
+        // 停止读取和计时器
+        stopReading();
+        stopIdleTimer();
+        
+        // 断开硬件连接
+        try {
+            if (rrlib != null) {
+                rrlib.DisConnect();
+            }
+            PowerUtil.power("0"); // 设备下电
+        } catch (Exception e) {
+            Log.e(TAG, "断开连接异常", e);
+        }
+
+        isConnected = false;
+        releaseWakeLock(); // 释放唤醒锁
+        notifyStatusChange(RFIDStatus.STATUS_DISCONNECTED);
+    }
+
+    // ======================== 自动断开相关 ========================
+    /**
+     * 设置自动断开闲置时间(毫秒)
+     * @param time 闲置时间,<=0则关闭自动断开
+     */
+    public void setIdleDisconnectTime(long time) {
+        this.idleDisconnectTime = time;
+        if (isConnected) {
+            restartIdleTimer();
+        }
+    }
+
+    /**
+     * 启动闲置计时器
+     */
+    private void startIdleTimer() {
+        stopIdleTimer();
+        if (idleDisconnectTime <= 0) return;
+
+        idleTimer = new Timer("RFIDIdleTimer", true);
+        idleTimer.scheduleAtFixedRate(new TimerTask() {
+            private long lastActiveTime = System.currentTimeMillis();
+
+            @Override
+            public void run() {
+                long currentTime = System.currentTimeMillis();
+                // 检测闲置超时
+                if (currentTime - lastActiveTime >= idleDisconnectTime) {
+                    mainHandler.post(() -> {
+                        Log.d(TAG, "RFID闲置超时,自动断开连接");
+                        disconnect();
+                        notifyStatusChange(RFIDStatus.STATUS_IDLE_DISCONNECT);
+                    });
+                }
+                // 心跳检测(可选)
+                if (isConnected && !isReading) {
+                    checkHeartbeat();
+                }
+            }
+        }, HEARTBEAT_INTERVAL, HEARTBEAT_INTERVAL);
+    }
+
+    /**
+     * 重置闲置计时器(有操作时调用)
+     */
+    private void resetIdleTimer() {
+        if (idleTimer != null) {
+            idleTimer.cancel();
+            startIdleTimer();
+        }
+    }
+
+    /**
+     * 停止闲置计时器
+     */
+    private void stopIdleTimer() {
+        if (idleTimer != null) {
+            idleTimer.cancel();
+            idleTimer = null;
+        }
+    }
+
+    /**
+     * 重启闲置计时器
+     */
+    private void restartIdleTimer() {
+        stopIdleTimer();
+        startIdleTimer();
+    }
+
+    /**
+     * 心跳检测(确保连接有效)
+     */
+    private void checkHeartbeat() {
+        try {
+            if (rrlib != null) {
+                rrlib.GetInventoryParameter(); // 简单的心跳指令
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "心跳检测失败,连接已失效", e);
+            mainHandler.post(this::disconnect);
+        }
+    }
+
+    // ======================== 息屏/亮屏处理API ========================
+    /**
+     * 息屏时的处理(外部调用)
+     * 建议在Activity的onPause/onStop中调用
+     */
+    public void onScreenOff() {
+        Log.d(TAG, "屏幕熄灭,处理RFID操作");
+        // 息屏时停止读取,但保持连接(可根据需求调整)
+        stopReading();
+        // 申请唤醒锁,保证后台连接不被系统杀死
+        acquireWakeLock();
+    }
+
+    /**
+     * 亮屏时的处理(外部调用)
+     * 建议在Activity的onResume中调用
+     */
+    public void onScreenOn() {
+        Log.d(TAG, "屏幕点亮,恢复RFID操作");
+        releaseWakeLock(); // 释放唤醒锁
+        resetIdleTimer(); // 重置闲置计时器
+        // 亮屏后可自动恢复读取(可选,根据业务需求)
+        // if (isConnected) startReading(tagReadCallback);
+    }
+
+    /**
+     * 申请唤醒锁
+     */
+    private void acquireWakeLock() {
+        if (wakeLock != null && !wakeLock.isHeld()) {
+            wakeLock.acquire(10 * 60 * 1000); // 最多持有10分钟
+            Log.d(TAG, "唤醒锁已申请");
+        }
+    }
+
+    /**
+     * 释放唤醒锁
+     */
+    private void releaseWakeLock() {
+        if (wakeLock != null && wakeLock.isHeld()) {
+            wakeLock.release();
+            Log.d(TAG, "唤醒锁已释放");
+        }
+    }
+
+    // ======================== 读写操作核心API ========================
+    /**
+     * 开始读取标签
+     */
+    public synchronized boolean startReading(TagReadCallback callback) {
+        if (!isConnected) {
+            notifyError("未连接RFID设备");
+            return false;
+        }
+
+        this.tagReadCallback = callback;
+        isReading = true;
+        resetIdleTimer(); // 重置闲置计时器
+
+        try {
+            // 清空历史数据
+            rrlib.getInventoryTagMapList().clear();
+            rrlib.getInventoryTagResultList().clear();
+            // 设置回调
+            rrlib.SetCallBack(new InternalTagCallback());
+            boolean success = rrlib.StartRead() == 0;
+            
+            if (success) {
+                notifyStatusChange(RFIDStatus.STATUS_READING);
+                Log.d(TAG, "开始读取标签成功");
+            } else {
+                notifyError("开始读取标签失败");
+            }
+            return success;
+        } catch (Exception e) {
+            Log.e(TAG, "开始读取标签异常", e);
+            notifyError("读取异常:" + e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 停止读取标签
+     */
+    public synchronized void stopReading() {
+        if (!isConnected || !isReading) return;
+
+        try {
+            if (rrlib != null) {
+                rrlib.StopRead();
+            }
+            isReading = false;
+            resetIdleTimer(); // 重置闲置计时器
+            notifyStatusChange(RFIDStatus.STATUS_READ_STOPPED);
+            Log.d(TAG, "停止读取标签成功");
+        } catch (Exception e) {
+            Log.e(TAG, "停止读取标签异常", e);
+            notifyError("停止读取异常:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 读取标签数据
+     */
+    public String readData(String epc, byte mem, int wordPtr, byte num, byte[] password) {
+        if (!isConnected) {
+            notifyError("未连接RFID设备");
+            return null;
+        }
+
+        resetIdleTimer(); // 重置闲置计时器
+        try {
+            ReaderParameter param = rrlib.GetInventoryParameter();
+            if (param.AsciiPtr == 1) {
+                epc = asciiToHex(epc);
+            }
+
+            String result = param.TidLen == 0 
+                ? rrlib.ReadDataByEPC(epc, mem, wordPtr, num, password)
+                : rrlib.ReadDataByTID(epc, mem, wordPtr, num, password);
+            
+            Log.d(TAG, "读取标签数据成功:" + result);
+            return result;
+        } catch (Exception e) {
+            Log.e(TAG, "读取标签数据异常", e);
+            notifyError("读取数据异常:" + e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 写入标签数据
+     */
+    public boolean writeData(String epc, byte mem, int wordPtr, byte[] password, String data) {
+        if (!isConnected) {
+            notifyError("未连接RFID设备");
+            return false;
+        }
+
+        resetIdleTimer(); // 重置闲置计时器
+        try {
+            ReaderParameter param = rrlib.GetInventoryParameter();
+            if (param.AsciiPtr == 1) {
+                epc = asciiToHex(epc);
+            }
+
+            int result = param.TidLen == 0
+                ? rrlib.WriteDataByEPC(epc, mem, wordPtr, password, data)
+                : rrlib.WriteDataByTID(epc, mem, (byte) wordPtr, password, data);
+            
+            boolean success = result == 0;
+            if (success) {
+                Log.d(TAG, "写入标签数据成功");
+            } else {
+                notifyError("写入数据失败,错误码:" + result);
+            }
+            return success;
+        } catch (Exception e) {
+            Log.e(TAG, "写入标签数据异常", e);
+            notifyError("写入数据异常:" + e.getMessage());
+            return false;
+        }
+    }
+
+    // ======================== 辅助方法 ========================
+    private String asciiToHex(String asciiStr) {
+        char[] chars = asciiStr.toCharArray();
+        StringBuilder hex = new StringBuilder();
+        for (char ch : chars) {
+            hex.append(Integer.toHexString((int) ch));
+        }
+        return hex.toString();
+    }
+
+    // ======================== 回调与通知 ========================
+    /**
+     * 设置RFID状态回调(外部监听连接/读取状态)
+     */
+    public void setRFIDStatusCallback(RFIDStatusCallback callback) {
+        this.statusCallback = callback;
+    }
+
+    /**
+     * 通知状态变化
+     */
+    private void notifyStatusChange(RFIDStatus status) {
+        if (statusCallback != null) {
+            mainHandler.post(() -> statusCallback.onStatusChange(status));
+        }
+    }
+
+    /**
+     * 通知错误信息
+     */
+    private void notifyError(String error) {
+        if (tagReadCallback != null) {
+            mainHandler.post(() -> tagReadCallback.onReadError(error));
+        }
+        if (statusCallback != null) {
+            mainHandler.post(() -> statusCallback.onError(error));
+        }
+    }
+
+    /**
+     * 内部标签读取回调
+     */
+    private class InternalTagCallback implements TagCallback {
+        @Override
+        public void tagCallback(ReadTag readTag) {
+            mainHandler.post(() -> {
+                if (tagReadCallback != null && isReading) {
+                    List<InventoryTagMap> tagList = rrlib.getInventoryTagMapList();
+                    tagReadCallback.onTagRead(tagList);
+                    resetIdleTimer(); // 有数据读取,重置计时器
+                }
+            });
+        }
+
+      
+        
+        @Override
+        public void FinishCallBack() {
+            // 标签读取完成回调
+            isReading = false;
+            mainHandler.post(() -> {
+                notifyStatusChange(RFIDStatus.STATUS_READ_STOPPED);
+            });
+        }
+
+        @Override
+        public int CRCErrorCallBack(int reason) {
+            // CRC错误回调处理
+            return 0;
+        }
+
+        @Override
+        public int tagCallbackFailed(int reason) {
+            // 标签回调失败处理
+            mainHandler.post(() -> {
+                if (tagReadCallback != null) {
+                    tagReadCallback.onReadError("标签读取失败,错误码:" + reason);
+                }
+            });
+            return 0;
+        }
+    }
+
+    // ======================== 外部接口定义 ========================
+    /**
+     * 标签读取回调
+     */
+    public interface TagReadCallback {
+        void onTagRead(List<InventoryTagMap> tagList);
+        void onReadError(String error);
+    }
+
+    /**
+     * RFID状态枚举
+     */
+    public enum RFIDStatus {
+        STATUS_INIT_FAILED,    // 初始化失败
+        STATUS_CONNECTING,     // 连接中
+        STATUS_CONNECTED,      // 已连接
+        STATUS_CONNECT_FAILED, // 连接失败
+        STATUS_DISCONNECTING,  // 断开中
+        STATUS_DISCONNECTED,   // 已断开
+        STATUS_IDLE_DISCONNECT,// 闲置自动断开
+        STATUS_READING,        // 读取中
+        STATUS_READ_STOPPED    // 读取已停止
+    }
+
+    /**
+     * RFID状态回调(外部监听整体状态)
+     */
+    public interface RFIDStatusCallback {
+        void onStatusChange(RFIDStatus status);
+        void onError(String errorMsg);
+    }
+
+    // ======================== 状态查询API ========================
+    public boolean isConnected() {
+        return isConnected;
+    }
+
+    public boolean isReading() {
+        return isReading;
+    }
+
+    public ReaderParameter getInventoryParameter() {
+        if (isConnected && rrlib != null) {
+            try {
+                readerParameter = rrlib.GetInventoryParameter();
+            } catch (Exception e) {
+                Log.e(TAG, "获取参数失败", e);
+            }
+        }
+        return readerParameter;
+    }
+
+    public void setInventoryParameter(ReaderParameter param) {
+        if (isConnected && rrlib != null) {
+            try {
+                rrlib.SetInventoryParameter(param);
+                readerParameter = param;
+                resetIdleTimer();
+            } catch (Exception e) {
+                Log.e(TAG, "设置参数失败", e);
+                notifyError("设置参数异常:" + e.getMessage());
+            }
+        }
+    }
+
+    // ======================== 生命周期管理 ========================
+    /**
+     * 销毁服务(外部调用,释放所有资源)
+     */
+    public void destroy() {
+        disconnect();
+        stopIdleTimer();
+        releaseWakeLock();
+        mContext = null;
+        statusCallback = null;
+        tagReadCallback = null;
+        Log.d(TAG, "RFIDService已销毁,资源释放完成");
+    }
+}

+ 97 - 0
UI/CF.APP/chicken_farm/android/app/src/main/java/com/vber/chicken_farm/services/SoundService.java

@@ -0,0 +1,97 @@
+package com.vber.chicken_farm.service;
+
+import android.content.Context;
+import android.media.SoundPool;
+import android.media.AudioManager;
+import android.util.Log;
+
+import java.util.HashMap;
+
+import com.vber.chicken_farm.R;
+
+public class SoundService {
+    private static final String TAG = "SoundService";
+    private Context context;
+    
+    // 音效相关
+    private static HashMap<Integer, Integer> soundMap = new HashMap<Integer, Integer>();
+    private static SoundPool soundPool;
+    private static AudioManager am;
+    private static boolean soundLoaded = false;
+    
+    public SoundService(Context context) {
+        this.context = context;
+        // 初始化音效
+        initSound();
+    }
+    
+    /**
+     * 初始化音效
+     */
+    private void initSound() {
+        try {
+            soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 5);
+            soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
+                @Override
+                public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
+                    if (status == 0) {
+                        // 声音加载成功
+                        soundLoaded = true;
+                    } else {
+                        Log.e(TAG, "声音加载失败,状态码: " + status);
+                    }
+                }
+            });
+            // 注意:这里需要确保在res/raw目录下有barcodebeep.ogg文件
+            // 如果文件名不同,请相应修改
+            soundMap.put(1, soundPool.load(context, R.raw.barcodebeep, 1));
+            am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        } catch (Exception e) {
+            Log.e(TAG, "初始化音效失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 播放音效
+     */
+    public void playBeep() {
+        try {
+            if (soundPool != null && soundMap.containsKey(1) && soundLoaded) {
+                soundPool.play(soundMap.get(1), 1, 1, 0, 0, 1);
+            } else if (soundPool != null && soundMap.containsKey(1)) {
+                // 声音还在加载中,等待片刻后重试一次
+                new Thread(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            Thread.sleep(100); // 等待100毫秒
+                            if (soundLoaded) {
+                                ((android.app.Activity) context).runOnUiThread(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        soundPool.play(soundMap.get(1), 1, 1, 0, 0, 1);
+                                    }
+                                });
+                            }
+                        } catch (InterruptedException e) {
+                            Log.e(TAG, "播放音效线程中断: " + e.getMessage());
+                        }
+                    }
+                }).start();
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "播放音效失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 释放音效资源
+     */
+    public void release() {
+        soundLoaded = false;
+        if (soundPool != null) {
+            soundPool.release();
+            soundPool = null;
+        }
+    }
+}

+ 119 - 0
UI/CF.APP/chicken_farm/lib/core/services/pda/pda_scan_service.dart

@@ -0,0 +1,119 @@
+import 'package:flutter/services.dart';
+
+class PdaScanService {
+  // 与Android端CHANNEL_NAME一致
+  static const MethodChannel _channel = MethodChannel(
+    'com.vber.chicken_farm/scan',
+  );
+
+  /// 初始化监听
+  static void initScanListener({
+    required Function(String) onScanResult,
+    required Function(String) onScanError,
+  }) {
+    _channel.setMethodCallHandler((MethodCall call) async {
+      switch (call.method) {
+        case 'onScanResult':
+          onScanResult(call.arguments);
+          break;
+        case 'onScanError':
+          onScanError(call.arguments);
+          break;
+        default:
+          break;
+      }
+    });
+  }
+
+  // -------------------------- 扫描头管理API --------------------------
+  /// 提前打开扫描头(进入扫码场景时调用)
+  static Future<bool> openScanHead() async {
+    try {
+      final bool result = await _channel.invokeMethod('openScanHead');
+      return result;
+    } on PlatformException catch (e) {
+      throw Exception('打开扫描头失败:${e.message}');
+    }
+  }
+
+  /// 手动关闭扫描头(离开扫码场景时调用)
+  static Future<bool> closeScanHead() async {
+    try {
+      final bool result = await _channel.invokeMethod('closeScanHead');
+      return result;
+    } on PlatformException catch (e) {
+      throw Exception('关闭扫描头失败:${e.message}');
+    }
+  }
+
+  /// 重置扫描头闲置超时(手动刷新闲置计时)
+  static Future<bool> resetIdleTimeout() async {
+    try {
+      final bool result = await _channel.invokeMethod('resetIdleTimeout');
+      return result;
+    } on PlatformException catch (e) {
+      throw Exception('重置闲置超时失败:${e.message}');
+    }
+  }
+
+  // -------------------------- 扫码解码API --------------------------
+  /// 开始单次扫码(复用已打开的扫描头)
+  static Future<bool> startScan() async {
+    try {
+      final bool result = await _channel.invokeMethod('startScan');
+      return result;
+    } on PlatformException catch (e) {
+      throw Exception('开始扫码失败:${e.message}');
+    }
+  }
+
+  /// 停止单次扫码(保留扫描头)
+  static Future<bool> stopSingleScan() async {
+    try {
+      final bool result = await _channel.invokeMethod('stopSingleScan');
+      return result;
+    } on PlatformException catch (e) {
+      throw Exception('停止扫码失败:${e.message}');
+    }
+  }
+
+  // -------------------------- 状态查询API --------------------------
+  /// 获取是否正在扫码
+  static Future<bool> isScanning() async {
+    try {
+      final bool result = await _channel.invokeMethod('isScanning');
+      return result;
+    } on PlatformException catch (e) {
+      throw Exception('获取扫码状态失败:${e.message}');
+    }
+  }
+
+  /// 获取扫描头是否打开
+  static Future<bool> isScanHeadOpened() async {
+    try {
+      final bool result = await _channel.invokeMethod('isScanHeadOpened');
+      return result;
+    } on PlatformException catch (e) {
+      throw Exception('获取扫描头状态失败:${e.message}');
+    }
+  }
+
+  // -------------------------- 灯光设置API --------------------------
+  /// 设置灯光模式
+  /// mode: 0-关闭 1-仅瞄准 2-仅照明 3-交替 4-同时开启
+  static Future<String> setLightingMode(int mode) async {
+    try {
+      final String result = await _channel.invokeMethod('setLightingMode', {
+        'mode': mode,
+      });
+      return result;
+    } on PlatformException catch (e) {
+      throw Exception('设置灯光失败:${e.message}');
+    }
+  }
+
+  /// 释放通道资源
+  static void dispose() {
+    _channel.setMethodCallHandler(null);
+  }
+}

+ 0 - 42
UI/CF.APP/chicken_farm/lib/core/services/pda/pda_scanner_service.dart

@@ -1,42 +0,0 @@
-import 'package:flutter/services.dart';
-
-class PdaScannerService {
-  static const platform = MethodChannel('com.vber.chicken_farm/pda_scanner');
-
-  /// 调用PDA设备的扫码功能
-  ///
-  /// 返回扫码结果字符串,如果扫码失败会抛出异常
-  static Future<String> scanBarcode() async {
-    try {
-      final String result = await platform.invokeMethod('scanBarcode');
-      return result;
-    } on PlatformException catch (e) {
-      throw Exception("扫码失败: ${e.message}");
-    }
-  }
-  
-  /// 读取RFID标签
-  ///
-  /// 返回RFID标签字符串,如果读取失败会抛出异常
-  static Future<String> readRfid() async {
-    try {
-      final String result = await platform.invokeMethod('readRfid');
-      return result;
-    } on PlatformException catch (e) {
-      throw Exception("RFID读取失败: ${e.message}");
-    }
-  }
-  
-  /// 写入RFID标签
-  ///
-  /// [data] 要写入的数据
-  /// 返回写入结果,如果写入失败会抛出异常
-  static Future<String> writeRfid(String data) async {
-    try {
-      final String result = await platform.invokeMethod('writeRfid', {'data': data});
-      return result;
-    } on PlatformException catch (e) {
-      throw Exception("RFID写入失败: ${e.message}");
-    }
-  }
-}

+ 62 - 35
UI/CF.APP/chicken_farm/lib/pages/breeding/cage_change_page.dart

@@ -1,7 +1,8 @@
+import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:flutter/material.dart';
 import 'package:chicken_farm/components/vb_app_bar.dart';
 import 'package:chicken_farm/core/utils/toast.dart';
-import 'package:chicken_farm/core/services/pda/pda_scanner_service.dart';
+import 'package:chicken_farm/core/services/pda/pda_scan_service.dart';
 
 class CageChangePage extends StatefulWidget {
   const CageChangePage({super.key});
@@ -19,6 +20,55 @@ class _CageChangePageState extends State<CageChangePage> {
   bool _isScanningSource = false;
   bool _isScanningTarget = false;
   bool _isScanningRfid = false;
+  int _scanTag = 1;
+
+  @override
+  void initState() {
+    super.initState();
+    PdaScanService.openScanHead().then((success) {
+      if (success) {
+        logger.d("扫描头已打开");
+      } else {
+        logger.d("扫描头打开失败");
+      }
+    });
+    // 初始化监听
+    PdaScanService.initScanListener(
+      onScanResult: (result) {
+        logger.d("扫码结果:$result");
+        if (_scanTag == 1) {
+          // 源笼号
+          setState(() {
+            _sourceCageId = result;
+            _isScanningSource = false;
+          });
+        } else if (_scanTag == 2) {
+          // 目标笼号
+          setState(() {
+            _targetCageId = result;
+            _isScanningTarget = false;
+          });
+        } else if (_scanTag == 3) {}
+        // 扫码成功后停止解码(保留扫描头)
+        PdaScanService.stopSingleScan();
+      },
+      onScanError: (error) {
+        logger.d("扫码错误:$error");
+        setState(() {
+          _isScanningSource = false;
+        });
+      },
+    );
+  }
+
+  @override
+  void dispose() {
+    // 手动关闭扫描头,立即释放资源
+    PdaScanService.closeScanHead();
+    // 释放通道
+    PdaScanService.dispose();
+    super.dispose();
+  }
 
   @override
   Widget build(BuildContext context) {
@@ -222,22 +272,10 @@ class _CageChangePageState extends State<CageChangePage> {
       _isScanningSource = true;
     });
 
-    try {
-      final result = await PdaScannerService.scanBarcode();
-      if (mounted) {
-        setState(() {
-          _sourceCageId = result;
-          _isScanningSource = false;
-        });
-        ToastUtil.success('源笼号扫描成功');
-      }
-    } catch (e) {
-      if (mounted) {
-        setState(() {
-          _isScanningSource = false;
-        });
-        ToastUtil.error('扫描失败: $e');
-      }
+    _scanTag = 1;
+    bool success = await PdaScanService.startScan();
+    if (!success) {
+      ToastUtil.error("扫码失败");
     }
   }
 
@@ -249,22 +287,10 @@ class _CageChangePageState extends State<CageChangePage> {
       _isScanningTarget = true;
     });
 
-    try {
-      final result = await PdaScannerService.scanBarcode();
-      if (mounted) {
-        setState(() {
-          _targetCageId = result;
-          _isScanningTarget = false;
-        });
-        ToastUtil.success('目标笼号扫描成功');
-      }
-    } catch (e) {
-      if (mounted) {
-        setState(() {
-          _isScanningTarget = false;
-        });
-        ToastUtil.error('扫描失败: $e');
-      }
+    _scanTag = 2;
+    bool success = await PdaScanService.startScan();
+    if (!success) {
+      ToastUtil.error("扫码失败");
     }
   }
 
@@ -278,7 +304,8 @@ class _CageChangePageState extends State<CageChangePage> {
 
     try {
       // 使用实际的RFID读取功能
-      final result = await PdaScannerService.readRfid();
+      final result = "qwe"; //await PdaScanService.readRFId();
+
       if (mounted) {
         setState(() {
           // 每次添加一个新的电子编号到数组开头(模拟按一次实体按钮扫描一个电子编号)
@@ -332,4 +359,4 @@ class _CageChangePageState extends State<CageChangePage> {
       _rfids.clear();
     });
   }
-}
+}

+ 0 - 82
UI/CF.APP/chicken_farm/lib/services/pda_service.dart

@@ -1,82 +0,0 @@
-import 'dart:async';
-import 'package:flutter/services.dart';
-
-/// PDA服务类,用于处理扫描和RFID功能
-class PdaService {
-  static const MethodChannel _methodChannel = MethodChannel(
-    'com.vber.chicken_farm/pda_scanner',
-  );
-
-  static const EventChannel _eventChannel = EventChannel(
-    'com.vber.chicken_farm/pda_scanner_event',
-  );
-
-  static final PdaService _instance = PdaService._internal();
-  factory PdaService() => _instance;
-  PdaService._internal();
-
-  StreamSubscription? _scanSubscription;
-  final StreamController<String?> _scanResultController =
-      StreamController<String?>.broadcast();
-
-  /// 扫描条形码/二维码
-  Future<void> scanBarcode() async {
-    try {
-      await _methodChannel.invokeMethod('scanBarcode');
-    } on PlatformException catch (e) {
-      print("扫描失败: ${e.message}");
-    }
-  }
-
-  /// 读取RFID
-  Future<String?> readRfid() async {
-    try {
-      final String? rfid = await _methodChannel.invokeMethod('readRfid');
-      return rfid;
-    } on PlatformException catch (e) {
-      print("读取RFID失败: ${e.message}");
-      return null;
-    }
-  }
-
-  /// 写入RFID
-  Future<String?> writeRfid(String data) async {
-    try {
-      final String? result = await _methodChannel.invokeMethod('writeRfid', {
-        'data': data,
-      });
-      return result;
-    } on PlatformException catch (e) {
-      print("写入RFID失败: ${e.message}");
-      return null;
-    }
-  }
-
-  /// 监听扫描结果
-  Stream<String?> get scanResults => _scanResultController.stream;
-
-  /// 开始监听扫描结果
-  void startListeningScanResults() {
-    _scanSubscription = _eventChannel.receiveBroadcastStream().listen(
-      (dynamic result) {
-        _scanResultController.sink.add(result as String?);
-      },
-      onError: (dynamic error) {
-        print("接收扫描结果出错: $error");
-        _scanResultController.sink.add(null);
-      },
-    );
-  }
-
-  /// 停止监听扫描结果
-  void stopListeningScanResults() {
-    _scanSubscription?.cancel();
-    _scanSubscription = null;
-  }
-
-  /// 释放资源
-  void dispose() {
-    stopListeningScanResults();
-    _scanResultController.close();
-  }
-}

+ 0 - 173
UI/CF.APP/chicken_farm/lib/widgets/pda_demo_page.dart

@@ -1,173 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:chicken_farm/services/pda_service.dart';
-
-class PdaDemoPage extends StatefulWidget {
-  const PdaDemoPage({Key? key}) : super(key: key);
-
-  @override
-  State<PdaDemoPage> createState() => _PdaDemoPageState();
-}
-
-class _PdaDemoPageState extends State<PdaDemoPage> {
-  final PdaService _pdaService = PdaService();
-  String? _scanResult;
-  String? _rfidResult;
-  bool _isScanning = false;
-
-  @override
-  void initState() {
-    super.initState();
-    // 开始监听扫描结果
-    _pdaService.startListeningScanResults();
-    // 监听扫描结果流
-    _pdaService.scanResults.listen((result) {
-      setState(() {
-        _scanResult = result;
-        _isScanning = false;
-      });
-    });
-  }
-
-  @override
-  void dispose() {
-    // 停止监听扫描结果
-    _pdaService.stopListeningScanResults();
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        title: const Text('PDA功能演示'),
-      ),
-      body: Padding(
-        padding: const EdgeInsets.all(16.0),
-        child: Column(
-          crossAxisAlignment: CrossAxisAlignment.stretch,
-          children: [
-            // 扫描功能区域
-            Card(
-              child: Padding(
-                padding: const EdgeInsets.all(16.0),
-                child: Column(
-                  crossAxisAlignment: CrossAxisAlignment.stretch,
-                  children: [
-                    const Text(
-                      '条码扫描',
-                      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
-                    ),
-                    const SizedBox(height: 16),
-                    ElevatedButton(
-                      onPressed: _isScanning
-                          ? null
-                          : () {
-                              setState(() {
-                                _isScanning = true;
-                                _scanResult = null;
-                              });
-                              _pdaService.scanBarcode();
-                            },
-                      child: _isScanning
-                          ? const Row(
-                              mainAxisAlignment: MainAxisAlignment.center,
-                              children: [
-                                SizedBox(
-                                  width: 20,
-                                  height: 20,
-                                  child: CircularProgressIndicator(
-                                    strokeWidth: 2,
-                                    valueColor:
-                                        AlwaysStoppedAnimation<Color>(Colors.white),
-                                  ),
-                                ),
-                                SizedBox(width: 10),
-                                Text('扫描中...')
-                              ],
-                            )
-                          : const Text('触发扫描'),
-                    ),
-                    const SizedBox(height: 16),
-                    const Text('扫描结果:'),
-                    const SizedBox(height: 8),
-                    Container(
-                      padding: const EdgeInsets.all(12),
-                      decoration: BoxDecoration(
-                        border: Border.all(color: Colors.grey),
-                        borderRadius: BorderRadius.circular(4),
-                      ),
-                      child: Text(
-                        _scanResult ?? '暂无扫描结果',
-                        style: TextStyle(
-                          color: _scanResult == null ? Colors.grey : Colors.black,
-                        ),
-                      ),
-                    ),
-                  ],
-                ),
-              ),
-            ),
-            const SizedBox(height: 20),
-            // RFID功能区域
-            Card(
-              child: Padding(
-                padding: const EdgeInsets.all(16.0),
-                child: Column(
-                  crossAxisAlignment: CrossAxisAlignment.stretch,
-                  children: [
-                    const Text(
-                      'RFID读写',
-                      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
-                    ),
-                    const SizedBox(height: 16),
-                    ElevatedButton(
-                      onPressed: () async {
-                        setState(() {
-                          _rfidResult = null;
-                        });
-                        final rfid = await _pdaService.readRfid();
-                        setState(() {
-                          _rfidResult = rfid;
-                        });
-                      },
-                      child: const Text('读取RFID'),
-                    ),
-                    const SizedBox(height: 16),
-                    ElevatedButton(
-                      onPressed: () async {
-                        setState(() {
-                          _rfidResult = null;
-                        });
-                        final result = await _pdaService.writeRfid('TestData123');
-                        setState(() {
-                          _rfidResult = result;
-                        });
-                      },
-                      child: const Text('写入RFID'),
-                    ),
-                    const SizedBox(height: 16),
-                    const Text('RFID结果:'),
-                    const SizedBox(height: 8),
-                    Container(
-                      padding: const EdgeInsets.all(12),
-                      decoration: BoxDecoration(
-                        border: Border.all(color: Colors.grey),
-                        borderRadius: BorderRadius.circular(4),
-                      ),
-                      child: Text(
-                        _rfidResult ?? '暂无RFID结果',
-                        style: TextStyle(
-                          color: _rfidResult == null ? Colors.grey : Colors.black,
-                        ),
-                      ),
-                    ),
-                  ],
-                ),
-              ),
-            ),
-          ],
-        ),
-      ),
-    );
-  }
-}