webview
This commit is contained in:
parent
d52dee56be
commit
786f077bd6
|
|
@ -49,6 +49,11 @@ android {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
packaging {
|
||||||
|
jniLibs {
|
||||||
|
useLegacyPackaging = true
|
||||||
|
}
|
||||||
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
}
|
}
|
||||||
|
|
@ -59,8 +64,7 @@ dependencies {
|
||||||
implementation("com.jakewharton.timber:timber:5.0.1")
|
implementation("com.jakewharton.timber:timber:5.0.1")
|
||||||
implementation("androidx.cardview:cardview:1.0.0")
|
implementation("androidx.cardview:cardview:1.0.0")
|
||||||
implementation("com.iqiyi.xcrash:xcrash-android-lib:3.1.0")
|
implementation("com.iqiyi.xcrash:xcrash-android-lib:3.1.0")
|
||||||
implementation("io.github.justson:agentweb-core:v5.1.1-androidx")
|
implementation("org.mozilla.geckoview:geckoview-omni:115.0.20230710165010")
|
||||||
implementation("com.github.Justson:Downloader:v5.0.4-androidx")
|
|
||||||
implementation("com.google.android.material:material:1.12.0")
|
implementation("com.google.android.material:material:1.12.0")
|
||||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
implementation("com.github.getActivity:XXPermissions:18.2")
|
implementation("com.github.getActivity:XXPermissions:18.2")
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,14 @@
|
||||||
# AgentWeb 混淆规则
|
# GeckoView 混淆规则
|
||||||
-keep class com.just.agentweb.** { *; }
|
-dontwarn org.mozilla.geckoview.**
|
||||||
-dontwarn com.just.agentweb.**
|
-keep class org.mozilla.gecko.util.DebugConfig { *; }
|
||||||
|
|
||||||
|
# AndroidInterface 桥接类保护
|
||||||
|
-keep class com.stand.standapp.AndroidInterface { *; }
|
||||||
|
-keep class com.stand.standapp.AndroidInterface$Companion { *; }
|
||||||
|
|
||||||
# 忽略不存在的可选依赖报错
|
# 忽略不存在的可选依赖报错
|
||||||
-dontwarn com.alipay.sdk.**
|
|
||||||
-dontwarn com.download.library.**
|
|
||||||
-dontwarn com.google.android.material.snackbar.**
|
-dontwarn com.google.android.material.snackbar.**
|
||||||
|
|
||||||
# 保护 JavaScript 交互接口 (非常重要!)
|
|
||||||
-keepattributes *Annotation*
|
|
||||||
-keepattributes *JavascriptInterface*
|
|
||||||
-keepclassmembers class * {
|
|
||||||
@android.webkit.JavascriptInterface <methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 保护你自己的 JS 交互类
|
|
||||||
-keep class com.stand.standapp.AndroidInterface { *; }
|
|
||||||
|
|
||||||
# OkHttp 混淆规则
|
# OkHttp 混淆规则
|
||||||
-keepattributes Signature
|
-keepattributes Signature
|
||||||
-keepattributes InnerClasses
|
-keepattributes InnerClasses
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
(function() {
|
||||||
|
// 在页面上下文中注入桥接脚本,避免 cross-compartment 访问被拒
|
||||||
|
var code = [
|
||||||
|
'(function() {',
|
||||||
|
' var _realAndroid = window.android;',
|
||||||
|
' window.android = new Proxy({}, {',
|
||||||
|
' get: function(_, method) {',
|
||||||
|
' if (method === "then") return;',
|
||||||
|
' return function() {',
|
||||||
|
' var args = Array.prototype.slice.call(arguments);',
|
||||||
|
' var result = prompt("bridge:" + method, JSON.stringify(args));',
|
||||||
|
' try { return JSON.parse(result); } catch(e) { return result; }',
|
||||||
|
' };',
|
||||||
|
' }',
|
||||||
|
' });',
|
||||||
|
' if (_realAndroid) {',
|
||||||
|
' Object.keys(_realAndroid).forEach(function(k) {',
|
||||||
|
' try { window.android[k] = _realAndroid[k]; } catch(e) {}',
|
||||||
|
' });',
|
||||||
|
' }',
|
||||||
|
' setInterval(function() {',
|
||||||
|
' var code = prompt("bridge:_poll", "");',
|
||||||
|
' if (code) { try { eval(code); } catch(e) {} }',
|
||||||
|
' }, 500);',
|
||||||
|
'})();'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.textContent = code;
|
||||||
|
var parent = document.head || document.documentElement;
|
||||||
|
if (parent) {
|
||||||
|
parent.appendChild(script);
|
||||||
|
script.remove();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "standapp-bridge",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "JS-Native bridge for StandAPP",
|
||||||
|
"content_scripts": [{
|
||||||
|
"matches": ["*://*/*"],
|
||||||
|
"js": ["bridge.js"],
|
||||||
|
"run_at": "document_start",
|
||||||
|
"all_frames": true
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
@ -53,7 +53,7 @@ public class pttActivity extends AppCompatActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
// SendAtUtil.toLogin("gzzdweb01", "123456", "61.243.1.123", 1, 1);
|
// SendAtUtil.toLogin("gzzdweb01", "123456", "61.243.1.123", 1, 1);
|
||||||
SendAtUtil.toLogin("poc2", "poc123456", "220.154.131.231", 1, 1);
|
SendAtUtil.toLogin("test004", "123456", "220.154.131.231", 1, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,98 @@ package com.stand.standapp
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.webkit.JavascriptInterface
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import android.print.PrintAttributes
|
|
||||||
import android.print.PrintManager
|
|
||||||
import com.stand.standapp.R
|
import com.stand.standapp.R
|
||||||
import com.stand.standapp.utils.LogManager
|
import com.stand.standapp.utils.LogManager
|
||||||
import com.stand.standapp.printer.PrinterManager
|
import com.stand.standapp.printer.PrinterManager
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import com.example.kingway.ptt.SendAtUtil
|
import com.example.kingway.ptt.SendAtUtil
|
||||||
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
class AndroidInterface(private val activity: MainActivity) {
|
class AndroidInterface(private val activity: MainActivity) {
|
||||||
|
|
||||||
private var isSpeaking = false
|
private var isSpeaking = false
|
||||||
private var lastSpeakTime = 0L
|
private var lastSpeakTime = 0L
|
||||||
private val MIN_SPEAK_DURATION = 200L // 最小通话时长 200ms
|
private val MIN_SPEAK_DURATION = 200L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PTT 开始说话 (带状态保护)
|
* JS bridge 调度入口
|
||||||
|
* @param method JS 调用的方法名
|
||||||
|
* @param argsJson JSON 数组格式的参数
|
||||||
|
* @return 返回值(JSON 字符串或空串)
|
||||||
*/
|
*/
|
||||||
@JavascriptInterface
|
fun dispatch(method: String, argsJson: String): String {
|
||||||
|
return try {
|
||||||
|
val args = if (argsJson.isNotEmpty()) JSONArray(argsJson) else JSONArray()
|
||||||
|
when (method) {
|
||||||
|
"pttSpeakPlay" -> { pttSpeakPlay(); "" }
|
||||||
|
"pttGetLoginStatus" -> pttGetLoginStatus()
|
||||||
|
"pttGetLoginId" -> pttGetLoginId()
|
||||||
|
"pttSpeakRelease" -> { pttSpeakRelease(); "" }
|
||||||
|
"pttForceRelease" -> { pttForceRelease(); "" }
|
||||||
|
"pttGetGroupInfo" -> { pttGetGroupInfo(); "" }
|
||||||
|
"pttGetGroupMemberInfo" -> { pttGetGroupMemberInfo(args.optString(0, "")); "" }
|
||||||
|
"simulatePttConflict" -> { simulatePttConflict(); "" }
|
||||||
|
"pttSendAtCmd" -> { pttSendAtCmd(args.optString(0, "")); "" }
|
||||||
|
"pttJumpGroup" -> { pttJumpGroup(args.optString(0, "")); "" }
|
||||||
|
"getPrinterStatus" -> getPrinterStatus()
|
||||||
|
"printText" -> {
|
||||||
|
printText(optStringOrNull(args, 0), args.optInt(1, 0), args.optInt(2, 0), args.optInt(3, 0)); ""
|
||||||
|
}
|
||||||
|
"printBarcode" -> {
|
||||||
|
printBarcode(optStringOrNull(args, 0), args.optInt(1, 0), args.optInt(2, 0), args.optInt(3, 0), args.optInt(4, 0), args.optInt(5, 0)); ""
|
||||||
|
}
|
||||||
|
"printQR" -> {
|
||||||
|
printQR(optStringOrNull(args, 0), args.optInt(1, 0), args.optInt(2, 0)); ""
|
||||||
|
}
|
||||||
|
"printImage" -> {
|
||||||
|
printImage(optStringOrNull(args, 0), args.optInt(1, 0), args.optInt(2, 0), args.optInt(3, 0)); ""
|
||||||
|
}
|
||||||
|
"printColumns" -> {
|
||||||
|
if (args.length() >= 8) {
|
||||||
|
printColumns(optStringOrNull(args, 0), args.optInt(1, 0), optStringOrNull(args, 2), args.optInt(3, 0),
|
||||||
|
optStringOrNull(args, 4), args.optInt(5, 0), optStringOrNull(args, 6), args.optInt(7, 0))
|
||||||
|
}
|
||||||
|
""
|
||||||
|
}
|
||||||
|
"printColumnsWithHeight" -> {
|
||||||
|
if (args.length() >= 9) {
|
||||||
|
printColumnsWithHeight(optStringOrNull(args, 0), args.optInt(1, 0), optStringOrNull(args, 2), args.optInt(3, 0),
|
||||||
|
optStringOrNull(args, 4), args.optInt(5, 0), optStringOrNull(args, 6), args.optInt(7, 0), args.optInt(8, 0))
|
||||||
|
}
|
||||||
|
""
|
||||||
|
}
|
||||||
|
"cutPaper" -> { cutPaper(args.optBoolean(0, false)); "" }
|
||||||
|
"printTestPage" -> { printTestPage(); "" }
|
||||||
|
"checkPaper" -> { checkPaper(); "" }
|
||||||
|
"exportLogs" -> exportLogs()
|
||||||
|
"uploadLogs" -> { uploadLogs(); "" }
|
||||||
|
"printPage" -> { printPage(); "" }
|
||||||
|
"printRawData" -> { printRawData(optStringOrNull(args, 0)); "" }
|
||||||
|
"startUpdate" -> { startUpdate(optStringOrNull(args, 0)); "" }
|
||||||
|
"exitApp" -> { exitApp(); "" }
|
||||||
|
"getVersionName" -> getVersionName()
|
||||||
|
"getAboutInfo" -> getAboutInfo()
|
||||||
|
"saveMessage" -> { saveMessage(optStringOrNull(args, 0)); "" }
|
||||||
|
"getMessages" -> getMessages()
|
||||||
|
"clearMessages" -> { clearMessages(); "" }
|
||||||
|
"onPageFinished" -> { onPageFinished(); "" }
|
||||||
|
else -> {
|
||||||
|
Timber.tag("Bridge").w("Unknown method: $method")
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.tag("Bridge").e(e, "Dispatch error: $method")
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optStringOrNull(args: JSONArray, index: Int): String? {
|
||||||
|
return if (index < args.length() && !args.isNull(index)) args.getString(index) else null
|
||||||
|
}
|
||||||
|
|
||||||
fun pttSpeakPlay() {
|
fun pttSpeakPlay() {
|
||||||
if (!isSpeaking) {
|
if (!isSpeaking) {
|
||||||
isSpeaking = true
|
isSpeaking = true
|
||||||
|
|
@ -34,34 +105,20 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 PTT 登录状态: "00"-未登录 "01"-登录中 "02"-已登录
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun pttGetLoginStatus(): String {
|
fun pttGetLoginStatus(): String {
|
||||||
return com.example.kingway.ptt.CallBackResolution.getPttLoginStatus()
|
return com.example.kingway.ptt.CallBackResolution.getPttLoginStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 PTT 登录 ID
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun pttGetLoginId(): String {
|
fun pttGetLoginId(): String {
|
||||||
return com.example.kingway.ptt.CallBackResolution.getPttLoginId()
|
return com.example.kingway.ptt.CallBackResolution.getPttLoginId()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PTT 登录
|
|
||||||
*/
|
|
||||||
|
|
||||||
@JavascriptInterface
|
|
||||||
fun pttSpeakRelease() {
|
fun pttSpeakRelease() {
|
||||||
if (isSpeaking) {
|
if (isSpeaking) {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
val duration = now - lastSpeakTime
|
val duration = now - lastSpeakTime
|
||||||
|
|
||||||
if (duration < MIN_SPEAK_DURATION) {
|
if (duration < MIN_SPEAK_DURATION) {
|
||||||
// 如果按得太快,延迟释放
|
|
||||||
val delay = MIN_SPEAK_DURATION - duration
|
val delay = MIN_SPEAK_DURATION - duration
|
||||||
Timber.tag("PTT").d("Speak duration too short ($duration ms), delaying release by $delay ms")
|
Timber.tag("PTT").d("Speak duration too short ($duration ms), delaying release by $delay ms")
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
|
@ -82,62 +139,34 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制释放 PTT (用于异常恢复)
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun pttForceRelease() {
|
fun pttForceRelease() {
|
||||||
isSpeaking = false
|
isSpeaking = false
|
||||||
SendAtUtil.speakRelease()
|
SendAtUtil.speakRelease()
|
||||||
Timber.tag("PTT").d("Force Release")
|
Timber.tag("PTT").d("Force Release")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PTT 获取群组
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun pttGetGroupInfo() {
|
fun pttGetGroupInfo() {
|
||||||
SendAtUtil.getGroupInfo()
|
SendAtUtil.getGroupInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PTT 获取成员
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun pttGetGroupMemberInfo(gid: String) {
|
fun pttGetGroupMemberInfo(gid: String) {
|
||||||
SendAtUtil.getGroupMemberInfo(gid)
|
SendAtUtil.getGroupMemberInfo(gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟 PTT 账号冲突 (测试用)
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun simulatePttConflict() {
|
fun simulatePttConflict() {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
MainActivity.showPttConflictDialog()
|
MainActivity.showPttConflictDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送原始 AT 指令 (调试用)
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun pttSendAtCmd(cmd: String) {
|
fun pttSendAtCmd(cmd: String) {
|
||||||
SendAtUtil.sendRawCmd(cmd)
|
SendAtUtil.sendRawCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PTT 切换群组
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun pttJumpGroup(gid: String) {
|
fun pttJumpGroup(gid: String) {
|
||||||
SendAtUtil.jumpGroup(gid)
|
SendAtUtil.jumpGroup(gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:获取打印机当前连接状态
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun getPrinterStatus(): String {
|
fun getPrinterStatus(): String {
|
||||||
return try {
|
return try {
|
||||||
val json = JSONObject()
|
val json = JSONObject()
|
||||||
|
|
@ -151,10 +180,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:通用打印文本
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun printText(text: String?, size: Int, align: Int, space: Int) {
|
fun printText(text: String?, size: Int, align: Int, space: Int) {
|
||||||
if (text.isNullOrEmpty()) return
|
if (text.isNullOrEmpty()) return
|
||||||
try {
|
try {
|
||||||
|
|
@ -168,10 +193,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:打印条码
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun printBarcode(data: String?, size: Int, height: Int, align: Int, text_pos: Int, type: Int) {
|
fun printBarcode(data: String?, size: Int, height: Int, align: Int, text_pos: Int, type: Int) {
|
||||||
if (data.isNullOrEmpty()) return
|
if (data.isNullOrEmpty()) return
|
||||||
try {
|
try {
|
||||||
|
|
@ -181,10 +202,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:打印二维码
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun printQR(data: String?, size: Int, l_m: Int) {
|
fun printQR(data: String?, size: Int, l_m: Int) {
|
||||||
if (data.isNullOrEmpty()) return
|
if (data.isNullOrEmpty()) return
|
||||||
try {
|
try {
|
||||||
|
|
@ -194,10 +211,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:打印图片 (从 Assets 获取)
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun printImage(fileName: String?, width: Int, align: Int, mode: Int) {
|
fun printImage(fileName: String?, width: Int, align: Int, mode: Int) {
|
||||||
if (fileName.isNullOrEmpty()) return
|
if (fileName.isNullOrEmpty()) return
|
||||||
try {
|
try {
|
||||||
|
|
@ -210,10 +223,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:分栏打印 (4列)
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun printColumns(c1: String?, p1: Int, c2: String?, p2: Int, c3: String?, p3: Int, c4: String?, p4: Int) {
|
fun printColumns(c1: String?, p1: Int, c2: String?, p2: Int, c3: String?, p3: Int, c4: String?, p4: Int) {
|
||||||
try {
|
try {
|
||||||
PrinterManager.getFactory()?.Printcolumncontent(c1 ?: "", p1, c2 ?: "", p2, c3 ?: "", p3, c4 ?: "", p4)
|
PrinterManager.getFactory()?.Printcolumncontent(c1 ?: "", p1, c2 ?: "", p2, c3 ?: "", p3, c4 ?: "", p4)
|
||||||
|
|
@ -222,10 +231,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:分栏打印 (带行高)
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun printColumnsWithHeight(c1: String?, p1: Int, c2: String?, p2: Int, c3: String?, p3: Int, c4: String?, p4: Int, height: Int) {
|
fun printColumnsWithHeight(c1: String?, p1: Int, c2: String?, p2: Int, c3: String?, p3: Int, c4: String?, p4: Int, height: Int) {
|
||||||
try {
|
try {
|
||||||
PrinterManager.getFactory()?.Printcolumncontent(c1 ?: "", p1, c2 ?: "", p2, c3 ?: "", p3, c4 ?: "", p4, height)
|
PrinterManager.getFactory()?.Printcolumncontent(c1 ?: "", p1, c2 ?: "", p2, c3 ?: "", p3, c4 ?: "", p4, height)
|
||||||
|
|
@ -234,10 +239,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:切纸
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun cutPaper(allCut: Boolean) {
|
fun cutPaper(allCut: Boolean) {
|
||||||
try {
|
try {
|
||||||
PrinterManager.getFactory()?.let {
|
PrinterManager.getFactory()?.let {
|
||||||
|
|
@ -250,10 +251,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:打印自检页
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun printTestPage() {
|
fun printTestPage() {
|
||||||
try {
|
try {
|
||||||
PrinterManager.getFactory()?.PrintTestPage()
|
PrinterManager.getFactory()?.PrintTestPage()
|
||||||
|
|
@ -262,10 +259,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:主动检查纸张状态 (会触发回调)
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun checkPaper() {
|
fun checkPaper() {
|
||||||
try {
|
try {
|
||||||
PrinterManager.getFactory()?.Check_Paper()
|
PrinterManager.getFactory()?.Check_Paper()
|
||||||
|
|
@ -274,11 +267,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:压缩并导出日志
|
|
||||||
* 返回压缩后的文件路径
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun exportLogs(): String {
|
fun exportLogs(): String {
|
||||||
val zipPath = LogManager.zipLogs(activity)
|
val zipPath = LogManager.zipLogs(activity)
|
||||||
if (zipPath != null) {
|
if (zipPath != null) {
|
||||||
|
|
@ -294,10 +282,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:直接上传日志到后台
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun uploadLogs() {
|
fun uploadLogs() {
|
||||||
LogManager.uploadLogs(activity) { success, message ->
|
LogManager.uploadLogs(activity) { success, message ->
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
|
@ -307,38 +291,15 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JS 调用:标准系统打印 (PDF/网络打印机/AirPrint)
|
* 系统打印(GeckoView 不支持 WebView.createPrintDocumentAdapter)
|
||||||
* 适用于打印精美的 A4 单据或页面
|
* 如需此功能,需用 GeckoSession 生成 PDF 后打印
|
||||||
*/
|
*/
|
||||||
@JavascriptInterface
|
|
||||||
fun printPage() {
|
fun printPage() {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
try {
|
Toast.makeText(activity, "GeckoView 暂不支持系统打印,请使用热敏打印功能", Toast.LENGTH_SHORT).show()
|
||||||
val printManager = activity.getSystemService(Context.PRINT_SERVICE) as PrintManager
|
|
||||||
// 获取 WebView 的打印适配器
|
|
||||||
val webView = activity.getAgentWeb()?.webCreator?.getWebView()
|
|
||||||
if (webView == null) {
|
|
||||||
Toast.makeText(activity, "WebView 未就绪", Toast.LENGTH_SHORT).show()
|
|
||||||
return@post
|
|
||||||
}
|
|
||||||
|
|
||||||
val printAdapter = webView.createPrintDocumentAdapter("Document")
|
|
||||||
val jobName = activity.getString(R.string.app_name) + " Print Job"
|
|
||||||
|
|
||||||
printManager.print(jobName, printAdapter, PrintAttributes.Builder().build())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.tag("Print").e(e, "系统打印失败")
|
|
||||||
Toast.makeText(activity, "打印服务启动失败", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:原始数据打印
|
|
||||||
* 适用于热敏小票打印机 (蓝牙、USB、网络)
|
|
||||||
* @param data String 可以是 JSON 字符串,包含打印的指令或文本
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun printRawData(data: String?) {
|
fun printRawData(data: String?) {
|
||||||
if (data.isNullOrEmpty()) return
|
if (data.isNullOrEmpty()) return
|
||||||
try {
|
try {
|
||||||
|
|
@ -355,10 +316,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:在线升级 (手动触发检查)
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun startUpdate(apkUrl: String?) {
|
fun startUpdate(apkUrl: String?) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
try {
|
try {
|
||||||
|
|
@ -369,10 +326,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:退出应用
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun exitApp() {
|
fun exitApp() {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
try {
|
try {
|
||||||
|
|
@ -384,10 +337,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取版本号给 H5 判断是否需要升级
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun getVersionName(): String {
|
fun getVersionName(): String {
|
||||||
return try {
|
return try {
|
||||||
val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
|
val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
|
||||||
|
|
@ -397,10 +346,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:获取“关于”信息
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun getAboutInfo(): String {
|
fun getAboutInfo(): String {
|
||||||
return try {
|
return try {
|
||||||
val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
|
val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
|
||||||
|
|
@ -409,36 +354,29 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
json.put("versionName", packageInfo.versionName)
|
json.put("versionName", packageInfo.versionName)
|
||||||
json.put("model", android.os.Build.MODEL)
|
json.put("model", android.os.Build.MODEL)
|
||||||
json.put("osVersion", android.os.Build.VERSION.RELEASE)
|
json.put("osVersion", android.os.Build.VERSION.RELEASE)
|
||||||
|
|
||||||
// 增加 CPU 架构信息
|
|
||||||
val abis = android.os.Build.SUPPORTED_ABIS
|
val abis = android.os.Build.SUPPORTED_ABIS
|
||||||
json.put("cpuAbi", abis.joinToString(","))
|
json.put("cpuAbi", abis.joinToString(","))
|
||||||
|
|
||||||
// 增加当前进程是否为 64 位的判断
|
|
||||||
val is64Bit = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
val is64Bit = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||||
android.os.Process.is64Bit()
|
android.os.Process.is64Bit()
|
||||||
} else {
|
} else {
|
||||||
abis.any { it.contains("64") }
|
abis.any { it.contains("64") }
|
||||||
}
|
}
|
||||||
json.put("is64Bit", is64Bit)
|
json.put("is64Bit", is64Bit)
|
||||||
|
|
||||||
json.toString()
|
json.toString()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
"{ \"appName\": \"消防值守台\", \"versionName\": \"1.0.0\" }"
|
"{ \"appName\": \"消防值守台\", \"versionName\": \"1.0.0\" }"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:保存消息记录
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun saveMessage(message: String?) {
|
fun saveMessage(message: String?) {
|
||||||
if (message.isNullOrEmpty()) return
|
if (message.isNullOrEmpty()) return
|
||||||
try {
|
try {
|
||||||
val prefs = activity.getSharedPreferences("msg_records", Context.MODE_PRIVATE)
|
val prefs = activity.getSharedPreferences("msg_records", Context.MODE_PRIVATE)
|
||||||
val currentMsgs = prefs.getString("history", "") ?: ""
|
val currentMsgs = prefs.getString("history", "") ?: ""
|
||||||
val updatedMsgs = if (currentMsgs.isEmpty()) message else "$message|---|$currentMsgs"
|
val updatedMsgs = if (currentMsgs.isEmpty()) message else "$message|---|$currentMsgs"
|
||||||
// 限制保存最近5000条
|
|
||||||
val list = updatedMsgs.split("|---|").take(5000)
|
val list = updatedMsgs.split("|---|").take(5000)
|
||||||
prefs.edit().putString("history", list.joinToString("|---|")).apply()
|
prefs.edit().putString("history", list.joinToString("|---|")).apply()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
@ -446,10 +384,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:获取消息记录
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun getMessages(): String {
|
fun getMessages(): String {
|
||||||
return try {
|
return try {
|
||||||
val prefs = activity.getSharedPreferences("msg_records", Context.MODE_PRIVATE)
|
val prefs = activity.getSharedPreferences("msg_records", Context.MODE_PRIVATE)
|
||||||
|
|
@ -459,10 +393,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 调用:清空消息记录
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun clearMessages() {
|
fun clearMessages() {
|
||||||
try {
|
try {
|
||||||
activity.getSharedPreferences("msg_records", Context.MODE_PRIVATE).edit().clear().apply()
|
activity.getSharedPreferences("msg_records", Context.MODE_PRIVATE).edit().clear().apply()
|
||||||
|
|
@ -474,10 +404,6 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* JS 回调:告知 App 页面加载完成
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
fun onPageFinished() {
|
fun onPageFinished() {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -3,52 +3,44 @@ package com.stand.standapp
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.KeyEvent
|
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 androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import org.mozilla.geckoview.GeckoResult
|
||||||
|
import org.mozilla.geckoview.GeckoRuntime
|
||||||
|
import org.mozilla.geckoview.GeckoSession
|
||||||
|
import org.mozilla.geckoview.GeckoSessionSettings
|
||||||
|
import org.mozilla.geckoview.GeckoView
|
||||||
|
import org.mozilla.geckoview.WebRequestError
|
||||||
import timber.log.Timber
|
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() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var instance: MainActivity? = null
|
private var instance: MainActivity? = null
|
||||||
|
|
||||||
|
// 待执行的 JS 代码(由 bridge.js 轮询获取)
|
||||||
|
@Volatile
|
||||||
|
private var pendingJsCode: String? = null
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun executeJs(script: String) {
|
fun executeJs(script: String) {
|
||||||
instance?.let { activity ->
|
synchronized(pendingJsCode ?: Any()) {
|
||||||
activity.runOnUiThread {
|
// 合并多次调用(用换行分隔)
|
||||||
activity.mAgentWeb?.jsAccessEntrace?.quickCallJs(script)
|
pendingJsCode = if (pendingJsCode != null) "$pendingJsCode;$script" else script
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun onPttLoginSuccess() {
|
fun onPttLoginSuccess() {
|
||||||
Timber.tag("MainActivity").d("onPttLoginSuccess called from Native")
|
Timber.tag("MainActivity").d("onPttLoginSuccess called from Native")
|
||||||
executeJs("onPttLoginSuccess")
|
executeJs("onPttLoginSuccess()")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 硬件按键触发
|
// 硬件按键触发
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun onHardwarePttDown() {
|
fun onHardwarePttDown() {
|
||||||
instance?.runOnUiThread {
|
instance?.runOnUiThread {
|
||||||
// 直接调用 JSBridge 对象的逻辑以复用保护机制
|
|
||||||
val jsInterface = instance?.mAgentWeb?.webCreator?.webView?.tag as? AndroidInterface
|
|
||||||
// 或者更直接一点,我们把逻辑抽离出来
|
|
||||||
instance?.pttInterface?.pttSpeakPlay()
|
instance?.pttInterface?.pttSpeakPlay()
|
||||||
// 通知 H5 更新 UI
|
executeJs("onHardwarePttDown()")
|
||||||
executeJs("onHardwarePttDown")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +48,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
fun onHardwarePttUp() {
|
fun onHardwarePttUp() {
|
||||||
instance?.runOnUiThread {
|
instance?.runOnUiThread {
|
||||||
instance?.pttInterface?.pttSpeakRelease()
|
instance?.pttInterface?.pttSpeakRelease()
|
||||||
executeJs("onHardwarePttUp")
|
executeJs("onHardwarePttUp()")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,10 +75,23 @@ class MainActivity : AppCompatActivity() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取并清除待执行的 JS 代码(由 bridge.js 轮询)
|
||||||
|
@JvmStatic
|
||||||
|
fun pollPendingJs(): String {
|
||||||
|
synchronized(pendingJsCode ?: Any()) {
|
||||||
|
val code = pendingJsCode ?: ""
|
||||||
|
pendingJsCode = null
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var mAgentWeb: AgentWeb? = null
|
private var geckoSession: GeckoSession? = null
|
||||||
|
private var geckoRuntime: GeckoRuntime? = null
|
||||||
private var pttInterface: AndroidInterface? = null
|
private var pttInterface: AndroidInterface? = null
|
||||||
|
private var canGoBack = false
|
||||||
|
private lateinit var geckoView: GeckoView
|
||||||
|
|
||||||
private val mHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
private val mHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
||||||
private val TIMEOUT_MS = 30000L
|
private val TIMEOUT_MS = 30000L
|
||||||
|
|
@ -99,64 +104,33 @@ class MainActivity : AppCompatActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
instance = this
|
instance = this
|
||||||
try {
|
try {
|
||||||
|
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
val container = findViewById<LinearLayout>(R.id.container)
|
geckoView = findViewById<GeckoView>(R.id.geckoView)
|
||||||
if (container == null) return
|
?: return
|
||||||
|
|
||||||
findViewById<android.widget.Button>(R.id.btn_retry)?.setOnClickListener {
|
findViewById<android.widget.Button>(R.id.btn_retry)?.setOnClickListener {
|
||||||
hideErrorPage()
|
hideErrorPage()
|
||||||
mAgentWeb?.webCreator?.webView?.reload()
|
geckoSession?.reload()
|
||||||
startTimeoutTimer()
|
startTimeoutTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从配置中获取服务器地址
|
// 创建 GeckoRuntime
|
||||||
val serverUrl = AppConfig.getServerUrl(this)
|
geckoRuntime = createGeckoRuntime()
|
||||||
val finalUrl = "$serverUrl/example/print_demo.html?t=${System.currentTimeMillis()}"
|
|
||||||
Timber.tag("MainActivity").d("Loading URL: $finalUrl")
|
|
||||||
|
|
||||||
mAgentWeb = AgentWeb.with(this)
|
// 安装桥接扩展
|
||||||
.setAgentWebParent(container, LinearLayout.LayoutParams(-1, -1))
|
geckoRuntime?.webExtensionController?.installBuiltIn(
|
||||||
.useDefaultIndicator()
|
"resource://android/assets/extensions/bridge/"
|
||||||
.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() {
|
|
||||||
// ...
|
|
||||||
})
|
|
||||||
.addJavascriptInterface("android", AndroidInterface(this).also { pttInterface = it })
|
|
||||||
.createAgentWeb()
|
|
||||||
|
|
||||||
.ready()
|
// 创建 AndroidInterface
|
||||||
.go(finalUrl)
|
pttInterface = AndroidInterface(this)
|
||||||
|
|
||||||
|
// 创建 GeckoSession 并配置
|
||||||
|
createSessionAndLoad()
|
||||||
|
|
||||||
startTimeoutTimer()
|
startTimeoutTimer()
|
||||||
|
|
||||||
// 配置 WebView 属性以支持 SSE 和跨域
|
|
||||||
|
|
||||||
mAgentWeb?.let { agent ->
|
|
||||||
try {
|
|
||||||
val webView = agent.webCreator.webView
|
|
||||||
val settings = webView.settings
|
|
||||||
// 允许混合内容 (HTTP/HTTPS)
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
|
||||||
}
|
|
||||||
// 允许跨域和存储
|
|
||||||
settings.allowUniversalAccessFromFileURLs = true
|
|
||||||
settings.allowFileAccessFromFileURLs = true
|
|
||||||
settings.domStorageEnabled = true
|
|
||||||
settings.javaScriptEnabled = true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.tag("MainActivity").e(e, "WebView settings error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// .go("http://152.32.186.199/local/print_demo.html")
|
|
||||||
|
|
||||||
// 启动时检查更新
|
// 启动时检查更新
|
||||||
UpdateManager(this).checkUpdate()
|
UpdateManager(this).checkUpdate()
|
||||||
|
|
||||||
|
|
@ -165,14 +139,126 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAgentWeb(): AgentWeb? {
|
private fun createGeckoRuntime(): GeckoRuntime {
|
||||||
return mAgentWeb
|
val runtime = GeckoRuntime.getDefault(this)
|
||||||
|
runtime.settings.aboutConfigEnabled = true
|
||||||
|
runtime.settings.consoleOutputEnabled = true
|
||||||
|
return runtime
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSessionAndLoad() {
|
||||||
|
val session = GeckoSession()
|
||||||
|
val sessionSettings = session.settings
|
||||||
|
|
||||||
|
// 配置 GeckoSession 设置(对应原 WebView settings)
|
||||||
|
sessionSettings.allowJavascript = true
|
||||||
|
sessionSettings.userAgentMode = GeckoSessionSettings.USER_AGENT_MODE_MOBILE
|
||||||
|
|
||||||
|
val runtime = geckoRuntime ?: return
|
||||||
|
|
||||||
|
// 设置各种代理
|
||||||
|
setupDelegates(session)
|
||||||
|
|
||||||
|
// 打开会话
|
||||||
|
session.open(runtime)
|
||||||
|
|
||||||
|
geckoSession = session
|
||||||
|
|
||||||
|
// 将 session 绑定到 GeckoView
|
||||||
|
geckoView.setSession(session)
|
||||||
|
|
||||||
|
// 加载页面
|
||||||
|
val serverUrl = AppConfig.getServerUrl(this)
|
||||||
|
val finalUrl = "$serverUrl/example/print_demo.html?t=${System.currentTimeMillis()}"
|
||||||
|
Timber.tag("MainActivity").d("Loading URL: $finalUrl")
|
||||||
|
session.loadUri(finalUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupDelegates(session: GeckoSession) {
|
||||||
|
// NavigationDelegate - 处理页面加载错误
|
||||||
|
session.navigationDelegate = object : GeckoSession.NavigationDelegate {
|
||||||
|
override fun onLoadError(
|
||||||
|
session: GeckoSession,
|
||||||
|
uri: String?,
|
||||||
|
error: WebRequestError
|
||||||
|
): GeckoResult<String>? {
|
||||||
|
Timber.tag("MainActivity").e("Page load error: ${error.code} - ${error.category}")
|
||||||
|
runOnUiThread { showErrorPage() }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCanGoBack(session: GeckoSession, canGoBack: Boolean) {
|
||||||
|
this@MainActivity.canGoBack = canGoBack
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProgressDelegate - 页面加载进度
|
||||||
|
session.progressDelegate = object : GeckoSession.ProgressDelegate {
|
||||||
|
override fun onPageStart(session: GeckoSession, url: String) {
|
||||||
|
Timber.tag("MainActivity").d("Page started: $url")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageStop(session: GeckoSession, success: Boolean) {
|
||||||
|
Timber.tag("MainActivity").d("Page stopped, success: $success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PromptDelegate - 处理 JS bridge 和 console 消息
|
||||||
|
session.promptDelegate = object : GeckoSession.PromptDelegate {
|
||||||
|
override fun onTextPrompt(
|
||||||
|
session: GeckoSession,
|
||||||
|
prompt: GeckoSession.PromptDelegate.TextPrompt
|
||||||
|
): GeckoResult<GeckoSession.PromptDelegate.PromptResponse>? {
|
||||||
|
val message = prompt.message ?: ""
|
||||||
|
|
||||||
|
if (message.startsWith("bridge:")) {
|
||||||
|
// JS bridge 消息
|
||||||
|
val payload = message.substring(7)
|
||||||
|
if (payload == "_poll") {
|
||||||
|
// 轮询待执行的 JS 代码
|
||||||
|
val code = pollPendingJs()
|
||||||
|
return GeckoResult.fromValue(prompt.confirm(code))
|
||||||
|
}
|
||||||
|
val result = handleBridgeCall(payload, prompt.defaultValue ?: "")
|
||||||
|
return GeckoResult.fromValue(prompt.confirm(result))
|
||||||
|
}
|
||||||
|
// 默认处理(显示对话框)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAlertPrompt(
|
||||||
|
session: GeckoSession,
|
||||||
|
prompt: GeckoSession.PromptDelegate.AlertPrompt
|
||||||
|
): GeckoResult<GeckoSession.PromptDelegate.PromptResponse>? {
|
||||||
|
Timber.tag("WebConsole").d("Alert: ${prompt.message}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleBridgeCall(method: String, argsJson: String): String {
|
||||||
|
return try {
|
||||||
|
pttInterface?.dispatch(method, argsJson) ?: ""
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.tag("Bridge").e(e, "Bridge call failed: $method")
|
||||||
|
"{\"error\":\"${e.message}\"}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGeckoSession(): GeckoSession? {
|
||||||
|
return geckoSession
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAndroidInterface(): AndroidInterface? {
|
||||||
|
return pttInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showExitDialog() {
|
fun showExitDialog() {
|
||||||
// 调用 JS 函数
|
executeJs("addExitText()")
|
||||||
mAgentWeb?.jsAccessEntrace?.quickCallJs("addExitText")
|
|
||||||
|
|
||||||
androidx.appcompat.app.AlertDialog.Builder(this)
|
androidx.appcompat.app.AlertDialog.Builder(this)
|
||||||
.setTitle("提示")
|
.setTitle("提示")
|
||||||
.setMessage("确定要退出应用吗?")
|
.setMessage("确定要退出应用吗?")
|
||||||
|
|
@ -205,29 +291,24 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
if (mAgentWeb?.handleKeyEvent(keyCode, event) == true) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
|
val session = geckoSession
|
||||||
|
if (canGoBack && session != null) {
|
||||||
|
session.goBack()
|
||||||
|
return true
|
||||||
|
}
|
||||||
showExitDialog()
|
showExitDialog()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.onKeyDown(keyCode, event)
|
return super.onKeyDown(keyCode, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
mAgentWeb?.webLifeCycle?.onPause()
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
mAgentWeb?.webLifeCycle?.onResume()
|
|
||||||
super.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
mAgentWeb?.webLifeCycle?.onDestroy()
|
|
||||||
cancelTimeoutTimer()
|
cancelTimeoutTimer()
|
||||||
|
geckoView.releaseSession()
|
||||||
|
geckoSession?.close()
|
||||||
|
geckoSession = null
|
||||||
|
instance = null
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,10 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<LinearLayout
|
<org.mozilla.geckoview.GeckoView
|
||||||
android:id="@+id/container"
|
android:id="@+id/geckoView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent" />
|
||||||
android:orientation="vertical" />
|
|
||||||
|
|
||||||
<!-- 加载等待页 -->
|
<!-- 加载等待页 -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,15 @@
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.kotlin.compose) apply false
|
alias(libs.plugins.kotlin.compose) apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理 GeckoView Glean 依赖冲突:强制使用统一版本
|
||||||
|
subprojects {
|
||||||
|
project.configurations.all {
|
||||||
|
resolutionStrategy.eachDependency {
|
||||||
|
if (requested.group == "org.mozilla.telemetry" && requested.name.contains("glean")) {
|
||||||
|
useVersion("52.7.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ dependencyResolutionManagement {
|
||||||
maven { url = uri("https://maven.aliyun.com/repository/public") }
|
maven { url = uri("https://maven.aliyun.com/repository/public") }
|
||||||
// 第三方开源库必备源
|
// 第三方开源库必备源
|
||||||
maven { url = uri("https://jitpack.io") }
|
maven { url = uri("https://jitpack.io") }
|
||||||
|
// GeckoView (Mozilla)
|
||||||
|
maven { url = uri("https://maven.mozilla.org/maven2/") }
|
||||||
// 兜底源
|
// 兜底源
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue