1414 lines
57 KiB
Java
1414 lines
57 KiB
Java
package com.gh.download;
|
||
|
||
import android.content.Context;
|
||
import android.content.Intent;
|
||
import android.os.Build;
|
||
import android.os.Handler;
|
||
import android.os.Looper;
|
||
import android.os.Message;
|
||
import android.text.TextUtils;
|
||
|
||
import androidx.annotation.NonNull;
|
||
import androidx.annotation.Nullable;
|
||
import androidx.annotation.WorkerThread;
|
||
import androidx.collection.ArrayMap;
|
||
import androidx.lifecycle.LiveData;
|
||
import androidx.lifecycle.MutableLiveData;
|
||
|
||
import com.gh.gamecenter.common.base.GlobalActivityManager;
|
||
import com.gh.gamecenter.common.utils.ExtensionsKt;
|
||
import com.gh.gamecenter.common.utils.SensorsBridge;
|
||
import com.gh.gamecenter.core.AppExecutor;
|
||
import com.gh.gamecenter.common.constant.Constants;
|
||
import com.gh.gamecenter.feature.entity.TagStyleEntity;
|
||
import com.gh.gamecenter.feature.entity.CustomPageTrackData;
|
||
import com.gh.gamecenter.feature.entity.TagStyleEntity;
|
||
import com.gh.gamecenter.feature.exposure.ExposureEvent;
|
||
import com.gh.common.exposure.ExposureUtils;
|
||
import com.gh.gamecenter.common.exposure.meta.MetaUtil;
|
||
import com.gh.common.history.HistoryHelper;
|
||
import com.gh.common.simulator.SimulatorGameManager;
|
||
import com.gh.common.util.DataCollectionUtils;
|
||
import com.gh.gamecenter.common.utils.DeviceUtils;
|
||
import com.gh.common.util.DialogUtils;
|
||
import com.gh.gamecenter.core.utils.GsonUtils;
|
||
import com.gh.common.util.HomePluggableHelper;
|
||
import com.gh.common.util.LunchType;
|
||
import com.gh.gamecenter.common.utils.NetworkUtils;
|
||
import com.gh.common.util.PackageInstaller;
|
||
import com.gh.common.util.PackageUtils;
|
||
import com.gh.gamecenter.core.utils.PageSwitchDataHelper;
|
||
import com.gh.gamecenter.core.utils.SPUtils;
|
||
import com.gh.gamecenter.BuildConfig;
|
||
import com.gh.gamecenter.core.utils.SentryHelper;
|
||
import com.gh.gamecenter.download.DownloadedGameIdAndPackageNameDao;
|
||
import com.gh.gamecenter.feature.entity.ApkEntity;
|
||
import com.gh.gamecenter.feature.entity.GameEntity;
|
||
import com.gh.gamecenter.entity.GameUpdateEntity;
|
||
import com.gh.gamecenter.entity.HomePluggableFilterEntity;
|
||
import com.gh.gamecenter.feature.entity.PluginLocation;
|
||
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.vspace.VHelper;
|
||
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.DataWatcher;
|
||
import com.lightgame.download.DownloadConfig;
|
||
import com.lightgame.download.DownloadDao;
|
||
import com.lightgame.download.DownloadEntity;
|
||
import com.lightgame.download.DownloadStatus;
|
||
import com.lightgame.download.DownloadStatusListener;
|
||
import com.lightgame.download.FileUtils;
|
||
import com.lightgame.download.HttpDnsManager;
|
||
import com.lightgame.utils.Utils;
|
||
|
||
import org.greenrobot.eventbus.EventBus;
|
||
|
||
import java.io.File;
|
||
import java.util.ArrayList;
|
||
import java.util.Arrays;
|
||
import java.util.HashMap;
|
||
import java.util.HashSet;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.Set;
|
||
import java.util.concurrent.ConcurrentHashMap;
|
||
import java.util.concurrent.LinkedBlockingQueue;
|
||
|
||
public class DownloadManager implements DownloadStatusListener {
|
||
|
||
private static final String UPDATE_IS_READ_MARK = "update_is_read";
|
||
private static final String DOWNLOADING_IS_READ_MARK = "downloading_is_read";
|
||
private static final String DOWNLOADED_IS_READ_MARK = "downloaded_is_read";
|
||
|
||
private final Context mContext;
|
||
private final Handler mHandler;
|
||
|
||
private final ArrayMap<String, Long> lastTimeMap;
|
||
private final ArrayMap<String, LinkedBlockingQueue<String>> platformMap;
|
||
private final Map<String, ConcurrentHashMap<String, DownloadEntity>> gameMap;
|
||
|
||
private final ArrayMap<String, DownloadStatus> statusMap;
|
||
private final ConcurrentHashMap<String, DownloadEntity> downloadingMap;
|
||
|
||
private final ArrayList<DownloadEntity> mInvisiblePendingTaskList; // 用户不可见的 pending 任务
|
||
private final DownloadDao mDownloadDao;
|
||
private final DownloadedGameIdAndPackageNameDao mDownloadedGameIdAndPackageNameDao;
|
||
|
||
private final MutableLiveData<List<DownloadEntity>> mDownloadEntityListLiveData; // 下载任务变更 (主要还是数量变更) 的 LiveData
|
||
|
||
private final Set<String> mUpdateMarks;
|
||
|
||
@Override
|
||
public void onTaskCancelled(DownloadEntity entity) {
|
||
EBDownloadStatus status = new EBDownloadStatus("delete", entity.getName(),
|
||
entity.getPlatform(), entity.getUrl(), entity.getPackageName(), entity.getGameId());
|
||
status.setPluggable(entity.isPluggable());
|
||
EventBus.getDefault().post(status);
|
||
|
||
DownloadManager.getInstance().putStatus(entity.getUrl(), DownloadStatus.delete);
|
||
|
||
downloadingMap.remove(entity.getUrl());
|
||
|
||
notifyDownloadLiveDataChanged();
|
||
}
|
||
|
||
@Override
|
||
public void onTaskAdded(DownloadEntity entity) {
|
||
EventBus.getDefault().post(new EBDownloadStatus("download", entity.getName(),
|
||
entity.getPlatform(), entity.getUrl(), entity.getPackageName(), entity.getGameId()));
|
||
|
||
downloadingMap.put(entity.getUrl(), entity);
|
||
|
||
DownloadWorkManager.addWorker();
|
||
|
||
notifyDownloadLiveDataChanged();
|
||
}
|
||
|
||
@Override
|
||
public void onTaskError(DownloadEntity entity) {
|
||
// 下载进度超出是任务出错,但不需要去掉状态栏通知 https://gitlab.ghzs.com/pm/halo-app-issues/issues/496
|
||
if (entity.getStatus() != DownloadStatus.overflow) {
|
||
downloadingMap.remove(entity.getUrl());
|
||
}
|
||
|
||
notifyDownloadLiveDataChanged();
|
||
}
|
||
|
||
@Override
|
||
public void onTaskStatusChanged(DownloadEntity entity) {
|
||
|
||
}
|
||
|
||
@Override
|
||
public void onTaskDone(DownloadEntity entity) {
|
||
downloadingMap.remove(entity.getUrl());
|
||
|
||
mDownloadedGameIdAndPackageNameDao.removeOldPackageItem(entity.getPackageName());
|
||
mDownloadedGameIdAndPackageNameDao.add(entity.getGameId() + entity.getPackageName());
|
||
|
||
if (downloadingMap.isEmpty()) {
|
||
DownloadWorkManager.cancelWorker();
|
||
}
|
||
|
||
notifyDownloadLiveDataChanged();
|
||
}
|
||
|
||
@Override
|
||
public void onTaskPaused(DownloadEntity entity) {
|
||
|
||
}
|
||
|
||
private DownloadManager() {
|
||
mContext = HaloApp.getInstance().getApplicationContext();
|
||
mDownloadDao = DownloadDao.getInstance(mContext);
|
||
mDownloadedGameIdAndPackageNameDao = new DownloadedGameIdAndPackageNameDao();
|
||
mDownloadEntityListLiveData = new MutableLiveData<>();
|
||
|
||
mUpdateMarks = SPUtils.getStringSet(UPDATE_IS_READ_MARK);
|
||
|
||
// 只有下载模块需要这坨东西,因此移动到这里初始化
|
||
ConnectionUtils.initHttpsUrlConnection(mContext);
|
||
|
||
updateDownloadMetaMap();
|
||
|
||
lastTimeMap = new ArrayMap<>();
|
||
platformMap = new ArrayMap<>();
|
||
gameMap = new ConcurrentHashMap<>();
|
||
statusMap = new ArrayMap<>();
|
||
downloadingMap = new ConcurrentHashMap<>();
|
||
// mDownloadSnapshotList = new ArrayList<>();
|
||
mInvisiblePendingTaskList = new ArrayList<>();
|
||
|
||
mHandler = new Handler(Looper.getMainLooper()) {
|
||
@Override
|
||
public void handleMessage(Message msg) {
|
||
String url = (String) msg.obj;
|
||
switch (msg.what) {
|
||
case DownloadConfig.CONTINUE_DOWNLOAD_TASK:
|
||
case DownloadConfig.CONTINUE_DOWNLOAD_AUTO_TASK:
|
||
if (lastTimeMap.get(url) != null && System.currentTimeMillis() - lastTimeMap.get(url) >= 1000) {
|
||
DownloadEntity downloadEntity = getDownloadEntityByUrl(url);
|
||
if (downloadEntity != null) {
|
||
resume(downloadEntity, msg.what == DownloadConfig.CONTINUE_DOWNLOAD_AUTO_TASK);
|
||
}
|
||
}
|
||
break;
|
||
case DownloadConfig.PAUSE_DOWNLOAD_TASK:
|
||
if (lastTimeMap.get(url) != null && System.currentTimeMillis() - lastTimeMap.get(url) >= 1000) {
|
||
pause(url);
|
||
}
|
||
break;
|
||
case DownloadConfig.DOWNLOAD_ROLL:
|
||
LinkedBlockingQueue<String> queue = platformMap.get(url);
|
||
if (queue.size() > 1) {
|
||
queue.offer(queue.poll());
|
||
Message message = Message.obtain();
|
||
message.obj = url;
|
||
message.what = DownloadConfig.DOWNLOAD_ROLL;
|
||
sendMessageDelayed(message, 3000);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
};
|
||
|
||
List<DownloadEntity> list = getAllDownloadEntity();
|
||
for (DownloadEntity downloadEntity : list) {
|
||
statusMap.put(downloadEntity.getUrl(), downloadEntity.getStatus());
|
||
if (!DownloadStatus.done.equals(downloadEntity.getStatus())) {
|
||
downloadingMap.put(downloadEntity.getUrl(), downloadEntity);
|
||
}
|
||
}
|
||
}
|
||
|
||
public static DownloadManager getInstance() {
|
||
return SingletonHolder.INSTANCE;
|
||
}
|
||
|
||
/**
|
||
* 创建下载任务
|
||
*
|
||
* @param context 上下文
|
||
* @param gameEntity 游戏实体 (取 apk 里的第一个来进行下载)
|
||
* @param asVGame 是否以畅玩游戏的形式下载
|
||
* @param entrance 来源 (细节见 https://git.ghzs.com/halo/android/assistant-android/-/wikis/entrance)
|
||
* @param location 跟来源是类似的东西,我也不知道具体规则是怎么样的 :C
|
||
* @param isSubscribe 是否是订阅下载(在WiFi环境下才下载)
|
||
* @param traceEvent 曝光来源实体,用于记录一些简单的下载路径
|
||
*/
|
||
public static void createDownload(Context context,
|
||
GameEntity gameEntity,
|
||
boolean asVGame,
|
||
String entrance,
|
||
String location,
|
||
boolean isSubscribe,
|
||
@Nullable ExposureEvent traceEvent) {
|
||
createDownload(context, gameEntity.getApk().get(0), gameEntity, asVGame, gameEntity.isDualBtnModeEnabled(), entrance, location, isSubscribe, traceEvent);
|
||
}
|
||
|
||
/**
|
||
* 创建下载任务
|
||
*
|
||
* @param context 上下文
|
||
* @param gameEntity 游戏实体 (信息用于)
|
||
* @param apkEntity 安装包实体,实际用于下载的信息文件
|
||
* @param entrance 来源 (细节见 https://git.ghzs.com/halo/android/assistant-android/-/wikis/entrance)
|
||
* @param location 跟来源是类似的东西,我也不知道具体规则是怎么样的 :C
|
||
* @param isSubscribe 是否是订阅下载(在WiFi环境下才下载)
|
||
* @param traceEvent 曝光来源实体,用于记录一些简单的下载路径
|
||
* @param asVGame 作为畅玩游戏下载
|
||
* @param isDualDownloadTypeEnabled 双下载按钮模式是否启用
|
||
*/
|
||
public static void createDownload(final Context context,
|
||
ApkEntity apkEntity,
|
||
GameEntity gameEntity,
|
||
boolean asVGame,
|
||
boolean isDualDownloadTypeEnabled,
|
||
String entrance,
|
||
String location,
|
||
boolean isSubscribe,
|
||
@Nullable ExposureEvent traceEvent) {
|
||
|
||
// 插件版本下载互斥弹窗
|
||
List<String> mutexPackage = gameEntity.getMutexPackage();
|
||
if (mutexPackage != null && mutexPackage.size() > 0) {
|
||
for (String pkg : mutexPackage) {
|
||
if (PackagesManager.isInstalled(pkg)) {
|
||
DialogUtils.showDownloadMutexDialog(context);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
String path;
|
||
String downloadId = PackageInstaller.createDownloadId(gameEntity.getName());
|
||
String gameCategory = gameEntity.getCategory();
|
||
if (SimulatorGameManager.isSimulatorGame(gameEntity)) {
|
||
path = SimulatorGameManager.getPathByType(gameEntity.getSimulatorType()) + "/" + gameEntity.getName() + "." + apkEntity.getFormat();
|
||
|
||
// 下载模拟器游戏配置文件,地址是 "模拟器游戏类型根目录/cheat/"
|
||
if (!TextUtils.isEmpty(gameEntity.getSimulatorGameConfig())) {
|
||
String configFilePath = SimulatorGameManager.getPathByType(gameEntity.getSimulatorType()) + "/cheat/" + apkEntity.getPackageName() + ".ini";
|
||
AppExecutor.getIoExecutor().execute(() -> {
|
||
FileUtils.downloadFile(gameEntity.getSimulatorGameConfig(), configFilePath);
|
||
});
|
||
}
|
||
} else {
|
||
path = PackageInstaller.getDownloadPathWithId(downloadId, apkEntity.getFormat());
|
||
}
|
||
|
||
File file = new File(path);
|
||
DownloadEntity entity = DownloadDao.getInstance(context).get(apkEntity.getUrl());
|
||
//判断当前链接没有下载记录并且文件已经存在则删除这个文件
|
||
if (entity == null && file.exists()) {
|
||
file.delete();
|
||
}
|
||
|
||
DownloadEntity downloadEntity = new DownloadEntity();
|
||
downloadEntity.setUrl(apkEntity.getUrl());
|
||
downloadEntity.setName(gameEntity.getName());
|
||
downloadEntity.setPath(path);
|
||
downloadEntity.setPluginDesc(gameEntity.getPluginDesc());
|
||
downloadEntity.setETag(apkEntity.getEtag());
|
||
downloadEntity.setIcon(gameEntity.getIcon());
|
||
downloadEntity.setPlatform(apkEntity.getPlatform());
|
||
downloadEntity.setPackageName(apkEntity.getPackageName());
|
||
downloadEntity.setGameId(gameEntity.getId());
|
||
downloadEntity.setEntrance(entrance);
|
||
downloadEntity.setLocation(location);
|
||
downloadEntity.setFormat(apkEntity.getFormat());
|
||
downloadEntity.setVersionName(apkEntity.getVersion());
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.APK_MD5, apkEntity.getMd5());
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.DOWNLOAD_ID, downloadId);
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.RAW_GAME_ICON, gameEntity.getRawIcon());
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.GAME_ICON_SUBSCRIPT, gameEntity.getIconSubscript());
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.IS_PLATFORM_RECOMMEND, apkEntity.getRecommend() != null ? "true" : "false");
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.GAME_NAME, gameEntity.getName());
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.GAME_CATEGORY_IN_CHINESE, gameEntity.getCategoryChinese());
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.AD_ICON_ACTIVE, String.valueOf(gameEntity.getAdIconActive()));
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.IS_AD_DATA, String.valueOf(gameEntity.isAdData()));
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.DOWNLOAD_STATUS_IN_CHINESE, gameEntity.getDownloadStatusChinese());
|
||
ExtensionsKt.putGameCategory(downloadEntity, gameCategory != null ? gameCategory : "");
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.APK_SIZE, apkEntity.getSize());
|
||
if (gameEntity.getIconFloat() != null) {
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.GAME_ICON_FLOAT_TOP_TEXT, gameEntity.getIconFloat().getUpperLeftText());
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.GAME_ICON_FLOAT_TOP_COLOR, gameEntity.getIconFloat().getUpperLeftColor());
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.GAME_ICON_FLOAT_BOTTOM_TEXT, gameEntity.getIconFloat().getBottomText());
|
||
}
|
||
if (gameEntity.isModdedGame()) {
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.EXTRA_IS_MODDED_GAME, "true");
|
||
}
|
||
if (SimulatorGameManager.isSimulatorGame(gameEntity)) {
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.SIMULATOR_GAME, apkEntity.getFormat());
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.SIMULATOR, GsonUtils.toJson(gameEntity.getSimulator()));
|
||
}
|
||
|
||
if (asVGame) {
|
||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.EXTRA_DOWNLOAD_TYPE, Constants.VGAME);
|
||
ExtensionsKt.addMetaExtra(downloadEntity, DownloadConfig.KEY_PROGRESS_CALLBACK_INTERVAL, "200");
|
||
ExtensionsKt.addMetaExtra(downloadEntity, VHelper.KEY_REQUIRED_G_APPS, gameEntity.getGAppsSwitch());
|
||
ExtensionsKt.addMetaExtra(downloadEntity, VHelper.KEY_BIT, apkEntity.getBit());
|
||
}
|
||
|
||
// 记录是否为双下载按钮模式
|
||
if (isDualDownloadTypeEnabled) {
|
||
if (asVGame) {
|
||
ExtensionsKt.setVGameDownloadModeInDualDownloadMode(downloadEntity);
|
||
} else {
|
||
ExtensionsKt.setLocalDownloadModeInDualDownloadMode(downloadEntity);
|
||
}
|
||
}
|
||
|
||
HashMap<String, String> map = PageSwitchDataHelper.popLastPageData();
|
||
if (map != null && map.containsKey(PageSwitchDataHelper.PAGE_GAME_DETAIL_RECOMMEND)) {
|
||
ExtensionsKt.addMetaExtra(downloadEntity, PageSwitchDataHelper.PAGE_GAME_DETAIL_RECOMMEND, "true");
|
||
}
|
||
|
||
int installed = 0;
|
||
for (ApkEntity apk : gameEntity.getApk()) {
|
||
if (PackagesManager.isInstalled(apk.getPackageName())) {
|
||
installed++;
|
||
}
|
||
}
|
||
downloadEntity.setInstalled(installed);
|
||
|
||
// todo 不是应该实时判断吗?
|
||
if (PackageUtils.isCanPluggable(apkEntity)) {
|
||
downloadEntity.setPluggable(true);
|
||
} else if (PackageUtils.isCanUpdate(apkEntity, gameEntity.getId())
|
||
|| PackageUtils.isNonPluginUpdatable(apkEntity, gameEntity)) {
|
||
downloadEntity.setUpdate(true);
|
||
}
|
||
|
||
downloadEntity.setPlugin(!TextUtils.isEmpty(apkEntity.getGhVersion()));
|
||
|
||
ExposureUtils.DownloadType downloadType = ExposureUtils.getDownloadType(apkEntity, gameEntity.getId(), asVGame);
|
||
gameEntity.setIsPlatformRecommend(apkEntity.getRecommend() != null);
|
||
ExposureEvent downloadExposureEvent = ExposureUtils.logADownloadExposureEvent(gameEntity, apkEntity.getPlatform(), traceEvent, downloadType);
|
||
|
||
// 将下载事件放入 downloadEntity 中供下载完成时取出使用
|
||
downloadEntity.setExposureTrace(GsonUtils.toJson(downloadExposureEvent));
|
||
|
||
// 保存所有游戏标签
|
||
List<String> tags = new ArrayList<>();
|
||
for (TagStyleEntity tag : gameEntity.getTagStyle()) {
|
||
tags.add(tag.getName());
|
||
}
|
||
downloadEntity.setTags(tags);
|
||
|
||
// 将自定义页面相关埋点数据放入 downloadEntity 中供下载完成时取出使用
|
||
CustomPageTrackData customPageTrackData = gameEntity.getCustomPageTrackData();
|
||
if (customPageTrackData != null) {
|
||
String trackJson = GsonUtils.toJson(customPageTrackData);
|
||
downloadEntity.setCustomPageTrackData(trackJson);
|
||
}
|
||
|
||
if (isSubscribe) {
|
||
DownloadManager.getInstance().subscribe(downloadEntity);
|
||
} else {
|
||
HistoryHelper.insertGameEntity(gameEntity);
|
||
DownloadManager.getInstance().add(downloadEntity);
|
||
}
|
||
|
||
if (asVGame) {
|
||
String[] vaKvs = {
|
||
"game_name", gameEntity.getName(),
|
||
"game_id", gameEntity.getId(),
|
||
"game_type", gameEntity.getCategoryChinese(),
|
||
"game_schema_type", gameEntity.getGameBitChinese()
|
||
};
|
||
List<String> vaList = new ArrayList<>(Arrays.asList(vaKvs));
|
||
if (customPageTrackData != null) {
|
||
vaList.addAll(Arrays.asList(customPageTrackData.toKV()));
|
||
}
|
||
SensorsBridge.trackEventWithExposureSource("HaloFunGameDownloadClick",
|
||
downloadExposureEvent.getSource(),
|
||
vaList.toArray(new String[0]));
|
||
}
|
||
|
||
String trackDownloadType = "";
|
||
if (asVGame) {
|
||
trackDownloadType = "畅玩下载";
|
||
} else {
|
||
trackDownloadType = "本地下载";
|
||
}
|
||
|
||
|
||
String[] arrayKv = {
|
||
"game_id", gameEntity.getId(),
|
||
"game_name", gameEntity.getName(),
|
||
"game_type", gameEntity.getCategoryChinese(),
|
||
"game_label", String.join(",", tags),
|
||
"game_schema_type", gameEntity.getGameBitChinese(),
|
||
"page_name", GlobalActivityManager.getCurrentPageEntity().getPageName(),
|
||
"page_id", GlobalActivityManager.getCurrentPageEntity().getPageId(),
|
||
"page_business_id", GlobalActivityManager.getCurrentPageEntity().getPageBusinessId(),
|
||
"last_page_name", GlobalActivityManager.getLastPageEntity().getPageName(),
|
||
"last_page_id", GlobalActivityManager.getLastPageEntity().getPageId(),
|
||
"last_page_business_id", GlobalActivityManager.getLastPageEntity().getPageBusinessId(),
|
||
"download_status", gameEntity.getDownloadStatusChinese(),
|
||
"download_type", trackDownloadType
|
||
};
|
||
|
||
List<String> kvs = new ArrayList<>(Arrays.asList(arrayKv));
|
||
if (customPageTrackData != null) {
|
||
kvs.addAll(Arrays.asList(customPageTrackData.toKV()));
|
||
}
|
||
|
||
SensorsBridge.trackEventWithExposureSource("DownloadProcessBegin",
|
||
downloadExposureEvent.getSource(), kvs.toArray(new String[0])
|
||
);
|
||
|
||
//TODO remove
|
||
DownloadManager.getInstance().putStatus(downloadEntity.getUrl(), DownloadStatus.downloading);
|
||
|
||
DownloadManager.getInstance().markDownloadingTaskAsUnread();
|
||
// 收集下载数据
|
||
DataCollectionUtils.uploadDownload(context, downloadEntity, "开始");
|
||
}
|
||
|
||
/**
|
||
* 清理不存在本地 APK 文件的任务
|
||
*/
|
||
public void clearTasksThatFileBeenDeleted() {
|
||
try {
|
||
for (DownloadEntity entity : getAllDownloadEntity()) {
|
||
if (entity.getStatus() == DownloadStatus.done) {
|
||
if (FileUtils.isEmptyFile(entity.getPath())) {
|
||
cancel(entity.getUrl());
|
||
}
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
SentryHelper.INSTANCE.onEvent("CLEAR_DELETED_TASK_ERROR", "exception_digest", e.getLocalizedMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 添加一个下载任务
|
||
* <p>
|
||
* 请优先使用 createDownload()
|
||
*/
|
||
@Deprecated
|
||
public void add(DownloadEntity downloadEntity) {
|
||
add(downloadEntity, false);
|
||
}
|
||
|
||
/**
|
||
* 添加一个下载任务
|
||
* <p>
|
||
* 请优先使用 createDownload()
|
||
*
|
||
* @param ignoreDownloaded 忽略已经下载的内容
|
||
*/
|
||
@Deprecated
|
||
public void add(DownloadEntity downloadEntity, boolean ignoreDownloaded) {
|
||
updateDownloadMetaMap();
|
||
|
||
if (downloadEntity != null) {
|
||
String url = downloadEntity.getUrl();
|
||
checkDownloadEntryRecordValidate(url);
|
||
|
||
if (isDownloadCompleted(url) && !ignoreDownloaded) {
|
||
downloadEntity.setStatus(DownloadStatus.done);
|
||
NDataChanger.INSTANCE.notifyDataChanged(downloadEntity);
|
||
} else if (!isTaskDownloading(url)) {
|
||
startDownloadService(downloadEntity, DownloadStatus.add);
|
||
}
|
||
put(url, System.currentTimeMillis());
|
||
putStatus(url, DownloadStatus.downloading);
|
||
}
|
||
Utils.log(DownloadManager.class.getSimpleName(), "add");
|
||
}
|
||
|
||
/**
|
||
* 继续下载任务
|
||
*
|
||
* @param automatic 是否是自动下载
|
||
*/
|
||
public void resume(DownloadEntity downloadEntity, boolean automatic) {
|
||
if (downloadEntity != null) {
|
||
String url = downloadEntity.getUrl();
|
||
checkDownloadEntryRecordValidate(url);
|
||
if (isDownloadCompleted(url)) {
|
||
downloadEntity.setStatus(DownloadStatus.done);
|
||
NDataChanger.INSTANCE.notifyDataChanged(downloadEntity);
|
||
} else if (!isTaskDownloading(url)) {
|
||
DownloadEntity daoEntity = mDownloadDao.get(downloadEntity.getUrl());
|
||
if (automatic) {
|
||
if (daoEntity != null) {
|
||
daoEntity.getMeta().put(DownloadDataHelper.DOWNLOAD_RESUME_WAY, DownloadDataHelper.DOWNLOAD_RESUME_AUTO);
|
||
mDownloadDao.newOrUpdate(daoEntity);
|
||
}
|
||
downloadEntity.getMeta().put(DownloadDataHelper.DOWNLOAD_RESUME_WAY, DownloadDataHelper.DOWNLOAD_RESUME_AUTO);
|
||
} else {
|
||
if (daoEntity != null) {
|
||
daoEntity.getMeta().put(DownloadDataHelper.DOWNLOAD_RESUME_WAY, DownloadDataHelper.DOWNLOAD_RESUME_MANUAL);
|
||
mDownloadDao.newOrUpdate(daoEntity);
|
||
}
|
||
downloadEntity.getMeta().put(DownloadDataHelper.DOWNLOAD_RESUME_WAY, DownloadDataHelper.DOWNLOAD_RESUME_MANUAL);
|
||
}
|
||
startDownloadService(downloadEntity, DownloadStatus.resume);
|
||
}
|
||
put(url, System.currentTimeMillis());
|
||
putStatus(url, DownloadStatus.downloading);
|
||
}
|
||
Utils.log(DownloadManager.class.getSimpleName(), "resume");
|
||
}
|
||
|
||
/**
|
||
* 添加一个下载任务(WiFi时自动下载)
|
||
*/
|
||
public void subscribe(DownloadEntity downloadEntity) {
|
||
updateDownloadMetaMap();
|
||
|
||
if (downloadEntity != null) {
|
||
String url = downloadEntity.getUrl();
|
||
checkDownloadEntryRecordValidate(url);
|
||
if (isDownloadCompleted(url)) {
|
||
downloadEntity.setStatus(DownloadStatus.done);
|
||
NDataChanger.INSTANCE.notifyDataChanged(downloadEntity);
|
||
} else if (!isTaskDownloading(url)) {
|
||
startDownloadService(downloadEntity, DownloadStatus.subscribe);
|
||
}
|
||
put(url, System.currentTimeMillis());
|
||
putStatus(url, DownloadStatus.subscribe);
|
||
}
|
||
Utils.log(DownloadManager.class.getSimpleName(), "subscribe");
|
||
}
|
||
|
||
/**
|
||
* 根据url到本地sqlite数据库中查找并获取该文件的保存路径, 并检查改路径下文件是否存在,不存在则删除该条无效记录
|
||
*
|
||
* @param url 下载任务的标识url
|
||
*/
|
||
private boolean checkDownloadEntryRecordValidate(String url) {
|
||
DownloadEntity entry = getDownloadEntityByUrl(url);
|
||
if (entry != null && ((int) entry.getPercent()) != 0) {
|
||
File file = new File(entry.getPath());
|
||
if (!file.exists()) {
|
||
mDownloadDao.delete(url);
|
||
Utils.log(DownloadManager.class.getSimpleName(), "文件不存在,删除该条无效数据库记录!");
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 任务是否已经下载完成
|
||
*/
|
||
public boolean isDownloadCompleted(String url) {
|
||
DownloadEntity entry = getDownloadEntityByUrl(url);
|
||
|
||
if (entry != null && entry.getPercent() == 100) {
|
||
Utils.log(DownloadManager.class.getSimpleName(), "文件已经下载完成!");
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 任务是否已经下载中
|
||
*/
|
||
public boolean isTaskDownloading(String url) {
|
||
if (NDataChanger.INSTANCE.getDownloadingTaskUrlSet().contains(url)) {
|
||
Utils.log(DownloadManager.class.getSimpleName(), url + "正在下载!");
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
private Intent getIntent(DownloadEntity entry, DownloadStatus status) {
|
||
Intent service = new Intent(mContext, NDownloadService.class);
|
||
service.putExtra(DownloadConfig.KEY_DOWNLOAD_ENTRY, entry);
|
||
service.putExtra(DownloadConfig.KEY_DOWNLOAD_ACTION, status.name());
|
||
return service;
|
||
}
|
||
|
||
/**
|
||
* 根据url获取某一个下载任务
|
||
*
|
||
* @param url 下载链接
|
||
* @return null表示下载列表中不存在该任务,否则返回下载任务
|
||
*/
|
||
@Nullable
|
||
@WorkerThread
|
||
public DownloadEntity getDownloadEntityByUrl(String url) {
|
||
if (TextUtils.isEmpty(url)) return null;
|
||
return mDownloadDao.get(url);
|
||
}
|
||
|
||
/**
|
||
* 获取所有的下载任务快照
|
||
*
|
||
* @return 下载任务快照列表
|
||
*/
|
||
@NonNull
|
||
private ArrayList<DownloadEntity> getAllDownloadEntitySnapshots() {
|
||
return mDownloadDao.getAllSnapshots();
|
||
}
|
||
|
||
/**
|
||
* 获取快照
|
||
*
|
||
* @param url 下载地址
|
||
*/
|
||
@Nullable
|
||
public DownloadEntity getDownloadEntitySnapshot(String url) {
|
||
return mDownloadDao.getSnapshot(url);
|
||
}
|
||
|
||
/**
|
||
* 获取快照
|
||
*
|
||
* @param gameEntity 游戏实体
|
||
*/
|
||
@Nullable
|
||
public DownloadEntity getDownloadEntitySnapshot(GameEntity gameEntity) {
|
||
if (gameEntity == null || gameEntity.getApk().size() == 0) return null;
|
||
return getDownloadEntitySnapshot(gameEntity.getApk().get(0).getUrl(), gameEntity.getId());
|
||
}
|
||
|
||
/**
|
||
* 获取快照
|
||
* <p>
|
||
* 优先根据游戏 ID 获取,次选根据 url 获取
|
||
*
|
||
* @param url 下载链接
|
||
* @param gameId 游戏 ID
|
||
* @return null表示下载列表中不存在该任务,否则返回下载任务
|
||
*/
|
||
@Nullable
|
||
public DownloadEntity getDownloadEntitySnapshot(String url, String gameId) {
|
||
DownloadEntity snapshot = null;
|
||
|
||
if (!TextUtils.isEmpty(gameId)) {
|
||
snapshot = mDownloadDao.getSnapshotByGameId(gameId);
|
||
}
|
||
|
||
if (snapshot == null && !TextUtils.isEmpty(url)) {
|
||
snapshot = mDownloadDao.getSnapshot(url);
|
||
}
|
||
|
||
return snapshot;
|
||
}
|
||
|
||
/**
|
||
* 根据包名获取下载任务快照 (仅保证下载状态一致)
|
||
*
|
||
* @param packageName 包名 (多包名一样时取第一个,若使用场景里有多包名,请使用 url 获取下载任务)
|
||
* @return null 表示下载列表中不存在该任务,否则返回下载任务
|
||
*/
|
||
@Nullable
|
||
public DownloadEntity getDownloadEntitySnapshotByPackageName(String packageName) {
|
||
if (TextUtils.isEmpty(packageName)) return null;
|
||
return mDownloadDao.getSnapshotByPackageName(packageName);
|
||
}
|
||
|
||
/**
|
||
* 根据包名获取某一个下载任务 (涉及数据库查询,请优先在子线程调用)
|
||
*
|
||
* @param packageName 包名
|
||
* @return null表示下载列表中不存在该任务,否则返回下载任务
|
||
*/
|
||
@Nullable
|
||
@WorkerThread
|
||
public DownloadEntity getDownloadEntityByPackageName(String packageName) {
|
||
if (mDownloadDao == null) return null;
|
||
|
||
List<DownloadEntity> downloadList = mDownloadDao.getAll();
|
||
|
||
if (downloadList == null) return null;
|
||
|
||
for (DownloadEntity downloadEntity : downloadList) {
|
||
if (packageName.equals(downloadEntity.getPackageName())) {
|
||
return downloadEntity;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public void put(String url, long time) {
|
||
lastTimeMap.put(url, time);
|
||
}
|
||
|
||
public void putQueue(String name, LinkedBlockingQueue<String> queue) {
|
||
platformMap.put(name, queue);
|
||
}
|
||
|
||
public LinkedBlockingQueue<String> getQueue(String name) {
|
||
return platformMap.get(name);
|
||
}
|
||
|
||
public void removePlatform(String name, String platform) {
|
||
LinkedBlockingQueue<String> queue = platformMap.get(name);
|
||
if (queue != null) {
|
||
queue.remove(platform);
|
||
platformMap.put(name, queue);
|
||
}
|
||
}
|
||
|
||
public void initGameMap() {
|
||
gameMap.clear();
|
||
List<DownloadEntity> list = getAllDownloadEntitySnapshots();
|
||
if (list.size() != 0) {
|
||
String name;
|
||
for (DownloadEntity downloadEntity : list) {
|
||
name = downloadEntity.getName();
|
||
ConcurrentHashMap<String, DownloadEntity> map = gameMap.get(name);
|
||
if (map == null) {
|
||
map = new ConcurrentHashMap<>();
|
||
gameMap.put(name, map);
|
||
}
|
||
map.put(downloadEntity.getPlatform(), downloadEntity);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取所有下载列表中的任务
|
||
*/
|
||
@NonNull
|
||
public List<DownloadEntity> getAllDownloadEntity() {
|
||
List<DownloadEntity> all = mDownloadDao.getAll();
|
||
return all != null ? all : new ArrayList<>();
|
||
}
|
||
|
||
public List<DownloadEntity> getAllSimulatorDownloadEntity() {
|
||
List<DownloadEntity> downloadEntityList = getAllDownloadEntity();
|
||
ArrayList<DownloadEntity> filteredDownloadEntityList = new ArrayList<>();
|
||
for (DownloadEntity downloadEntity : downloadEntityList) {
|
||
if (ExtensionsKt.isSimulatorGame(downloadEntity) && downloadEntity.getStatus() == DownloadStatus.done) {
|
||
filteredDownloadEntityList.add(downloadEntity);
|
||
}
|
||
}
|
||
return filteredDownloadEntityList;
|
||
}
|
||
|
||
/**
|
||
* 获取下载列表中除静默下载、模拟器下载、畅玩下载以外的任务
|
||
*/
|
||
public List<DownloadEntity> getAllDownloadEntityExcludeSilentTask() {
|
||
List<DownloadEntity> all = getAllDownloadEntity();
|
||
return filterSilentDownloadTask(all);
|
||
}
|
||
|
||
/**
|
||
* 获取下载列表中除下载状态为完成以外的任务
|
||
*/
|
||
public List<DownloadEntity> getAllDownloadEntityExcludeDoneTask() {
|
||
List<DownloadEntity> all = getAllDownloadEntity();
|
||
return filterDoneDownloadTask(all);
|
||
}
|
||
|
||
/**
|
||
* 获取下载列表中的畅玩下载任务快照
|
||
*/
|
||
public ArrayList<DownloadEntity> getAllVDownloadTaskSnapshots() {
|
||
List<DownloadEntity> downloadList = getAllDownloadEntitySnapshots();
|
||
|
||
ArrayList<DownloadEntity> filteredDownloadEntityList = new ArrayList<>();
|
||
|
||
for (DownloadEntity downloadEntity : downloadList) {
|
||
if (Constants.VGAME.equals(ExtensionsKt.getMetaExtra(downloadEntity, Constants.EXTRA_DOWNLOAD_TYPE))
|
||
|| Constants.DUAL_DOWNLOAD_VGAME.equals(ExtensionsKt.getMetaExtra(downloadEntity, Constants.EXTRA_DOWNLOAD_TYPE))) {
|
||
filteredDownloadEntityList.add(downloadEntity);
|
||
}
|
||
}
|
||
|
||
return filteredDownloadEntityList;
|
||
}
|
||
|
||
private ArrayList<DownloadEntity> filterSilentDownloadTask(List<DownloadEntity> downloadEntityList) {
|
||
ArrayList<DownloadEntity> filteredDownloadEntityList = new ArrayList<>();
|
||
|
||
if (downloadEntityList == null) return filteredDownloadEntityList;
|
||
|
||
for (DownloadEntity downloadEntity : downloadEntityList) {
|
||
if (!ExtensionsKt.isSimulatorGame(downloadEntity)) {
|
||
String extraDownloadType = ExtensionsKt.getMetaExtra(downloadEntity, Constants.EXTRA_DOWNLOAD_TYPE);
|
||
if (!Constants.SILENT_UPDATE.equals(extraDownloadType)
|
||
&& !Constants.SIMULATOR_DOWNLOAD.equals(extraDownloadType)
|
||
&& !Constants.VGAME.equals(extraDownloadType)
|
||
&& !Constants.DUAL_DOWNLOAD_VGAME.equals(extraDownloadType)
|
||
) {
|
||
filteredDownloadEntityList.add(downloadEntity);
|
||
}
|
||
} else {
|
||
if (downloadEntity.getStatus() != DownloadStatus.done) {
|
||
filteredDownloadEntityList.add(downloadEntity);
|
||
}
|
||
}
|
||
}
|
||
|
||
return filteredDownloadEntityList;
|
||
}
|
||
|
||
private ArrayList<DownloadEntity> filterDoneDownloadTask(List<DownloadEntity> downloadEntityList) {
|
||
ArrayList<DownloadEntity> filteredDownloadEntityList = new ArrayList<>();
|
||
|
||
if (downloadEntityList == null) return filteredDownloadEntityList;
|
||
|
||
for (DownloadEntity downloadEntity : downloadEntityList) {
|
||
if (downloadEntity.getStatus() != DownloadStatus.done) {
|
||
filteredDownloadEntityList.add(downloadEntity);
|
||
}
|
||
}
|
||
|
||
return filteredDownloadEntityList;
|
||
}
|
||
|
||
public ArrayMap<String, DownloadEntity> getEntryMap(String name) {
|
||
final ConcurrentHashMap<String, DownloadEntity> chm = gameMap.get(name);
|
||
if (chm == null) return null;
|
||
final ArrayMap<String, DownloadEntity> arrayMap = new ArrayMap<>();
|
||
for (String key : chm.keySet()) {
|
||
arrayMap.put(key, chm.get(key));
|
||
}
|
||
return arrayMap;
|
||
}
|
||
|
||
private void putStatus(String url, DownloadStatus status) {
|
||
statusMap.put(url, status);
|
||
}
|
||
|
||
public DownloadStatus getStatus(String url) {
|
||
return statusMap.get(url);
|
||
}
|
||
|
||
public ArrayMap<String, DownloadStatus> getStatusMap() {
|
||
return statusMap;
|
||
}
|
||
|
||
public void sendMessageDelayed(Message msg, long delayMillis) {
|
||
mHandler.sendMessageDelayed(msg, delayMillis);
|
||
}
|
||
|
||
/**
|
||
* 根据url取消下载,并删除已下载的文件
|
||
*/
|
||
public void cancel(String url) {
|
||
cancel(url, true, false, false);
|
||
}
|
||
|
||
/**
|
||
* 根据url取消下载,并删除已下载的文件
|
||
*
|
||
* @param automatic 是否是安装完自动删除
|
||
*/
|
||
public void cancel(String url, boolean isDeleteFile, boolean automatic, boolean cancelSilently) {
|
||
DownloadEntity entry = mDownloadDao.getSnapshot(url);
|
||
DownloadDataSimpleHelper.INSTANCE.removeDownloadSimpleEntity(url);
|
||
if (entry != null) {
|
||
AppExecutor.getIoExecutor().execute(() -> {
|
||
NDownloadBridge.INSTANCE.cancel(url);
|
||
|
||
mDownloadDao.delete(url);
|
||
// 避免非原子操作导致 snapshot 和 record 不一致,这里尝试清理 snapshot 的数据
|
||
mDownloadDao.update(entry, true);
|
||
|
||
if (isDeleteFile) {
|
||
FileUtils.deleteFile(entry.getPath());
|
||
}
|
||
Utils.log(DownloadManager.class.getSimpleName(), "cancel==>record were deleted!");
|
||
|
||
if (automatic) {
|
||
entry.getMeta().put(DownloadDataHelper.DOWNLOAD_CANCEL_WAY, DownloadDataHelper.DOWNLOAD_CANCEL_AUTO);
|
||
} else {
|
||
entry.getMeta().put(DownloadDataHelper.DOWNLOAD_CANCEL_WAY, DownloadDataHelper.DOWNLOAD_CANCEL_MANUAL);
|
||
}
|
||
|
||
initGameMap();
|
||
|
||
entry.setUpdate(false);
|
||
|
||
if (cancelSilently) {
|
||
entry.setStatus(DownloadStatus.cancel);
|
||
cancelAndNotify(entry, true);
|
||
return;
|
||
}
|
||
|
||
// 将原来安装完成后在 downloadService 完成的功能放到这里,避免因为 ANR 造成闪退
|
||
AppExecutor.getUiExecutor().executeWithDelay(() -> {
|
||
entry.setStatus(DownloadStatus.cancel);
|
||
cancelAndNotify(entry, false);
|
||
}, 0);
|
||
});
|
||
}
|
||
}
|
||
|
||
private void cancelAndNotify(DownloadEntity entry, boolean cancelSilently) {
|
||
mDownloadDao.removeErrorMessage(entry.getUrl());
|
||
|
||
// 改任务队列的状态
|
||
NDataChanger.INSTANCE.getDownloadingTaskUrlSet().remove(entry.getUrl());
|
||
NDataChanger.INSTANCE.getDownloadEntries().remove(entry.getUrl());
|
||
if (!cancelSilently) {
|
||
NDataChanger.INSTANCE.notifyDataChanged(entry);
|
||
onTaskCancelled(entry);
|
||
}
|
||
|
||
Utils.log(DownloadManager.class.getSimpleName(), "cancel");
|
||
}
|
||
|
||
/**
|
||
* 暂停所有正在下载的任务
|
||
*/
|
||
public void pauseAll() {
|
||
synchronized (NDataChanger.INSTANCE.getDownloadEntries()) {
|
||
for (DownloadEntity entity : NDataChanger.INSTANCE.getDownloadEntries().values()) {
|
||
pause(entity.getUrl());
|
||
}
|
||
}
|
||
Utils.log(DownloadManager.class.getSimpleName(), "pause all");
|
||
}
|
||
|
||
/**
|
||
* 根据任务url暂停下载
|
||
*/
|
||
public void pause(String url) {
|
||
checkDownloadEntryRecordValidate(url);
|
||
DownloadEntity entry = NDataChanger.INSTANCE.getDownloadEntries().get(url);
|
||
if (entry != null) {
|
||
startDownloadService(entry, DownloadStatus.pause);
|
||
put(url, System.currentTimeMillis());
|
||
statusMap.put(url, DownloadStatus.pause);
|
||
}
|
||
Utils.log(DownloadManager.class.getSimpleName(), "pause");
|
||
}
|
||
|
||
/**
|
||
* 初始化下载
|
||
* 1.修正下载状态(由于进程突然中断导致的状态异常)
|
||
* 2.启动下载服务
|
||
* 3.检查是否显示下载通知栏
|
||
*/
|
||
public void initDownloadService() {
|
||
final Set<String> urlSet = NDataChanger.INSTANCE.getDownloadingTaskUrlSet();
|
||
for (DownloadEntity downloadEntity : getAllDownloadEntity()) {
|
||
if (!urlSet.contains(downloadEntity.getUrl())
|
||
&& (downloadEntity.getStatus().equals(DownloadStatus.downloading)
|
||
|| downloadEntity.getStatus().equals(DownloadStatus.waiting))) {
|
||
downloadEntity.setStatus(DownloadStatus.subscribe);
|
||
mDownloadDao.newOrUpdate(downloadEntity);
|
||
NDataChanger.INSTANCE.notifyDataChanged(downloadEntity);
|
||
}
|
||
}
|
||
|
||
startDownloadService();
|
||
checkAndRetryDownload();
|
||
}
|
||
|
||
/**
|
||
* 添加下载观察者
|
||
*/
|
||
public void addObserver(DataWatcher dataWatcher) {
|
||
Utils.log(DownloadManager.class.getSimpleName(), "addObserver");
|
||
NDataChanger.INSTANCE.addObserver(dataWatcher);
|
||
|
||
notifyDownloadStatusASAP(dataWatcher);
|
||
}
|
||
|
||
/**
|
||
* 移除下载观察者
|
||
*/
|
||
public void removeObserver(DataWatcher dataWatcher) {
|
||
Utils.log(DownloadManager.class.getSimpleName(), "removeObserver");
|
||
NDataChanger.INSTANCE.deleteObserver(dataWatcher);
|
||
}
|
||
|
||
/**
|
||
* 立马通知 dataWatcher 更新下载任务状态
|
||
*/
|
||
private void notifyDownloadStatusASAP(DataWatcher dataWatcher) {
|
||
for (DownloadEntity downloadEntity : getAllDownloadEntitySnapshots()) {
|
||
dataWatcher.onDataInit(downloadEntity);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化下载服务
|
||
*/
|
||
public void startDownloadService() {
|
||
Intent serviceIntent = new Intent(mContext, NDownloadService.class);
|
||
// 当满足系统版本大于 8.0 并且应用在后台运行时以前台服务开启
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||
&& !PackageUtils.isAppOnForeground(mContext)) {
|
||
serviceIntent.putExtra(NDownloadService.KEY_SERVICE_ACTION, NDownloadService.START_FOREGROUND);
|
||
mContext.startForegroundService(serviceIntent);
|
||
} else {
|
||
/*
|
||
* sentry上报oppo手机无法启动服务,原因:OPPO手机自动熄屏一段时间后,会启用系统自带的电量优化管理,禁止一切自启动的APP(用户设置的自启动白名单除外),所以在服务启动的地方进行try/catch防止崩溃,
|
||
* https://sentry.ghzs.com/organizations/lightgame/issues/10707/?project=22&query=is%3Aunresolved+assigned%3Ame&statsPeriod=14d,
|
||
* https://stackoverflow.com/questions/38764497/security-exception-unable-to-start-service-user-0-is-restricted
|
||
*/
|
||
ExtensionsKt.tryWithDefaultCatch(() -> {
|
||
mContext.startService(serviceIntent);
|
||
return null;
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 启动下载服务
|
||
*/
|
||
private void startDownloadService(DownloadEntity downloadEntity, DownloadStatus status) {
|
||
// 在启动服务时添加该条下载的网络状态
|
||
if (status == DownloadStatus.add || status == DownloadStatus.resume) {
|
||
String network = DeviceUtils.getNetwork(mContext);
|
||
DownloadEntity daoEntity = mDownloadDao.get(downloadEntity.getUrl());
|
||
if (daoEntity != null) {
|
||
daoEntity.getMeta().put(DownloadEntity.NETWORK_STATUS_KEY, network);
|
||
mDownloadDao.newOrUpdate(daoEntity);
|
||
}
|
||
downloadEntity.getMeta().put(DownloadEntity.NETWORK_STATUS_KEY, network);
|
||
}
|
||
|
||
if (status == DownloadStatus.add || status == DownloadStatus.subscribe) {
|
||
DownloadDataSimpleEntity simpleEntity =
|
||
DownloadDataSimpleHelper.INSTANCE.getDownloadDataSimpleEntity(downloadEntity.getUrl());
|
||
|
||
if (simpleEntity == null || simpleEntity.isFirstTimeDownload() == null) {
|
||
DownloadDataSimpleHelper.INSTANCE.updateDownloadSimpleEntityFirstTimeDownload(downloadEntity.getUrl(), true);
|
||
}
|
||
}
|
||
|
||
Intent serviceIntent = getIntent(downloadEntity, status);
|
||
// 当满足系统版本大于 8.0 并且应用在后台运行时以前台服务开启
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||
&& !PackageUtils.isAppOnForeground(mContext)) {
|
||
serviceIntent.putExtra(NDownloadService.KEY_SERVICE_ACTION, NDownloadService.START_FOREGROUND);
|
||
mContext.startForegroundService(serviceIntent);
|
||
} else {
|
||
mContext.startService(serviceIntent);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查并尝试重试下载
|
||
*/
|
||
public void checkAndRetryDownload() {
|
||
if (!NetworkUtils.isWifiConnected(mContext)) return;
|
||
|
||
for (DownloadEntity downloadEntity : DownloadManager.getInstance().getAllDownloadEntityExcludeDoneTask()) {
|
||
if (DownloadStatus.neterror.equals(downloadEntity.getStatus())
|
||
|| DownloadStatus.timeout.equals(downloadEntity.getStatus())
|
||
|| DownloadStatus.subscribe.equals(downloadEntity.getStatus())) {
|
||
DownloadManager.getInstance().put(downloadEntity.getUrl(), System.currentTimeMillis());
|
||
Message msg = Message.obtain();
|
||
msg.what = DownloadConfig.CONTINUE_DOWNLOAD_AUTO_TASK;
|
||
msg.obj = downloadEntity.getUrl();
|
||
DownloadManager.getInstance().sendMessageDelayed(msg, 1000);
|
||
}
|
||
Utils.log("checkAndRetryDownload::" + downloadEntity.getStatus());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取 Toolbar 需要显示的下载管理红点的数字
|
||
*
|
||
* @param updateList 更新/插件化数据
|
||
* @return null: 不显示小红点
|
||
*/
|
||
@Nullable
|
||
public String getDownloadOrUpdateCount(List<GameUpdateEntity> updateList) {
|
||
boolean containsReadDownloadingTask = false; // 存在已读的下载中任务
|
||
boolean showRedPoint = false;
|
||
int downloadingSize = 0;
|
||
|
||
for (DownloadEntity downloadEntity : getAllDownloadEntityExcludeSilentTask()) {
|
||
if (DownloadStatus.done.equals(downloadEntity.getStatus())) {
|
||
String mark = downloadEntity.getMeta().get(DOWNLOADED_IS_READ_MARK);
|
||
if (TextUtils.isEmpty(mark)) showRedPoint = true;
|
||
} else {
|
||
// 存在已读的下载中任务就直接不返回下载中数量,因为在新建下载时就执行了将所有下载中任务的已读变为未读的操作
|
||
if (!TextUtils.isEmpty(downloadEntity.getMeta().get(DOWNLOADING_IS_READ_MARK))) {
|
||
containsReadDownloadingTask = true;
|
||
}
|
||
downloadingSize++;
|
||
}
|
||
}
|
||
|
||
if (downloadingSize != 0 && !containsReadDownloadingTask) {
|
||
return String.valueOf(downloadingSize);
|
||
}
|
||
|
||
if (showRedPoint) return "";
|
||
|
||
if (updateList != null) {
|
||
// 首页永久忽略的插件化列表
|
||
List<HomePluggableFilterEntity> permanentInactiveUpdateList = HomePluggableHelper.getPermanentInactivePluggablePackage();
|
||
for (GameUpdateEntity updateEntity : updateList) {
|
||
if (updateEntity.isShowPlugin(PluginLocation.only_index) && !mUpdateMarks.contains(updateEntity.getId() + updateEntity.getPackageName())) {
|
||
// 判断该更新的的包名是否被永久忽略
|
||
if (permanentInactiveUpdateList != null) {
|
||
boolean isPluggablePermanentInactive = false;
|
||
for (HomePluggableFilterEntity filterEntity : permanentInactiveUpdateList) {
|
||
if (filterEntity.getPkgName().equals(updateEntity.getPackageName())) {
|
||
isPluggablePermanentInactive = true;
|
||
}
|
||
}
|
||
if (!isPluggablePermanentInactive) {
|
||
return "";
|
||
}
|
||
} else {
|
||
return "";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 获取未读更新的数量
|
||
*
|
||
* @param updateList 可更新&插件化实体列表
|
||
* @return 未读更新的数量
|
||
*/
|
||
public int getUnreadUpdateCount(List<GameUpdateEntity> updateList) {
|
||
int unreadUpdateCount = 0;
|
||
if (updateList != null) {
|
||
for (GameUpdateEntity updateEntity : updateList) {
|
||
if (updateEntity.isShowPlugin(PluginLocation.only_index)
|
||
&& !mUpdateMarks.contains(updateEntity.getId() + updateEntity.getPackageName())) {
|
||
unreadUpdateCount++;
|
||
}
|
||
}
|
||
}
|
||
return unreadUpdateCount;
|
||
}
|
||
|
||
/**
|
||
* @return 是否存在未读的下载完成任务
|
||
*/
|
||
public boolean isContainsUnreadDownloadedTask() {
|
||
for (DownloadEntity downloadEntity : getAllDownloadEntityExcludeSilentTask()) {
|
||
if (DownloadStatus.done.equals(downloadEntity.getStatus())) {
|
||
String mark = downloadEntity.getMeta().get(DOWNLOADED_IS_READ_MARK);
|
||
if (TextUtils.isEmpty(mark)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 标记下载已完成的任务为已读 (用于下载管理页入口的 toolbar 红点显示)
|
||
*/
|
||
public void markDownloadedTaskAsRead() {
|
||
AppExecutor.getIoExecutor().execute(() -> {
|
||
boolean markHasChanged = false;
|
||
|
||
List<DownloadEntity> all = getAllDownloadEntityExcludeSilentTask();
|
||
for (DownloadEntity downloadEntity : all) {
|
||
DownloadStatus status = downloadEntity.getStatus();
|
||
if (status == DownloadStatus.done) {
|
||
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.update(downloadEntity, false);
|
||
if (!markHasChanged) markHasChanged = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (markHasChanged) {
|
||
EventBus.getDefault().post(new EBDownloadStatus("download", "", "", "", "", ""));
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 标记下载中任务为已读状态(用于下载页及外部toolbar)
|
||
*/
|
||
public void markDownloadingTaskAsRead() {
|
||
markDownloadingTaskAsReadOrUnread(true);
|
||
}
|
||
|
||
/**
|
||
* 标记下载中任务为未读状态(用于下载页及外部toolbar)
|
||
*/
|
||
public void markDownloadingTaskAsUnread() {
|
||
markDownloadingTaskAsReadOrUnread(false);
|
||
}
|
||
|
||
/**
|
||
* 更改下载中任务的已读状态(用于下载页及外部toolbar)
|
||
*/
|
||
private void markDownloadingTaskAsReadOrUnread(boolean isRead) {
|
||
AppExecutor.getIoExecutor().execute(() -> {
|
||
boolean markHasChanged = false;
|
||
|
||
List<DownloadEntity> all = getAllDownloadEntityExcludeSilentTask();
|
||
for (DownloadEntity downloadEntity : all) {
|
||
if (downloadEntity.getStatus() != DownloadStatus.done) {
|
||
if (isRead) {
|
||
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.update(downloadEntity, false);
|
||
if (!markHasChanged) markHasChanged = true;
|
||
}
|
||
} else {
|
||
downloadEntity.getMeta().put(DOWNLOADING_IS_READ_MARK, "");
|
||
mDownloadDao.update(downloadEntity, false);
|
||
if (!markHasChanged) markHasChanged = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (markHasChanged) {
|
||
EventBus.getDefault().post(new EBDownloadStatus("download", "", "", "", "", ""));
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 标记可用更新为已读 (用于下载管理页入口的 toolbar 红点显示)
|
||
*/
|
||
public void markUpdatableTaskAsRead() {
|
||
AppExecutor.getIoExecutor().execute(() -> {
|
||
boolean markHasChanged = false;
|
||
|
||
ArrayList<GameUpdateEntity> updates = PackageRepository.INSTANCE.getGameUpdate();
|
||
for (GameUpdateEntity update : updates) {
|
||
if (update == null) continue;
|
||
String mark = update.getId() + update.getPackageName();
|
||
if (!mUpdateMarks.contains(mark)) {
|
||
mUpdateMarks.add(mark);
|
||
if (!markHasChanged) markHasChanged = true;
|
||
}
|
||
}
|
||
|
||
if (markHasChanged) {
|
||
saveUpdateMarkToStorage();
|
||
EventBus.getDefault().post(new EBDownloadStatus("download", "", "", "", "", ""));
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 将可用更新标记为已读的事件
|
||
*/
|
||
public void saveUpdateMarkToStorage() {
|
||
ArrayList<GameUpdateEntity> updates = PackageRepository.INSTANCE.getGameUpdate();
|
||
if (updates.size() == mUpdateMarks.size()) {
|
||
SPUtils.setStringSet(UPDATE_IS_READ_MARK, mUpdateMarks);
|
||
return;
|
||
}
|
||
|
||
Set<String> marks = new HashSet<>();
|
||
for (GameUpdateEntity update : updates) {
|
||
String mark = update.getId() + update.getPackageName();
|
||
marks.add(mark);
|
||
}
|
||
SPUtils.setStringSet(UPDATE_IS_READ_MARK, marks);
|
||
}
|
||
|
||
/**
|
||
* 更新数据库中的下载实体
|
||
*
|
||
* @deprecated 随意更新数据库可能会出现意想不到的情况,建议不要随意调用
|
||
*/
|
||
@Deprecated()
|
||
public void updateDownloadEntity(DownloadEntity downloadEntity) {
|
||
mDownloadDao.update(downloadEntity, false);
|
||
}
|
||
|
||
/**
|
||
* 恢复(重试)下载
|
||
*
|
||
* @param url 下载地址
|
||
*/
|
||
public void resumeDownload(String url) {
|
||
put(url, System.currentTimeMillis());
|
||
Message msg = Message.obtain();
|
||
msg.what = DownloadConfig.CONTINUE_DOWNLOAD_AUTO_TASK;
|
||
msg.obj = url;
|
||
sendMessageDelayed(msg, 1000);
|
||
}
|
||
|
||
/**
|
||
* 添加不可见的 pending 下载任务
|
||
*
|
||
* @param downloadEntity 任务信息实体
|
||
*/
|
||
public void addInvisiblePendingTask(DownloadEntity downloadEntity) {
|
||
boolean isAlreadyExist = false;
|
||
for (DownloadEntity existTask : mInvisiblePendingTaskList) {
|
||
if (existTask.getUrl().equals(downloadEntity.getUrl())) {
|
||
isAlreadyExist = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!isAlreadyExist) {
|
||
mInvisiblePendingTaskList.add(downloadEntity);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 启动所有不可见的 pending 下载任务
|
||
*/
|
||
public void resumeAllInvisiblePendingTask() {
|
||
for (DownloadEntity task : mInvisiblePendingTaskList) {
|
||
if ("false".equals(task.getMeta().get("force_real_name"))) {
|
||
add(task, false);
|
||
}
|
||
}
|
||
mInvisiblePendingTaskList.clear();
|
||
}
|
||
|
||
public LiveData<List<DownloadEntity>> getDownloadEntityLiveData() {
|
||
return mDownloadEntityListLiveData;
|
||
}
|
||
|
||
/**
|
||
* 手动触发下载 LiveData 变更
|
||
*/
|
||
public void notifyDownloadLiveDataChanged() {
|
||
AppExecutor.getIoExecutor().execute(() -> mDownloadEntityListLiveData.postValue(getAllDownloadEntity()));
|
||
}
|
||
|
||
/**
|
||
* 更新下载请求头的相关信息
|
||
*/
|
||
public static void updateDownloadMetaMap() {
|
||
String isOverwrite;
|
||
String installType = SPUtils.getString(Constants.SP_INSTALL_TYPE);
|
||
if (LunchType.UPDATE.name().equals(installType)) {
|
||
isOverwrite = "true";
|
||
} else {
|
||
isOverwrite = "false";
|
||
}
|
||
|
||
HashMap<String, String> map = new HashMap<>();
|
||
map.put(HttpDnsManager.APP_VERSION, BuildConfig.VERSION_NAME);
|
||
map.put(HttpDnsManager.CHANNEL, HaloApp.getInstance().getChannel());
|
||
map.put(HttpDnsManager.GID, HaloApp.getInstance().getGid());
|
||
map.put(HttpDnsManager.OAID, HaloApp.getInstance().getOAID());
|
||
map.put(HttpDnsManager.USER_ID, UserManager.getInstance().getUserId());
|
||
map.put(HttpDnsManager.IMEI, MetaUtil.getBase64EncodedIMEI());
|
||
map.put(HttpDnsManager.TOKEN, UserManager.getInstance().getToken());
|
||
map.put(HttpDnsManager.IS_OVERWRITE, isOverwrite);
|
||
map.put(HttpDnsManager.INSTALL_TYPE, installType);
|
||
map.put(HttpDnsManager.ANDROID_ID, MetaUtil.getBase64EncodedAndroidId());
|
||
map.put(HttpDnsManager.ANDROID_SDK_VERSION, String.valueOf(Build.VERSION.SDK_INT));
|
||
|
||
HttpDnsManager.metaMap = map;
|
||
}
|
||
|
||
private static class SingletonHolder {
|
||
private static final DownloadManager INSTANCE = new DownloadManager();
|
||
}
|
||
}
|