Files
assistant-android/app/src/main/java/com/halo/assistant/HaloApp.java

595 lines
22 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.halo.assistant;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Build;
import android.os.StrictMode;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.collection.ArrayMap;
import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.multidex.MultiDexApplication;
import androidx.webkit.WebViewCompat;
import com.alibaba.android.arouter.launcher.ARouter;
import com.facebook.animated.giflite.GifDecoder;
import com.facebook.imageformat.DefaultImageFormats;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
import com.facebook.imagepipeline.core.ImagePipelineFactory;
import com.facebook.imagepipeline.core.ImageTranscoderType;
import com.facebook.imagepipeline.core.MemoryChunkType;
import com.facebook.imagepipeline.decoder.ImageDecoderConfig;
import com.gh.base.GlobalActivityLifecycleObserver;
import com.gh.common.FixedRateJobHelper;
import com.gh.common.filter.RegionSettingHelper;
import com.gh.common.util.DataUtils;
import com.gh.common.util.DownloadNotificationHelper;
import com.gh.common.util.DownloadObserver;
import com.gh.common.util.HomeBottomBarHelper;
import com.gh.common.util.PackageHelper;
import com.gh.common.util.PackageUtils;
import com.gh.common.videolog.VideoRecordUtils;
import com.gh.download.simple.DownloadMessageHandler;
import com.gh.download.simple.SimpleDownloadDatabase;
import com.gh.download.simple.SimpleDownloadManager;
import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.Injection;
import com.gh.gamecenter.common.constant.Config;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.common.exposure.meta.MetaUtil;
import com.gh.gamecenter.common.image.EmptyDecoder;
import com.gh.gamecenter.common.tracker.Tracker;
import com.gh.gamecenter.common.utils.DarkModeUtils;
import com.gh.gamecenter.common.utils.DeviceUtils;
import com.gh.gamecenter.common.utils.ExtensionsKt;
import com.gh.gamecenter.common.utils.ImageUtils;
import com.gh.gamecenter.common.utils.PackageFlavorHelper;
import com.gh.gamecenter.core.AppExecutor;
import com.gh.gamecenter.core.iinterface.IApplication;
import com.gh.gamecenter.core.utils.DisplayUtils;
import com.gh.gamecenter.core.utils.GsonUtils;
import com.gh.gamecenter.core.utils.SPUtils;
import com.gh.gamecenter.entity.SubjectRecommendEntity;
import com.gh.gamecenter.fragment.MainWrapperRepository;
import com.gh.gamecenter.login.user.UserRepository;
import com.gh.gamecenter.oaid.OAIDHelper;
import com.gh.gamecenter.packagehelper.PackageRepository;
import com.gh.gamecenter.core.provider.IFlavorProvider;
import com.gh.gamecenter.provider.FlavorProviderImp;
import com.gh.gamecenter.receiver.ActivitySkipReceiver;
import com.gh.gamecenter.receiver.DownloadReceiver;
import com.gh.gamecenter.receiver.InstallAndUninstallReceiver;
import com.gh.gamecenter.receiver.InstallReceiver;
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;
import com.shuyu.gsyvideoplayer.player.PlayerFactory;
import java.lang.reflect.Method;
import java.util.List;
import java.util.ServiceLoader;
import io.reactivex.plugins.RxJavaPlugins;
import tv.danmaku.ijk.media.exo2.Exo2PlayerManager;
import tv.danmaku.ijk.media.exo2.ExoPlayerCacheManager;
public class HaloApp extends MultiDexApplication {
private static HaloApp mInstance;
private static final ArrayMap<String, Object> sObjectMap = new ArrayMap<>();
private String mChannel;
private String mGid;
private String mTemporaryLocalDeviceId; // 临时设备ID (本地生成与GID不相关)
private String mUA;
private String mOAID = "";
private String mServerUserMark = ""; // 服务端用来标记用户是新/老的标记,有 new 和 old 两个,获取不到时为空
private boolean mIsPostInitialized = false; // 是否已经延迟初始化过相关组件,避免重复初始化
private boolean mIsReinstallTheSameVersion = false; // 当前用户是否重新安装了一次当前版本后的第一次打开
public long deviceRamSize = 0;
public boolean isBrandNewInstall = false; // 当前用户是否是安装光环后第一次打开
public boolean isNewForThisVersion = false; // 当前用户是否是安装当前版本后第一次打开 (包括全新和更新)
public boolean isRunningForeground = false; // 标记当前 APP 是否处于前台运行中
public boolean isAlreadyUpAndRunning = false; // 应用是否处于运行中状态 (进入到 MainActivity 就当运行中)
private List<String> webViewAbiList;
private IFlavorProvider mFlavorProvider = new FlavorProviderImp();
private final ServiceLoader<IApplication> mApplicationList = ServiceLoader.load(IApplication.class, this.getClass().getClassLoader());
public static void put(String key, Object object) {
sObjectMap.put(key, object);
}
public static Object get(String key, boolean isRemove) {
if (isRemove) {
return sObjectMap.remove(key);
} else {
return sObjectMap.get(key);
}
}
public static void remove(String key) {
sObjectMap.remove(key);
}
public void setGid(String gid) {
mGid = gid;
}
public String getGid() {
return mGid;
}
public void refreshGid() {
DataUtils.getGid();
}
public void setLocalTemporaryDeviceId(String key) {
mTemporaryLocalDeviceId = key;
}
public String getTemporaryLocalDeviceId() {
return mTemporaryLocalDeviceId;
}
public void setServerUserMark(String mark) {
mServerUserMark = mark;
}
public String getServerUserMark() {
return mServerUserMark;
}
public static synchronized HaloApp getInstance() {
return mInstance;
}
public String getChannel() {
// 存在 IO 初始化线程阻塞(万物皆可阻塞)导致 mChannel 为空的情况,这里特殊处理下
if (TextUtils.isEmpty(mChannel)) {
return "";
} else {
return mChannel;
}
}
public void setChannel(String channel) {
mChannel = channel;
}
public void setOAID(String oaid) {
mOAID = oaid;
}
public String getOAID() {
return mOAID;
}
public String getUserAgent() {
if (TextUtils.isEmpty(mUA)) {
mUA = DeviceUtils.getUserAgent();
}
return mUA;
}
public List<String> getWebViewAbiList() {
return webViewAbiList;
}
@Override
public void onCreate() {
super.onCreate();
initArouter();
if (!Injection.appInit(this)) {
return;
}
mInstance = this;
// 每个进程都用自己的进程名作为后缀的文件夹来存 WebView cache
// https://sentry.shanqu.cc/organizations/lightgame/issues/285063/?project=22&query=is%3Aunresolved
// https://stackoverflow.com/a/61748345/4812571 这个无效
// WebView.disableWebView() 也无效
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try {
String process = getProcessName();
WebView.setDataDirectorySuffix(process);
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
for (IApplication application : mApplicationList) {
application.onCreate(mInstance);
}
// 似乎只是 load SO 不涉及方法调用,所以可以在隐私政策前调用吧?
OAIDHelper.INSTANCE.doSystemLoad();
// 70ms
PlayerFactory.setPlayManager(Exo2PlayerManager.class);
CacheFactory.setCacheManager(ExoPlayerCacheManager.class);
AppExecutor.getIoExecutor().execute(() -> {
initDataHelper();
ExtensionsKt.doOnMainProcessOnly(this, () -> Tracker.init(this));
initFresco();
deviceRamSize = DeviceUtils.getTotalRamSizeOfDevice(this);
mChannel = mFlavorProvider.getChannelStr(this);
// 异步初始化 SP
SPUtils.getString("");
DownloadDbManager.init(this);
});
RxJavaPlugins.setIoSchedulerHandler(scheduler -> AppExecutor.INSTANCE.getCachedScheduler());
if (isUserAcceptPrivacyPolicy(this)) {
postInit(false);
}
//设置严格模式
if (BuildConfig.DEBUG) {
StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build();
StrictMode.setVmPolicy(policy);
}
// debug 包和 jenkins 内部包启用 dokit
if (PackageFlavorHelper.IS_TEST_FLAVOR) {
try {
Class<?> clazz = Class.forName("com.didichuxing.doraemonkit.DoraemonKit");
Method method = clazz.getMethod("install", Application.class);
method.invoke(null, this);
} catch (Exception e) {
e.printStackTrace();
}
}
registerActivityLifecycleCallbacks(new GlobalActivityLifecycleObserver());
DarkModeUtils.INSTANCE.initDarkMode();
}
/**
* 需要延迟初始化的东西,以下代码调用都放置到用户同意了隐私政策之后
*/
public void postInit(boolean initImmediately) {
if (mIsPostInitialized) return;
long delay = 500;
if (initImmediately) {
delay = 0;
}
postInit(delay);
mIsPostInitialized = true;
}
private void postInit(long delay) {
// 初始化 sentry
DataUtils.init(this, mChannel);
// 获取/更新 GID 和 读 SP 的操作不需要 delay
DataUtils.getGid();
OAIDHelper.INSTANCE.getOAID(this, s -> {
setOAID(s);
MetaUtil.INSTANCE.refreshMeta();
return null;
});
// 获取 settings 配置
ExtensionsKt.doOnMainProcessOnly(this, com.gh.common.constant.Config::getGhzsSettings);
String localTemporaryDeviceId = SPUtils.getString(Constants.SP_TEMPORARY_DEVICE_ID);
if (!TextUtils.isEmpty(localTemporaryDeviceId)) {
HaloApp.getInstance().setLocalTemporaryDeviceId(localTemporaryDeviceId);
}
// 刷新内存中的用户信息,避免应用进程重建时因没有用户信息数据而显示为掉登录状态
// 必须放在外面,否则不能及时刷新用户数据
UserRepository.getInstance().getLoginUserInfo();
MainWrapperRepository.Companion.getInstance().getHomeNavBar();
AppExecutor.getUiExecutor().executeWithDelay(() -> {
FixedRateJobHelper.begin();
RegionSettingHelper.getRegionSetting();
ExtensionsKt.doOnMainProcessOnly(this, () -> {
PackageRepository.initData();
PackageHelper.refreshLocalPackageList();
PackageHelper.initList();
});
initReceiver();
initPackageChangesReceiver();
initConnectivityChangesReceiver();
initTimeConsumingAction();
getWebviewAbiList();
// 注册回调以用于做各种统计
ProcessLifecycleOwner.get().getLifecycle().addObserver(new ProcessorLifeCycleOwner());
// 初始化畅玩相关数据
ExtensionsKt.doOnMainProcessOnly(this, this::retrieveVGameInfoIfNeeded);
// 开发环境不要强制捕获相关异常,这些异常通常是需要处理的
if (!BuildConfig.DEBUG) {
RxJavaPlugins.setErrorHandler(throwable -> {
if (Config.DEFAULT_CHANNEL.equals(mChannel)) {
Utils.toast(this, "当前页面未捕获的异常:" + throwable.toString());
}
});
}
}, delay);
}
private void initArouter() {
if (BuildConfig.DEBUG) { // 这两行必须写在init之前否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行必须开启调试模式线上版本需要关闭,否则有安全风险)
}
ARouter.init(this); // 尽可能早推荐在Application中初始化
}
public void getWebviewAbiList() {
int versionCode = SPUtils.getInt(Constants.SP_WEBVIEW_VERSION_CODE);
final PackageInfo webviewPackageInfo = WebViewCompat.getCurrentWebViewPackage(this);
if (webviewPackageInfo == null) return;
int webviewVersionCode = webviewPackageInfo.versionCode;
String json = SPUtils.getString(Constants.SP_WEBVIEW_ABI_LIST);
if (versionCode == webviewVersionCode && !json.isEmpty()) {
try {
webViewAbiList = GsonUtils.fromJsonList(json);
return;
} catch (AssertionError e) {
e.printStackTrace();
}
}
AppExecutor.getIoExecutor().execute(() -> {
final String webviewPath = PackageUtils.getWebviewPath(this);
if (webviewPath == null) return;
final List<String> abiList = PackageUtils.getApkAbiList(webviewPath);
webViewAbiList = abiList;
SPUtils.setString(Constants.SP_WEBVIEW_ABI_LIST, GsonUtils.toJson(abiList));
SPUtils.setInt(Constants.SP_WEBVIEW_VERSION_CODE, webviewVersionCode);
});
}
private void initDataHelper() {
VideoRecordUtils.init(this, AppExecutor.getLogExecutor());
}
// todo 动态注册和静态注册并行的话会触发多次回调
// 3.5 开始将 targetSdk 升级至 26原来写在 Manifest 的部分 receiver 由于系统限制需要换成在运行时注册
private void initReceiver() {
DownloadReceiver downloadReceiver = new DownloadReceiver();
IntentFilter downloadFilter = new IntentFilter();
downloadFilter.addAction(DownloadNotificationHelper.ACTION_DOWNLOAD);
downloadFilter.addAction(DownloadNotificationHelper.ACTION_VDOWNLOAD);
this.registerReceiver(downloadReceiver, downloadFilter);
InstallReceiver installReceiver = new InstallReceiver();
IntentFilter installFilter = new IntentFilter();
installFilter.addAction(DownloadNotificationHelper.ACTION_INSTALL);
this.registerReceiver(installReceiver, installFilter);
ActivitySkipReceiver skipReceiver = new ActivitySkipReceiver();
IntentFilter skipFilter = new IntentFilter();
skipFilter.addAction(ActivitySkipReceiver.ACTION_ACTIVITY_SKIP);
this.registerReceiver(skipReceiver, skipFilter);
}
private void initPackageChangesReceiver() {
InstallAndUninstallReceiver receiver = new InstallAndUninstallReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
this.registerReceiver(receiver, intentFilter);
}
private void initConnectivityChangesReceiver() {
NetworkStateReceiver receiver = new NetworkStateReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
this.registerReceiver(receiver, intentFilter);
}
private void initTimeConsumingAction() {
AppExecutor.getIoExecutor().execute(() -> {
// 初始化全局下载监听
DownloadObserver.initObserver();
if ("miui".equals(Build.MANUFACTURER) && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
DisplayUtils.sShouldUseLegacyMiuiStatusBarMethod = true;
}
// 避免在华为设备上出现 `Register too many Broadcast Receivers` 异常,可见 https://github.com/llew2011/HuaWeiVerifier
LoadedApkHuaWei.hookHuaWeiVerifier(this);
SimpleDownloadManager.INSTANCE.init();
DownloadMessageHandler.INSTANCE.init(SimpleDownloadDatabase.getInstance().downloadDao());
// 预加载游戏库图标
SubjectRecommendEntity barData = HomeBottomBarHelper.getDefaultGameBarData();
if (!TextUtils.isEmpty(barData.getIconSelect())) {
ImageUtils.getPicasso().load(Uri.parse(barData.getIconSelect())).fetch();
}
if (!TextUtils.isEmpty(barData.getIconUnselect())) {
ImageUtils.getPicasso().load(Uri.parse(barData.getIconUnselect())).fetch();
}
});
}
public void initFresco() {
// 初始化 Fresco(BigImageViewer 已包含Fresco)
if (!ImageUtils.isFrescoInitialized()) {
// 在 5.0 & 5.1 设备上 disable native code原因是应用附带了 arm64 的 SO 以后在部分 5.0/5.1 设备
// 会出现找不到 arm64 so 的情况,具体可见
// https://sentry.ghzs.com/organizations/lightgame/issues/53107/
// 所以这里尝试在 5.0 & 5.1 设备上关闭 fresco 的 native 解码,应该会让 5.0 & 5.1 的设备 OOM 概率提高,但先试试效果
// 同时禁用动图
ImagePipelineConfig.Builder pipelineConfigBuilder = ImagePipelineConfig.newBuilder(this);
ImageDecoderConfig.Builder decodeConfigBuilder = ImageDecoderConfig.newBuilder();
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) {
pipelineConfigBuilder.setMemoryChunkType(MemoryChunkType.BUFFER_MEMORY)
.setImageTranscoderType(ImageTranscoderType.JAVA_TRANSCODER);
decodeConfigBuilder.overrideDecoder(DefaultImageFormats.GIF, new GifDecoder()).build();
String manufacture = Build.MANUFACTURER.toLowerCase();
// OPPO 和 VIVO 的 5.1.1 设备还会去加载 WEBP_ANIMATED 的 SO
// 实测没有发现有地方使用 WEBP_ANIMATED 的图片,这里用空占位图来替换 WEBP 动图
if ("oppo".equals(manufacture) || "vivo".equals(manufacture)) {
decodeConfigBuilder.overrideDecoder(DefaultImageFormats.WEBP_ANIMATED, new EmptyDecoder()).build();
}
pipelineConfigBuilder
.setImageDecoderConfig(decodeConfigBuilder.build())
.experiment()
.setNativeCodeDisabled(true);
// 图片仅加载静态图片
ImageUtils.disableAnimatedImage();
}
try {
BigImageViewer.initialize(FrescoImageLoader.with(this, pipelineConfigBuilder.build()));
} catch (Throwable e) {
e.printStackTrace();
}
}
}
private void retrieveVGameInfoIfNeeded() {
VHelper.init(this);
}
public boolean isReinstallTheSameVersion() {
return mIsReinstallTheSameVersion;
}
public void setIsReinstallTheSameVersion(boolean value) {
mIsReinstallTheSameVersion = value;
}
public Application getApplication() {
return this;
}
/**
* 判断当前用户是否是已经同意过隐私政策协议
* 覆盖安装进入过首页的也当作是同意了
*/
public static boolean isUserAcceptPrivacyPolicy(Context context) {
return !PreferenceManager.getDefaultSharedPreferences(context).getBoolean("isNewFirstLaunchV" + PackageUtils.getGhVersionName(), true)
|| !SPUtils.getBooleanWithContext(context, Constants.SP_BRAND_NEW_USER, true)
|| SPUtils.getBooleanWithContext(context, Constants.SP_IS_USER_ACCEPTED_PRIVACY_STATEMENT, false);
}
public IFlavorProvider getFlavorProvider() {
return mFlavorProvider;
}
// @NonNull
// @Override
// public Configuration getWorkManagerConfiguration() {
// return new Configuration.Builder().build();
// }
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
for (IApplication application : mApplicationList) {
application.attachBaseContext();
}
}
@Override
public void onLowMemory() {
super.onLowMemory();
clearMemoryCaches();
for (IApplication application : mApplicationList) {
application.onLowMemory();
}
}
@Override
public void onConfigurationChanged(@NonNull android.content.res.Configuration newConfig) {
super.onConfigurationChanged(newConfig);
for (IApplication application : mApplicationList) {
application.onConfigurationChanged(newConfig);
}
}
@Override
public void onTerminate() {
super.onTerminate();
for (IApplication application : mApplicationList) {
application.onTerminate();
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= TRIM_MEMORY_MODERATE) {
clearMemoryCaches();
}
for (IApplication application : mApplicationList) {
application.onTrimMemory(level);
}
}
private void clearMemoryCaches() {
// 有一定机率在 ImagePipelineFactory 还没完成初始化就被调用,
// 直接粗暴地 catch 异常 "ImagePipelineFactory was not initialized!"
// 毕竟这里只是内存不足时候的操作,不用保证执行结果
try {
ImagePipelineFactory.getInstance().getImagePipeline().clearMemoryCaches();
} catch (NullPointerException e) {
e.printStackTrace();
}
ImageUtils.getPicassoLruCache().clear();
ImageUtils.clearUrlCaches();
}
}