Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic feature module support #109

Open
johnsonlee opened this issue Nov 5, 2019 · 7 comments
Open

Dynamic feature module support #109

johnsonlee opened this issue Nov 5, 2019 · 7 comments
Assignees
Labels

Comments

@johnsonlee
Copy link
Collaborator

No description provided.

@johnsonlee johnsonlee self-assigned this Nov 5, 2019
johnsonlee added a commit that referenced this issue Nov 6, 2019
@Yang-yongwen
Copy link

使用 0.27.1 版本尝试了一下,第一次的时候编译没有问题,但是如果只在 application module 里面使用了 booster 插件,还是扫不到 dynamic feature 的代码,看了下实现是直接把 BoosterAppTransform 的 scopes 改成了 FULL_PROJECT,而 BoosterFeatureTransform 的 scopes 则是FULL_WITH_FEATURE。后来再重新试了一下,发现BoosterAppTransform的 scopes 又改回 FULL_WITH_FEATURE,这时候又会出现之前的编译错误(com.android.build.api.transform.TransformException: com.android.tools.r8.utils.FeatureClassMapping$FeatureMappingException)。看了一下这个错误,是因为base.jar 里面包含了 dynamic-feature.jar 的类,所以在 transformDexWithDexSplitterForDebug 会出现类重复的编译错误。

之前说 Transform 使用了 FULL_WITH_FEATURE的 scopes 后,要打开了混淆之后才能扫到dynamic feature 的类,是因为下面的代码:

    // if variantScope.consumesFeatureJars(), add streams of classes from features or
    // dynamic-features.
    // The main dex list calculation for the bundle also needs the feature classes for reference
    // only
    if (variantScope.consumesFeatureJars() || variantScope.getNeedsMainDexListForBundle()) {
       transformManager.addStream(
              OriginalStream.builder(project, "metadata-classes")
                       .addContentTypes(TransformManager.CONTENT_CLASS)
                       .addScope(InternalScope.FEATURES)
                        .setArtifactCollection(
                                variantScope.getArtifactCollection(
                                        METADATA_VALUES, PROJECT, METADATA_CLASSES))
                         .build());
    }

    @Override
    public boolean consumesFeatureJars() {
        return getType().isBaseModule()
                && getVariantConfiguration().getBuildType().isMinifyEnabled()
                && globalScope.hasDynamicFeatures();
    }

只有满足 variantScope.consumesFeatureJars() || variantScope.getNeedsMainDexListForBundle()条件才会把 dynamic feature 添加到 Transform 的输入,而条件一则需要打开混淆才为 true,条件二则需要打开 multi dex。因此在 debug 版本没有打开混淆的情况下,即使使用了FULL_WITH_FEATURE 的 scopes 也扫不到 dynamic feature 的类。

而 3.2.1 不会出现上面的编译错误,3.5.1 会,则是由于因为下面的代码:

3.2.1
        // Add transform to create merged runtime classes if this is a feature or dynamic-feature.
        // Merged runtime classes are needed if code minification is enabled in multi-apk project.
        if (variantData.getType().isFeatureSplit()) {
            createMergeClassesTransform(variantScope);
        }
3.5.1
        // Add transform to create merged runtime classes if this is a feature, a dynamic-feature,
        // or a base module consuming feature jars. Merged runtime classes are needed if code
        // minification is enabled in a project with features or dynamic-features.
        if (variantData.getType().isFeatureSplit() || variantScope.consumesFeatureJars()) {
            createMergeClassesTransform(variantScope);
        }

对比两个版本的代码可以知道,3.2.1 只有在 dynamic feature module 上 才会使用 MergeClassesTransform,而 3.5.1 则在base module 也有可能使用。前面提到的 base.jar 就是由 MergeClassesTransform 生成的。看了下 MergeClassesTransform 的实现,它只会通过 getReferencedScopes (FULL_PROJECT)读取 class 输入,并打包成 jar 输出到其他目录,不影响 Transform 的流程(Regarding Streams, this is a no-op transform as it does not write any output to any stream) 。既然它的 scopes 是 FULL_PROJECT,那它生成的 base.jar 为什么会包含 dynamic feature module 的类呢。原因如下:

    @NonNull
    private List<TransformStream> grabReferencedStreams(@NonNull Transform transform) {
        Set<? super Scope> requestedScopes = transform.getReferencedScopes();
        if (requestedScopes.isEmpty()) {
            return ImmutableList.of();
        }

        List<TransformStream> streamMatches = Lists.newArrayListWithExpectedSize(streams.size());

        Set<ContentType> requestedTypes = transform.getInputTypes();
        for (TransformStream stream : streams) {
            // streams may contain more than we need. In this case, we'll provide the whole
            // stream as-is since it's not actually consumed.
            // It'll be up to the TransformTask to make sure that the content of the stream is
            // usable (for instance when a stream
            // may contain two scopes, these scopes could be combined or not, impacting consumption)
            Set<ContentType> availableTypes = stream.getContentTypes();
            Set<? super Scope> availableScopes = stream.getScopes();

            Set<ContentType> commonTypes = Sets.intersection(requestedTypes,
                    availableTypes);
            Set<? super Scope> commonScopes = Sets.intersection(requestedScopes, availableScopes);

            if (!commonTypes.isEmpty() && !commonScopes.isEmpty()) {
                streamMatches.add(stream);
            }
        }

        return streamMatches;
    }

这里是生成 Referenced streams 的地方,从注释可以知道,streams 可能会包含多于 Transform 所声明的,对应到我们这里的情况就是多出了 dynamic feature 的 stream。而MergeClassesTransform直接把所有的Referenced inputs (streams)都打到 jar 包里面去,所以 base.jar 就包含了 dynamic feature module 的代码。

针对上面的两个问题,我自己想到的解决办法是:

  1. debug 也打开 混淆,但是这时候所使用的Proguard配置文件里面关闭所有的优化项,这样应该就能把 dynamic feature 添加到输入 stream 中去,同时又不会去混淆文件影响 debug,或者也可以使用 multi dex(对于大一点的项目基本都要使用,但是只打开 multidex 而不打开混淆会有个比较奇怪的问题,就是 Transform 能扫描到 dynamic feature 的类,也能修改并输出,因为看输出的 jar 包里面的class 是有插桩代码的,但是 dynamic feature 最终产出的 apk 却不包含 插桩代码,这里面的编译流程还没搞清楚)
  2. Hook MergeClassesTransform,将 dynamic feature 的 input 排除掉,不打入到 base.jar,尝试了一下编译出来的包运行正常,但不知道有没有其他坑。。。

不过感觉上面的两个方法都不是很优雅,抛出来大家讨论一下看有没有更好的方法~

@johnsonlee
Copy link
Collaborator Author

0.27.1-SNAPSHOT官方 demo 测试过,可以扫描到 feature module 下的 classes

@Yang-yongwen
Copy link

用官方 demo 测试 0.27.1-SNAPSHOT,测试结果还是和之前一样,测试 case 及 输出如下:
测试 case:
屏幕快照 2019-11-16 下午3 35 18
输出:
minifyEnabled = true,能扫到 dynamic module 的类,但是编译失败
屏幕快照 2019-11-16 下午3 33 13
minifyEnabled = false,不能扫到dynamic module 的类
屏幕快照 2019-11-16 下午3 33 43

@johnsonlee
Copy link
Collaborator Author

这样可以看到 dynamic feature 模块下的 classes

./gradlew :app:bundleDebug --info 

@johnsonlee
Copy link
Collaborator Author

:app:bundleRelease 确实会构建失败,看起来像是 Android Gradle Plugin 的 bug

@JayFang1993
Copy link

@Yang-yongwen 上述的问题你是采用的方案2解决的吗?

@xeemoo
Copy link

xeemoo commented Dec 13, 2024

在 Booster v4.16+ 对于 Dynamic Feature 模块的支持有可行的方案吗?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants