Android接入AWS SDK,想实现运行时动态切换AWS的用户账号及运行时环境,遇到问题记录

业务场景

我们项目有dev和prod两个运行时环境,dev、prod在AWS上的账号是分开的,服务端的API也是分开的,也就是两个环境。现在的需求是,希望在App运行时能动态的实现两个运行环境的互换。

实现过程

1、服务端的API动态切换,这个很容易就能实现,根据环境标识使用相应的一套API就可以。(不同的地方,比如baseUrl、baserLbsUrl、各个第三方平台的key等)

2、切换AWS的初始化环境,因为不同的环境是根据配置的信息去初始化的,也就是需要重置初始化环境。

遇到的问题

1、AWS SDK中提供的初始化API,强制限制只能初始化一次。使用的是如下API

 try {
            val amplifyConfig = if (BuildEnvConfig.isProdEnv()) {
                R.raw.amplifyconfiguration_prod
            } else {
                R.raw.amplifyconfiguration
            }

            val config = AmplifyConfiguration.builder(application, amplifyConfig)
                .devMenuEnabled(false)
                .build()

            Amplify.configure(config, application)
        } catch (e: AmplifyException) {
            e.printStackTrace()
        }

初始化配置Amplify.configure(config, application) 进入方法内部,如下截图
在这里插入图片描述

发现它内部有一个配置同步锁,在Amplify类中的定义如下

 private static final AtomicBoolean CONFIGURATION_LOCK = new AtomicBoolean(false);

一旦初始化后,若再次初始化,则抛出如下异常

throw new AmplifyException(
   "The client issued a subsequent call to `Amplify.configure` after the first had already succeeded.", "Be sure to only call Amplify.configure once"
  );

CONFIGURATION_LOCK是私有的静态常量,也就是说我们改不了

2、使用Java的反射技术(代码是使用的Kotlin语言),去尝试修改,代码如下:

 val amplifyClass: Class<*> = Amplify::class.java
 val configurationLock = amplifyClass.getDeclaredField("CONFIGURATION_LOCK")
 configurationLock.isAccessible = true
 configurationLock.set(null, AtomicBoolean(false))

编译运行,发现还是不行,遇到了如下异常(是UserAgent类的)

throw new AmplifyException(
       "User-Agent was already configured successfully.",
       "User-Agent is configured internally during Amplify configuration. " +
        "This method should not be called externally."
);

通过翻阅源码,发现UserAgent类有提供重置方法

UserAgent.reset()

我们再次编译运行,发现还是不行,遇到了如下异常(是Category类的)

 if (!State.NOT_CONFIGURED.equals(state.get())) {
          throw new AmplifyException(
                    "Category " + getCategoryType() + " has already been configured or is currently configuring.", "Ensure that you are only attempting configuration once."
                );
  }

翻阅Category类的源码,看到了如下代码

   /**
     * Records the initialization state. See {@link State} for possible values.
     */
    private final AtomicReference<State> state;

    /**
     * Constructs a new, not-yet-configured, Category.
     */
    public Category() {
        this.plugins = new ConcurrentHashMap<>();
        this.state = new AtomicReference<>(State.NOT_CONFIGURED);
    }

也就是state变量的值,我们通过常规的手段修改不了,我们继续使用反射技术,代码如下:

 try {
            val categoryClass = Class.forName("com.amplifyframework.core.category.Category\$State")
            val notConfiguredField = categoryClass.getField("NOT_CONFIGURED")
            notConfiguredField.isAccessible = true
            val notConfigured = notConfiguredField.get(null)

            val authCategoryState = Class.forName("com.amplifyframework.auth.AuthCategory").superclass.getDeclaredField("state")
            authCategoryState.isAccessible = true
            authCategoryState.set(Amplify.Auth, AtomicReference(notConfigured))

            val analyticsState = Class.forName("com.amplifyframework.analytics.AnalyticsCategory").superclass.getDeclaredField("state")
            analyticsState.isAccessible = true
            analyticsState.set(Amplify.Analytics, AtomicReference(notConfigured))

            val apiState = Class.forName("com.amplifyframework.api.ApiCategory").superclass.getDeclaredField("state")
            apiState.isAccessible = true
            apiState.set(Amplify.API, AtomicReference(notConfigured))

            val loggingState = Class.forName("com.amplifyframework.logging.LoggingCategory").superclass.getDeclaredField("state")
            loggingState.isAccessible = true
            loggingState.set(Amplify.Logging, AtomicReference(notConfigured))

            val storageState = Class.forName("com.amplifyframework.storage.StorageCategory").superclass.getDeclaredField("state")
            storageState.isAccessible = true
            storageState.set(Amplify.Storage, AtomicReference(notConfigured))

            val hubState = Class.forName("com.amplifyframework.hub.HubCategory").superclass.getDeclaredField("state")
            hubState.isAccessible = true
            hubState.set(Amplify.Hub, AtomicReference(notConfigured))

            val dataStoreState = Class.forName("com.amplifyframework.datastore.DataStoreCategory").superclass.getDeclaredField("state")
            dataStoreState.isAccessible = true
            dataStoreState.set(Amplify.DataStore, AtomicReference(notConfigured))

            val predictionsState = Class.forName("com.amplifyframework.predictions.PredictionsCategory").superclass.getDeclaredField("state")
            predictionsState.isAccessible = true
            predictionsState.set(Amplify.Predictions, AtomicReference(notConfigured))

            val categoriesMap = buildCategoriesMap()
            val categories = amplifyClass.getDeclaredField("CATEGORIES")
            categories.isAccessible = true
            categories.set(null, categoriesMap)
            
            val initializationPool = amplifyClass.getDeclaredField("INITIALIZATION_POOL")
            initializationPool.isAccessible = true
            initializationPool.set(null, Executors.newFixedThreadPool(categoriesMap.size))
        } catch (e: Exception) {
            e.printStackTrace()
        }

编译、运行,这次发现终于不再包异常了,那问题解决了吗?实现了我们在运行时,动态切换AWS账号及环境的目的了没?答案是:没有!。 你没看错,现在呢是编译、运行一切正常,但是就是没实现运行时动态切AWS账号及环境。

关于重置AWS SDK 初始化相关限制的完整代码如下:
package com.majiapp.app

import android.annotation.SuppressLint
import android.app.Application
import com.amplifyframework.AmplifyException
import com.amplifyframework.core.Amplify
import com.amplifyframework.core.AmplifyConfiguration
import com.amplifyframework.core.category.Category
import com.amplifyframework.core.category.CategoryType
import com.amplifyframework.core.plugin.Plugin
import com.amplifyframework.util.UserAgent
import com.majiapp.R
import com.majiapp.buildconfig.BuildEnvConfig
import com.majiapp.tu.TuApplication
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference

object AppEvnInit {

    fun init(appContext: TuApplication) {
        resetCategoriesState()
        initAmplify(appContext)
    }

    private fun initAmplify(application: Application) {
        try {
            val amplifyConfig = if (BuildEnvConfig.isProdEnv()) {
                R.raw.amplifyconfiguration_prod
            } else {
                R.raw.amplifyconfiguration
            }

            val config = AmplifyConfiguration.builder(application, amplifyConfig)
                .devMenuEnabled(false)
                .build()

            Amplify.configure(config, application)
        } catch (e: AmplifyException) {
            e.printStackTrace()
        }
    }

    @SuppressLint("VisibleForTests")
    private fun resetCategoriesState() {
        try {
            UserAgent.reset()

            val categoryClass = Class.forName("com.amplifyframework.core.category.Category\$State")
            val notConfiguredField = categoryClass.getField("NOT_CONFIGURED")
            notConfiguredField.isAccessible = true
            val notConfigured = notConfiguredField.get(null)

            val authCategoryState = Class.forName("com.amplifyframework.auth.AuthCategory").superclass.getDeclaredField("state")
            authCategoryState.isAccessible = true
            authCategoryState.set(Amplify.Auth, AtomicReference(notConfigured))

            val analyticsState = Class.forName("com.amplifyframework.analytics.AnalyticsCategory").superclass.getDeclaredField("state")
            analyticsState.isAccessible = true
            analyticsState.set(Amplify.Analytics, AtomicReference(notConfigured))

            val apiState = Class.forName("com.amplifyframework.api.ApiCategory").superclass.getDeclaredField("state")
            apiState.isAccessible = true
            apiState.set(Amplify.API, AtomicReference(notConfigured))

            val loggingState = Class.forName("com.amplifyframework.logging.LoggingCategory").superclass.getDeclaredField("state")
            loggingState.isAccessible = true
            loggingState.set(Amplify.Logging, AtomicReference(notConfigured))

            val storageState = Class.forName("com.amplifyframework.storage.StorageCategory").superclass.getDeclaredField("state")
            storageState.isAccessible = true
            storageState.set(Amplify.Storage, AtomicReference(notConfigured))

            val hubState = Class.forName("com.amplifyframework.hub.HubCategory").superclass.getDeclaredField("state")
            hubState.isAccessible = true
            hubState.set(Amplify.Hub, AtomicReference(notConfigured))

            val dataStoreState = Class.forName("com.amplifyframework.datastore.DataStoreCategory").superclass.getDeclaredField("state")
            dataStoreState.isAccessible = true
            dataStoreState.set(Amplify.DataStore, AtomicReference(notConfigured))

            val predictionsState = Class.forName("com.amplifyframework.predictions.PredictionsCategory").superclass.getDeclaredField("state")
            predictionsState.isAccessible = true
            predictionsState.set(Amplify.Predictions, AtomicReference(notConfigured))

            val amplifyClass: Class<*> = Amplify::class.java
            val configurationLock = amplifyClass.getDeclaredField("CONFIGURATION_LOCK")
            configurationLock.isAccessible = true
            configurationLock.set(null, AtomicBoolean(false))

            val categoriesMap = buildCategoriesMap()
            val categories = amplifyClass.getDeclaredField("CATEGORIES")
            categories.isAccessible = true
            categories.set(null, categoriesMap)

            val initializationPool = amplifyClass.getDeclaredField("INITIALIZATION_POOL")
            initializationPool.isAccessible = true
            initializationPool.set(null, Executors.newFixedThreadPool(categoriesMap.size))
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun buildCategoriesMap(): LinkedHashMap<CategoryType, Category<out Plugin<*>?>> {
        val categories = LinkedHashMap<CategoryType, Category<out Plugin<*>?>>()
        categories[CategoryType.AUTH] = Amplify.Auth // This must be before ANALYTICS, API, STORAGE, & PREDICTIONS
        categories[CategoryType.ANALYTICS] = Amplify.Analytics
        categories[CategoryType.API] = Amplify.API
        categories[CategoryType.LOGGING] = Amplify.Logging
        categories[CategoryType.STORAGE] = Amplify.Storage
        categories[CategoryType.HUB] = Amplify.Hub
        categories[CategoryType.DATASTORE] = Amplify.DataStore
        categories[CategoryType.PREDICTIONS] = Amplify.Predictions
        return categories
    }

}
折中的解决办法

在切换环境时,记录下一次运行时(存入SharedPreferences中),想要运行的环境,之后kill App进程,点击App Icon重新打开App。(这个功能是给公司内部自己人用的)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值