commit c06678d72a12c7f5bac3e8080e805da07f49f8fd
parent 5fd6aec2566b11ebc0581b1e036beafb11409027
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sun,  1 Oct 2023 12:29:02 +0200

refactor: init optimization

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt | 5+++--
Mcore/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt | 13+++++++------
Mcore/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt | 53++++++++++++++++++++++++++++-------------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/action/impl/ExportChatMessages.kt | 9+++------
Mcore/src/main/kotlin/me/rhunk/snapenhance/features/impl/ScopeSync.kt | 7++++---
Mcore/src/main/kotlin/me/rhunk/snapenhance/features/impl/spying/MessageLogger.kt | 10++++------
Mcore/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
7 files changed, 115 insertions(+), 76 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt b/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt @@ -26,12 +26,12 @@ import me.rhunk.snapenhance.e2ee.E2EEImplementation import me.rhunk.snapenhance.messaging.ModDatabase import me.rhunk.snapenhance.messaging.StreaksReminder import me.rhunk.snapenhance.scripting.RemoteScriptManager -import me.rhunk.snapenhance.ui.overlay.SettingsOverlay import me.rhunk.snapenhance.ui.manager.MainActivity import me.rhunk.snapenhance.ui.manager.data.InstallationSummary import me.rhunk.snapenhance.ui.manager.data.ModInfo import me.rhunk.snapenhance.ui.manager.data.PlatformInfo import me.rhunk.snapenhance.ui.manager.data.SnapchatAppInfo +import me.rhunk.snapenhance.ui.overlay.SettingsOverlay import me.rhunk.snapenhance.ui.setup.Requirements import me.rhunk.snapenhance.ui.setup.SetupActivity import java.io.ByteArrayInputStream @@ -43,6 +43,8 @@ import java.security.cert.X509Certificate class RemoteSideContext( val androidContext: Context ) { + val coroutineScope = CoroutineScope(Dispatchers.IO) + private var _activity: WeakReference<ComponentActivity>? = null lateinit var bridgeService: BridgeService @@ -78,7 +80,6 @@ class RemoteSideContext( } .components { add(VideoFrameDecoder.Factory()) }.build() } - val coroutineScope = CoroutineScope(Dispatchers.IO) fun reload() { log.verbose("Loading RemoteSideContext") diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt b/core/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt @@ -11,6 +11,9 @@ import android.os.Process import android.widget.Toast import com.google.gson.Gson import com.google.gson.GsonBuilder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import me.rhunk.snapenhance.core.Logger import me.rhunk.snapenhance.core.bridge.BridgeClient import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper @@ -27,13 +30,11 @@ import me.rhunk.snapenhance.manager.impl.FeatureManager import me.rhunk.snapenhance.nativelib.NativeConfig import me.rhunk.snapenhance.nativelib.NativeLib import me.rhunk.snapenhance.scripting.core.CoreScriptRuntime -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors import kotlin.reflect.KClass import kotlin.system.exitProcess class ModContext { - private val executorService: ExecutorService = Executors.newCachedThreadPool() + val coroutineScope = CoroutineScope(Dispatchers.IO) lateinit var androidContext: Context lateinit var bridgeClient: BridgeClient @@ -73,13 +74,13 @@ class ModContext { } } - fun executeAsync(runnable: ModContext.() -> Unit) { - executorService.submit { + fun executeAsync(runnable: suspend ModContext.() -> Unit) { + coroutineScope.launch { runCatching { runnable() }.onFailure { longToast("Async task failed " + it.message) - Logger.xposedLog("Async task failed", it) + log.error("Async task failed", it) } } } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/SnapEnhance.kt @@ -3,6 +3,10 @@ package me.rhunk.snapenhance import android.app.Activity import android.app.Application import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import me.rhunk.snapenhance.bridge.SyncCallback import me.rhunk.snapenhance.core.Logger import me.rhunk.snapenhance.core.bridge.BridgeClient @@ -13,8 +17,7 @@ import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo import me.rhunk.snapenhance.data.SnapClassCache import me.rhunk.snapenhance.hook.HookStage import me.rhunk.snapenhance.hook.hook -import kotlin.time.ExperimentalTime -import kotlin.time.measureTime +import kotlin.system.measureTimeMillis class SnapEnhance { @@ -55,7 +58,13 @@ class SnapEnhance { return@connect } runCatching { - init() + measureTimeMillis { + runBlocking { + init(this) + } + }.also { + appContext.log.verbose("init took ${it}ms") + } }.onSuccess { isBridgeInitialized = true }.onFailure { @@ -92,34 +101,28 @@ class SnapEnhance { } } - @OptIn(ExperimentalTime::class) - private fun init() { - measureTime { - with(appContext) { - reloadConfig() + private fun init(scope: CoroutineScope) { + with(appContext) { + reloadConfig() + scope.launch(Dispatchers.IO) { initNative() - executeAsync { - translation.userLocale = getConfigLocale() - translation.loadFromBridge(bridgeClient) - } - - mappings.loadFromBridge(bridgeClient) - mappings.init(androidContext) - eventDispatcher.init() - //if mappings aren't loaded, we can't initialize features - if (!mappings.isMappingsLoaded()) return - features.init() - syncRemote() - scriptRuntime.connect(bridgeClient.getScriptingInterface()) + translation.userLocale = getConfigLocale() + translation.loadFromBridge(bridgeClient) } - }.also { time -> - appContext.log.verbose("init took $time") + + mappings.loadFromBridge(bridgeClient) + mappings.init(androidContext) + eventDispatcher.init() + //if mappings aren't loaded, we can't initialize features + if (!mappings.isMappingsLoaded()) return + features.init() + scriptRuntime.connect(bridgeClient.getScriptingInterface()) + syncRemote() } } - @OptIn(ExperimentalTime::class) private fun onActivityCreate() { - measureTime { + measureTimeMillis { with(appContext) { features.onActivityCreate() actionManager.init() diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/action/impl/ExportChatMessages.kt b/core/src/main/kotlin/me/rhunk/snapenhance/action/impl/ExportChatMessages.kt @@ -5,7 +5,6 @@ import android.content.DialogInterface import android.os.Environment import android.text.InputType import android.widget.EditText -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.joinAll @@ -44,8 +43,6 @@ class ExportChatMessages : AbstractAction() { context.feature(Messaging::class).conversationManager } - private val coroutineScope = CoroutineScope(Dispatchers.Default) - private val dialogLogs = mutableListOf<String>() private var currentActionDialog: AlertDialog? = null @@ -83,7 +80,7 @@ class ExportChatMessages : AbstractAction() { } private suspend fun askAmountOfMessages() = suspendCancellableCoroutine { cont -> - coroutineScope.launch(Dispatchers.Main) { + context.coroutineScope.launch(Dispatchers.Main) { val input = EditText(context.mainActivity) input.inputType = InputType.TYPE_CLASS_NUMBER input.setSingleLine() @@ -132,7 +129,7 @@ class ExportChatMessages : AbstractAction() { } override fun run() { - coroutineScope.launch(Dispatchers.Main) { + context.coroutineScope.launch(Dispatchers.Main) { exportType = askExportType() ?: return@launch mediaToDownload = if (exportType == ExportFormat.HTML) askMediaToDownload() else null amountOfMessages = askAmountOfMessages() @@ -289,7 +286,7 @@ class ExportChatMessages : AbstractAction() { logDialog(conversationSize) - coroutineScope.launch { + context.coroutineScope.launch { conversations.forEach { conversation -> launch { runCatching { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/ScopeSync.kt b/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/ScopeSync.kt @@ -1,6 +1,8 @@ package me.rhunk.snapenhance.features.impl -import kotlinx.coroutines.* +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import me.rhunk.snapenhance.core.event.events.impl.SendMessageWithContentEvent import me.rhunk.snapenhance.core.messaging.SocialScope import me.rhunk.snapenhance.data.ContentType @@ -13,7 +15,6 @@ class ScopeSync : Feature("Scope Sync", loadParams = FeatureLoadParams.INIT_SYNC } private val updateJobs = mutableMapOf<String, Job>() - private val coroutineScope = CoroutineScope(Dispatchers.Main) private fun sync(conversationId: String) { context.database.getDMOtherParticipant(conversationId)?.also { participant -> @@ -31,7 +32,7 @@ class ScopeSync : Feature("Scope Sync", loadParams = FeatureLoadParams.INIT_SYNC event.destinations.conversations.map { it.toString() }.forEach { conversationId -> updateJobs[conversationId]?.also { it.cancel() } - updateJobs[conversationId] = (coroutineScope.launch { + updateJobs[conversationId] = (context.coroutineScope.launch { delay(DELAY_BEFORE_SYNC) sync(conversationId) }) diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/spying/MessageLogger.kt b/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/spying/MessageLogger.kt @@ -18,8 +18,7 @@ import me.rhunk.snapenhance.features.FeatureLoadParams import me.rhunk.snapenhance.ui.addForegroundDrawable import me.rhunk.snapenhance.ui.removeForegroundDrawable import java.util.concurrent.Executors -import kotlin.time.ExperimentalTime -import kotlin.time.measureTime +import kotlin.system.measureTimeMillis private fun Any.longHashCode(): Long { var h = 1125899906842597L @@ -91,17 +90,16 @@ class MessageLogger : Feature("MessageLogger", return computeMessageIdentifier(conversationId, serverMessageId) } - @OptIn(ExperimentalTime::class) override fun asyncOnActivityCreate() { if (!isEnabled || !context.database.hasArroyo()) { return } - measureTime { + measureTimeMillis { val conversationIds = context.database.getFeedEntries(PREFETCH_FEED_COUNT).map { it.key!! } - if (conversationIds.isEmpty()) return@measureTime + if (conversationIds.isEmpty()) return@measureTimeMillis fetchedMessages.addAll(messageLoggerInterface.getLoggedIds(conversationIds.toTypedArray(), PREFETCH_MESSAGE_COUNT).toList()) - }.also { context.log.verbose("Loaded ${fetchedMessages.size} cached messages in $it") } + }.also { context.log.verbose("Loaded ${fetchedMessages.size} cached messages in ${it}ms") } } override fun init() { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/manager/impl/FeatureManager.kt @@ -1,5 +1,7 @@ package me.rhunk.snapenhance.manager.impl +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.core.Logger import me.rhunk.snapenhance.features.Feature @@ -24,22 +26,31 @@ import me.rhunk.snapenhance.features.impl.ui.PinConversations import me.rhunk.snapenhance.features.impl.ui.UITweaks import me.rhunk.snapenhance.manager.Manager import me.rhunk.snapenhance.ui.menu.impl.MenuViewInjector -import java.util.concurrent.Executors import kotlin.reflect.KClass +import kotlin.system.measureTimeMillis -class FeatureManager(private val context: ModContext) : Manager { - private val asyncLoadExecutorService = Executors.newFixedThreadPool(5) +class FeatureManager( + private val context: ModContext +) : Manager { private val features = mutableListOf<Feature>() private fun register(vararg featureClasses: KClass<out Feature>) { - featureClasses.forEach { clazz -> - runCatching { - clazz.constructors.first().call().also { feature -> - feature.context = context - features.add(feature) + runBlocking { + featureClasses.forEach { clazz -> + context.coroutineScope.launch { + runCatching { + clazz.java.constructors.first().newInstance() + .let { it as Feature } + .also { + it.context = context + synchronized(features) { + features.add(it) + } + } + }.onFailure { + Logger.xposedLog("Failed to register feature ${clazz.simpleName}", it) + } } - }.onFailure { - Logger.xposedLog("Failed to register feature ${clazz.simpleName}", it) } } } @@ -95,34 +106,61 @@ class FeatureManager(private val context: ModContext) : Manager { initializeFeatures() } - private fun featureInitializer(isAsync: Boolean, param: Int, action: (Feature) -> Unit) { - features.filter { it.loadParams and param != 0 }.forEach { feature -> - val callback = { - runCatching { - action(feature) - }.onFailure { - context.log.error("Failed to init feature ${feature.featureKey}", it) - context.longToast("Failed to load feature ${feature.featureKey}! Check logcat for more details.") - } + private fun initFeatures( + syncParam: Int, + asyncParam: Int, + syncAction: (Feature) -> Unit, + asyncAction: (Feature) -> Unit + ) { + fun tryInit(feature: Feature, block: () -> Unit) { + runCatching { + block() + }.onFailure { + context.log.error("Failed to init feature ${feature.featureKey}", it) + context.longToast("Failed to init feature ${feature.featureKey}! Check logcat for more details.") } - if (!isAsync) { - callback() - return@forEach + } + + features.toList().forEach { feature -> + if (feature.loadParams and syncParam != 0) { + tryInit(feature) { + syncAction(feature) + } } - asyncLoadExecutorService.submit { - callback() + if (feature.loadParams and asyncParam != 0) { + context.coroutineScope.launch { + tryInit(feature) { + asyncAction(feature) + } + } } } } private fun initializeFeatures() { //TODO: async called when all features are initiated ? - featureInitializer(false, FeatureLoadParams.INIT_SYNC) { it.init() } - featureInitializer(true, FeatureLoadParams.INIT_ASYNC) { it.asyncInit() } + measureTimeMillis { + initFeatures( + FeatureLoadParams.INIT_SYNC, + FeatureLoadParams.INIT_ASYNC, + Feature::init, + Feature::asyncInit + ) + }.also { + context.log.verbose("feature manager init took $it ms") + } } override fun onActivityCreate() { - featureInitializer(false, FeatureLoadParams.ACTIVITY_CREATE_SYNC) { it.onActivityCreate() } - featureInitializer(true, FeatureLoadParams.ACTIVITY_CREATE_ASYNC) { it.asyncOnActivityCreate() } + measureTimeMillis { + initFeatures( + FeatureLoadParams.ACTIVITY_CREATE_SYNC, + FeatureLoadParams.ACTIVITY_CREATE_ASYNC, + Feature::onActivityCreate, + Feature::asyncOnActivityCreate + ) + }.also { + context.log.verbose("feature manager onActivityCreate took $it ms") + } } } \ No newline at end of file