Compare commits

...

3 Commits

79 changed files with 1783 additions and 127 deletions

View File

@ -1,12 +1,57 @@
stages:
- analysis
- sendmail
- android-build && analysis
- docker-build && sendmail
- deploy-trigger
## 代码检查
android_build:
tags:
- offline-test
stage: android-build && analysis
image: hub.shanqu.cc/devops/ci-android:jdk11
variables:
KUBERNETES_CPU_LIMIT: "16"
GIT_SUBMODULE_STRATEGY: recursive
Apk_Path: "app/build/outputs/apk/**/release/*.apk"
script:
- export GRADLE_USER_HOME=./.gradle
- chmod +x ./gradlew
- ./scripts/meta_build.sh --config_id 6400549c21c2c94ead074500 --sdk_platform toutiao --sdk_version 5.3.0 --channel BD-GHZS-ZP-KY --activate_reporting_ratio 60 --first_lanuch_jump e1wibGlua190eXBlXCI6XCJcIixcImxpbmtfaWRcIjpcIlwiLFwibGlua190ZXh0XCI6XCJcIixcImhvbWVfaW5kZXhcIjpcIlwiLFwiYm90dG9tX2luZGV4XCI6XCJcIn0= --unix_timestamp 1677657618 --output ./release/com.gh.gamecenter_5.17.4_694_BD-GHZS-ZP-KY_toutiao_5.3.0_1677657618.apk
- rm -rf ./.gradle/caches/build-cache-1
cache:
paths:
- .gradle
artifacts:
paths:
- Dockerfile
expire_in: 15 mins
only:
- feature-meta_build
# 构建推送docker镜像
docker-build:
tags:
- offline-test
stage: docker-build && sendmail
image: hub.shanqu.cc/library/docker:latest
variables:
GIT_STRATEGY: none
script:
- projectPath=`echo $CI_PROJECT_PATH | sed 's#/#-#g'`
- docker build -t registry.cn-shenzhen.aliyuncs.com/ghzs/$projectPath:latest .
- docker push registry.cn-shenzhen.aliyuncs.com/ghzs/$projectPath:latest
- docker run -e PROJECTKEY=$projectPath -e EMAIL=$GITLAB_USER_EMAIL -e BRANCH=$CI_COMMIT_REF_NAME --name send-email --rm hub.shanqu.cc/platform/send-sonar-report:latest
cache:
paths:
- .gradle
policy: pull
only:
- feature-meta_build
# 代码检查
sonarqube_analysis:
tags:
- offline-test
stage: analysis
stage: android-build && analysis
image: sonarsource/sonar-scanner-cli:latest
dependencies: [] #禁止传递来的artifact
script:
@ -27,17 +72,12 @@ sonarqube_analysis:
-Dsonar.gitlab.merge_request_discussion=true
-Dsonar.java.binaries=. # 如果不使用Maven或Gradle进行分析则必须手动提供测试二进制文件
only:
- dev
## 发送简易检测结果报告
send_sonar_report:
tags:
- offline-test
stage: sendmail
image: hub.shanqu.cc/library/docker:latest
dependencies: [] #禁止传递来的artifact
script:
- group=`echo $CI_PROJECT_PATH | sed 's#/#-#g'`
- docker run -e PROJECTKEY=$group -e EMAIL=$GITLAB_USER_EMAIL --name send-email --rm hub.shanqu.cc/platform/send-sonar-report:latest
only:
- dev
- feature-meta_build
## 触发多项目构建
trigger_job:
stage: deploy-trigger
trigger:
project: devops/automation/build-eci
branch: dev

8
.gitmodules vendored
View File

@ -1,13 +1,13 @@
[submodule "libraries/LGLibrary"]
path = libraries/LGLibrary
url = git@git.shanqu.cc:android/common-library.git
url = ../../../android/common-library.git
branch = master
[submodule "vspace-bridge"]
path = vspace-bridge
url = git@git.shanqu.cc:cwzs/android/vspace-bridge.git
url = ../../../cwzs/android/vspace-bridge.git
[submodule "module_common/src/debug/assets/assistant-android-mock"]
path = module_common/src/debug/assets/assistant-android-mock
url = git@git.shanqu.cc:halo/android/assistant-android-mock.git
url = ../../../halo/android/assistant-android-mock.git
[submodule "ndownload"]
path = ndownload
url = git@git.shanqu.cc:android/ndownload.git
url = ../../../android/ndownload.git

15
Dockerfile Normal file
View File

@ -0,0 +1,15 @@
FROM openjdk:11-jdk
WORKDIR /project
SHELL ["/bin/bash", "-c"]
#配置SDK环境变量
ENV ANDROID_SDK_ROOT /usr/lib/sdk
ENV ANDROID_HOME /usr/lib/sdk
ENV PATH $ANDROID_SDK_ROOT:$PATH
ENV PATH=$PATH:${ANDROID_HOME}/cmdline-tools/cmdline-tools/bin/
ENV GRADLE_USER_HOME /project/.gradle
RUN source ~/.bashrc
RUN sed -i "s@http://\(deb\|security\).debian.org@https://mirrors.aliyun.com@g" /etc/apt/sources.list \
&& apt-get --quiet update --yes \
&& apt-get --quiet install --yes lib32stdc++6 lib32z1 libncurses5 util-linux bash tzdata librdkafka-dev pkgconf \
&& rm -rf /var/lib/apt/lists/*
COPY .gradle /project/.gradle

View File

@ -9,6 +9,9 @@ import groovy.xml.XmlUtil
android {
String CONFIG_ID = ""
String FIRST_LAUNCH = ""
buildFeatures {
viewBinding true
dataBinding true
@ -67,9 +70,13 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-fresco.txt'
/**
* All third-party appid/appkey
*/
// 推广用的配置 id
buildConfigField "String", "CONFIG_ID", "\"${CONFIG_ID}\""
// 首次启动的跳转配置
buildConfigField "String", "FIRST_LAUNCH", "\"${FIRST_LAUNCH}\""
// All third-party appid/appkey
buildConfigField "String", "API_HOST", "\"${API_HOST}\""
buildConfigField "String", "NEW_API_HOST", "\"${NEW_API_HOST}\""
buildConfigField "String", "VAPI_HOST", "\"${VAPI_HOST}\""
@ -312,6 +319,7 @@ dependencies {
exclude group: 'androidx.swiperefreshlayout'
}
implementation(project(':module_vpn'))
implementation(project(':module_pkg'))
// 默认不接入光能模块,提高编译速度
// debugImplementation(project(':module_energy')) {
// exclude group: 'androidx.swiperefreshlayout'

View File

@ -119,6 +119,8 @@
android:name="io.sentry.breadcrumbs.system-events"
android:value="false" />
<service android:name = "com.gh.ndownload.NDownloadService" />
<activity
android:name="com.gh.gamecenter.SplashScreenActivity"
android:configChanges="keyboardHidden|orientation|screenSize"

View File

@ -593,6 +593,7 @@ public class BindingAdapters {
case pause:
case timeout:
case neterror:
case diskisfull:
case waiting:
progressBar.setText(R.string.downloading);
if (downloadEntity.isPluggable() && PackagesManager.INSTANCE.isInstalled(downloadEntity.getPackageName())) {

View File

@ -22,8 +22,12 @@ import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.feature.entity.ApkEntity
import com.gh.gamecenter.feature.entity.SimulatorEntity
import com.gh.gamecenter.entity.TrackableEntity
import com.gh.ndownload.NDataChanger
import com.halo.assistant.HaloApp
import com.lightgame.download.*
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import com.lightgame.download.DownloadStatus
import com.lightgame.download.FileUtils
import com.lightgame.utils.Utils
import java.lang.ref.WeakReference
import java.text.DecimalFormat
@ -95,6 +99,9 @@ class SimulatorDownloadManager private constructor() {
downloadDialog?.dismiss()
}
}
DownloadStatus.diskisfull == downloadEntity.status -> {
ToastUtils.showToast("存储空间已满,下载任务已暂停")
}
DownloadStatus.neterror == downloadEntity.status -> {
ToastUtils.showToast("网络不稳定,下载任务已暂停")
}
@ -290,13 +297,16 @@ class SimulatorDownloadManager private constructor() {
HaloApp.put(simulator.name, simulator)
if (entity != null) {
when (entity.status) {
DownloadStatus.pause, DownloadStatus.subscribe,
DownloadStatus.neterror, DownloadStatus.timeout -> {
DownloadStatus.pause,
DownloadStatus.subscribe,
DownloadStatus.neterror,
DownloadStatus.timeout,
DownloadStatus.diskisfull -> {
DownloadManager.getInstance().addObserver(dataWatcher)
uiExecutor.executeWithDelay(Runnable { DownloadManager.getInstance().resume(entity, true) }, 200)
downloadDialog?.show()
}
DownloadStatus.done -> DataChanger.notifyDataChanged(entity)
DownloadStatus.done -> NDataChanger.notifyDataChanged(entity)
else -> createDownload(apkEntity, simulator)
}

View File

@ -262,6 +262,7 @@ public class DetailDownloadUtils {
case downloading:
case redirected:
case pause:
case diskisfull:
case overflow:
String downloadingText = "游戏加载中 " + downloadEntity.getPercent() + "%";
String resumeText = "继续加载 " + downloadEntity.getPercent() + "%";
@ -348,6 +349,7 @@ public class DetailDownloadUtils {
case timeout:
case neterror:
case subscribe:
case diskisfull:
case pause:
viewHolder.mDownloadPb.setText("继续加载 " + viewHolder.downloadEntity.getPercent() + "%");
viewHolder.mDownloadPb.setButtonStyle(DownloadButton.ButtonStyle.DOWNLOADING_NORMAL);

View File

@ -310,6 +310,7 @@ object DownloadItemUtils {
DownloadStatus.timeout,
DownloadStatus.neterror,
DownloadStatus.subscribe,
DownloadStatus.diskisfull,
DownloadStatus.overflow -> {
buttonStyle = DownloadButton.ButtonStyle.NORMAL
setText(R.string.resume)
@ -429,6 +430,7 @@ object DownloadItemUtils {
DownloadStatus.pause,
DownloadStatus.timeout,
DownloadStatus.neterror,
DownloadStatus.diskisfull,
DownloadStatus.subscribe,
DownloadStatus.overflow -> {
if (isMultiVersion) {

View File

@ -98,18 +98,19 @@ object DownloadNotificationHelper {
DownloadStatus.waiting -> builder.setContentText("等待中")
DownloadStatus.subscribe,
DownloadStatus.timeout,
DownloadStatus.diskisfull,
DownloadStatus.neterror -> builder.setContentText("已暂停连接WiFi自动下载")
else -> builder.setContentText("暂停中")
}
builder.setProgress(PROGRESS_MAX, entity.percent.toInt(), false)
}
when {
entity.status == DownloadStatus.done -> {
when (entity.status) {
DownloadStatus.done -> {
builder.setSortKey("A")
builder.setOngoing(true) // 垃圾华为 sortKey 不起效 priority 也不起效,要将下载完成任务的通知置顶只能设置为 ongoing喷了
}
entity.status == DownloadStatus.downloading -> builder.setSortKey("B")
DownloadStatus.downloading -> builder.setSortKey("B")
else -> builder.setSortKey("C")
}

View File

@ -87,7 +87,9 @@ object DownloadObserver {
)
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true))
return
} else if (DownloadStatus.neterror == downloadEntity.status || DownloadStatus.timeout == downloadEntity.status) {
} else if (DownloadStatus.neterror == downloadEntity.status
|| DownloadStatus.timeout == downloadEntity.status
|| DownloadStatus.diskisfull == downloadEntity.status) {
if (downloadEntity.meta[Constants.MARK_RETRY_DOWNLOAD].isNullOrEmpty()
&& NetworkUtils.isWifiConnected(HaloApp.getInstance().application)
) {
@ -98,7 +100,12 @@ object DownloadObserver {
Utils.log("DownloadObserver", "下载重试->" + downloadEntity.toJson())
}
} else {
Utils.toast(mApplication, "网络不稳定,下载任务已暂停")
if (DownloadStatus.diskisfull == downloadEntity.status) {
ToastUtils.toast("磁盘已满,请清理空间后重试下载")
} else {
ToastUtils.toast("网络不稳定,下载任务已暂停")
}
DataLogUtils.uploadNeterrorLog(mApplication, downloadEntity)
debugOnly {

View File

@ -1,16 +1,17 @@
package com.gh.common.xapk
import android.content.Context
import com.gh.gamecenter.core.AppExecutor
import com.gh.common.util.*
import com.gh.common.util.DownloadNotificationHelper
import com.gh.common.util.PackageInstaller
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.utils.debugOnly
import com.gh.gamecenter.common.utils.getExtension
import com.gh.gamecenter.common.utils.throwExceptionInDebug
import com.gh.gamecenter.common.utils.tryCatchInRelease
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.SentryHelper
import com.gh.ndownload.NDataChanger
import com.halo.assistant.HaloApp
import com.lightgame.download.DataChanger
import com.lightgame.download.DownloadEntity
import com.lightgame.utils.Utils
import java.text.DecimalFormat
@ -81,7 +82,7 @@ object XapkInstaller : IXapkUnzipListener {
}
downloadEntity.meta[XAPK_UNZIP_PERCENT] = percent.toString()
downloadEntity.meta[XAPK_UNZIP_STATUS] = XapkUnzipStatus.UNZIPPING.name
DataChanger.notifyDataChanged(downloadEntity)
NDataChanger.notifyDataChanged(downloadEntity)
}
debugOnly {
@ -111,7 +112,7 @@ object XapkInstaller : IXapkUnzipListener {
AppExecutor.uiExecutor.execute {
downloadEntity.meta[XAPK_UNZIP_PERCENT] = "0.0"
downloadEntity.meta[XAPK_UNZIP_STATUS] = XapkUnzipStatus.CANCEL.name
DataChanger.notifyDataChanged(downloadEntity)
NDataChanger.notifyDataChanged(downloadEntity)
DownloadManager.getInstance().updateDownloadEntity(downloadEntity)
}
}
@ -122,7 +123,7 @@ object XapkInstaller : IXapkUnzipListener {
AppExecutor.uiExecutor.execute {
downloadEntity.meta[XAPK_UNZIP_STATUS] = XapkUnzipStatus.FAILURE.name
DownloadNotificationHelper.addOrUpdateDownloadNotification(downloadEntity)
DataChanger.notifyDataChanged(downloadEntity)
NDataChanger.notifyDataChanged(downloadEntity)
DownloadManager.getInstance().updateDownloadEntity(downloadEntity)
}
@ -156,7 +157,7 @@ object XapkInstaller : IXapkUnzipListener {
downloadEntity.meta[XAPK_UNZIP_PERCENT] = "100.0"
downloadEntity.meta[XAPK_UNZIP_STATUS] = XapkUnzipStatus.SUCCESS.name
DataChanger.notifyDataChanged(downloadEntity)
NDataChanger.notifyDataChanged(downloadEntity)
DownloadManager.getInstance().updateDownloadEntity(downloadEntity)
}

View File

@ -13,8 +13,8 @@ import com.gh.gamecenter.common.utils.getExtension
import com.gh.gamecenter.common.utils.getMetaExtra
import com.gh.gamecenter.common.utils.isSimulatorGame
import com.gh.gamecenter.core.utils.SentryHelper
import com.gh.ndownload.NDataChanger
import com.halo.assistant.HaloApp
import com.lightgame.download.DataChanger
import com.lightgame.download.DownloadConfig
import com.lightgame.download.DownloadEntity
import com.lightgame.download.DownloadStatus
@ -128,6 +128,7 @@ object DownloadDataHelper {
payloadObject.put("platform", downloadEntity.platform)
payloadObject.put("package", downloadEntity.packageName)
payloadObject.put("filename", getFileName(downloadEntity))
payloadObject.put("task_num", NDataChanger.downloadingTasks.size)
jsonObject.put("payload", payloadObject)
} catch (e: Exception) {
e.printStackTrace()
@ -202,6 +203,8 @@ object DownloadDataHelper {
// 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("game_id", downloadEntity.gameId)
@ -210,7 +213,10 @@ object DownloadDataHelper {
payloadObject.put("package", downloadEntity.packageName)
payloadObject.put("filename", getFileName(downloadEntity))
payloadObject.put("launch_ms", startupTime)
payloadObject.put("parallel", downloadEntity.meta[DOWNLOAD_THREAD_SIZE]?.toInt() ?: 0)
payloadObject.put("task_num", NDataChanger.downloadingTasks.size)
if (parallel != null) {
payloadObject.put("parallel", parallel)
}
jsonObject.put("payload", payloadObject)
} catch (e: Exception) {
e.printStackTrace()
@ -234,7 +240,9 @@ object DownloadDataHelper {
// 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("game_id", downloadEntity.gameId)
@ -243,7 +251,9 @@ object DownloadDataHelper {
payloadObject.put("package", downloadEntity.packageName)
payloadObject.put("filename", getFileName(downloadEntity))
payloadObject.put("total_size", sizeInMB)
payloadObject.put("parallel", downloadEntity.meta[DOWNLOAD_THREAD_SIZE]?.toInt() ?: 0)
if (parallel != null) {
payloadObject.put("parallel", parallel)
}
if (statusAlias == "下载完成") {
val elapsedTimeString = downloadEntity.meta[DownloadConfig.KEY_DOWNLOAD_ELAPSED_TIME]
@ -264,7 +274,7 @@ object DownloadDataHelper {
payloadObject.put("speed", speed)
}
} else {
payloadObject.put("task_num", DataChanger.downloadingTasks.size)
payloadObject.put("task_num", NDataChanger.downloadingTasks.size)
}
payloadObject.put("completed_size", downloadEntity.progress / 1024 / 1024)
if (downloadEntity.status == DownloadStatus.resume) {
@ -301,6 +311,8 @@ object DownloadDataHelper {
// 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("game_id", downloadEntity.gameId)
@ -311,8 +323,10 @@ object DownloadDataHelper {
payloadObject.put("speed_progress", JSONArray(averageSpeedList))
payloadObject.put("is_finished", downloadEntity.status == DownloadStatus.done)
payloadObject.put("completed_size", downloadEntity.progress / 1024 / 1024)
payloadObject.put("parallel", downloadEntity.meta[DOWNLOAD_THREAD_SIZE]?.toInt() ?: 0)
payloadObject.put("task_num", DataChanger.downloadingTasks.size)
if (parallel != null) {
payloadObject.put("parallel", parallel)
}
payloadObject.put("task_num", NDataChanger.downloadingTasks.size)
jsonObject.put("payload", payloadObject)
} catch (e: Exception) {
e.printStackTrace()
@ -335,7 +349,7 @@ object DownloadDataHelper {
* 在后台唤醒的情况下 下载状态可能无法修正
* see [DownloadManager.initDownloadService]
*/
if (downloadEntity.status == DownloadStatus.downloading && DataChanger.downloadingTasks[downloadEntity.url] != null) {
if (downloadEntity.status == DownloadStatus.downloading && NDataChanger.downloadingTasks[downloadEntity.url] != null) {
var sheet = mDownloadHeartbeatSheet[downloadEntity.url]
if (sheet == null) {
sheet = JSONObject()

View File

@ -49,17 +49,17 @@ import com.gh.gamecenter.eventbus.EBDownloadStatus;
import com.gh.gamecenter.manager.PackagesManager;
import com.gh.gamecenter.login.user.UserManager;
import com.gh.gamecenter.packagehelper.PackageRepository;
import com.gh.ndownload.NDataChanger;
import com.gh.ndownload.NDownloadBridge;
import com.gh.ndownload.NDownloadService;
import com.halo.assistant.HaloApp;
import com.lightgame.download.ConnectionUtils;
import com.lightgame.download.DataChanger;
import com.lightgame.download.DataWatcher;
import com.lightgame.download.DownloadConfig;
import com.lightgame.download.DownloadDao;
import com.lightgame.download.DownloadEntity;
import com.lightgame.download.DownloadService;
import com.lightgame.download.DownloadStatus;
import com.lightgame.download.DownloadStatusListener;
import com.lightgame.download.DownloadStatusManager;
import com.lightgame.download.DownloadTask;
import com.lightgame.download.FileUtils;
import com.lightgame.download.HttpDnsManager;
@ -91,9 +91,9 @@ public class DownloadManager implements DownloadStatusListener {
private final Map<String, ConcurrentHashMap<String, DownloadEntity>> gameMap;
private final ArrayMap<String, DownloadStatus> statusMap;
private final ArrayMap<String, DownloadEntity> downloadingMap;
private final ConcurrentHashMap<String, DownloadEntity> downloadingMap;
private ArrayList<DownloadEntity> mInvisiblePendingTaskList; // 用户不可见的 pending 任务
private final ArrayList<DownloadEntity> mInvisiblePendingTaskList; // 用户不可见的 pending 任务
private final DownloadDao mDownloadDao;
private final DownloadedGameIdAndPackageNameDao mDownloadedGameIdAndPackageNameDao;
@ -169,8 +169,6 @@ public class DownloadManager implements DownloadStatusListener {
mUpdateMarks = SPUtils.getStringSet(UPDATE_IS_READ_MARK);
DownloadStatusManager.getInstance().registerTaskStatusListener(this);
// 只有下载模块需要这坨东西,因此移动到这里初始化
ConnectionUtils.initHttpsUrlConnection(mContext);
@ -180,7 +178,7 @@ public class DownloadManager implements DownloadStatusListener {
platformMap = new ArrayMap<>();
gameMap = new ConcurrentHashMap<>();
statusMap = new ArrayMap<>();
downloadingMap = new ArrayMap<>();
downloadingMap = new ConcurrentHashMap<>();
// mDownloadSnapshotList = new ArrayList<>();
mInvisiblePendingTaskList = new ArrayList<>();
@ -226,10 +224,6 @@ public class DownloadManager implements DownloadStatusListener {
}
}
public ArrayMap<String, DownloadEntity> getDownloadingMap() {
return downloadingMap;
}
public static DownloadManager getInstance() {
return SingletonHolder.INSTANCE;
}
@ -444,7 +438,7 @@ public class DownloadManager implements DownloadStatusListener {
if (isDownloadCompleted(url)) {
downloadEntity.setStatus(DownloadStatus.done);
DataChanger.INSTANCE.notifyDataChanged(downloadEntity);
NDataChanger.INSTANCE.notifyDataChanged(downloadEntity);
} else if (!isTaskDownloading(url)) {
startDownloadService(downloadEntity, DownloadStatus.add);
}
@ -465,7 +459,7 @@ public class DownloadManager implements DownloadStatusListener {
checkDownloadEntryRecordValidate(url);
if (isDownloadCompleted(url)) {
downloadEntity.setStatus(DownloadStatus.done);
DataChanger.INSTANCE.notifyDataChanged(downloadEntity);
NDataChanger.INSTANCE.notifyDataChanged(downloadEntity);
} else if (!isTaskDownloading(url)) {
DownloadEntity daoEntity = mDownloadDao.get(downloadEntity.getUrl());
if (automatic) {
@ -500,7 +494,7 @@ public class DownloadManager implements DownloadStatusListener {
checkDownloadEntryRecordValidate(url);
if (isDownloadCompleted(url)) {
downloadEntity.setStatus(DownloadStatus.done);
DataChanger.INSTANCE.notifyDataChanged(downloadEntity);
NDataChanger.INSTANCE.notifyDataChanged(downloadEntity);
} else if (!isTaskDownloading(url)) {
startDownloadService(downloadEntity, DownloadStatus.subscribe);
}
@ -546,7 +540,7 @@ public class DownloadManager implements DownloadStatusListener {
* 任务是否已经下载中
*/
public boolean isTaskDownloading(String url) {
if (DataChanger.INSTANCE.getDownloadingTasks().get(url) != null) {
if (NDataChanger.INSTANCE.getDownloadingTasks().get(url) != null) {
Utils.log(DownloadManager.class.getSimpleName(), url + "正在下载!");
return true;
}
@ -554,7 +548,7 @@ public class DownloadManager implements DownloadStatusListener {
}
private Intent getIntent(DownloadEntity entry, DownloadStatus status) {
Intent service = new Intent(mContext, DownloadService.class);
Intent service = new Intent(mContext, NDownloadService.class);
service.putExtra(DownloadConfig.KEY_DOWNLOAD_ENTRY, entry);
service.putExtra(DownloadConfig.KEY_DOWNLOAD_ACTION, status.name());
return service;
@ -583,6 +577,16 @@ public class DownloadManager implements DownloadStatusListener {
return mDownloadDao.getAllSnapshots();
}
/**
* 获取快照
*
* @param url 下载地址
*/
@Nullable
public DownloadEntity getDownloadEntitySnapshot(String url) {
return mDownloadDao.getSnapshot(url);
}
/**
* 获取快照
*
@ -823,6 +827,8 @@ public class DownloadManager implements DownloadStatusListener {
DownloadEntity entry = mDownloadDao.getSnapshot(url);
if (entry != null) {
AppExecutor.getIoExecutor().execute(() -> {
NDownloadBridge.INSTANCE.cancel(url);
mDownloadDao.delete(url);
if (isDeleteFile) {
@ -856,19 +862,16 @@ public class DownloadManager implements DownloadStatusListener {
private void cancelAndNotify(DownloadEntity entry, boolean cancelSilently) {
mDownloadDao.removeErrorMessage(entry.getUrl());
DownloadTask task = DataChanger.INSTANCE.getDownloadingTasks().get(entry.getUrl());
DownloadTask task = NDataChanger.INSTANCE.getDownloadingTasks().get(entry.getUrl());
if (task != null) {
task.cancel();
// 改任务队列的状态
DataChanger.INSTANCE.getDownloadingTasks().remove(entry.getUrl());
if (!cancelSilently) {
DataChanger.INSTANCE.notifyDataChanged(entry);
}
NDataChanger.INSTANCE.getDownloadingTasks().remove(entry.getUrl());
}
DataChanger.INSTANCE.getDownloadEntries().remove(entry.getUrl());
NDataChanger.INSTANCE.getDownloadEntries().remove(entry.getUrl());
if (!cancelSilently) {
DataChanger.INSTANCE.notifyDataChanged(entry);
DownloadStatusManager.getInstance().onTaskCancelled(entry);
NDataChanger.INSTANCE.notifyDataChanged(entry);
onTaskCancelled(entry);
}
Utils.log(DownloadManager.class.getSimpleName(), "cancel");
@ -878,7 +881,7 @@ public class DownloadManager implements DownloadStatusListener {
* 暂停所有正在下载的任务
*/
public void pauseAll() {
for (DownloadEntity entity : DataChanger.INSTANCE.getDownloadEntries().values()) {
for (DownloadEntity entity : NDataChanger.INSTANCE.getDownloadEntries().values()) {
pause(entity.getUrl());
}
Utils.log(DownloadManager.class.getSimpleName(), "pause all");
@ -889,7 +892,7 @@ public class DownloadManager implements DownloadStatusListener {
*/
public void pause(String url) {
checkDownloadEntryRecordValidate(url);
DownloadEntity entry = DataChanger.INSTANCE.getDownloadEntries().get(url);
DownloadEntity entry = NDataChanger.INSTANCE.getDownloadEntries().get(url);
if (entry != null) {
startDownloadService(entry, DownloadStatus.pause);
put(url, System.currentTimeMillis());
@ -905,14 +908,14 @@ public class DownloadManager implements DownloadStatusListener {
* 3.检查是否显示下载通知栏
*/
public void initDownloadService() {
final List<String> urlList = new ArrayList<>(DataChanger.INSTANCE.getDownloadingTasks().keySet());
final List<String> urlList = new ArrayList<>(NDataChanger.INSTANCE.getDownloadingTasks().keySet());
for (DownloadEntity downloadEntity : getAllDownloadEntity()) {
if (!urlList.contains(downloadEntity.getUrl()) &&
(downloadEntity.getStatus().equals(DownloadStatus.downloading)
|| downloadEntity.getStatus().equals(DownloadStatus.waiting))) {
downloadEntity.setStatus(DownloadStatus.subscribe);
mDownloadDao.newOrUpdate(downloadEntity);
DataChanger.INSTANCE.notifyDataChanged(downloadEntity);
NDataChanger.INSTANCE.notifyDataChanged(downloadEntity);
}
}
@ -925,7 +928,7 @@ public class DownloadManager implements DownloadStatusListener {
*/
public void addObserver(DataWatcher dataWatcher) {
Utils.log(DownloadManager.class.getSimpleName(), "addObserver");
DataChanger.INSTANCE.addObserver(dataWatcher);
NDataChanger.INSTANCE.addObserver(dataWatcher);
notifyDownloadStatusASAP(dataWatcher);
}
@ -935,7 +938,7 @@ public class DownloadManager implements DownloadStatusListener {
*/
public void removeObserver(DataWatcher dataWatcher) {
Utils.log(DownloadManager.class.getSimpleName(), "removeObserver");
DataChanger.INSTANCE.deleteObserver(dataWatcher);
NDataChanger.INSTANCE.deleteObserver(dataWatcher);
}
/**
@ -951,11 +954,11 @@ public class DownloadManager implements DownloadStatusListener {
* 初始化下载服务
*/
public void startDownloadService() {
Intent serviceIntent = new Intent(mContext, DownloadService.class);
Intent serviceIntent = new Intent(mContext, NDownloadService.class);
// 当满足系统版本大于 8.0 并且应用在后台运行时以前台服务开启
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
&& !PackageUtils.isAppOnForeground(mContext)) {
serviceIntent.putExtra(DownloadService.KEY_SERVICE_ACTION, DownloadService.START_FOREGROUND);
serviceIntent.putExtra(NDownloadService.KEY_SERVICE_ACTION, NDownloadService.START_FOREGROUND);
mContext.startForegroundService(serviceIntent);
} else {
/*
@ -995,7 +998,7 @@ public class DownloadManager implements DownloadStatusListener {
// 当满足系统版本大于 8.0 并且应用在后台运行时以前台服务开启
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
&& !PackageUtils.isAppOnForeground(mContext)) {
serviceIntent.putExtra(DownloadService.KEY_SERVICE_ACTION, DownloadService.START_FOREGROUND);
serviceIntent.putExtra(NDownloadService.KEY_SERVICE_ACTION, NDownloadService.START_FOREGROUND);
mContext.startForegroundService(serviceIntent);
} else {
mContext.startService(serviceIntent);
@ -1126,7 +1129,7 @@ public class DownloadManager implements DownloadStatusListener {
String mark = downloadEntity.getMeta().get(DOWNLOADED_IS_READ_MARK);
if (TextUtils.isEmpty(mark)) {
downloadEntity.getMeta().put(DOWNLOADED_IS_READ_MARK, DOWNLOADED_IS_READ_MARK);
mDownloadDao.newOrUpdate(downloadEntity);
mDownloadDao.update(downloadEntity, false);
if (!markHasChanged) markHasChanged = true;
}
}
@ -1166,12 +1169,12 @@ public class DownloadManager implements DownloadStatusListener {
String mark = downloadEntity.getMeta().get(DOWNLOADING_IS_READ_MARK);
if (TextUtils.isEmpty(mark)) {
downloadEntity.getMeta().put(DOWNLOADING_IS_READ_MARK, DOWNLOADING_IS_READ_MARK);
mDownloadDao.newOrUpdate(downloadEntity);
mDownloadDao.update(downloadEntity, false);
if (!markHasChanged) markHasChanged = true;
}
} else {
downloadEntity.getMeta().put(DOWNLOADING_IS_READ_MARK, "");
mDownloadDao.newOrUpdate(downloadEntity);
mDownloadDao.update(downloadEntity, false);
if (!markHasChanged) markHasChanged = true;
}
}
@ -1229,7 +1232,7 @@ public class DownloadManager implements DownloadStatusListener {
* 更新数据库中的下载实体
*/
public void updateDownloadEntity(DownloadEntity downloadEntity) {
mDownloadDao.newOrUpdate(downloadEntity);
mDownloadDao.update(downloadEntity, false);
}
/**

View File

@ -83,10 +83,6 @@ object DownloadMessageHandler : InnerDownloadListener {
}
}
override fun onDownloadComplete(id: String?, elapsedTime: Long) {
// do nothing
}
/**
* 重定向中的回调
* @param id 下载 id
@ -218,6 +214,10 @@ object DownloadMessageHandler : InnerDownloadListener {
// do nothing
}
override fun onDownloadComplete(id: String?, elapsedTime: Long) {
// do nothing
}
fun registerListener(id: String, listener: DownloadListener) {
var listenerList = mListenerMap[id]
if (listenerList == null) {

View File

@ -37,6 +37,7 @@ import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.entity.PrivacyPolicyEntity
import com.gh.gamecenter.feature.utils.PlatformUtils
import com.gh.gamecenter.pkg.PkgHelper
import com.gh.vspace.VHelper
import com.halo.assistant.HaloApp
import com.lightgame.download.FileUtils
@ -386,6 +387,10 @@ class SplashScreenActivity : BaseActivity() {
checkAndPostUsageStats()
updateGameSubstituteRepository()
if (BuildConfig.CONFIG_ID.isNotEmpty()) {
PkgHelper.requestPkgConfig(BuildConfig.CONFIG_ID)
}
// 获取自动刷新的cd获取版本对应表
val time = mSharedPreferences!!.getString("refresh_time", null)
val format = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())

View File

@ -161,7 +161,8 @@ public class GameDownloadFragment extends BaseFragment implements View.OnClickLi
adapter.notifyItemChanged(adapter.getBase() + location + 1);
}
}
if (downloadEntity.getStatus() == DownloadStatus.neterror) {
if (downloadEntity.getStatus() == DownloadStatus.neterror
|| downloadEntity.getStatus() == DownloadStatus.diskisfull) {
adapter.notifyItemChanged(adapter.getBase());
}
} else {

View File

@ -231,6 +231,7 @@ class GameDownloadFragmentAdapter extends BaseRecyclerAdapter<ViewHolder> {
} else if (status.equals(DownloadStatus.pause)
|| status.equals(DownloadStatus.timeout)
|| status.equals(DownloadStatus.neterror)
|| status.equals(DownloadStatus.diskisfull)
|| status.equals(DownloadStatus.subscribe)) {
viewHolder.binding.dmItemTvStartorpause.setText(R.string.resume);
viewHolder.binding.dmItemTvStartorpause.setButtonStyle(DownloadButton.ButtonStyle.NORMAL);
@ -240,6 +241,8 @@ class GameDownloadFragmentAdapter extends BaseRecyclerAdapter<ViewHolder> {
|| status.equals(DownloadStatus.neterror)
|| status.equals(DownloadStatus.subscribe)) {
viewHolder.binding.dmItemTvSpeed.setText("等待WIFI");
} else if (status.equals(DownloadStatus.diskisfull)) {
viewHolder.binding.dmItemTvSpeed.setText("已暂停,磁盘空间不足");
} else {
viewHolder.binding.dmItemTvSpeed.setText("已暂停");
}

View File

@ -11,6 +11,7 @@ import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.entity.HomeDataEntity
import com.gh.gamecenter.entity.SubjectRecommendEntity
import com.gh.gamecenter.pkg.PkgHelper
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
@ -64,6 +65,20 @@ class HomeSearchToolWrapperViewModel(application: Application) : AndroidViewMode
if (homeTab.size == 0) {
homeTab.add(SubjectRecommendEntity(type = "home"))
}
// 从推广包配置信息里找是否需要默认选中一个 tab
PkgHelper.getPkgConfig(true)?.let { pkgLinkEntity ->
if (pkgLinkEntity.shouldStayAtHomePage) {
for ((index, tab) in homeTab.withIndex()) {
if (pkgLinkEntity.type == tab.type
&& (pkgLinkEntity.link == tab.link || tab.link == null)) {
defaultTabPosition = index
break
}
}
}
}
if (!isRefresh) {
tabs.postValue(homeTab)
}

View File

@ -29,6 +29,7 @@ import com.facebook.imagepipeline.image.ImageInfo;
import com.gh.common.constant.Config;
import com.gh.common.dialog.PrivacyPolicyDialogFragment;
import com.gh.common.dialog.ReserveDialog;
import com.gh.common.util.DirectUtils;
import com.gh.common.util.HomeBottomBarHelper;
import com.gh.common.util.IntegralLogHelper;
import com.gh.common.util.LogUtils;
@ -41,6 +42,7 @@ import com.gh.gamecenter.common.callback.BiCallback;
import com.gh.gamecenter.common.callback.OnDoubleTapListener;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.common.constant.EntranceConsts;
import com.gh.gamecenter.common.entity.PkgConfigEntity;
import com.gh.gamecenter.common.eventbus.EBReuse;
import com.gh.gamecenter.common.syncpage.SyncPageRepository;
import com.gh.gamecenter.common.tracker.TrackerLogger;
@ -67,6 +69,7 @@ import com.gh.gamecenter.login.entity.UserInfoEntity;
import com.gh.gamecenter.message.MessageUnreadRepository;
import com.gh.gamecenter.message.MessageUnreadViewModel;
import com.gh.gamecenter.personal.HaloPersonalFragment;
import com.gh.gamecenter.pkg.PkgHelper;
import com.gh.gamecenter.servers.GameServersPublishFragment;
import com.gh.gamecenter.servers.GameServersTestFragment;
import com.gh.gamecenter.subject.SubjectFragment;
@ -271,6 +274,43 @@ public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable implem
.getUnreadMessageTotalLiveData().observe(this, isShow -> ExtensionsKt.goneIf(mBinding.mainIvMessageHint, !isShow));
}
private void applyPkgConfig() {
PkgConfigEntity.PkgLinkEntity pkgLinkEntity = PkgHelper.INSTANCE.getPkgConfig(false);
if (pkgLinkEntity != null) {
String bottomTab = pkgLinkEntity.getHomeBottomTab();
if (!pkgLinkEntity.getShouldStayAtHomePage()) {
// 不停留在首页,执行跳转,标记已用
PkgHelper.INSTANCE.markConfigUsed();
DirectUtils.directToLinkPage(requireContext(), pkgLinkEntity, "推广包配置", "首页");
} else if (!"home".equals(bottomTab)) {
// 停留首页,但选中底部 tab 不是首页的,执行选中,标记已用
PkgHelper.INSTANCE.markConfigUsed();
// TODO 根据具体 tab 来作为跳转的具体位置,避免硬编码
int targetIndex = INDEX_HOME;
switch (bottomTab) {
case "game_lib":
targetIndex = INDEX_GAME;
break;
case "community":
targetIndex = INDEX_BBS;
break;
case "video":
targetIndex = INDEX_VIDEO;
break;
case "gh":
targetIndex = INDEX_PERSONAL;
break;
}
mViewPager.setCurrentItem(targetIndex);
onPageChanged(targetIndex);
changeColor(targetIndex);
}
}
}
public void getDialog() {
mViewModel.requestOpeningData();
mViewModel.getPrivacyPolicyDialog().observe(this, it -> {
@ -354,6 +394,8 @@ public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable implem
}
});
}
applyPkgConfig();
}
@Override

View File

@ -47,8 +47,8 @@ import com.gh.gamecenter.entity.AppEntity;
import com.gh.gamecenter.feature.entity.GameEntity;
import com.gh.gamecenter.common.retrofit.Response;
import com.gh.gamecenter.retrofit.RetrofitManager;
import com.gh.ndownload.NDataChanger;
import com.halo.assistant.HaloApp;
import com.lightgame.download.DataChanger;
import com.lightgame.download.DataWatcher;
import com.lightgame.download.DownloadEntity;
import com.lightgame.download.DownloadStatus;
@ -158,6 +158,8 @@ public class UpdateManager {
}
} else if (DownloadStatus.neterror.equals(downloadEntity.getStatus())) {
Utils.toast(mApplicationContext, "网络错误,请稍后重试");
} else if (DownloadStatus.diskisfull.equals(downloadEntity.getStatus())) {
Utils.toast(mApplicationContext, "磁盘已满,请清理后重试");
} else if (DownloadStatus.timeout.equals(downloadEntity.getStatus())) {
Utils.toast(mApplicationContext, "请求超时,请稍后重试");
} else if (DownloadStatus.notfound.equals(downloadEntity.getStatus())) {
@ -479,7 +481,7 @@ public class UpdateManager {
if (!isSilentUpdate
&& DownloadManager.getInstance().isTaskDownloading(appEntity.getUrl())) {
try {
DownloadEntity entity = DataChanger.INSTANCE.getDownloadEntries().get(appEntity.getUrl());
DownloadEntity entity = NDataChanger.INSTANCE.getDownloadEntries().get(appEntity.getUrl());
if (entity != null) {
ExtensionsKt.addMetaExtra(entity, Constants.EXTRA_DOWNLOAD_TYPE, "不再是静默更新");

View File

@ -0,0 +1,62 @@
package com.gh.gamecenter.pkg
import android.util.Base64
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.PkgConfigEntity
import com.gh.gamecenter.common.utils.toObject
import com.gh.gamecenter.core.provider.IPkgProvider
import com.gh.gamecenter.core.utils.SPUtils
import java.nio.charset.Charset
object PkgHelper {
private var mPkgConfigLink: PkgConfigEntity.PkgLinkEntity? = null
private const val SP_PKG_CONFIG_IS_USED = "pkg_config_is_used"
private val mPkgProvider by lazy {
ARouter.getInstance().build(RouteConsts.provider.pkg).navigation() as? IPkgProvider<PkgConfigEntity>
}
fun getPkgConfig(isFromHomeTopTab: Boolean): PkgConfigEntity.PkgLinkEntity? {
if (mPkgConfigLink == null
&& !SPUtils.getBoolean(SP_PKG_CONFIG_IS_USED, false)
&& BuildConfig.FIRST_LAUNCH.isNotEmpty()
) {
mPkgConfigLink =
String(Base64.decode(BuildConfig.FIRST_LAUNCH, Base64.DEFAULT), Charset.defaultCharset()).toObject()
return if (isFromHomeTopTab) {
if (mPkgConfigLink?.shouldStayAtHomePage != true && mPkgConfigLink?.homeBottomTab == "home") {
markConfigUsed()
mPkgConfigLink
} else {
null
}
} else {
mPkgConfigLink
}
}
return mPkgConfigLink
}
fun markConfigUsed() {
mPkgConfigLink = null
SPUtils.setBoolean(SP_PKG_CONFIG_IS_USED, true)
}
/**
* 获取特殊包配置
*/
fun requestPkgConfig(configId: String) {
if (SPUtils.getBoolean(SP_PKG_CONFIG_IS_USED, false)) return
mPkgProvider?.requestPkgConfig(configId) {
mPkgConfigLink = it.data?.link
}
}
}

View File

@ -0,0 +1,37 @@
package com.gh.ndownload
import com.gh.gamecenter.core.runOnUiThread
import com.lightgame.download.DownloadEntity
import com.lightgame.download.DownloadTask
import java.util.*
/**
* 下载进度变化通知
*/
object NDataChanger : Observable() {
val downloadingTasks: MutableMap<String, DownloadTask> = Collections.synchronizedMap(HashMap())//当前正在下载的任务队列
val downloadEntries: MutableMap<String, DownloadEntity> = Collections.synchronizedMap(HashMap())//包含所有下载任务的任务队列
@Synchronized
fun notifyDataChanged(entry: DownloadEntity?) {
runOnUiThread {
if (entry != null) {
if (downloadEntries[entry.url] != null) {
downloadEntries[entry.url] = entry
}
setChanged()
notifyObservers(entry)
}
}
}
@Synchronized
fun pauseDownloadingTask(url: String) {
runOnUiThread {
val downloadTask = downloadingTasks.remove(url)
downloadTask?.pause()
}
}
}

View File

@ -0,0 +1,409 @@
package com.gh.ndownload
import android.text.TextUtils
import com.gh.download.DownloadDataHelper
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.utils.NetworkUtils
import com.gh.gamecenter.common.utils.addMetaExtra
import com.gh.gamecenter.common.utils.deepClone
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.ToastUtils
import com.halo.assistant.HaloApp
import com.lg.download.DownloadError
import com.lg.download.listener.InnerDownloadListener
import com.lg.ndownload.*
import com.lightgame.download.DownloadDao
import com.lightgame.download.DownloadEntity
import com.lightgame.download.FileUtils
import com.lightgame.utils.Utils
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.math.RoundingMode
import java.net.URL
import java.net.URLConnection
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.*
import java.util.concurrent.ConcurrentHashMap
/**
* 是串联丑陋的下载模块与新下载模块之间的桥梁
*/
object NDownloadBridge : InnerDownloadListener, IErrorRetryHandler {
private const val TAG = "NDownloadBridge"
private const val DEFAULT_DOWNLOAD_THREAD_SIZE = 2
private const val NO_NETWORK_MAX_RETRY_COUNT = 5
private var mRetryTimer: Timer? = null // 用于错误重试的 timer
private val mRetryCountMap: ConcurrentHashMap<String, Int> by lazy { ConcurrentHashMap() }
private val mStartUpTimeMap: ConcurrentHashMap<String, Long> by lazy { ConcurrentHashMap() }
init {
DownloadQueue.getInstance().setMaxDownloadingTask(3)
}
override fun onError(id: String, error: DownloadError?, exception: Exception?) {
Utils.log(TAG, "id $id encounter $error -> ${exception?.message}")
resetRetryCount(id)
getDownloadEntity(id)?.let { downloadEntity ->
when (error) {
DownloadError.EMPTY_URL,
DownloadError.HTTP_NOT_FOUND,
DownloadError.CONTENT_LENGTH_IS_ZERO -> {
doCancel(downloadEntity)
downloadEntity.status = com.lightgame.download.DownloadStatus.notfound
}
DownloadError.HIJACKED -> {
doCancel(downloadEntity)
downloadEntity.status = com.lightgame.download.DownloadStatus.hijack
}
DownloadError.DOWNLOAD_SIZE_NOT_MATCH_CONTENT_LENGTH -> {
doCancel(downloadEntity)
downloadEntity.status = com.lightgame.download.DownloadStatus.overflow
DownloadManager.getInstance().onTaskError(downloadEntity)
}
DownloadError.PREVIOUS_DOWNLOAD_IS_DELETED,
DownloadError.FILE_CORRUPTED -> {
doCancel(downloadEntity)
ToastUtils.toast("文件已损坏,请重新下载")
downloadEntity.status = com.lightgame.download.DownloadStatus.cancel
}
DownloadError.PRE_DOWNLOAD_INTERNAL_ERROR,
DownloadError.DOWNLOAD_CONNECTION_ERROR,
DownloadError.PRE_DOWNLOAD_CONNECTION_ERROR -> {
downloadEntity.status = com.lightgame.download.DownloadStatus.neterror
DownloadManager.getInstance().onTaskError(downloadEntity)
NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity, true)
DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
}
DownloadError.DISK_IS_FULL -> {
downloadEntity.status = com.lightgame.download.DownloadStatus.diskisfull
NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity, true)
DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
}
DownloadError.RESOURCE_IS_FORBIDDEN -> {
val errorString = exception?.message
if (!errorString.isNullOrEmpty()) {
handleForbiddenException(downloadEntity, errorString)
}
doCancel(downloadEntity)
}
DownloadError.PERSONAL_NO_CERTIFICATION -> {
doCancel(downloadEntity)
downloadEntity.status = com.lightgame.download.DownloadStatus.uncertificated
}
DownloadError.PERSONAL_TEENAGER -> {
doCancel(downloadEntity)
downloadEntity.status = com.lightgame.download.DownloadStatus.unqualified
}
else -> {
// do nothing
}
}
NDataChanger.notifyDataChanged(downloadEntity)
}
}
override fun onProgress(id: String?, progress: Float) {
getDownloadEntity(id)?.let { downloadEntity ->
var percent = (progress * 100.0)
val df = DecimalFormat("#.#", DecimalFormatSymbols(Locale.CHINA))
df.roundingMode = RoundingMode.FLOOR
percent = df.format(percent).toDouble()
downloadEntity.progress = (downloadEntity.size * progress).toLong()
downloadEntity.percent = percent
// 有可能还处于 redirected 状态,这里检查并置换状态
if (com.lightgame.download.DownloadStatus.downloading != downloadEntity.status) {
downloadEntity.status = com.lightgame.download.DownloadStatus.downloading
}
DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
NDataChanger.notifyDataChanged(downloadEntity)
}
}
override fun onProgressWithoutThrottle(id: String?, progress: Float) {
if (id != null && (mStartUpTimeMap[id] ?: 0) > 0) {
val startupTime: Long = System.currentTimeMillis() - (mStartUpTimeMap[id] ?: 0)
getDownloadEntity(id)?.let { downloadEntity ->
downloadEntity.meta.put(DownloadEntity.DOWNLOAD_STARTUP_TIME_KEY, startupTime.toString())
}
mStartUpTimeMap[id] = 0
}
}
override fun onSizeReceived(id: String?, fileSize: Long) {
Utils.log(TAG, "id $id onSizeReceived $fileSize")
getDownloadEntity(id)?.let { downloadEntity ->
downloadEntity.size = fileSize
DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
}
}
override fun onReadyToDownload(id: String?, actualThreadSize: Int) {
Utils.log(TAG, "id $id onReadyToDownload, actualThreadSize is -> $actualThreadSize")
getDownloadEntity(id)?.let { downloadEntity ->
downloadEntity.meta[DownloadDataHelper.DOWNLOAD_THREAD_SIZE] = actualThreadSize.toString()
DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
}
}
override fun onStatusChanged(id: String, status: com.lg.download.DownloadStatus?) {
Utils.log(TAG, "id $id onStatusChanged $status")
resetRetryCount(id)
getDownloadEntity(id)?.let { originDownloadEntity ->
// 状态更新使用全新的拷贝对象,避免因为不同线程对数据的变更造成通知错乱
val downloadEntity = originDownloadEntity.deepClone<DownloadEntity>() ?: return@let
when (status) {
com.lg.download.DownloadStatus.CANCELLED -> {
doCancel(downloadEntity)
DownloadManager.getInstance().onTaskPaused(downloadEntity)
NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity, true)
}
com.lg.download.DownloadStatus.PAUSED -> {
downloadEntity.speed = 0
downloadEntity.status = com.lightgame.download.DownloadStatus.pause
NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity, false)
}
com.lg.download.DownloadStatus.WAITINGWIFI -> {
downloadEntity.status = com.lightgame.download.DownloadStatus.waiting
}
com.lg.download.DownloadStatus.DOWNLOADING -> {
downloadEntity.status = com.lightgame.download.DownloadStatus.downloading
DownloadManager.getInstance().onTaskAdded(downloadEntity)
}
com.lg.download.DownloadStatus.QUEUED -> {
downloadEntity.status = com.lightgame.download.DownloadStatus.add
}
else -> {
Utils.log(TAG, "status not supported yet -> $status")
}
}
DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
NDataChanger.notifyDataChanged(downloadEntity)
}
}
override fun onDownloadComplete(id: String?, elapsedTime: Long) {
getDownloadEntity(id)?.let { originDownloadEntity ->
val downloadEntity = originDownloadEntity.deepClone<DownloadEntity>() ?: return@let
downloadEntity.speed = 0
downloadEntity.progress = downloadEntity.size
downloadEntity.percent = 100.0
downloadEntity.end = System.currentTimeMillis()
downloadEntity.status = com.lightgame.download.DownloadStatus.done
downloadEntity.addMetaExtra(com.lightgame.download.DownloadConfig.KEY_DOWNLOAD_ELAPSED_TIME, elapsedTime.toString())
DownloadManager.getInstance().onTaskDone(downloadEntity)
NDownloadService.getService()?.removeFromDataChangerAndResumeWaitingTask(downloadEntity, true)
DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
NDataChanger.notifyDataChanged(downloadEntity)
}
}
override fun onSpeedChanged(id: String, speed: Float) {
getDownloadEntity(id)?.let { downloadEntity ->
downloadEntity.speed = speed.toLong()
NDataChanger.notifyDataChanged(downloadEntity)
}
}
override fun onRedirectingUrl(id: String, connection: URLConnection?, config: DownloadConfig?) {
getDownloadEntity(id)?.let { downloadEntity ->
if (!TextUtils.isEmpty(downloadEntity.eTag)) {
val eTag: String = getRealETag(connection?.getHeaderField("Halo-ETag") ?: "")
downloadEntity.haloEtag = eTag
DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
}
NDataChanger.notifyDataChanged(downloadEntity)
}
}
override fun onRedirectedUrl(id: String, connection: URLConnection?, redirectedUrl: String?) {
getDownloadEntity(id)?.let { downloadEntity ->
downloadEntity.finalRedirectedUrl = redirectedUrl
val url = URL(redirectedUrl)
if (url.host != null && url.path != null) {
downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] = url.host
downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] = url.path
DownloadDao.getInstance(HaloApp.getInstance()).update(downloadEntity, false)
}
downloadEntity.status = com.lightgame.download.DownloadStatus.redirected
// downloadEntity 的 etag 的值用来判断是否需要进行劫持校验,值不参与到最后判断是否被劫持的校验中去
val shouldCompareETag = !TextUtils.isEmpty(downloadEntity.eTag)
val currentETag = connection?.getHeaderField("ETag")
if (shouldCompareETag
&& !TextUtils.isEmpty(currentETag)
&& currentETag != downloadEntity.haloEtag
) {
// 链接被劫持,抛出异常
downloadEntity.status = com.lightgame.download.DownloadStatus.hijack
}
NDataChanger.notifyDataChanged(downloadEntity)
}
}
override fun getRetryOption(id: String, e: Exception?): Int {
val downloadEntity =
DownloadDao.getInstance(HaloApp.getInstance()).getSnapshot(id) ?: return IErrorRetryHandler.RETRY_DIRECTLY
return if (e is IOException && FileUtils.getFreeSpaceByPath(downloadEntity.path) < 10F) {
IErrorRetryHandler.REFUSE_TO_RETRY
} else if (NetworkUtils.isWifiConnected(HaloApp.getInstance()) || isDownloadViaTrafficAllowed(downloadEntity)) {
IErrorRetryHandler.RETRY_DIRECTLY
} else {
IErrorRetryHandler.RETRY_MANUALLY
}
}
override fun retryManually(id: String, retryRunnable: Runnable, pauseRunnable: Runnable) {
if (mRetryTimer == null) {
mRetryTimer = Timer()
}
Utils.log(TAG, "about to retry download manually")
mRetryTimer?.schedule(object : TimerTask() {
override fun run() {
if (NetworkUtils.isWifiConnected(HaloApp.getInstance())) {
resetRetryCount(id)
retryRunnable.run()
} else {
mRetryCountMap[id] = (mRetryCountMap[id] ?: 0) + 1
if (mRetryCountMap[id]!! <= NO_NETWORK_MAX_RETRY_COUNT) {
retryManually(id, retryRunnable, pauseRunnable)
} else {
resetRetryCount(id)
pauseRunnable.run()
}
}
}
}, GlobalDownloadConfig.ERROR_RETRY_INTERVAL)
}
private fun isDownloadViaTrafficAllowed(downloadEntity: DownloadEntity?): Boolean {
val mNetworkMobileStatus = "2G3G4G5G"
val networkStatus: String? = downloadEntity?.meta?.get(DownloadEntity.NETWORK_STATUS_KEY)
Utils.log("download network status$networkStatus")
return if (networkStatus == null) false else mNetworkMobileStatus.contains(networkStatus)
}
fun download(downloadEntity: DownloadEntity) {
val config = DownloadConfigBuilder()
.setUniqueId(downloadEntity.url)
.setFileName("")
.setUrl(downloadEntity.url)
.setPathToStore(downloadEntity.path)
.setHttpClient(NHttpClient())
.setDownloadThreadSize(DEFAULT_DOWNLOAD_THREAD_SIZE)
.setDownloadListener(NDownloadBridge)
.setErrorRetryHandler(this)
.setDownloadExecutor(AppExecutor.downloadExecutor)
.setMeta(downloadEntity.meta as HashMap<String, String>?)
.setErrorRetryHandler(this)
.build()
mStartUpTimeMap[downloadEntity.url] = System.currentTimeMillis()
DownloadQueue.getInstance().submitNewTask(config)
}
fun resume(id: String?) {
id?.let {
DownloadQueue.getInstance().resume(id)
getDownloadEntity(id)?.let { downloadEntity ->
mStartUpTimeMap[downloadEntity.url] = System.currentTimeMillis()
}
}
}
fun pause(id: String?) {
id?.let {
DownloadQueue.getInstance().pause(id)
}
}
fun cancel(id: String?) {
id?.let {
NDataChanger.downloadEntries.remove(id)
DownloadQueue.getInstance().cancel(id)
}
}
private fun doCancel(downloadEntity: DownloadEntity) {
DownloadManager.getInstance().cancel(downloadEntity.url)
}
private fun getRealETag(originETag: String): String {
var realETag = originETag
if (!TextUtils.isEmpty(realETag) && realETag.startsWith("\"") && realETag.endsWith("\"")) {
realETag = realETag.substring(1, realETag.length - 1)
}
return realETag
}
private fun handleForbiddenException(downloadEntity: DownloadEntity, errorString: String) {
try {
val resultObject = JSONObject(errorString)
if ("403001" == resultObject.getString("code")) {
// 未实名
if (resultObject.has("force") && !resultObject.getBoolean("force")) {
downloadEntity.meta["force_real_name"] = "false"
}
downloadEntity.status = com.lightgame.download.DownloadStatus.uncertificated
} else if ("403002" == resultObject.getString("code")) {
// 未成年
if (resultObject.has("force") && !resultObject.getBoolean("force")) {
downloadEntity.meta["force_real_name"] = "false"
}
downloadEntity.status = com.lightgame.download.DownloadStatus.unqualified
} else if ("403003" == resultObject.getString("code")) {
// 该游戏未接入防沉迷系统禁止下载
downloadEntity.status = com.lightgame.download.DownloadStatus.unavailable
} else if ("403004" == resultObject.getString("code")) {
// 后台禁止该设备下载
downloadEntity.status = com.lightgame.download.DownloadStatus.banned
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
private fun resetRetryCount(id: String) {
mRetryCountMap[id] = 0
}
private fun getDownloadEntity(id: String?): DownloadEntity? {
return DownloadManager.getInstance().getDownloadEntitySnapshot(id)
}
}

View File

@ -0,0 +1,276 @@
package com.gh.ndownload;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import com.gh.download.DownloadManager;
import com.lightgame.BuildConfig;
import com.lightgame.download.DownloadConfig;
import com.lightgame.download.DownloadDao;
import com.lightgame.download.DownloadEntity;
import com.lightgame.download.DownloadStatus;
import com.lightgame.download.DownloadTask;
import com.lightgame.download.ForegroundNotificationManager;
import com.lightgame.utils.Utils;
import java.util.Random;
public class NDownloadService extends Service {
public static final String KEY_SERVICE_ACTION = "service_action";
public static final String START_FOREGROUND = "start_foreground";
private static final String SERVICE_CHANNEL_ID = "Halo_Download";
private static NDownloadService sService = null;
private ForegroundNotificationManager mForegroundNotificationManager;
@Nullable
public static NDownloadService getService() {
return sService;
}
@Override
public void onCreate() {
super.onCreate();
sService = this;
mForegroundNotificationManager = new ForegroundNotificationManager(this, this.getApplication());
Utils.log(NDownloadService.class.getSimpleName(), "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Utils.log(NDownloadService.class.getSimpleName(), "onStartCommand");
int randomNotificationId = getNextIntExcludeZero();
if (intent != null && intent.getExtras() != null) {
String serviceAction = intent.getStringExtra(KEY_SERVICE_ACTION);
if (START_FOREGROUND.equals(serviceAction)) {
startForegroundIfNeeded(randomNotificationId);
}
String statusString = intent.getStringExtra(DownloadConfig.KEY_DOWNLOAD_ACTION);
if (!TextUtils.isEmpty(statusString)) {
// 看不懂的操作
DownloadStatus status = DownloadStatus.valueOf(statusString);
DownloadEntity entry = (DownloadEntity) intent.getSerializableExtra(DownloadConfig.KEY_DOWNLOAD_ENTRY);
DownloadEntity downloadEntity = DownloadDao.getInstance(this).get(entry.getUrl());
if (downloadEntity == null) {
// 设置开始下载时间(排序用到)
entry.setStart(System.currentTimeMillis());
}
// 置换最新状态
entry.setStatus(status);
// 删除下载错误信息
entry.setError("");
DownloadDao.getInstance(this).removeErrorMessage(entry.getUrl());
if (BuildConfig.DEBUG) {
Utils.log(NDownloadService.class.getSimpleName(), entry.getPackageName() + "==>" + entry.getStatus().getStatus());
}
switch (status) {
case subscribe:
subscribeDownload(entry);
break;
case add:
addDownload(entry);
break;
case pause:
case timeout:
case neterror:
case diskisfull:
pauseDownload(entry);
break;
case resume:
resumeDownload(entry);
break;
case cancel:
cancelDownload(entry);
break;
default:
if (BuildConfig.DEBUG) {
Utils.log(NDownloadService.class.getSimpleName(), "无法处理的下载状态:" + entry.getStatus().getStatus());
}
break;
}
} else {
if (BuildConfig.DEBUG) {
Utils.log(NDownloadService.class.getSimpleName(), "启动了一个空的下载服务");
}
}
} else {
// 无参时也默认以前台启动
startForegroundIfNeeded(randomNotificationId);
}
stopForegroundIfNeeded(randomNotificationId);
// 不需要 intent 为空的重建
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
synchronized void subscribeDownload(DownloadEntity entry) {
NDataChanger.INSTANCE.getDownloadEntries().put(entry.getUrl(), entry);
DownloadDao.getInstance(this).newOrUpdate(entry);
NDataChanger.INSTANCE.notifyDataChanged(entry);
DownloadManager.getInstance().onTaskAdded(entry);
}
synchronized void addDownload(DownloadEntity entry) {
if (NDataChanger.INSTANCE.getDownloadingTasks().size() >= DownloadConfig.MAX_DOWNLOADING_SIZE) {
// 1.改任务队列的状态
entry.setStatus(DownloadStatus.waiting);
NDataChanger.INSTANCE.getDownloadEntries().put(entry.getUrl(), entry);
// 2.改数据库状态
DownloadDao.getInstance(this).newOrUpdate(entry);
// 3.通知更新
NDataChanger.INSTANCE.notifyDataChanged(entry);
} else {
startDownload(entry);
}
DownloadManager.getInstance().onTaskAdded(entry);
}
synchronized void pauseDownload(DownloadEntity entry) {
if (entry != null) {
entry.setStatus(DownloadStatus.pause);
// 1.改任务队列的状态
NDownloadBridge.INSTANCE.pause(entry.getUrl());
NDataChanger.INSTANCE.pauseDownloadingTask(entry.getUrl());
// 2.改数据库状态
DownloadDao.getInstance(this).update(entry, false);
}
}
synchronized void resumeDownload(DownloadEntity entry) {
addDownload(entry);
}
synchronized void cancelDownload(DownloadEntity downloadEntity) {
DownloadTask task = NDataChanger.INSTANCE.getDownloadingTasks()
.get(downloadEntity.getUrl());
if (task != null) {
NDownloadBridge.INSTANCE.cancel(downloadEntity.getUrl());
// 改任务队列的状态
NDataChanger.INSTANCE.getDownloadingTasks().remove(downloadEntity.getUrl());
NDataChanger.INSTANCE.notifyDataChanged(downloadEntity);
}
NDataChanger.INSTANCE.getDownloadEntries().remove(downloadEntity.getUrl());
NDataChanger.INSTANCE.notifyDataChanged(downloadEntity);
DownloadManager.getInstance().onTaskCancelled(downloadEntity);
}
synchronized void startDownload(DownloadEntity entry) {
DownloadTask task = NDataChanger.INSTANCE.getDownloadingTasks().get(entry.getUrl());
if (task == null) {
// 刷新初始状态
NDataChanger.INSTANCE.notifyDataChanged(entry);
entry.getMeta().put(DownloadEntity.DOWNLOAD_STARTUP_STATUS_KEY, entry.getStatus().getStatus());
// 1.改任务队列的状态
entry.setStatus(DownloadStatus.downloading);
NDataChanger.INSTANCE.getDownloadEntries().put(entry.getUrl(), entry);
NDataChanger.INSTANCE.getDownloadingTasks().put(entry.getUrl(), new DownloadTask(null, entry, this));
// 2.改数据库状态
DownloadDao.getInstance(this).newOrUpdate(entry);
// 3.执行下载
NDownloadBridge.INSTANCE.download(entry);
// 4.通知更新
NDataChanger.INSTANCE.notifyDataChanged(entry);
} else {
Utils.log("Fuck this, we received multiple download request for the same url");
}
}
/**
* 从 DataChanger 里移除掉数据,并找到下一个处于 waiting 状态的任务继续
* @param downloadEntity 需要移除的数据
* @param removeCompletely 是否完全移除false 时只从下载中 hashMap 移除
*/
public synchronized void removeFromDataChangerAndResumeWaitingTask(DownloadEntity downloadEntity, boolean removeCompletely) {
NDataChanger.INSTANCE.getDownloadingTasks().remove(downloadEntity.getUrl());
if (removeCompletely) {
NDataChanger.INSTANCE.getDownloadEntries().remove(downloadEntity.getUrl());
}
for (DownloadEntity entry : NDataChanger.INSTANCE.getDownloadEntries().values()) {
if (DownloadStatus.waiting.equals(entry.getStatus())) {
// 刷新状态(从等待中到开始下载,状态应该变为继续下载)
entry.setStatus(DownloadStatus.resume);
startDownload(entry);
if (BuildConfig.DEBUG) {
Utils.log(NDownloadService.class.getSimpleName(), entry.getPackageName() + "==>" + entry.getStatus().getStatus());
}
return;
}
}
}
private void startForegroundIfNeeded(int notificationId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(SERVICE_CHANNEL_ID, "Halo Download", NotificationManager.IMPORTANCE_LOW);
NotificationManager manager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);
Notification notification = new NotificationCompat.Builder(this, SERVICE_CHANNEL_ID)
.setSmallIcon(com.lightgame.R.drawable.ic_download_notification)
.build();
if (mForegroundNotificationManager != null) {
mForegroundNotificationManager.notify(notificationId, notification);
}
}
}
private void stopForegroundIfNeeded(int notificationId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (mForegroundNotificationManager != null) {
mForegroundNotificationManager.cancel(notificationId);
}
}
}
/**
* 获取 0 以外的随机 int 数值
*/
private int getNextIntExcludeZero() {
int randomNumber = new Random().nextInt();
if (randomNumber != 0) {
return randomNumber;
} else {
return getNextIntExcludeZero();
}
}
}

View File

@ -0,0 +1,31 @@
package com.gh.ndownload
import android.text.TextUtils
import com.lg.download.httpclient.DefaultHttpClient
import com.lightgame.download.HttpDnsManager
class NHttpClient : DefaultHttpClient() {
override fun addHeader(url: String?, meta: HashMap<String, String>?) {
super.addHeader(url, meta)
mConnection.setRequestProperty(HttpDnsManager.APP_VERSION, HttpDnsManager.metaMap[HttpDnsManager.APP_VERSION])
mConnection.setRequestProperty(HttpDnsManager.CHANNEL, HttpDnsManager.metaMap[HttpDnsManager.CHANNEL])
mConnection.setRequestProperty(HttpDnsManager.GID, HttpDnsManager.metaMap[HttpDnsManager.GID])
mConnection.setRequestProperty(HttpDnsManager.USER_ID, HttpDnsManager.metaMap[HttpDnsManager.USER_ID])
mConnection.setRequestProperty(HttpDnsManager.IMEI, HttpDnsManager.metaMap[HttpDnsManager.IMEI])
mConnection.setRequestProperty(HttpDnsManager.OAID, HttpDnsManager.metaMap[HttpDnsManager.OAID])
mConnection.setRequestProperty(HttpDnsManager.TOKEN, HttpDnsManager.metaMap[HttpDnsManager.TOKEN])
mConnection.setRequestProperty(HttpDnsManager.IS_OVERWRITE, HttpDnsManager.metaMap[HttpDnsManager.IS_OVERWRITE])
val isEmulator = meta?.get("is_emulator")
val isForcedRealName = meta?.get("force_real_name")
if (!TextUtils.isEmpty(isForcedRealName) && "false" == isForcedRealName) {
mConnection.setRequestProperty("force", isForcedRealName)
}
if (!TextUtils.isEmpty(isEmulator)) {
mConnection.setRequestProperty("simulator", isEmulator)
}
}
}

View File

@ -188,6 +188,7 @@ class HomeRecentVGameAdapter(context: Context) : DiffUtilAdapter<VGameItemData>(
DownloadStatus.uncertificated,
DownloadStatus.unqualified,
DownloadStatus.notfound,
DownloadStatus.diskisfull,
DownloadStatus.unavailable,
DownloadStatus.overflow -> {
binding.root.setOnClickListener {

View File

@ -101,7 +101,7 @@ object VArchiveHelper {
.setHttpClient(DefaultHttpClient())
.setDownloadThreadSize(2)
.setDownloadListener(DownloadMessageHandler)
.setDownloadExecutor(AppExecutor.ioExecutor)
.setDownloadExecutor(AppExecutor.downloadExecutor)
.build()
)
}

View File

@ -387,6 +387,7 @@ class VDownloadManagerAdapter(
DownloadStatus.timeout,
DownloadStatus.neterror,
DownloadStatus.subscribe,
DownloadStatus.diskisfull,
DownloadStatus.overflow -> {
btnText = context.getString(R.string.resume)
downloadDes.visibility = View.VISIBLE

View File

@ -55,6 +55,7 @@ data class VGameItemData(
DownloadStatus.cancel,
DownloadStatus.timeout,
DownloadStatus.neterror,
DownloadStatus.diskisfull,
DownloadStatus.hijack,
DownloadStatus.uncertificated,
DownloadStatus.unqualified,
@ -62,6 +63,8 @@ data class VGameItemData(
DownloadStatus.unavailable,
DownloadStatus.overflow -> {
controlText = "重试"
shouldShowMask = true
shouldShowControlTv = true
}
else -> {
// do nothing

View File

@ -176,6 +176,7 @@ class VSpaceDialogFragment : BaseDraggableDialogFragment() {
overflow,
timeout,
neterror,
diskisfull,
waiting,
subscribe -> {
downloadBtn.setText(R.string.waiting)

View File

@ -48,6 +48,7 @@ class VSpaceLoadingFragment : BaseFragment<Any>() {
hijack,
notfound,
neterror,
diskisfull,
overflow -> requireActivity().finish()
unqualified -> {
ToastUtils.toast("暂不支持未成年人下载")

View File

@ -69,6 +69,7 @@ import com.gh.gamecenter.receiver.NetworkStateReceiver;
import com.gh.vspace.VHelper;
import com.github.piasy.biv.BigImageViewer;
import com.github.piasy.biv.loader.fresco.FrescoImageLoader;
import com.lg.ndownload.DownloadDbManager;
import com.lightgame.utils.Utils;
import com.llew.huawei.verifier.LoadedApkHuaWei;
import com.shuyu.gsyvideoplayer.cache.CacheFactory;
@ -218,6 +219,8 @@ public class HaloApp extends MultiDexApplication {
// 异步初始化 SP
SPUtils.getString("");
DownloadDbManager.init(this);
});
RxJavaPlugins.setIoSchedulerHandler(scheduler -> AppExecutor.INSTANCE.getCachedScheduler());

View File

@ -26,7 +26,7 @@
android:layout_height="wrap_content"
android:background="@color/background"
android:gravity="center"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"

View File

@ -29,7 +29,7 @@
android:background="@color/background"
android:gravity="center"
app:elevation="0dp"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<include
android:id="@+id/reuseSearchBar"

View File

@ -48,7 +48,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/head_container"

View File

@ -18,7 +18,7 @@
android:background="@color/background_white"
android:fitsSystemWindows="true"
app:elevation="0dp"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<com.gh.gamecenter.common.view.ScrimAwareCollapsingToolbarLayout
android:id="@+id/collapsingToolbar"

View File

@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:background="@color/white"
android:gravity="center"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<LinearLayout
android:layout_width="match_parent"

View File

@ -20,7 +20,7 @@
android:background="@color/background"
android:fitsSystemWindows="true"
android:gravity="center"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<com.gh.gamecenter.common.view.ScrimAwareCollapsingToolbarLayout
android:id="@+id/collapsingToolbar"

View File

@ -26,7 +26,7 @@
android:background="@color/background_white"
android:fitsSystemWindows="true"
android:gravity="center"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"

View File

@ -25,7 +25,7 @@
android:layout_height="wrap_content"
android:background="@null"
app:elevation="0dp"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/recordForumsContainer"

View File

@ -20,7 +20,7 @@
android:fitsSystemWindows="true"
android:gravity="center"
app:elevation="0dp"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@id/collapsingToolbar"

View File

@ -19,7 +19,7 @@
android:background="@color/background_white"
android:gravity="center"
app:elevation="0dp"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@id/collapsingToolbar"

View File

@ -18,7 +18,7 @@
android:background="@color/background_white"
android:fitsSystemWindows="true"
app:elevation="0dp"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<com.gh.gamecenter.common.view.ScrimAwareCollapsingToolbarLayout
android:id="@+id/collapsingToolbar"

View File

@ -21,7 +21,7 @@
android:layout_height="wrap_content"
android:background="@null"
app:elevation="0dp"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<com.gh.common.view.CollapsingMotionLayout
android:id="@+id/motionLayout"

View File

@ -15,7 +15,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"

View File

@ -13,7 +13,7 @@
android:layout_marginTop="8dp"
android:background="@color/background_white"
android:gravity="center"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"

View File

@ -22,7 +22,7 @@
android:background="@color/background"
android:gravity="center"
app:elevation="0dp"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<include
android:id="@+id/search_bar"

View File

@ -24,7 +24,7 @@
android:layout_height="wrap_content"
android:background="@color/transparent"
app:elevation="0dp"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbar"

View File

@ -11,7 +11,7 @@
android:layout_height="wrap_content"
android:background="@color/background_white"
android:gravity="center"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior">
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/subject_type_list"

View File

@ -14,7 +14,7 @@
android:background="@color/background_white"
android:fitsSystemWindows="true"
android:gravity="center"
app:layout_behavior=".common.view.FixAppBarLayoutBehavior"
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior"
tools:visibility="visible">
<com.google.android.material.appbar.CollapsingToolbarLayout

View File

@ -0,0 +1,48 @@
package com.gh.gamecenter
import android.app.Activity
import android.content.Context
import android.text.TextUtils
import com.bytedance.applog.AppLog
import com.bytedance.applog.GameReportHelper
import com.bytedance.applog.InitConfig
import com.bytedance.applog.util.UriConstants
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
/**
* 5.3.0 版本的 SDK
**/
object TeaHelper {
@JvmStatic
fun init(context: Context, activity: Activity, channel: String) {
val config = InitConfig("163824", channel)
config.setUriConfig(UriConstants.DEFAULT)
config.appName = "guanghuan1"
config.setEnablePlay(true)
config.setAutoStart(true)
AppLog.init(context, config)
AppLog.setOaidObserver {
if (!TextUtils.isEmpty(it.id)) {
HaloApp.getInstance().oaid = it.id
MetaUtil.refreshMeta()
}
Utils.log("oaid is $it.id")
}
// gameReportHelper ?!
GameReportHelper.onEventRegister("wechat", true)
GameReportHelper.onEventPurchase("gift", "flower", "008", 1, "wechat", "¥", true, 1)
Utils.log("init TeaHelper")
}
@JvmStatic
fun onEvent(type: String) {
AppLog.onEventV3(type)
}
}

View File

@ -3,6 +3,7 @@ package com.gh.gamecenter.provider
import android.app.Activity
import android.app.Application
import android.text.TextUtils
import com.gh.gamecenter.BuildConfig
import com.bytedance.hume.readapk.HumeSDK
import com.gh.gamecenter.TeaHelper
import com.gh.gamecenter.core.provider.IFlavorProvider
@ -16,7 +17,7 @@ class FlavorProviderImp : IFlavorProvider {
override fun init(application: Application, activity: Activity) {
mIsLuckyEnoughToUseTea = amILucky()
mIsLuckyEnoughToUseTea = amILucky(BuildConfig.ACTIVATE_REPORTING_RATIO)
if (mIsLuckyEnoughToUseTea) {
TeaHelper.init(application, activity, getChannelStr(application))

View File

@ -76,6 +76,8 @@ object RouteConsts {
const val vpn = "/vpn/vpn"
const val pkg = "/pkg/pkg"
const val sensors = "/sensors/sensors"
}

View File

@ -0,0 +1,19 @@
package com.gh.gamecenter.common.entity
import com.google.gson.annotations.SerializedName
class PkgConfigEntity(
@SerializedName("code") var code: Int? = null,
@SerializedName("data") var data: Data? = Data()
) {
data class Data(
@SerializedName("first_lanuch_jump") var link: PkgLinkEntity? = PkgLinkEntity(),
)
class PkgLinkEntity(
@SerializedName("home_index")
var shouldStayAtHomePage: Boolean = false,
@SerializedName("bottom_index")
var homeBottomTab: String = ""
) : LinkEntity()
}

View File

@ -206,24 +206,25 @@ object MetaUtil {
?: return "unknown"
return when (activeNetwork.type) {
ConnectivityManager.TYPE_WIFI -> "Wifi"
ConnectivityManager.TYPE_WIMAX -> "WifiMax"
ConnectivityManager.TYPE_WIFI -> "WIFI"
ConnectivityManager.TYPE_WIMAX -> "WIMAX"
ConnectivityManager.TYPE_MOBILE -> {
val telephonyManager = application.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
if (telephonyManager.simState != TelephonyManager.SIM_STATE_READY) return "unknown"
when (telephonyManager.networkType) {
// Unknown
TelephonyManager.NETWORK_TYPE_UNKNOWN -> "Cellular - Unknown"
TelephonyManager.NETWORK_TYPE_UNKNOWN -> "unknown"
// Cellular Data2G
TelephonyManager.NETWORK_TYPE_EDGE, TelephonyManager.NETWORK_TYPE_GPRS, TelephonyManager.NETWORK_TYPE_CDMA,
TelephonyManager.NETWORK_TYPE_IDEN, TelephonyManager.NETWORK_TYPE_1xRTT -> "Cellular - 2G"
TelephonyManager.NETWORK_TYPE_IDEN, TelephonyManager.NETWORK_TYPE_1xRTT -> "2G"
// Cellular Data3G
TelephonyManager.NETWORK_TYPE_UMTS, TelephonyManager.NETWORK_TYPE_HSDPA, TelephonyManager.NETWORK_TYPE_HSPA,
TelephonyManager.NETWORK_TYPE_HSPAP, TelephonyManager.NETWORK_TYPE_HSUPA, TelephonyManager.NETWORK_TYPE_EVDO_0,
TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyManager.NETWORK_TYPE_EVDO_B -> "Cellular - 3G"
TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyManager.NETWORK_TYPE_EVDO_B -> "3G"
// Cellular Data4G
TelephonyManager.NETWORK_TYPE_LTE -> "Cellular - 4G"
else -> "Cellular - Unknown Generation"
TelephonyManager.NETWORK_TYPE_LTE -> "4G"
TelephonyManager.NETWORK_TYPE_NR -> "5G"
else -> "unknown"
}
}

View File

@ -149,6 +149,9 @@ public class DeviceUtils {
case TelephonyManager.NETWORK_TYPE_LTE:
status = "4G";
break;
case TelephonyManager.NETWORK_TYPE_NR:
status = "5G";
break;
default:
status = "未知";
break;

View File

@ -68,6 +68,7 @@ import okhttp3.MediaType
import okhttp3.RequestBody
import org.json.JSONArray
import org.json.JSONObject
import java.io.*
import java.net.URI
import java.util.*
import java.util.concurrent.TimeUnit
@ -412,6 +413,25 @@ fun Context.showRegulationTestDialogIfNeeded(action: (() -> Unit)) {
}
}
/**
* Serializable 相关
*/
fun <T> Serializable.deepClone(): T? {
Utils.log("深复制对象 $this")
return try {
val baos = ByteArrayOutputStream()
val oos = ObjectOutputStream(baos)
oos.writeObject(this)
val bais = ByteArrayInputStream(baos.toByteArray())
val ois = ObjectInputStream(bais)
ois.readObject() as T
} catch (e: IOException) {
null
} catch (e: ClassNotFoundException) {
null
}
}
/**
* 在限定 interval 里只触发一次 action
*/

View File

@ -17,6 +17,7 @@ import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.common.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.provider.IActivationProvider
import com.gh.gamecenter.core.provider.IAppProvider
import com.gh.gamecenter.core.provider.IDirectProvider
@ -102,12 +103,14 @@ object PermissionHelper {
.subscribe { permission ->
when {
permission.granted -> {
emptyCallback.onCallback()
SPUtils.setBoolean(Constants.SP_USER_HAS_PERMANENTLY_DENIED_STORAGE_PERMISSION, false)
// 获得存储权限后刷新 gid 避免下载仍需实名
(ARouter.getInstance().build(RouteConsts.provider.app)
.navigation() as? IAppProvider)?.refreshGid()
(ARouter.getInstance().build(RouteConsts.provider.app).navigation() as? IAppProvider)?.refreshGid()
// 延迟 100ms 确保能够正常获取到 gid
AppExecutor.uiExecutor.executeWithDelay({
emptyCallback.onCallback()
}, 100)
SPUtils.setBoolean(Constants.SP_USER_HAS_PERMANENTLY_DENIED_STORAGE_PERMISSION, false)
}
permission.shouldShowRequestPermissionRationale -> {
// do nothing

View File

@ -60,6 +60,10 @@ object AppExecutor {
)
}
val downloadExecutor: ExecutorService by lazy {
Executors.newCachedThreadPool(GHThreadFactory("GH_DOWNLOAD_THREAD"))
}
val cachedScheduler by lazy { Schedulers.from(ioExecutor) }
class MainThreadExecutor : Executor {

View File

@ -0,0 +1,9 @@
package com.gh.gamecenter.core.provider
import com.alibaba.android.arouter.facade.template.IProvider
interface IPkgProvider<T> : IProvider {
fun requestPkgConfig(configId: String, onConfigReceived: (config: T) -> Unit)
}

1
module_pkg/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

87
module_pkg/build.gradle Normal file
View File

@ -0,0 +1,87 @@
if (isRelease.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
if (!isRelease.toBoolean()) {
applicationId "com.gh.pkg"
multiDexEnabled true
}
minSdk rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
buildConfigField "String", "API_HOST", "\"${API_HOST}\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
sourceSets {
main {
if (isRelease.toBoolean()) {
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
exclude 'manifest/**'
}
} else {
java {
srcDirs = ['src/main/java', "src/pkg/java"]
}
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
res.srcDirs = ['src/main/res','src/pkg/res']
}
}
}
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.name)
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildTypes {
debug {
if (!isRelease.toBoolean()) {
buildConfigField "String", "DEV_API_HOST", "\"${DEV_API_HOST}\""
}
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
if (!isRelease.toBoolean()) {
buildConfigField "String", "DEV_API_HOST", "\"${API_HOST}\""
}
}
}
}
dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
kapt "com.alibaba:arouter-compiler:$arouterVersion"
if (!isRelease.toBoolean()) {
implementation "androidx.multidex:multidex:${multiDex}"
}
compileOnly(project(path: ":module_common"))
}

25
module_pkg/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-renamesourcefileattribute SourceFile
# Keep Attribute
-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod,SourceFile,LineNumberTable

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gh.gamecenter.pkg">
<application>
</application>
</manifest>

View File

@ -0,0 +1,44 @@
package com.gh.gamecenter.pkg
import android.app.Application
import android.content.res.Configuration
import com.gh.gamecenter.core.iinterface.IApplication
import com.google.auto.service.AutoService
@AutoService(IApplication::class)
class HaloApp : IApplication {
override fun attachBaseContext() {
// Do nothing
}
override fun onCreate(application: Application) {
mApp = application
}
override fun onLowMemory() {
// Do nothing
}
override fun onTerminate() {
// Do nothing
}
override fun onTrimMemory(level: Int) {
// Do nothing
}
override fun onConfigurationChanged(newConfig: Configuration) {
// Do nothing
}
companion object {
private lateinit var mApp: Application
@JvmStatic
fun getInstance(): Application {
return mApp
}
}
}

View File

@ -0,0 +1,32 @@
package com.gh.gamecenter.pkg
import android.annotation.SuppressLint
import android.content.Context
import com.alibaba.android.arouter.facade.annotation.Route
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.PkgConfigEntity
import com.gh.gamecenter.core.provider.IPkgProvider
import com.gh.gamecenter.pkg.retrofit.RetrofitManager
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
@Route(path = RouteConsts.provider.pkg, name = "PKG 暴露服务")
class PkgProviderImpl : IPkgProvider<PkgConfigEntity> {
@SuppressLint("CheckResult")
override fun requestPkgConfig(configId: String, onConfigReceived: (config: PkgConfigEntity) -> Unit) {
RetrofitManager.getInstance().api.getPkgConfig(configId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
onConfigReceived.invoke(it)
}, {
it.printStackTrace()
})
}
override fun init(context: Context?) {
// Do nothing
}
}

View File

@ -0,0 +1,15 @@
package com.gh.gamecenter.pkg.retrofit
import com.gh.gamecenter.common.entity.PkgConfigEntity
import io.reactivex.Single
import retrofit2.http.*
interface ApiService {
/**
* 获取推广包配置
*/
@GET("pkg/{path}")
fun getPkgConfig(@Path("path") config: String): Single<PkgConfigEntity>
}

View File

@ -0,0 +1,20 @@
package com.gh.gamecenter.pkg.retrofit
import com.gh.gamecenter.common.retrofit.BaseRetrofitManager
import com.gh.gamecenter.common.utils.EnvHelper.getHost
import com.gh.gamecenter.core.utils.SingletonHolder
import com.gh.gamecenter.pkg.HaloApp
class RetrofitManager private constructor() : BaseRetrofitManager() {
val api: ApiService
init {
val context = HaloApp.getInstance().applicationContext
val okHttpNormalConfig = getOkHttpConfig(context, 0, 2)
api = provideService(okHttpNormalConfig, getHost(), ApiService::class.java)
}
companion object : SingletonHolder<RetrofitManager>(::RetrofitManager)
}

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.gh.gamecenter.pkg">
<!-- 允许应用程序访问网络连接 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 允许应用程序获取网络信息状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 允许应用程序读取电话状态 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- 允许应用程序获取当前或最近运行的应用 -->
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name=".PkgModuleApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppCompatTheme"
tools:replace="android:name,android:allowBackup">
<activity
android:name=".view.PkgActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="io.sentry.auto-init"
android:value="false" />
</application>
</manifest>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name" translatable="false">PKG</string>
</resources>

View File

@ -0,0 +1,36 @@
package com.gh.gamecenter.pkg
import androidx.multidex.MultiDexApplication
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.core.iinterface.IApplication
import java.util.*
class PkgModuleApp : MultiDexApplication() {
private val mApplicationList = ServiceLoader.load(IApplication::class.java, this.javaClass.classLoader)
override fun onCreate() {
super.onCreate()
initArouter()
mApp = this
for (application in mApplicationList) {
application.onCreate(this)
}
}
private fun initArouter() {
if (BuildConfig.DEBUG) { // 这两行必须写在init之前否则这些配置在init过程中将无效
ARouter.openLog() // 打印日志
ARouter.openDebug() // 开启调试模式(如果在InstantRun模式下运行必须开启调试模式线上版本需要关闭,否则有安全风险)
}
ARouter.init(this) // 尽可能早推荐在Application中初始化
}
companion object {
private lateinit var mApp: PkgModuleApp
fun getInstance(): PkgModuleApp {
return mApp
}
}
}

View File

@ -0,0 +1,86 @@
package com.gh.gamecenter.pkg.provider
import android.app.Activity
import android.app.Application
import android.content.Context
import com.alibaba.android.arouter.facade.annotation.Route
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.provider.IAppProvider
import com.gh.gamecenter.core.provider.IFlavorProvider
import com.gh.gamecenter.pkg.HaloApp
import com.gh.gamecenter.pkg.R
@Route(path = RouteConsts.provider.app, name = "Application暴露服务")
class AppProviderImpl : IAppProvider {
override fun init(context: Context?) {
// Do nothing
}
override fun get(key: String, isRemove: Boolean): Any? {
return ""
}
override fun put(key: String, any: Any) {
// do nothing
}
override fun getAppName(): String {
return HaloApp.getInstance().getString(R.string.app_name)
}
override fun getGid(): String {
return ""
}
override fun refreshGid() {
// Do nothing
}
override fun getOaid(): String {
return ""
}
override fun getChannel(): String {
return ""
}
override fun setChannel(channel: String) {
// Do nothing
}
override fun getUserAgent(): String {
return ""
}
override fun getServerUserMark(): String {
return ""
}
override fun getDeviceRamSize(): Long {
return 0L
}
override fun getTemporaryLocalDeviceId(): String {
return ""
}
override fun getFlavorProvider(): IFlavorProvider {
return object : IFlavorProvider {
override fun getChannelStr(application: Application): String {
return ""
}
override fun init(application: Application, activity: Activity) {
// do nothing
}
override fun logEvent(content: String) {
// do nothing
}
}
}
override fun isUserAcceptPrivacyPolicy(context: Context): Boolean {
return true
}
}

View File

@ -0,0 +1,15 @@
package com.gh.gamecenter.pkg.view
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.gh.gamecenter.pkg.R
class PkgActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pkg)
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn"
android:layout_width="200dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:text="开启/关闭 VPN" />
</RelativeLayout>

119
scripts/meta_build.sh Executable file
View File

@ -0,0 +1,119 @@
#!/usr/bin/env bash
# @author juntao
# @2023.03.06
git_sha=`git rev-parse --short HEAD`
versionName=$(awk -v FS="versionName = " 'NF>1{print $2}' dependencies.gradle | sed "s/\"//g")
versionCode=$(awk -v FS="versionCode = " 'NF>1{print $2}' dependencies.gradle | sed "s/\"//g")
build_time=$(TZ=Asia/Shanghai date +'%Y-%m%d-%H%M')
cwd=$(cd "$(dirname "$0")"; pwd)
apk_release_path=""
# 重置 app build.gradle
git checkout app/build.gradle
# 重置 module_common build.gradle
git checkout module_common/build.gradle
# 开启 mapping 上传
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' '1 a plugins { id "io.sentry.android.gradle" version "2.1.5" }' app/build.gradle
else
sed -i '1 a plugins { id "io.sentry.android.gradle" version "2.1.5" }' app/build.gradle
fi
./gradlew --stop
./gradlew clean
mkdir -p release/
OPTIONS=$(getopt -o '' -l config_id:,sdk_platform:,sdk_version:,channel:,activate_reporting_ratio:,first_launch_jump:,output:,unix_timestamp:, -- "$@")
eval set -- "$OPTIONS"
while true; do
case "$1" in
--config_id) config_id="$2"; shift 2;;
--sdk_platform) sdk_platform="$2"; shift 2;;
--sdk_version) sdk_version="$2"; shift 2;;
--channel) channel="$2"; shift 2;;
--activate_reporting_ratio) activate_reporting_ratio="$2"; shift 2;;
--first_launch_jump) first_launch_jump="$2"; shift 2;;
--output) output="$2"; shift 2;;
--unix_timestamp) unix_timestamp="$2"; shift 2;;
--) shift; break;;
*) echo "Invalid option: $1" >&2; exit 1;;
esac
done
function updateChannelIfNeeded {
if [ "${channel}" != "" ]; then
java -jar ${cwd}/ApkChannelPackage.jar put -c $channel $1 release
rm $1
find . -type f -name "*.apk" -exec mv {} "${apk_release_path}" \;
fi
}
# 保存 output 文件名
if [ "${output}" != "" ]; then
apk_release_path="$output"
fi
# 保存 config_id
if [ "${config_id}" != "" ]; then
sed -i "s/String CONFIG_ID = \"\"/String CONFIG_ID = \"${config_id}\"/g" app/build.gradle
fi
# 保存 first_launch_jump
if [ "${first_launch_jump}" != "" ]; then
sed -i "s/String FIRST_LAUNCH = \"\"/String FIRST_LAUNCH = \"${first_launch_jump}\"/g" app/build.gradle
fi
# 是否选择了 sdk 类型
if [ "${sdk_platform}" != "" ]; then
# 头条包
if [ "${sdk_platform}" == "toutiao" ]; then
if [ "${activate_reporting_ratio}" == "" ]; then
activate_reporting_ratio="100"
fi
# 调整上报比例
sed -i "75i buildConfigField "\"int\"", "\"ACTIVATE_REPORTING_RATIO\"", \""${activate_reporting_ratio}"\"" app/build.gradle
if [ "${sdk_version}" == "5.3.0" ]; then
sed -i "s/bytedanceApplog = \"6.14.1\"/bytedanceApplog = \"${sdk_version}\"/g" dependencies.gradle
rm app/src/tea/java/com/gh/gamecenter/TeaHelper.kt
mv app/src/tea/java/com/gh/gamecenter/TeaHelper app/src/tea/java/com/gh/gamecenter/TeaHelper.kt
else
sdk_version="6.14.1"
fi
./gradlew aTR -I init.gradle
echo "${sdk_version}_${activate_reporting_ratio}_光环助手_${versionName}_${versionCode}_头条推广包_${git_sha}_${build_time}"
cp -R app/build/outputs/apk/tea/release/app-tea-release.apk "${apk_release_path}"
fi
# 广点通包
if [ "${sdk_platform}" == "guangdiantong" ]; then
# 广点通 SDK 执行 ASM 后处理后会有问题
./gradlew aGR
echo "光环助手_${versionName}_${versionCode}_广点通推广包_${git_sha}_${build_time}"
cp -R app/build/outputs/apk/gdt/release/app-gdt-release.apk "${apk_release_path}"
fi
# 快手包
if [ "${sdk_platform}" == "kuaishou" ]; then
./gradlew aKR -I init.gradle
echo "光环助手_${versionName}_${versionCode}_快手推广包_${git_sha}_${build_time}"
cp -R app/build/outputs/apk/kuaishou/release/app-kuaishou-release.apk "${apk_release_path}"
fi
updateChannelIfNeeded ${apk_release_path}
fi
# 重置 app build.gradle
git checkout app/build.gradle
# 重置 module_common build.gradle
git checkout module_common/build.gradle

View File

@ -14,4 +14,5 @@ include ':ndownload'
include ':module_core_feature'
include ':module_lib'
include ':module_vpn'
include ':module_pkg'
include ':module_sensors_data'