This commit is contained in:
fengge 2026-04-19 02:01:52 +08:00
parent 722ece723f
commit a957afd5d4
12 changed files with 294 additions and 58 deletions

View File

@ -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")

View File

@ -13,6 +13,7 @@
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"

View File

@ -86,8 +86,9 @@
<p style="font-size: 12px; color: #666;">(适用于蓝牙/USB 小票机,传输原始 JSON)</p>
<button class="btn btn-orange" onclick="callUpdate()">3. 检查系统更新</button>
<button class="btn" style="background: #64748b;" onclick="callAbout()">4. 关于应用</button>
<button class="btn btn-red" onclick="callExit()">5. 退出应用</button>
<button class="btn" style="background: #10B981;" onclick="callUploadLogs()">4. 上传系统日志</button>
<button class="btn" style="background: #64748b;" onclick="callAbout()">5. 关于应用</button>
<button class="btn btn-red" onclick="callExit()">6. 退出应用</button>
<!-- WebSocket 控制区 -->
<div class="ws-card">
@ -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();

View File

@ -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<GroupMemberInfoDto> 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<String> 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");
}
}

View File

@ -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");
}

View File

@ -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<android.view.View>(R.id.loading_layout)?.visibility = android.view.View.GONE
} catch (e: Exception) {
Log.e("UI", "隐藏加载布局失败", e)
Timber.tag("UI").e(e, "隐藏加载布局失败")
}
}
}

View File

@ -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()
}
}

View File

@ -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")
}
}

View File

@ -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")
}
}

View File

@ -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()
}
}

View File

@ -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) {}
}
}
}
}

View File

@ -1,3 +1,3 @@
<resources>
<string name="app_name">StandAPP</string>
<string name="app_name">消防值守台</string>
</resources>