组件化攻略-R2.java的今生前世和静态变量

本文介绍了ButterKnife的优势及其生成R2.java的原因,详细阐述了R2.java的生成过程,并在组件化背景下讨论了静态变量的考量,包括Resource绑定方式和延续R.java方式的优缺点。最后,分析了组件化中使用非final静态变量可能导致的资源冲突问题,建议使用R2.java以避免冲突。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
ButterKnife已经成为众多开发者的选择。简直不要太好用。

其优势
  • 1、强大的Resource绑定和Method事件处理功能,简化代码,提升开发效率
  • 2、方便的处理Adapter里的ViewHolder绑定问题
  • 3、运行时不会影响APP效率,使用配置方便
  • 4、代码清晰,可读性强
截止 2019年9月6日。ButterKnife Version 10.1.0 (2019-02-13),已经有很多的支持了。

在这里插入图片描述


为什么有R2.java

原因

Library Module必须转换成final类型才能使用ButterKnife。
不是常量会提示attribute value must be constant
在这里插入图片描述

描述

ButterKnife是通过注解 简化编码工作。

Module是否生成final的ID
App Module
Library Module

具体的路径:
App Module:

app/build/generated/source/r/env1/debug/包路径/R.java 这里env1/debug是编译环境

public final class R {
    public static final class anim {
        public static final int a3 = 0x7f050000;
        public static final int a5 = 0x7f050001;
        .....
    }
    ......
}

Library Module:

module_name/build/generated/source/r/debug/包路径/R.java

public final class R {
    public static final class anim {
        public static int a3 = 0x7f010001;
        public static int a5 = 0x7f010002;
        public static int abc_fade_in = 0x7f010003;
        public static int abc_fade_out = 0x7f010004;
    }
    ......
}

R2.java如何生成?

Jakewharton大神引入了butterknife-gradle-plugin插件,用于生成变量类型为final的R2类。
先说结论:

将R.java文件复制一份,命名为R2.java,然后将R2.java的变量都改成 public static final(其实,就是加了final)。
先看主入口:

butterknife-gradle-plugin根据Module类型的不同进行区分处理。

class ButterKnifePlugin : Plugin<Project> {
  override fun apply(project: Project) {
    project.plugins.all {
      when (it) {
        is FeaturePlugin -> {
          project.extensions[FeatureExtension::class].run {
            configureR2Generation(project, featureVariants)
            configureR2Generation(project, libraryVariants)
          }
        }
        is LibraryPlugin -> {
          project.extensions[LibraryExtension::class].run {
            configureR2Generation(project, libraryVariants)
          }
        }
        is AppPlugin -> {
          project.extensions[AppExtension::class].run {
            configureR2Generation(project, applicationVariants)
          }
        }
      }
    }
  }
......
}
configureR2Generation做了什么呢?

铺垫一下
processResources定义是什么呢? 更详细的信息
将资源从其源目录复制到其目标目录,并可能对其进行处理。确保目标目录中没有过时的资源。

class ButterKnifePlugin : Plugin<Project> {
......
  private fun configureR2Generation(project: Project, variants: DomainObjectSet<out BaseVariant>) {
    variants.all { variant ->
      val outputDir = project.buildDir.resolve(
          "generated/source/r2/${variant.dirName}")

      val rPackage = getPackageName(variant)
      val once = AtomicBoolean()
      variant.outputs.all { output ->
        val processResources = output.processResources

        // Though there might be multiple outputs, their R files are all the same. Thus, we only
        // need to configure the task once with the R.java input and action.
        if (once.compareAndSet(false, true)) {
          // TODO: switch to better API once exists in AGP (https://issuetracker.google.com/118668005)
          val rFile =
              project.files(
                  when (processResources) {
                    is GenerateLibraryRFileTask -> processResources.textSymbolOutputFile
                    is LinkApplicationAndroidResourcesTask -> processResources.textSymbolOutputFile
                    else -> throw RuntimeException(
                        "Minimum supported Android Gradle Plugin is 3.1.0")
                  })
                  .builtBy(processResources)
          project.tasks.create("generate${variant.name.capitalize()}R2", R2Generator::class.java) {
            it.outputDir = outputDir
            it.rFile = rFile
            it.packageName = rPackage
            it.className = "R2"
            variant.registerJavaGeneratingTask(it, outputDir)
          }
        }
      }
    }
  }
......
}

在项目的processResources阶段,是会获取到R.java文件的信息,在 processResources task 中获取需要生成 R2 文件所在的目录和 R 文件,传参到 R2Generator进行进行下一步操作。

R2Generator处理流程
open class R2Generator : DefaultTask() {
  ......
  fun brewJava(
  rFile: File,
  outputDir: File,
  packageName: String,
  className: String
) {
  FinalRClassBuilder(packageName, className)
      .also { ResourceSymbolListReader(it).readSymbolTable(rFile) }
      .build()
      .writeTo(outputDir)
}

R2Generator只是封装了一层FinalRClassBuilder。

还是要看FinalRClassBuilder

这里我选取了最关键的一部分

fun addResourceField(type: String, fieldName: String, fieldValue: String) {
    if (type !in SUPPORTED_TYPES) {
      return
    }
    val fieldSpecBuilder = FieldSpec.builder(Int::class.javaPrimitiveType, fieldName)
        .addModifiers(PUBLIC, STATIC, FINAL)
        .initializer(fieldValue)

    fieldSpecBuilder.addAnnotation(getSupportAnnotationClass(type))

    val resourceType =
        resourceTypes.getOrPut(type) {
          TypeSpec.classBuilder(type).addModifiers(PUBLIC, STATIC, FINAL)
        }
    resourceType.addField(fieldSpecBuilder.build())
  }

在生成R2.java的时候,所有的field都被改成了public static final。


组件化中的变量考量

ButterKnife是一种便捷的工具。什么时候用?合不合适用?需要一定的考量?如果项目小,替换成本低,可以考虑全盘替换。
针对不同的情况,分而治之

改用 Resource 绑定的方式

优势:

  • 1、强大的Resource绑定和Method事件处理功能,简化代码,提升开发效率
  • 2、方便的处理Adapter里的ViewHolder绑定问题
  • 3、运行时不会影响APP效率,使用配置方便
  • 4、代码清晰,可读性强
延续R.java的方式
在ButterKnife中,有个Utils.findRequiredViewAsType方法。
**优势:** 沿用了findViewById,不改变原有编码方式,新增支持method事件处理功能。比如@OnClick
  • 不打算用ButterKnife。那就对findViewById做泛型封装,以减少代码量
    protected <T extends View> T generateFindViewById(int id) {
      return (T) findViewById(id);
    }
    

注意:
R2只是R的副本,除了多了个final 装饰,其他都是一样的。怎么改进你的编码架构,不是没有代价的。还是需要综合的考量。


继续思考组件化中的静态变量

R.java在Library Module中,非final装修会产生什么问题呢?
答:

  • 凡事规定必须使用 常量 的地方都无法使用R.java中的变量。
  • 不同module之间无法保证R.java的变量是不同的,非final声明就存在资源冲突的问题,这需要额外的保证。我建议用resourcePrefix进行强制前缀解决。
    详细可以查看《组件化攻略-资源合并与冲突》

所以,除了resourcePrefix强制前缀限制外,推荐还是R2.java有final有保障嘛。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值