From a957afd5d46f2eee5f305743e051470cf8dcdbb5 Mon Sep 17 00:00:00 2001
From: fengge <844143714@qq.com>
Date: Sun, 19 Apr 2026 02:01:52 +0800
Subject: [PATCH] =?UTF-8?q?=E6=97=A5=E5=BF=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/build.gradle.kts | 3 +-
app/src/main/AndroidManifest.xml | 1 +
app/src/main/assets/print_demo.html | 14 +-
.../kingway/ptt/CallBackResolution.java | 40 +++---
.../com/example/kingway/ptt/SendAtUtil.java | 27 ++--
.../com/stand/standapp/AndroidInterface.kt | 56 ++++++--
.../java/com/stand/standapp/LoginActivity.kt | 7 +-
.../java/com/stand/standapp/MainActivity.kt | 39 ++++-
.../java/com/stand/standapp/MyApplication.kt | 20 +++
.../java/com/stand/standapp/UpdateManager.kt | 8 +-
.../com/stand/standapp/utils/LogManager.kt | 135 ++++++++++++++++++
app/src/main/res/values/strings.xml | 2 +-
12 files changed, 294 insertions(+), 58 deletions(-)
create mode 100644 app/src/main/java/com/stand/standapp/MyApplication.kt
create mode 100644 app/src/main/java/com/stand/standapp/utils/LogManager.kt
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 73834c1..baec481 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -17,7 +17,7 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
ndk {
- abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
+ abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a"))
}
}
@@ -45,6 +45,7 @@ android {
}
dependencies {
+ implementation("com.jakewharton.timber:timber:5.0.1")
implementation("io.github.justson:agentweb-core:v5.1.1-androidx")
implementation("com.github.Justson:Downloader:v5.0.4-androidx")
implementation("com.google.android.material:material:1.12.0")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ce5ae38..5002e62 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -13,6 +13,7 @@
(适用于蓝牙/USB 小票机,传输原始 JSON)
-
-
+
+
+
@@ -255,6 +256,15 @@
}
}
+ // 上传日志
+ function callUploadLogs() {
+ if (window.android && window.android.uploadLogs) {
+ window.android.uploadLogs();
+ } else {
+ alert("上传日志接口不可用");
+ }
+ }
+
function callExit() {
if (window.android && window.android.exitApp) {
window.android.exitApp();
diff --git a/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java b/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java
index bc9c56b..58e94e1 100644
--- a/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java
+++ b/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java
@@ -1,12 +1,12 @@
package com.example.kingway.ptt;
-import android.util.Log;
+import timber.log.Timber;
import java.util.ArrayList;
import java.util.List;
/**解析接收到的AT指令*/
public class CallBackResolution {
- private static String TAG = "CallBackResolution";
+ private static String TAG = "PTT_RX";
private static String indexGroupId = "";//用户所在群组Id
private static String indexGroupName = "";//用户所在群组名称
private static List
memberInfoDtos = new ArrayList<>();
@@ -26,10 +26,10 @@ public class CallBackResolution {
String ngx = "\\u";
try {
- Log.i(TAG, "lisHex-->" + lisHex.toString());
+ Timber.tag(TAG).i("lisHex-->" + lisHex.toString());
String firstHex = lisHex.get(0);
String errorNum = lisHex.get(1);
- Log.d(TAG, "at_OEM_AT_cb firstHex: " + firstHex + ", errorNum: " + errorNum);
+ Timber.tag(TAG).d("at_OEM_AT_cb firstHex: " + firstHex + ", errorNum: " + errorNum);
switch (firstHex){
case "82"://登录状态通知指令
String loginId = lisHex.get(2) + "" + lisHex.get(3) + "" + lisHex.get(4) + "" + lisHex.get(5);
@@ -54,17 +54,17 @@ public class CallBackResolution {
break;
case "0b"://发起讲话返回状态
- Log.d(TAG, "at_OEM_AT_cb ptt speak...");
+ Timber.tag(TAG).d("at_OEM_AT_cb ptt speak...");
CallBackUtil.callBackSpeakPlaySuccess(errorNum.equals("00")? true : false);
break;
case "0c"://结束讲话返回状态
- Log.d(TAG, "at_OEM_AT_cb speak end...");
+ Timber.tag(TAG).d("at_OEM_AT_cb speak end...");
CallBackUtil.callBackSpeakReleaseSuccess(errorNum.equals("00")? true : false);
break;
case "80"://返回群组信息
- Log.d(TAG, "at_OEM_AT_cb get group info: " + lisHex);
+ Timber.tag(TAG).d("at_OEM_AT_cb get group info: " + lisHex);
String strGroup = "";
String groupId = lisHex.get(6) + "" + lisHex.get(7) + "" + lisHex.get(8) + "" + lisHex.get(9);
int groupNo = Integer.valueOf((lisHex.get(4) + "" + lisHex.get(5)), 16);
@@ -87,9 +87,9 @@ public class CallBackResolution {
} finally {
String tempTxt = "";
tempTxt = unicodeToString(strGroup);
- Log.i(TAG, " 组名称 " + tempTxt);
+ Timber.tag(TAG).i(" 组名称 " + tempTxt);
CallBackUtil.callBackGroupInfo(groupId, tempTxt, groupNo, groupCount);
- Log.d(TAG, "at_OEM_AT_cb group info groupId: " + groupId + ", groupNo: " + groupNo+", groupCount: " +groupCount);
+ Timber.tag(TAG).d("at_OEM_AT_cb group info groupId: " + groupId + ", groupNo: " + groupNo+", groupCount: " +groupCount);
if (1 == groupNo) groupInfos.clear();
GroupInfoDto groupInfo = new GroupInfoDto(groupId, tempTxt, groupNo, groupCount);
groupInfos.add(groupInfo);
@@ -197,7 +197,7 @@ public class CallBackResolution {
for (int i = 0; i < stringsTalk.size(); i++) {
tempTxt += unicode(stringsTalk.get(i));
}
- Log.i(TAG, " 临时讲话 信息通知的指令 " + tempTxt);
+ Timber.tag(TAG).i(" 临时讲话 信息通知的指令 " + tempTxt);
CallBackUtil.callBackTempCallNotify(tempTxt);
}
break;
@@ -230,7 +230,7 @@ public class CallBackResolution {
for (int i = 0; i < stringsJumpGroup.size(); i++) {
userGroupName += unicode(stringsJumpGroup.get(i));
}
- Log.i(TAG,"进入群组 " + userGroupName);
+ Timber.tag(TAG).i("进入群组 " + userGroupName);
indexGroupId = userGroupId;
indexGroupName = userGroupName;
CallBackUtil.callBackNotifyUpdateGroup(userGroupId, userGroupName);
@@ -257,7 +257,7 @@ public class CallBackResolution {
String time = strLis[2];
CallBackUtil.callBackNotifyLocationStatus(0, locationMemberId, latitude, longitude, time);
} catch (Exception e) {
- Log.e(TAG, "获取定位Exception-->" + e.getMessage());
+ Timber.tag(TAG).e(e, "获取定位Exception");
}
} else {
CallBackUtil.callBackNotifyLocationStatus(1, "", 0.0, 0.0, "");
@@ -311,14 +311,14 @@ public class CallBackResolution {
List strName = new ArrayList<>();
double latitude = 0.0, longitude = 0.0;
try {
- String string = "";
- for (int i = 4; i < lisHex.size(); i++) {
- string += lisHex.get(i).toString();
- }
- String address = hexString2String(string);
- Log.i(TAG, "address-->" + address);
+ String string = "";
+ for (int i = 4; i < lisHex.size(); i++) {
+ string += lisHex.get(i).toString();
+ }
+ String address = hexString2String(string);
+ Timber.tag(TAG).i("address-->" + address);
- String[] strLis = address.split(",");
+ String[] strLis = address.split(",");
latitude = Double.parseDouble(strLis[0]);
longitude = Double.parseDouble(strLis[1]);
int size = lisHex.size() - (8 + strLis[0].length() + strLis[1].length());
@@ -358,7 +358,7 @@ public class CallBackResolution {
break;
}
}catch (Exception e){
- Log.e(TAG,"at_OEM_AT_cb Exception " + e.getMessage());
+ Timber.tag(TAG).e(e, "at_OEM_AT_cb Exception");
}
}
diff --git a/app/src/main/java/com/example/kingway/ptt/SendAtUtil.java b/app/src/main/java/com/example/kingway/ptt/SendAtUtil.java
index e0be413..e25bd09 100644
--- a/app/src/main/java/com/example/kingway/ptt/SendAtUtil.java
+++ b/app/src/main/java/com/example/kingway/ptt/SendAtUtil.java
@@ -1,10 +1,17 @@
package com.example.kingway.ptt;
+import timber.log.Timber;
+
/**
* 指令发送类
* */
public class SendAtUtil {
+ private static void send(String cmd) {
+ Timber.tag("PTT_TX").i("CMD: %s", cmd);
+ MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd(cmd);
+ }
+
/**
* 账号登录接口
* account 账号
@@ -20,55 +27,55 @@ public class SendAtUtil {
String locationHex = str2HexStr("gps=" + location + ";");
String tempCallHex = str2HexStr("inv=" + tempCall + ";");
String loginAt = "010000" + ipHex + "" + accountHex + "" + pwdHex + locationHex + tempCallHex + "\r\n";
- MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd(loginAt);
- MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("00000000\r\n");
+ send(loginAt);
+ send("00000000\r\n");
}
/**
* 按下PTT开始呼叫
* */
public static void speakPlay(){
- MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0B0000");
+ send("0B0000");
}
/** 松开PTT结束呼叫*/
public static void speakRelease(){
AudioRecordManager.getInstance().stopRecord();
- MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0C0000");
+ send("0C0000");
}
/** 发送TCP包 登录成功后需要定时发送TCP包*/
public static void sendTCP(){
- MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("570000\r\n");
+ send("570000\r\n");
}
/** 发送UDP包 登录后需要定时发送UDP包*/
public static void sendUDP(){
- MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("580000\r\n");
+ send("580000\r\n");
}
/** 请求群组信息 */
public static void getGroupInfo(){
- MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0D0000");
+ send("0D0000");
}
/** 请求群组成员信息
* groupId 十六进制群组Id
* */
public static void getGroupMemberInfo(String groupId){
- MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0E0000" + groupId);
+ send("0E0000" + groupId);
}
/** 切换群组
* groupId 十六进制群组Id
* */
public static void jumpGroup(String groupId){
- MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("090000" + groupId);
+ send("090000" + groupId);
}
/** 请求好友信息 */
public static void getFriendInfo(){
- MyApplication.getAppInstance().getpNative().pttNativeSendAtCmd("0E000000000000\r\n");
+ send("0E000000000000\r\n");
}
diff --git a/app/src/main/java/com/stand/standapp/AndroidInterface.kt b/app/src/main/java/com/stand/standapp/AndroidInterface.kt
index c4369ba..915568f 100644
--- a/app/src/main/java/com/stand/standapp/AndroidInterface.kt
+++ b/app/src/main/java/com/stand/standapp/AndroidInterface.kt
@@ -3,15 +3,49 @@ package com.stand.standapp
import android.content.Context
import android.os.Handler
import android.os.Looper
-import android.util.Log
import android.webkit.JavascriptInterface
import android.widget.Toast
import android.print.PrintAttributes
import android.print.PrintManager
import com.stand.standapp.R
+import com.stand.standapp.utils.LogManager
+import timber.log.Timber
+import org.json.JSONObject
class AndroidInterface(private val activity: MainActivity) {
+ /**
+ * JS 调用:压缩并导出日志
+ * 返回压缩后的文件路径
+ */
+ @JavascriptInterface
+ fun exportLogs(): String {
+ val zipPath = LogManager.zipLogs(activity)
+ if (zipPath != null) {
+ Handler(Looper.getMainLooper()).post {
+ Toast.makeText(activity, "日志已打包: $zipPath", Toast.LENGTH_LONG).show()
+ }
+ return zipPath
+ } else {
+ Handler(Looper.getMainLooper()).post {
+ Toast.makeText(activity, "暂无日志或打包失败", Toast.LENGTH_SHORT).show()
+ }
+ return ""
+ }
+ }
+
+ /**
+ * JS 调用:直接上传日志到后台
+ */
+ @JavascriptInterface
+ fun uploadLogs() {
+ LogManager.uploadLogs(activity) { success, message ->
+ Handler(Looper.getMainLooper()).post {
+ Toast.makeText(activity, if (success) "日志上传成功" else "上传失败: $message", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
/**
* JS 调用:标准系统打印 (PDF/网络打印机/AirPrint)
* 适用于打印精美的 A4 单据或页面
@@ -33,7 +67,7 @@ class AndroidInterface(private val activity: MainActivity) {
printManager.print(jobName, printAdapter, PrintAttributes.Builder().build())
} catch (e: Exception) {
- Log.e("Print", "系统打印失败", e)
+ Timber.tag("Print").e(e, "系统打印失败")
Toast.makeText(activity, "打印服务启动失败", Toast.LENGTH_SHORT).show()
}
}
@@ -47,17 +81,17 @@ class AndroidInterface(private val activity: MainActivity) {
@JavascriptInterface
fun printRawData(data: String) {
try {
- Log.d("Print", "收到 H5 原始打印数据: $data")
+ Timber.tag("Print").d("收到 H5 原始打印数据: $data")
Handler(Looper.getMainLooper()).post {
try {
// 这里可以弹出一个选择框让用户选择打印机,或者根据配置自动连接
Toast.makeText(activity, "H5指令打印: $data", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
- Log.e("Print", "Toast显示失败", e)
+ Timber.tag("Print").e(e, "Toast显示失败")
}
}
} catch (e: Exception) {
- Log.e("Print", "指令解析失败", e)
+ Timber.tag("Print").e(e, "指令解析失败")
}
}
@@ -70,7 +104,7 @@ class AndroidInterface(private val activity: MainActivity) {
try {
UpdateManager(activity).checkUpdate()
} catch (e: Exception) {
- Log.e("Update", "启动更新检查失败", e)
+ Timber.tag("Update").e(e, "启动更新检查失败")
}
}
}
@@ -84,7 +118,7 @@ class AndroidInterface(private val activity: MainActivity) {
try {
activity.showExitDialog()
} catch (e: Exception) {
- Log.e("App", "显示退出弹窗失败", e)
+ Timber.tag("App").e(e, "显示退出弹窗失败")
activity.finish()
}
}
@@ -110,7 +144,7 @@ class AndroidInterface(private val activity: MainActivity) {
fun getAboutInfo(): String {
return try {
val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
- val json = org.json.JSONObject()
+ val json = JSONObject()
json.put("appName", activity.getString(R.string.app_name))
json.put("versionName", packageInfo.versionName)
json.put("model", android.os.Build.MODEL)
@@ -134,7 +168,7 @@ class AndroidInterface(private val activity: MainActivity) {
val list = updatedMsgs.split("|---|").take(5000)
prefs.edit().putString("history", list.joinToString("|---|")).apply()
} catch (e: Exception) {
- Log.e("Storage", "消息保存异常", e)
+ Timber.tag("Storage").e(e, "消息保存异常")
}
}
@@ -162,7 +196,7 @@ class AndroidInterface(private val activity: MainActivity) {
Toast.makeText(activity, "记录已清空", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
- Log.e("Storage", "清空记录异常", e)
+ Timber.tag("Storage").e(e, "清空记录异常")
}
}
@@ -176,7 +210,7 @@ class AndroidInterface(private val activity: MainActivity) {
activity.cancelTimeoutTimer()
activity.findViewById(R.id.loading_layout)?.visibility = android.view.View.GONE
} catch (e: Exception) {
- Log.e("UI", "隐藏加载布局失败", e)
+ Timber.tag("UI").e(e, "隐藏加载布局失败")
}
}
}
diff --git a/app/src/main/java/com/stand/standapp/LoginActivity.kt b/app/src/main/java/com/stand/standapp/LoginActivity.kt
index 0957fc3..8a96042 100644
--- a/app/src/main/java/com/stand/standapp/LoginActivity.kt
+++ b/app/src/main/java/com/stand/standapp/LoginActivity.kt
@@ -2,7 +2,7 @@ package com.stand.standapp
import android.content.Intent
import android.os.Bundle
-import android.util.Log
+import timber.log.Timber
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
@@ -68,7 +68,8 @@ class LoginActivity : AppCompatActivity() {
* 执行真实接口登录
*/
private fun performLogin(username: String, password: String, remember: Boolean) {
- val loginUrl = "https://template.gyouzhe.com/api/auth/login"
+ val serverUrl = AppConfig.getServerUrl(this)
+ val loginUrl = "$serverUrl/api/auth/login"
val bearerToken = "eyJraWQiOiJzLXYxIiwidHlwIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJhZG1pbiIsInR5cCI6ImFjY2VzcyIsImV4cCI6MTc3NjQwMTU2MSwianRpIjoiMjM0NjdiZGMtNTY0Zi00NmY0LWJhZTgtNzJhZmQ1MzhmYmZhIiwiZmdwIjoiY2FhNzYzOTI4YWRhYWZiYjgzMjE3NjExYmVjYTc3ZjMiLCJraWQiOiJzLXYxIn0.4Zf1eiKcUjGNoFylEzmZ2no1kJY-44CQ8QWmMA_UxvY"
val body = MultipartBody.Builder()
@@ -123,7 +124,7 @@ class LoginActivity : AppCompatActivity() {
Toast.makeText(this@LoginActivity, msg, Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
- Log.e("Login", "解析响应失败", e)
+ Timber.tag("Login").e(e, "解析响应失败")
Toast.makeText(this@LoginActivity, "服务器响应异常", Toast.LENGTH_SHORT).show()
}
}
diff --git a/app/src/main/java/com/stand/standapp/MainActivity.kt b/app/src/main/java/com/stand/standapp/MainActivity.kt
index 8b0512a..079f4d8 100644
--- a/app/src/main/java/com/stand/standapp/MainActivity.kt
+++ b/app/src/main/java/com/stand/standapp/MainActivity.kt
@@ -3,12 +3,20 @@ package com.stand.standapp
import android.os.Bundle
import android.view.KeyEvent
import android.view.ViewGroup
+import android.webkit.ConsoleMessage
+import android.webkit.WebResourceError
+import android.webkit.WebResourceRequest
+import android.webkit.WebView
import android.widget.LinearLayout
-import android.util.Log
-import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
+import timber.log.Timber
import com.just.agentweb.AgentWeb
+import com.just.agentweb.WebChromeClient
+import com.just.agentweb.WebViewClient
import com.stand.standapp.R
+import com.stand.standapp.AppConfig
+import com.stand.standapp.AndroidInterface
+import com.stand.standapp.UpdateManager
class MainActivity : AppCompatActivity() {
@@ -38,11 +46,31 @@ class MainActivity : AppCompatActivity() {
// 从配置中获取服务器地址
val serverUrl = AppConfig.getServerUrl(this)
val finalUrl = "$serverUrl/example/print_demo.html?t=${System.currentTimeMillis()}"
- Log.d("MainActivity", "Loading URL: $finalUrl")
+ Timber.tag("MainActivity").d("Loading URL: $finalUrl")
mAgentWeb = AgentWeb.with(this)
.setAgentWebParent(container, LinearLayout.LayoutParams(-1, -1))
.useDefaultIndicator()
+ .setWebChromeClient(object : WebChromeClient() {
+ override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
+ Timber.tag("WebConsole").d("${consoleMessage?.messageLevel()}: ${consoleMessage?.message()} -- From line ${consoleMessage?.lineNumber()} of ${consoleMessage?.sourceId()}")
+ return super.onConsoleMessage(consoleMessage)
+ }
+ })
+ .setWebViewClient(object : WebViewClient() {
+ override fun onPageFinished(view: WebView?, url: String?) {
+ super.onPageFinished(view, url)
+ cancelTimeoutTimer()
+ }
+
+ override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
+ Timber.tag("WebError").e("URL: ${request?.url}, Error: ${error?.description} (Code: ${error?.errorCode})")
+ if (request?.isForMainFrame == true) {
+ showErrorPage()
+ }
+ super.onReceivedError(view, request, error)
+ }
+ })
.addJavascriptInterface("android", AndroidInterface(this))
.createAgentWeb()
.ready()
@@ -66,7 +94,7 @@ class MainActivity : AppCompatActivity() {
settings.domStorageEnabled = true
settings.javaScriptEnabled = true
} catch (e: Exception) {
- Log.e("MainActivity", "WebView settings error: ${e.message}")
+ Timber.tag("MainActivity").e(e, "WebView settings error")
}
}
// .go("http://152.32.186.199/local/print_demo.html")
@@ -75,8 +103,7 @@ class MainActivity : AppCompatActivity() {
UpdateManager(this).checkUpdate()
} catch (e: Exception) {
- Log.e("MainActivity", "Fatal Crash: ${e.message}")
- e.printStackTrace()
+ Timber.tag("MainActivity").e(e, "Fatal Crash")
}
}
diff --git a/app/src/main/java/com/stand/standapp/MyApplication.kt b/app/src/main/java/com/stand/standapp/MyApplication.kt
new file mode 100644
index 0000000..75901c8
--- /dev/null
+++ b/app/src/main/java/com/stand/standapp/MyApplication.kt
@@ -0,0 +1,20 @@
+package com.stand.standapp
+
+import android.app.Application
+import com.stand.standapp.utils.LogManager
+import com.example.kingway.ptt.MyApplication as PTTMyApplication
+import timber.log.Timber
+
+class MyApplication : Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+ // Initialize Timber and File Logging
+ LogManager.init(this)
+
+ // Initialize PTT Module
+ PTTMyApplication.init(this)
+
+ Timber.tag("MyApplication").i("Application onCreate - Logging system and PTT started")
+ }
+}
diff --git a/app/src/main/java/com/stand/standapp/UpdateManager.kt b/app/src/main/java/com/stand/standapp/UpdateManager.kt
index a9c8352..580c368 100644
--- a/app/src/main/java/com/stand/standapp/UpdateManager.kt
+++ b/app/src/main/java/com/stand/standapp/UpdateManager.kt
@@ -10,7 +10,7 @@ import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore
-import android.util.Log
+import timber.log.Timber
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
@@ -63,7 +63,7 @@ class UpdateManager(private val context: Context) {
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
- Log.e("UpdateManager", "检查更新失败: ${e.message}")
+ Timber.tag("UpdateManager").e(e, "检查更新失败")
}
override fun onResponse(call: Call, response: Response) {
@@ -104,7 +104,7 @@ class UpdateManager(private val context: Context) {
handler.post { Toast.makeText(context, "更新服务提示: $msg", Toast.LENGTH_SHORT).show() }
}
} catch (e: Exception) {
- Log.e("UpdateManager", "解析或处理更新结果失败", e)
+ Timber.tag("UpdateManager").e(e, "解析或处理更新结果失败")
handler.post { Toast.makeText(context, "更新信息解析失败", Toast.LENGTH_SHORT).show() }
}
}
@@ -266,7 +266,7 @@ class UpdateManager(private val context: Context) {
intent.setDataAndType(installUri, "application/vnd.android.package-archive")
context.startActivity(intent)
} catch (e: Exception) {
- Log.e("UpdateManager", "安装失败", e)
+ Timber.tag("UpdateManager").e(e, "安装失败")
Toast.makeText(context, "无法自动调起安装,请在系统下载目录中手动安装", Toast.LENGTH_LONG).show()
}
}
diff --git a/app/src/main/java/com/stand/standapp/utils/LogManager.kt b/app/src/main/java/com/stand/standapp/utils/LogManager.kt
new file mode 100644
index 0000000..70643ec
--- /dev/null
+++ b/app/src/main/java/com/stand/standapp/utils/LogManager.kt
@@ -0,0 +1,135 @@
+package com.stand.standapp.utils
+
+import android.content.Context
+import com.stand.standapp.AppConfig
+import okhttp3.*
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.RequestBody.Companion.asRequestBody
+import timber.log.Timber
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.FileWriter
+import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.*
+import java.util.concurrent.Executors
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+
+object LogManager {
+ private val executor = Executors.newSingleThreadExecutor()
+ private const val MAX_DAYS = 15
+ private val client = OkHttpClient()
+
+ fun init(context: Context) {
+ Timber.plant(Timber.DebugTree())
+ val logDir = File(context.getExternalFilesDir(null), "logs")
+ if (!logDir.exists()) logDir.mkdirs()
+ cleanOldLogs(logDir)
+ Timber.plant(FileLoggingTree(logDir))
+ }
+
+ private fun cleanOldLogs(logDir: File) {
+ executor.execute {
+ try {
+ val calendar = Calendar.getInstance()
+ calendar.add(Calendar.DAY_OF_YEAR, -MAX_DAYS)
+ val thresholdDate = calendar.time
+ logDir.listFiles()?.forEach { file ->
+ if (file.isFile && file.name.endsWith(".log")) {
+ if (Date(file.lastModified()).before(thresholdDate)) file.delete()
+ }
+ }
+ } catch (e: Exception) {}
+ }
+ }
+
+ fun zipLogs(context: Context): String? {
+ val logDir = File(context.getExternalFilesDir(null), "logs")
+ if (!logDir.exists() || logDir.listFiles().isNullOrEmpty()) return null
+ val zipFile = File(context.getExternalFilesDir(null), "logs_${System.currentTimeMillis()}.zip")
+ return try {
+ ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
+ logDir.listFiles()?.forEach { file ->
+ if (file.isFile && file.name.endsWith(".log")) {
+ zos.putNextEntry(ZipEntry(file.name))
+ FileInputStream(file).use { it.copyTo(zos) }
+ zos.closeEntry()
+ }
+ }
+ }
+ zipFile.absolutePath
+ } catch (e: Exception) {
+ Timber.e(e, "Zip logs failed")
+ null
+ }
+ }
+
+ /**
+ * 上传日志到服务器
+ */
+ fun uploadLogs(context: Context, callback: (Boolean, String) -> Unit) {
+ executor.execute {
+ val zipPath = zipLogs(context)
+ if (zipPath == null) {
+ callback(false, "打包日志失败")
+ return@execute
+ }
+
+ val file = File(zipPath)
+ val serverUrl = AppConfig.getServerUrl(context)
+ val uploadUrl = "$serverUrl/api/attachment/upload"
+
+ // 使用固定的 Token (根据用户提供)
+ val bearerToken = "eyJraWQiOiJzLXYxIiwidHlwIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJhZG1pbiIsInR5cCI6ImFjY2VzcyIsImV4cCI6MTc3NjQ5MDczNSwianRpIjoiMjYzNjljYzQtYzJiMy00YTg0LWFiYTMtZDU4NmNiZDRmMmM0IiwiZmdwIjoiY2FhNzYzOTI4YWRhYWZiYjgzMjE3NjExYmVjYTc3ZjMiLCJraWQiOiJzLXYxIn0.AyuzNAe-R_GOBC65STH5qpHkmmUngX8rMBPI4CdIIhk"
+
+ val requestBody = MultipartBody.Builder()
+ .setType(MultipartBody.FORM)
+ .addFormDataPart("file", file.name, file.asRequestBody("application/zip".toMediaTypeOrNull()))
+ .build()
+
+ val request = Request.Builder()
+ .url(uploadUrl)
+ .header("Authorization", "Bearer $bearerToken")
+ .post(requestBody)
+ .build()
+
+ try {
+ client.newCall(request).execute().use { response ->
+ if (response.isSuccessful) {
+ callback(true, "上传成功")
+ // 上传成功后删除临时 zip 文件
+ file.delete()
+ } else {
+ callback(false, "服务器返回错误: ${response.code}")
+ }
+ }
+ } catch (e: IOException) {
+ callback(false, "网络连接失败: ${e.message}")
+ }
+ }
+ }
+
+ private class FileLoggingTree(private val logDir: File) : Timber.Tree() {
+ private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
+ private val fileNameFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+
+ override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
+ executor.execute {
+ val logFile = File(logDir, "${fileNameFormat.format(Date())}.log")
+ val priorityStr = when (priority) {
+ 2 -> "V"
+ 3 -> "D"
+ 4 -> "I"
+ 5 -> "W"
+ 6 -> "E"
+ 7 -> "A"
+ else -> "U"
+ }
+ val entry = "${dateFormat.format(Date())} $priorityStr/[$tag]: $message\n${t?.stackTraceToString() ?: ""}\n"
+ try { FileWriter(logFile, true).use { it.write(entry) } } catch (e: Exception) {}
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9bd964a..51d8518 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,3 @@
- StandAPP
+ 消防值守台
\ No newline at end of file