446 lines
21 KiB
Kotlin
446 lines
21 KiB
Kotlin
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)
|
||
}
|
||
}
|
||
} |