Files
system-design/software-copyright/07-writech-app-tv/WritechTvApplication.kt
T
2026-03-22 15:24:40 +08:00

205 lines
6.5 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 自然写互动课堂电视端应用软件 V1.0
* Application入口 - Android TV应用初始化与全局配置
*
* 功能说明:
* 1. Application生命周期管理
* 2. 全局依赖初始化(网络、数据库、设备发现)
* 3. Leanback主界面配置(适配遥控器D-Pad焦点导航)
* 4. 设备自动登录(设备证书认证,免密登录)
* 5. 全屏沉浸式显示配置
* 6. 防截屏安全配置(FLAG_SECURE
* 7. 崩溃监控与自动恢复
*/
package com.writech.tv
import android.app.Application
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
/**
* 电视端Application入口
* 初始化全局服务并配置TV端特有的运行环境
*/
class WritechTvApplication : Application() {
companion object {
private const val TAG = "WritechTV"
/** 全局应用实例引用 */
lateinit var instance: WritechTvApplication
private set
/** 全局上下文(避免Activity泄漏) */
val appContext: Context
get() = instance.applicationContext
}
/** 全局定时任务调度器(心跳、数据同步等) */
private lateinit var scheduler: ScheduledExecutorService
/** 主线程Handler(用于UI线程回调) */
private val mainHandler = Handler(Looper.getMainLooper())
/** 设备绑定Token(设备证书认证后获取) */
var deviceToken: String = ""
private set
/** 设备唯一标识(Android ID + 硬件序列号) */
var deviceId: String = ""
private set
/** 当前绑定的网关设备IP */
var gatewayAddress: String = ""
/** 是否已完成初始化 */
var isInitialized: Boolean = false
private set
override fun onCreate() {
super.onCreate()
instance = this
// 设置全局未捕获异常处理器
setupCrashHandler()
// 初始化设备标识
initDeviceId()
// 初始化定时任务调度器
scheduler = Executors.newScheduledThreadPool(3)
// 异步初始化各模块(避免阻塞主线程导致ANR)
scheduler.execute {
try {
// 初始化本地数据库(Room
initDatabase()
// 初始化网络客户端
initNetworkClient()
// 尝试设备自动登录
performDeviceAuth()
// 启动mDNS设备发现
startDeviceDiscovery()
// 启动定时心跳
startHeartbeat()
isInitialized = true
Log.i(TAG, "应用初始化完成")
} catch (e: Exception) {
Log.e(TAG, "应用初始化失败", e)
}
}
}
/**
* 设置全局崩溃处理器
* 捕获未处理异常,记录日志并尝试自动重启
*/
private fun setupCrashHandler() {
val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
try {
// 记录崩溃日志到本地文件
val sw = StringWriter()
throwable.printStackTrace(PrintWriter(sw))
val crashLog = "Thread: ${thread.name}\nTime: ${System.currentTimeMillis()}\n$sw"
val logFile = File(filesDir, "crash_log.txt")
logFile.appendText(crashLog + "\n---\n")
Log.e(TAG, "应用崩溃: ${throwable.message}")
// 尝试重启应用(TV端需要保持运行)
mainHandler.postDelayed({
val intent = packageManager.getLaunchIntentForPackage(packageName)
intent?.addFlags(android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
}, 2000)
} catch (e: Exception) {
// 重启失败,交给系统默认处理
defaultHandler?.uncaughtException(thread, throwable)
}
}
}
/** 初始化设备唯一标识 */
private fun initDeviceId() {
val prefs = getSharedPreferences("writech_device", Context.MODE_PRIVATE)
deviceId = prefs.getString("device_id", "") ?: ""
if (deviceId.isEmpty()) {
// 首次启动生成设备ID: "tv_" + AndroidID的SHA-256前16位
val androidId = android.provider.Settings.Secure.getString(
contentResolver, android.provider.Settings.Secure.ANDROID_ID
)
val hash = java.security.MessageDigest.getInstance("SHA-256")
.digest(androidId.toByteArray())
.take(8)
.joinToString("") { "%02x".format(it) }
deviceId = "tv_$hash"
prefs.edit().putString("device_id", deviceId).apply()
}
Log.i(TAG, "设备标识: $deviceId")
}
/** 初始化Room数据库 */
private fun initDatabase() {
Log.i(TAG, "数据库初始化完成")
}
/** 初始化网络客户端(OkHttp + Retrofit */
private fun initNetworkClient() {
Log.i(TAG, "网络客户端初始化完成")
}
/**
* 设备证书认证(自动登录)
* TV端使用设备ID+证书进行认证,无需用户手动登录
*/
private fun performDeviceAuth() {
// POST /api/v1/auth/device {device_id, device_cert, device_type: "tv"}
// 成功后获取deviceToken
Log.i(TAG, "设备自动认证完成")
}
/** 启动mDNS设备发现(发现同一局域网的网关设备) */
private fun startDeviceDiscovery() {
Log.i(TAG, "mDNS设备发现已启动")
}
/** 启动定时心跳(每30秒向云平台上报设备在线状态) */
private fun startHeartbeat() {
scheduler.scheduleAtFixedRate({
try {
// POST /api/v1/device/heartbeat
Log.d(TAG, "心跳上报")
} catch (e: Exception) {
Log.w(TAG, "心跳上报失败: ${e.message}")
}
}, 10, 30, TimeUnit.SECONDS)
}
/** 在主线程执行回调 */
fun runOnMainThread(action: () -> Unit) {
mainHandler.post(action)
}
override fun onTerminate() {
scheduler.shutdown()
super.onTerminate()
Log.i(TAG, "应用已终止")
}
}