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 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 webViewAbiList; private IFlavorProvider mFlavorProvider = new FlavorProviderImp(); private final ServiceLoader 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 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 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(); } }