【问题标题】:Third party library of dynamic feature module cannot access resources动态特征模块第三方库无法访问资源
【发布时间】:2020-01-23 18:37:23
【问题描述】:

我有一个具有动态功能模块的应用程序。在动态功能模块中,有一个带有图像、输入字段的表单,还有一个访问另一个第三方库的按钮。

第三方库有一个activity和fragment。在活动中打开片段时,我收到以下错误,尽管活动的布局中有容器:

没有为片段 SampleFragment{eed53f7 (5e4c0693-09a2-4725-a6de-1df49dd818f0) id=0x7f080053} 找到 id 0x7f080053 (com.app.sample:id/container) 的视图

访问此第三方库中的可绘制对象时,出现以下错误:

java.lang.NoSuchFieldError: 类 Lcom.third.library/R$drawable 中没有类型 I 的静态字段 ic_back;或其超类(“com.third.library.R$drawable”的声明出现在 /data/app/com.app.sample-QtC8XuamC1fHEVU4FUpWaA==/split_thirdparty.apk 中)

当我在没有动态功能模块的应用程序中使用这个库时很好。

【问题讨论】:

  • 第 3 方库是开放库吗(例如在 github 上可用),所以我们可以重现吗?它是仅在dynamic-feature-module1 中使用还是在App1 模块或dynamic-feature-module1 级别的其他模块中使用?
  • How to create a Minimal, Reproducible Example 可能值得一读,因为没有最少代码的错误消息相当抽象。

标签: android android-library dynamic-feature-module


【解决方案1】:

通常,当SplitCompat.installActivity(this) 没有在Activity2 中调用时,这将不起作用。虽然没有源代码,但您必须提取包并正确重新打包,因为Activity2(甚至整个库包)可能与 DFM 不兼容。

为您的基础应用启用 SplitCompat 后,您需要为您的应用在动态功能模块中下载的每个 Activity 启用 SplitCompat

这是我的另一个answer,它演示了通过反射进行访问。

【讨论】:

    【解决方案2】:

    动态交付是相对较新的功能,因此它有很多限制。其中一个限制是您无法以常规方式访问动态模块的代码和资源,因此它不能成为其他模块的依赖项。目前,您可以通过反射访问动态模块,并通过公共库模块中的公共接口定义动态特性,并在运行时使用ServiceLoader 加载它们的实际实现(位于动态特性模块中)。它有其性能缺点。可以使用 R8 使用 ServiceLoaderRewriter 将它们最小化,但不能完全删除。

    虽然使用反射很容易出错,但我们可以使用 @AutoService 将其最小化——AutoService 是一个注释处理器,它将扫描项目中使用 @AutoService 注释的类,对于它发现的任何类,它都会自动生成一个服务定义文件。

    这里是如何完成的小例子

    // All feature definitions extend this interface, T is the dependencies that the feature requires
    interface Feature<T> {
        fun getMainScreen(): Fragment
        fun getLaunchIntent(context: Context): Intent
        fun inject(dependencies: T)
    }
    
    interface VideoFeature : Feature<VideoFeature.Dependencies> {
        interface Dependencies {
            val okHttpClient: OkHttpClient
            val context: Context
            val handler: Handler
            val backgroundDispatcher: CoroutineDispatcher
        }
    }
    
    internal var videoComponent: VideoComponent? = null
        private set
    
    @AutoService(VideoFeature::class)
    class VideoFeatureImpl : VideoFeature {
        override fun getLaunchIntent(context: Context): Intent = Intent(context, VideoActivity::class.java)
    
        override fun getMainScreen(): Fragment = createVideoFragment()
    
        override fun inject(dependencies: VideoFeature.Dependencies) {
            if (videoComponent != null) {
                return
            }
    
            videoComponent = DaggerVideoComponent.factory()
                    .create(dependencies, this)
        }
    }
    

    并实际访问动态功能使用的代码

    
    inline fun <reified T : Feature<D>, D> FeatureManager.getFeature(
            dependencies: D
    ): T? {
        return if (isFeatureInstalled<T>()) {
            val serviceIterator = ServiceLoader.load(
                    T::class.java,
                    T::class.java.classLoader
            ).iterator()
    
            if (serviceIterator.hasNext()) {
                val feature = serviceIterator.next()
                feature.apply { inject(dependencies) }
            } else {
                null
            }
        } else {
            null
        }
    }
    

    取自here。那里还有更多信息,所以我建议您检查一下。

    一般来说,我只是不建议使用动态功能作为依赖项并相应地规划您的应用架构。

    希望对你有帮助。

    【讨论】:

      【解决方案3】:

      对于资源,这段代码部分可以使用

      R.id.settings 将是:

      getResources().getIdentifier("settings", "id", "com.library.package");
      

      【讨论】:

        猜你喜欢
        • 2021-02-23
        • 2018-06-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-19
        • 2012-11-05
        • 1970-01-01
        • 2017-05-30
        相关资源
        最近更新 更多