Files
assistant-android/app/src/main/java/com/gh/download/DownloadDataHelper.kt

446 lines
21 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.

package com.gh.download
import android.content.pm.PackageInfo
import android.text.TextUtils
import com.gh.common.util.PackageUtils
import com.gh.common.util.RealNameHelper
import com.gh.common.xapk.XapkInstaller
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.exposure.meta.MetaUtil.getMeta
import com.gh.gamecenter.common.loghub.LoghubUtils
import com.gh.gamecenter.common.utils.*
import com.gh.ndownload.NDataChanger
import com.gh.ndownload.NDownloadBridge
import com.halo.assistant.HaloApp
import com.lightgame.download.DownloadConfig
import com.lightgame.download.DownloadEntity
import com.lightgame.download.DownloadStatus
import com.lightgame.utils.Utils
import org.json.JSONArray
import org.json.JSONObject
object DownloadDataHelper {
private const val TAG = "DownloadDataHelper"
const val DOWNLOAD_RESUME_WAY = "download_resume_way"
const val DOWNLOAD_RESUME_MANUAL = "manual"
const val DOWNLOAD_RESUME_AUTO = "auto"
const val DOWNLOAD_CANCEL_WAY = "download_cancel_way"
const val DOWNLOAD_CANCEL_MANUAL = "manual"
const val DOWNLOAD_CANCEL_AUTO = "auto"
const val DOWNLOAD_THREAD_SIZE = "download_thread_size" // 线程数量0为传统单线程模式; 1为多线程的单线程; >1为多线程的实际线程数量
private val mDownloadSpeedMap = HashMap<String, MutableList<Long>>()
private val mDownloadHeartbeatList = mutableListOf<JSONObject>()
private val mDownloadHeartbeatSheet = HashMap<String, JSONObject>()
@JvmStatic
fun getDownloadStatusAlias(downloadEntity: DownloadEntity, downloadStatus: DownloadStatus? = null): String {
val status = downloadStatus ?: downloadEntity.status
return if (status == DownloadStatus.add) {
"开始下载"
} else if (status == DownloadStatus.pause) {
val pauseExtraString = downloadEntity.getMetaExtra(Constants.DOWNLOAD_PAUSE_EXTRA)
if (pauseExtraString.isEmpty()) {
"暂停下载"
} else {
"暂停下载-$pauseExtraString"
}
} else if (status == DownloadStatus.resume) {
if (downloadEntity.meta[DOWNLOAD_RESUME_WAY] == DOWNLOAD_RESUME_AUTO) {
"自动恢复下载"
} else {
"继续下载"
}
} else if (status == DownloadStatus.waiting) {
"暂停下载-等待中"
} else if (status == DownloadStatus.subscribe
|| status == DownloadStatus.neterror
|| status == DownloadStatus.timeout
|| status == DownloadStatus.diskisfull
|| status == DownloadStatus.diskioerror
) {
"暂停下载-连上WiFi自动下载"
} else if (status == DownloadStatus.done) {
if (downloadEntity.isSimulatorGame()) {
return "下载完成"
}
val packageInfo = if (PackageUtils.isDeviceUnableToHandleBigApkFile(downloadEntity.path)) {
PackageInfo()
} else {
val pm = HaloApp.getInstance().application.applicationContext.packageManager
pm.getPackageArchiveInfo(downloadEntity.path, 0)
}
if (packageInfo == null && XapkInstaller.PACKAGE_EXTENSION_NAME == downloadEntity.path.getExtension()) {
"解析包错误"
} else {
"下载完成"
}
} else if (status == DownloadStatus.delete || status == DownloadStatus.cancel) {
if (downloadEntity.meta[DOWNLOAD_CANCEL_WAY] == DOWNLOAD_CANCEL_AUTO) {
"自动删除任务"
} else {
"删除任务"
}
} else if (status == DownloadStatus.overflow) {
"解析包错误-下载过程中"
} else if (status == DownloadStatus.hijack || status == DownloadStatus.notfound) {
"下载失败"
} else if (status == DownloadStatus.uncertificated) {
"未实名"
} else if (status == DownloadStatus.unqualified) {
"未成年"
} else if (status == DownloadStatus.unavailable) {
"未接入防沉迷系统,暂不支持下载"
} else if (status == DownloadStatus.banned) {
"网络异常"
} else if (status == DownloadStatus.redirected) {
"重定向至最终地址"
} else if (status == DownloadStatus.resourceUnavailable) {
"资源不可用"
} else {
"未知状态"
}
}
fun uploadRedirectEvent(downloadEntity: DownloadEntity) {
val jsonObject = JSONObject()
try {
jsonObject.put("event", "download_redirect")
val startupStatus = downloadEntity.meta[DownloadEntity.DOWNLOAD_STARTUP_STATUS_KEY]
if (startupStatus != null) {
jsonObject.put("msg", getDownloadStatusAlias(downloadEntity, DownloadStatus.valueOf(startupStatus)))
}
jsonObject.put("meta", getMetaJson())
jsonObject.put("timestamp", System.currentTimeMillis() / 1000)
// payload
val payloadObject = JSONObject()
payloadObject.put("host", downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown")
payloadObject.put("path", downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown")
payloadObject.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
payloadObject.put("game_id", downloadEntity.gameId)
payloadObject.put("gameName", downloadEntity.name)
payloadObject.put("platform", downloadEntity.platform)
payloadObject.put("package", downloadEntity.packageName)
payloadObject.put("certification", RealNameHelper.getCertificationStatus())
payloadObject.put("filename", getFileName(downloadEntity))
payloadObject.put("download_type", if (downloadEntity.asVGame()) "畅玩下载" else "本地下载")
payloadObject.put("task_num", NDataChanger.downloadingTaskUrlSet.size)
jsonObject.put("payload", payloadObject)
} catch (e: Exception) {
e.printStackTrace()
}
Utils.log(TAG, "$jsonObject")
LoghubUtils.log(jsonObject, "download_debug", false)
}
@JvmStatic
fun uploadDownloadEvent(downloadEntity: DownloadEntity) {
val downloadSimpleEntity = DownloadDataSimpleHelper.getDownloadDataSimpleEntity(downloadEntity.url)
if (downloadEntity.status != DownloadStatus.downloading) {
uploadDownloadStatusEvent(downloadEntity)
}
if (downloadEntity.status == DownloadStatus.downloading) {
val startupTime =
downloadSimpleEntity?.downloadStartUpTime
if (startupTime != null) {
uploadDownloadStartupTimeEvent(downloadEntity, startupTime)
DownloadDataSimpleHelper.updateDownloadDataSimpleEntity(downloadEntity.url, downloadStartUpTime = null)
}
}
if (downloadEntity.status == DownloadStatus.downloading || downloadEntity.status == DownloadStatus.done) {
val time = downloadSimpleEntity?.downloadSpeedTime
val size = downloadSimpleEntity?.downloadSpeedSize
if (downloadEntity.speed == 0L || time == null || size == null) {
val currentTime = System.currentTimeMillis()
val currentProgress = downloadEntity.progress
DownloadDataSimpleHelper.updateDownloadDataSimpleEntity(downloadEntity.url, downloadSpeedTime = currentTime, downloadSpeedSize = currentProgress)
} else {
val offset = System.currentTimeMillis() - time.toLong()
if (offset > 5000) {
//记录并重置
val downloadedSize = downloadEntity.progress - size.toLong()
val averageSpeed = downloadedSize / offset
val speedList = mDownloadSpeedMap[downloadEntity.url]
if (speedList == null) {
mDownloadSpeedMap[downloadEntity.url] = arrayListOf(averageSpeed)
} else {
speedList.add(averageSpeed)
if (speedList.size >= 6 || downloadEntity.status == DownloadStatus.done) {
uploadDownloadAverageSpeed(downloadEntity, speedList)
if (downloadEntity.status == DownloadStatus.done) {
mDownloadSpeedMap.remove(downloadEntity.url)
} else {
speedList.clear()
}
}
}
val currentTime = System.currentTimeMillis()
val currentProgress = downloadEntity.progress
DownloadDataSimpleHelper.updateDownloadDataSimpleEntity(downloadEntity.url, downloadSpeedTime = currentTime, downloadSpeedSize = currentProgress)
}
}
}
}
private fun uploadDownloadStartupTimeEvent(downloadEntity: DownloadEntity, startupTime: Long) {
val jsonObject = JSONObject()
try {
jsonObject.put("event", "下载线程启动")
val startupStatus = downloadEntity.meta[DownloadEntity.DOWNLOAD_STARTUP_STATUS_KEY]
if (startupStatus != null) {
jsonObject.put("msg", getDownloadStatusAlias(downloadEntity, DownloadStatus.valueOf(startupStatus)))
}
jsonObject.put("meta", getMetaJson())
jsonObject.put("timestamp", System.currentTimeMillis() / 1000)
// payload
val payloadObject = JSONObject()
val parallel = downloadEntity.meta[DOWNLOAD_THREAD_SIZE]?.toInt()
payloadObject.put("host", downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown")
payloadObject.put("path", downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown")
payloadObject.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
payloadObject.put("game_id", downloadEntity.gameId)
payloadObject.put("gameName", downloadEntity.name)
payloadObject.put("platform", downloadEntity.platform)
payloadObject.put("package", downloadEntity.packageName)
payloadObject.put("certification", RealNameHelper.getCertificationStatus())
payloadObject.put("filename", getFileName(downloadEntity))
payloadObject.put("launch_ms", startupTime)
payloadObject.put("task_num", NDataChanger.downloadingTaskUrlSet.size)
if (parallel != null) {
payloadObject.put("parallel", parallel)
}
jsonObject.put("payload", payloadObject)
} catch (e: Exception) {
e.printStackTrace()
}
Utils.log(TAG, "$jsonObject")
LoghubUtils.log(jsonObject, "download_debug", false)
}
fun uploadDownloadStatusEvent(downloadEntity: DownloadEntity, extraStatus: String? = null) {
val jsonObject = JSONObject()
try {
val statusAlias = extraStatus ?: getDownloadStatusAlias(downloadEntity)
jsonObject.put("event", statusAlias)
jsonObject.put("msg", downloadEntity.error)
jsonObject.put("status", downloadEntity.status.status)
jsonObject.put("meta", getMetaJson())
jsonObject.put("timestamp", System.currentTimeMillis() / 1000)
// payload
val payloadObject = JSONObject()
val parallel = downloadEntity.meta[DOWNLOAD_THREAD_SIZE]?.toInt()
val sizeInMB = downloadEntity.size / 1024 / 1024
payloadObject.put("host", downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown")
payloadObject.put("path", downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown")
payloadObject.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
payloadObject.put("game_id", downloadEntity.gameId)
payloadObject.put("gameName", downloadEntity.name)
payloadObject.put("platform", downloadEntity.platform)
payloadObject.put("package", downloadEntity.packageName)
payloadObject.put("certification", RealNameHelper.getCertificationStatus())
payloadObject.put("filename", getFileName(downloadEntity))
payloadObject.put("total_size", sizeInMB)
payloadObject.put("download_type", if (downloadEntity.asVGame()) "畅玩下载" else "本地下载")
if (parallel != null) {
payloadObject.put("parallel", parallel)
}
if (statusAlias == "下载完成") {
val elapsedTimeString = downloadEntity.meta[DownloadConfig.KEY_DOWNLOAD_ELAPSED_TIME]
if (elapsedTimeString != null) {
var elapsedTime = elapsedTimeString.toLong()
// 下载时间统计时间为零时,手动改成 1000ms如果文件大小大于 50M 时上报 sentry
if (elapsedTime == 0L) {
elapsedTime = 1000L
}
val speed = downloadEntity.size / elapsedTime
payloadObject.put("speed", speed)
}
} else {
payloadObject.put("task_num", NDataChanger.downloadingTaskUrlSet.size)
}
payloadObject.put("completed_size", downloadEntity.progress / 1024 / 1024)
if (downloadEntity.status == DownloadStatus.resume) {
if (DownloadDataSimpleHelper.getDownloadDataSimpleEntity(downloadEntity.url)?.isFirstTimeDownload == true) {
payloadObject.put("is_first_start", true)
} else {
payloadObject.put("is_first_start", false)
}
}
if (downloadEntity.status == DownloadStatus.resume || downloadEntity.status == DownloadStatus.add) {
DownloadDataSimpleHelper.updateDownloadSimpleEntityFirstTimeDownload(downloadEntity.url, false)
}
jsonObject.put("payload", payloadObject)
} catch (e: Exception) {
e.printStackTrace()
}
Utils.log(TAG, "$jsonObject")
LoghubUtils.log(jsonObject, "download_debug", false)
}
private fun uploadDownloadAverageSpeed(downloadEntity: DownloadEntity, averageSpeedList: List<Long>) {
val jsonObject = JSONObject()
try {
jsonObject.put("event", "下载进度")
jsonObject.put("msg", "")
jsonObject.put("meta", getMetaJson())
jsonObject.put("timestamp", System.currentTimeMillis() / 1000)
// payload
val payloadObject = JSONObject()
val parallel = downloadEntity.meta[DOWNLOAD_THREAD_SIZE]?.toInt()
payloadObject.put("host", downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown")
payloadObject.put("path", downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown")
payloadObject.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
payloadObject.put("game_id", downloadEntity.gameId)
payloadObject.put("gameName", downloadEntity.name)
payloadObject.put("platform", downloadEntity.platform)
payloadObject.put("package", downloadEntity.packageName)
payloadObject.put("certification", RealNameHelper.getCertificationStatus())
payloadObject.put("filename", getFileName(downloadEntity))
payloadObject.put("speed_progress", JSONArray(averageSpeedList))
payloadObject.put("is_finished", downloadEntity.status == DownloadStatus.done)
payloadObject.put("completed_size", downloadEntity.progress / 1024 / 1024)
if (parallel != null) {
payloadObject.put("parallel", parallel)
}
payloadObject.put("task_num", NDataChanger.downloadingTaskUrlSet.size)
jsonObject.put("payload", payloadObject)
} catch (e: Exception) {
e.printStackTrace()
}
Utils.log(TAG, "$jsonObject")
LoghubUtils.log(jsonObject, "download_debug", false)
}
/**
* 分片检测下载进度,每隔15秒内记录一次,60秒上传一次
*
* 请见:https://git.shanqu.cc/stats/stats-issues/-/issues/188#note_66919
*/
fun uploadDownloadHeartbeat(upload: Boolean) {
val allDownloadEntity = DownloadManager.getInstance().allDownloadEntity
for (downloadEntity in allDownloadEntity) {
/**
* 在后台唤醒的情况下 下载状态可能无法修正
* see [DownloadManager.initDownloadService]
*/
if (downloadEntity.status == DownloadStatus.downloading
&& NDataChanger.downloadingTaskUrlSet.contains(downloadEntity.url)) {
var sheet = mDownloadHeartbeatSheet[downloadEntity.url]
if (sheet == null) {
sheet = JSONObject()
sheet.put("host", downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown")
sheet.put("path", downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown")
sheet.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
sheet.put("game_id", downloadEntity.gameId)
sheet.put("platform", downloadEntity.platform)
sheet.put("package", downloadEntity.packageName)
sheet.put("certification", RealNameHelper.getCertificationStatus())
sheet.put("filename", getFileName(downloadEntity))
sheet.put("download_type", if (downloadEntity.asVGame()) "畅玩下载" else "本地下载")
sheet.put("total_size", downloadEntity.size / 1024 / 1024)
sheet.put("current_progress_size", downloadEntity.progress / 1024)
mDownloadHeartbeatSheet[downloadEntity.url] = sheet
} else {
val progressSize = sheet.getLong("current_progress_size")
sheet.put(
"host",
downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown"
) // 初始化记录的 host 为空
sheet.put(
"path",
downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown"
) // 初始化记录的 path 为空
sheet.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
sheet.put("total_size", downloadEntity.size / 1024 / 1024) // 初始化记录的 total_size 有可能为0
sheet.put("progress_size", downloadEntity.progress / 1024 - progressSize)
sheet.put("current_progress_size", downloadEntity.progress / 1024)
mDownloadHeartbeatList.add(JSONObject(sheet.toString()))
}
} else {
mDownloadHeartbeatSheet.remove(downloadEntity.url)
}
}
if (upload && mDownloadHeartbeatList.isNotEmpty()) {
val jsonObject = JSONObject()
try {
jsonObject.put("event", "progress")
jsonObject.put("meta", getMetaJson())
jsonObject.put("timestamp", System.currentTimeMillis() / 1000)
val payloads = JSONArray()
for (heartbeatData in mDownloadHeartbeatList) {
heartbeatData.remove("current_progress_size")
payloads.put(heartbeatData)
}
jsonObject.put("payloads", payloads)
} catch (e: Exception) {
e.printStackTrace()
}
Utils.log(TAG, "$jsonObject")
mDownloadHeartbeatList.clear()
LoghubUtils.log(jsonObject, "download_debug", false)
}
}
private fun getMetaJson(): JSONObject {
val context = HaloApp.getInstance().application
val meta = getMeta()
// meta
val metaObject = JSONObject()
metaObject.put("dia", MetaUtil.getBase64EncodedAndroidId())
metaObject.put("android_sdk", meta.android_sdk)
metaObject.put("android_version", meta.android_version)
metaObject.put("appVersion", meta.appVersion)
metaObject.put("channel", meta.channel)
metaObject.put("gid", meta.gid)
metaObject.put("oaid", meta.oaid)
metaObject.put("manufacturer", meta.manufacturer)
metaObject.put("model", meta.model)
metaObject.put("network", DeviceUtils.getNetwork(context))
metaObject.put("os", meta.os)
metaObject.put("userId", meta.userId)
return metaObject
}
private fun getFileName(downloadEntity: DownloadEntity): String {
val downloadId = downloadEntity.getMetaExtra(Constants.DOWNLOAD_ID)
return if (!TextUtils.isEmpty(downloadId)) {
downloadId + downloadEntity.path.substring(downloadEntity.path.lastIndexOf("."))
} else {
downloadEntity.path.substring(downloadEntity.path.lastIndexOf("/") + 1)
}
}
}