From 7da823c249aa39b48b89393a104e2756cc79eb3f Mon Sep 17 00:00:00 2001 From: jjLv <1981429112@qq.com> Date: Thu, 12 Feb 2026 09:09:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=87=BA=E5=85=A5=E5=BA=93=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/bonus/canteen/SmartCanteenApp.java | 3 + .../InspectionWarehousingFragment.java | 39 +++- .../fragment/EnterWarehouseFragment.java | 5 + .../DocumentSelectionFragment.java | 8 + .../com/bonus/canteen/utils/CrashDumper.java | 50 +++++ .../com/bonus/canteen/utils/CrashMonitor.java | 39 ++++ .../main/java/com/bonus/canteen/utils/L.java | 146 ++++++++++---- .../com/bonus/canteen/utils/LogPackager.java | 186 ++++++++++++++++++ .../com/bonus/canteen/utils/WorkConfig.java | 4 +- 9 files changed, 431 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/com/bonus/canteen/utils/CrashDumper.java create mode 100644 app/src/main/java/com/bonus/canteen/utils/CrashMonitor.java create mode 100644 app/src/main/java/com/bonus/canteen/utils/LogPackager.java diff --git a/app/src/main/java/com/bonus/canteen/SmartCanteenApp.java b/app/src/main/java/com/bonus/canteen/SmartCanteenApp.java index 50afd80..4333303 100644 --- a/app/src/main/java/com/bonus/canteen/SmartCanteenApp.java +++ b/app/src/main/java/com/bonus/canteen/SmartCanteenApp.java @@ -25,6 +25,7 @@ import androidx.multidex.MultiDex; import com.bonus.canteen.db.AppDatabase; import com.bonus.canteen.db.ConfigRepository; +import com.bonus.canteen.utils.CrashMonitor; import com.bonus.canteen.utils.sdkinit.ANRWatchDogInit; import com.bonus.canteen.utils.sdkinit.UMengInit; import com.bonus.canteen.utils.sdkinit.XBasicLibInit; @@ -50,6 +51,8 @@ public class SmartCanteenApp extends Application { instance = this; initLibs(); initConfigRepository(); + // 注册全局崩溃捕获 + CrashMonitor.init(this); } /** * 初始化配置仓库 diff --git a/app/src/main/java/com/bonus/canteen/fragment/EnterFragment/InspectionWarehousingFragment.java b/app/src/main/java/com/bonus/canteen/fragment/EnterFragment/InspectionWarehousingFragment.java index d6aa734..a28392f 100644 --- a/app/src/main/java/com/bonus/canteen/fragment/EnterFragment/InspectionWarehousingFragment.java +++ b/app/src/main/java/com/bonus/canteen/fragment/EnterFragment/InspectionWarehousingFragment.java @@ -70,6 +70,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import androidx.fragment.app.FragmentTransaction; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; @@ -88,6 +89,7 @@ import com.bonus.canteen.entity.PhotoResponse; import com.bonus.canteen.entity.SpinnerBean; import com.bonus.canteen.entity.SupplierBean; import com.bonus.canteen.entity.WarehouseBean; +import com.bonus.canteen.fragment.EnterWarehouseFragment; import com.bonus.canteen.fragment.callback.ResponseCallBack; import com.bonus.canteen.fragment.commonFragment.DocumentSelectionFragment; import com.bonus.canteen.fragment.commonFragment.ProductSelectionFragment; @@ -137,6 +139,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import cn.hutool.core.util.ObjectUtil; @@ -276,12 +279,24 @@ public class InspectionWarehousingFragment extends BaseFragment name.getMaterialName() != null && !name.getMaterialName().isEmpty()) + goodsList = goodsList.stream().filter(name -> name.getMaterialName() != null && !name.getMaterialName().isEmpty() && (name.getIntoNum().compareTo(BigDecimal.ZERO) == 0 || name.getIntoNum().compareTo(name.getMaterialTotalNum()) < 0)) .collect(Collectors.toList()); + Map goodsMap = + goodsList.stream() + .collect(Collectors.toMap( + GoodsBean::getMaterialName, + Function.identity(), + (a, b) -> a // 防重复 key + )); pages = goodsList.stream() .map(GoodsBean::getMaterialName) .filter(name -> name != null && !name.isEmpty()) .toArray(String[]::new); + L.d(TAG,"pages====" + Arrays.toString(pages)); +// pages = goodsList.stream() +// .map(GoodsBean::getMaterialName) +// .filter(name -> name != null && !name.isEmpty()) +// .toArray(String[]::new); pagesList = List.of(pages); MultiPage[] multiPages = MultiPage.fromList(pagesList); mDestPage = multiPages[0]; @@ -295,7 +310,14 @@ public class InspectionWarehousingFragment extends BaseFragment 0) { + mTabSegment.hideSignCountView(i); + } else { + mTabSegment.showSignCountView(requireContext(), i, 1); + } } int space = DensityUtils.dp2px(getContext(), 16); mTabSegment.setHasIndicator(true); @@ -456,7 +478,7 @@ public class InspectionWarehousingFragment extends BaseFragment uploadEnterWarehouseData(new ArrayList<>(filteredGoods), new ResponseCallBack() { + .onPositive((poDialog, s) -> uploadEnterWarehouseData(new ArrayList<>(filteredGoods), new ResponseCallBack() { @Override public void onSuccess() { isUpload = false; @@ -565,7 +587,12 @@ public class InspectionWarehousingFragment extends BaseFragment callback.onFail(JSONObject.parseObject(result).get("msg").toString())); + //判断是否存在msg + if (JSONObject.parseObject(result).containsKey("msg")) { + requireActivity().runOnUiThread(() -> callback.onFail(JSONObject.parseObject(result).get("msg").toString())); + }else{ + requireActivity().runOnUiThread(() -> callback.onFail("服务器异常!!!")); + } return; } requireActivity().runOnUiThread(callback::onSuccess); @@ -825,9 +852,9 @@ public class InspectionWarehousingFragment extends BaseFragment { + + }); ThreadPoolManager.getExecutor().execute(() -> { String url = WorkConfig.getBaseUrl() + "/ims_warehouse_info/list?pageSize=" + 1000 + "&pageNum=" + 1; String result = service.httpGet(url, requireContext()); diff --git a/app/src/main/java/com/bonus/canteen/fragment/commonFragment/DocumentSelectionFragment.java b/app/src/main/java/com/bonus/canteen/fragment/commonFragment/DocumentSelectionFragment.java index 64ff310..f90d295 100644 --- a/app/src/main/java/com/bonus/canteen/fragment/commonFragment/DocumentSelectionFragment.java +++ b/app/src/main/java/com/bonus/canteen/fragment/commonFragment/DocumentSelectionFragment.java @@ -66,6 +66,7 @@ import org.easydarwin.easypusher.databinding.ActivityDocumentStroageListBinding; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import cn.hutool.core.util.ObjectUtil; import okhttp3.MediaType; @@ -331,6 +332,13 @@ public class DocumentSelectionFragment extends BaseFragment newList = new ArrayList<>(); + newList = goodsList.stream().filter(name -> name.getMaterialName() != null && !name.getMaterialName().isEmpty() && (name.getIntoNum().compareTo(BigDecimal.ZERO) == 0 || name.getIntoNum().compareTo(name.getMaterialTotalNum()) < 0)) + .collect(Collectors.toList()); + if (newList.isEmpty()){ + requireActivity().runOnUiThread(()-> XToastUtils.warning("当前验货单暂无可入库货品!")); + return; + } //展示出入库单据信息 showDetail(documentBean, goodsList); } diff --git a/app/src/main/java/com/bonus/canteen/utils/CrashDumper.java b/app/src/main/java/com/bonus/canteen/utils/CrashDumper.java new file mode 100644 index 0000000..776e4b7 --- /dev/null +++ b/app/src/main/java/com/bonus/canteen/utils/CrashDumper.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2026 xuexiangjys(xuexiangjys@163.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bonus.canteen.utils; + +import android.content.Context; +import android.os.Build; + +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; + +public final class CrashDumper { + + public static void dump(Context ctx, Throwable e) { + try { + File dir = new File(ctx.getExternalFilesDir(null), "crash"); + if (!dir.exists()) dir.mkdirs(); + + File file = new File(dir, + "crash-" + DateTimeHelper.getCurrentTimeStr() + ".txt"); + + PrintWriter pw = new PrintWriter( + new FileWriter(file, false)); + + pw.println("===== Device Info ====="); + pw.println(Build.MANUFACTURER + " " + Build.MODEL); + pw.println("SDK: " + Build.VERSION.SDK_INT); + pw.println(); + + e.printStackTrace(pw); + pw.close(); + + } catch (Exception ignored) {} + } +} diff --git a/app/src/main/java/com/bonus/canteen/utils/CrashMonitor.java b/app/src/main/java/com/bonus/canteen/utils/CrashMonitor.java new file mode 100644 index 0000000..860bbbc --- /dev/null +++ b/app/src/main/java/com/bonus/canteen/utils/CrashMonitor.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2026 xuexiangjys(xuexiangjys@163.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bonus.canteen.utils; + +import android.content.Context; + +public final class CrashMonitor { + + public static void init(Context context) { + Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { + // 1. dump 异常 + CrashDumper.dump(context, throwable); + + // 2. 打包日志 + crash + LogPackager.packAndUpload(context); + + // 3. 交还系统(或直接杀进程) + android.os.Process.killProcess( + android.os.Process.myPid() + ); + System.exit(10); + }); + } +} diff --git a/app/src/main/java/com/bonus/canteen/utils/L.java b/app/src/main/java/com/bonus/canteen/utils/L.java index 0bdb3ac..965d3d3 100644 --- a/app/src/main/java/com/bonus/canteen/utils/L.java +++ b/app/src/main/java/com/bonus/canteen/utils/L.java @@ -1,56 +1,120 @@ -/* - * Copyright (C) 2026 xuexiangjys(xuexiangjys@163.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - package com.bonus.canteen.utils; +import android.annotation.SuppressLint; +import android.content.Context; import android.util.Log; -public class L { +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +public final class L { + + private static final String TAG = "APP_LOG"; + private static final long MAX_SIZE = 10 * 1024 * 1024; // 10MB + private static File logDir; + private static final LinkedBlockingQueue LOG_QUEUE = new LinkedBlockingQueue<>(); + private static final ExecutorService LOG_EXECUTOR = + Executors.newSingleThreadExecutor(); // ★关键:单线程 + + private static final SimpleDateFormat DAY_FMT = + new SimpleDateFormat("yyyy-MM-dd", Locale.US); + private static final SimpleDateFormat TIME_FMT = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); + + /* ================= 初始化(Application 调用一次) ================= */ + + public static void init(Context context) { + logDir = new File(context.getExternalFilesDir("logs"), ""); + if (!logDir.exists()) { + logDir.mkdirs(); + } + startWriteLoop(); + } + + /* ================= 对外日志接口 ================= */ public static void d(String tag, String msg) { - StackTraceElement element = Thread.currentThread().getStackTrace()[3]; - String className = element.getFileName(); - String methodName = element.getMethodName(); - int line = element.getLineNumber(); - Log.d(tag, className + ":" + line + " → " + methodName + "(): " + msg); + log(Log.DEBUG, tag, msg); } + public static void i(String tag, String msg) { - StackTraceElement element = Thread.currentThread().getStackTrace()[3]; - String className = element.getFileName(); - String methodName = element.getMethodName(); - int line = element.getLineNumber(); - Log.i(tag, className + ":" + line + " → " + methodName + "(): " + msg); + log(Log.INFO, tag, msg); } - public static void e(String tag, String msg) { - StackTraceElement element = Thread.currentThread().getStackTrace()[3]; - String className = element.getFileName(); - String methodName = element.getMethodName(); - int line = element.getLineNumber(); - Log.e(tag, className + ":" + line + " → " + methodName + "(): " + msg); - } public static void w(String tag, String msg) { - StackTraceElement element = Thread.currentThread().getStackTrace()[3]; - String className = element.getFileName(); - String methodName = element.getMethodName(); - int line = element.getLineNumber(); - - Log.w(tag, className + ":" + line + " → " + methodName + "(): " + msg); + log(Log.WARN, tag, msg); } + public static void e(String tag, String msg) { + log(Log.ERROR, tag, msg); + } + + /* ================= 内部实现 ================= */ + + private static void log(int level, String tag, String msg) { + StackTraceElement e = Thread.currentThread().getStackTrace()[4]; + @SuppressLint("DefaultLocale") String log = String.format( + "[%s][%s:%d %s] %s", + TIME_FMT.format(new Date()), + e.getFileName(), + e.getLineNumber(), + e.getMethodName(), + msg + ); + + Log.println(level, tag, log); + LOG_QUEUE.offer(log); + } + + /* ================= 单线程写文件 ================= */ + + private static void startWriteLoop() { + LOG_EXECUTOR.execute(() -> { + while (true) { + try { + String log = LOG_QUEUE.take(); + writeToFile(log); + } catch (Exception ignored) { + } + } + }); + } + + private static void writeToFile(String log) { + try { + File file = getLogFile(); + + BufferedWriter bw = new BufferedWriter(new FileWriter(file, true)); + bw.write(log); + bw.newLine(); + bw.close(); + + } catch (Exception ignored) { + } + } + + private static File getLogFile() { + String day = DAY_FMT.format(new Date()); + File base = new File(logDir, "log-" + day + ".txt"); + + if (!base.exists() || base.length() < MAX_SIZE) { + return base; + } + + int index = 1; + while (true) { + File f = new File(logDir, "log-" + day + "-" + index + ".txt"); + if (!f.exists() || f.length() < MAX_SIZE) { + return f; + } + index++; + } + } } diff --git a/app/src/main/java/com/bonus/canteen/utils/LogPackager.java b/app/src/main/java/com/bonus/canteen/utils/LogPackager.java new file mode 100644 index 0000000..96f180e --- /dev/null +++ b/app/src/main/java/com/bonus/canteen/utils/LogPackager.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2026 xuexiangjys(xuexiangjys@163.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.bonus.canteen.utils; + +import android.content.Context; +import android.os.Build; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public final class LogPackager { + + private static final long MAX_LOG_DIR_SIZE = 10 * 1024 * 1024; // 10MB + private static final String LOG_DIR = "logs"; + private static final String CRASH_DIR = "crash"; + private static final String UPLOAD_DIR = "upload"; + + private LogPackager() {} + + /** + * 对外唯一入口(异步) + */ + public static void packAndUploadAsync(Context context) { + new Thread(() -> packAndUpload(context), "log-pack-thread").start(); + } + + /** + * 核心逻辑 + */ + static void packAndUpload(Context context) { + try { + File root = context.getExternalFilesDir(null); + if (root == null) return; + + File logsDir = new File(root, LOG_DIR); + File crashDir = new File(root, CRASH_DIR); + File uploadDir = new File(root, UPLOAD_DIR); + + if (!uploadDir.exists()) uploadDir.mkdirs(); + + // 1️⃣ 日志目录限流 + checkAndTrim(logsDir); + + // 2️⃣ 创建 zip + File zipFile = new File(uploadDir, buildZipName()); + ZipOutputStream zos = + new ZipOutputStream(new BufferedOutputStream( + new FileOutputStream(zipFile))); + + // 3️⃣ 设备信息 + zipText(zos, "device_info.txt", buildDeviceInfo()); + + // 4️⃣ logs + zipDir(logsDir, "logs", zos); + + // 5️⃣ crash + zipDir(crashDir, "crash", zos); + + zos.close(); + + // 6️⃣ 触发上传(你后面接服务器) +// LogUploader.uploadAsync(zipFile); + + } catch (Throwable t) { + // 这里不能再崩 + t.printStackTrace(); + } + } + + // ====================== 工具方法 ====================== + + private static String buildZipName() { + String time = new SimpleDateFormat( + "yyyyMMdd_HHmmss", Locale.US).format(new Date()); + return "log_crash_" + time + ".zip"; + } + + /** + * 设备 & 系统信息 + */ + private static String buildDeviceInfo() { + StringBuilder sb = new StringBuilder(); + sb.append("Manufacturer: ").append(Build.MANUFACTURER).append('\n'); + sb.append("Model: ").append(Build.MODEL).append('\n'); + sb.append("Board: ").append(Build.BOARD).append('\n'); + sb.append("CPU ABI: ").append(Build.SUPPORTED_ABIS[0]).append('\n'); + sb.append("Android: ").append(Build.VERSION.RELEASE).append('\n'); + sb.append("SDK: ").append(Build.VERSION.SDK_INT).append('\n'); + sb.append("Time: ").append(new Date()).append('\n'); + return sb.toString(); + } + + /** + * 打包目录 + */ + private static void zipDir(File dir, String rootName, ZipOutputStream zos) + throws IOException { + if (dir == null || !dir.exists() || !dir.isDirectory()) return; + + File[] files = dir.listFiles(); + if (files == null) return; + + for (File f : files) { + if (f.isFile()) { + zipFile(f, rootName + "/" + f.getName(), zos); + } + } + } + + /** + * 打包单文件 + */ + private static void zipFile(File file, String entryName, + ZipOutputStream zos) throws IOException { + + zos.putNextEntry(new ZipEntry(entryName)); + BufferedInputStream bis = + new BufferedInputStream(new FileInputStream(file)); + + byte[] buffer = new byte[4096]; + int len; + while ((len = bis.read(buffer)) != -1) { + zos.write(buffer, 0, len); + } + + bis.close(); + zos.closeEntry(); + } + + /** + * 打包文本内容(设备信息) + */ + private static void zipText(ZipOutputStream zos, + String entryName, String content) + throws IOException { + + zos.putNextEntry(new ZipEntry(entryName)); + zos.write(content.getBytes("UTF-8")); + zos.closeEntry(); + } + + /** + * 日志目录限流(超过 10MB 删最老) + */ + private static void checkAndTrim(File dir) { + if (dir == null || !dir.exists()) return; + + File[] files = dir.listFiles(); + if (files == null) return; + + long total = 0; + for (File f : files) total += f.length(); + + if (total <= MAX_LOG_DIR_SIZE) return; + + // 按时间排序,先删最老 + java.util.Arrays.sort(files, + (a, b) -> Long.compare(a.lastModified(), b.lastModified())); + + for (File f : files) { + if (total <= MAX_LOG_DIR_SIZE) break; + total -= f.length(); + //noinspection ResultOfMethodCallIgnored + f.delete(); + } + } +} diff --git a/app/src/main/java/com/bonus/canteen/utils/WorkConfig.java b/app/src/main/java/com/bonus/canteen/utils/WorkConfig.java index cda35ea..d124e57 100644 --- a/app/src/main/java/com/bonus/canteen/utils/WorkConfig.java +++ b/app/src/main/java/com/bonus/canteen/utils/WorkConfig.java @@ -27,8 +27,8 @@ public class WorkConfig { //本地 protected static String baseUrl = "http://192.168.20.234:48390/smart-canteen"; protected static String prefixesUrl = "http://192.168.20.234:48390"; - // protected static String baseUrl = "http://192.168.20.242:48380/smart-canteen"; - // protected static String prefixesUrl = "http://192.168.20.242:48380"; +// protected static String baseUrl = "http://192.168.20.242:48380/smart-canteen"; +// protected static String prefixesUrl = "http://192.168.20.242:48380"; protected static String fileUrl = "http://192.168.20.234:9090/lnyst/"; protected static String updateUrl = "https://www.baidu.com"; protected static String serverUri = "tcp://192.168.20.234:1883";