background

Because we need to provide the push capability within the project to other business access, currently we have access to the push of FCM(Google Push), HMS, Xiaomi, Vivo, OPPO, Aurora and many other platforms. However, business access may require only a few of them, leaving aside part of the SDK design, it is actually more complicated in terms of access cost. Here is a summary of the issues.

  1. You have to set up a lot of appKeys and things like that.
  2. Set some pleaceHolder like applicationId
  3. FCM and HMS are now accessed through plugins, and multiple plugins can confuse development behavior
  4. You need to set many push policies dynamically, and decide the current push policy according to different vendors.
  5. How to change an AAR to a source dependency.

To be honest, the more you write, the more problems there are, especially in the push business itself, where there is a lot of uncertainty.

How to solve these problems

Let’s first configure the push plugin under ‘com.android.application’, which can be defined in the plugin. Pass some dynamic configuration changes into the Plugin through the Extension of the Plugin, and then use these configurations to complete the push service aggregation we need.

For example, A app only needs HMS and Aurora, so it only needs to configure two of them and will not introduce other pushed code, while B app needs all of them, and will introduce all warehouses and dynamically generate configuration files according to these configurations.

What are the benefits of this approach? First of all, it is more intuitive, there are fewer places to configure, and it avoids the need for development to define these configuration policies, so that users only need to complete a simple initial configuration before they can use the push service.

buildSrc + setting

In the Amway buildSrc mode, we can debug plugins directly without pushing the local JAR. This solves the problem of plugins being very difficult to debug.

include ':plugin'
project(":plugin").projectDir = new File( ".. /plugin")
Copy the code

But buildSrc is also a little bit of a problem because it’s not particularly convenient for us to publish plugin projects. This time amway everyone a small gesture. We can use setting.gradle below buildSrc, and then we can introduce the plugin module, so that we can debug buildSrc quickly and publish the project easily.

Amway under my big guy’s a Demo project, interested can refer to this.

For plugin debugging, you can print the exception log in./gradlew taskName -s

If you do not know how to debug Gradle Plugin, you can learn the source code of Gradle Plugin, portal only Gradle Plugin debug

buildTypes resValue

We sometimes add resValue “string”, “AppName”, “app1” to buildTypes when we write build.gradle.

We will finally generate a resource file gradleresValue.xml under build/generated/, which will be merged into the project resource file during compilation. So how do we use this in our plugin?

                        val android = project.extensions.getByName("android") as BaseAppModuleExtension
                        val config = android.defaultConfig
                        config.resValue("string", key, value)
Copy the code

Extensions can be used to access other extensions in the current project. Call defaultConfig’s resValue method to add resource information just like you did in build.gradle.

Modify the Mainfest?

Due to specific coding requirements within the project, we need to set different placeHolder values depending on the applicationId.

Without using the ManifestPleaceHolder approach, can we consider ways to make changes to the finalized manifest after it is merged?

  val variantName = variant.name.capitalize()
  val processManifestTask = project.tasks.getByName("process${variantName}Manifest")
          as ProcessApplicationManifest
  processManifestTask.doLast {
  }
Copy the code

The above code retrieves the manifest-merged task from within projcet’s task. In the last article, I mentioned that Task is the core unit of Gradle Task. In fact, we can modify this Task in doFirst and doLast.

For example, after the task is completed, we can make some modifications to the XML Manifest through the file path, so that according to different code needs to do some additions to the Manifest, and finally, as long as the current Manifest file is overwritten, it will take effect on the entire project.

How do I add dependencies to my Plugin?

Have you ever thought about dependencies?

In fact, the implementation, the API and so on that we’re adding to the Project are just adding a data structure within the DefaultDependencyHandler of the Project that has group+name+version, Finally, add the real dependencies in the Configuration.

You can refer to my article Gradle Configuration.

  project.dependencies.add(Implement, "Com. Squareup. Okhttp3: okhttp: 4.9.0")
Copy the code

You can add dependencies to your project by calling the add method in your project.

How do I add another Plugin to my Plugin?

Since both FCM and HMS need to introduce a vendor written Plugin within the project, there are many problems when users try to access it. Can we integrate these dependencies with our own plugins?

dependencies {
    implementation gradleApi()
    implementation localGroovy()
    implementation 'Commons - IO: the Commons - IO: 2.6'
    implementation "Com. Squareup: javapoet: 1.13.0."
    implementation 'com. Huawei. Agconnect: agcp: 1.3.1.300'
    implementation "Com. Google. GMS: Google - services: 4.2.0"
}
Copy the code

First, we need to integrate the plugins we need through implementation. Otherwise we wouldn’t be able to reference their Plugin code.

        val plugins = project.pluginManager
        if(! plugins.hasPlugin("com.google.android.gms.google-services")) {
              plugins.apply(GoogleServicesPlugin::class.java)
        }
Copy the code

From the point of view of the code, we are just adding an extra Plugin to the Project, so it is relatively simple. Of course, this Plugin will actually work, you can rest assured.

Generate policy classes based on conditions

The first question is: is there any node in the Plugin that can generate Java code like APT?

Dear bosses, do not know to have learned about Jake great spirit of ButterKnife ‘. Com. Jakewharton ButterKnife plugin is how to generate the R2.

In the previous Module, r.ID was not final and could not be used by annotations. At this time, Jake hooked the Task generated by R file, and then copied a copy of R2.

android.applicationVariants.all { variant ->
     variant.outputs.all { output ->
       variant.outputs.all { output ->
      // 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)) {
        val processResources = output.processResourcesProvider.get(a)// TODO lazy

        // 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(
                      "Android Gradle Plugin is 3.3.0")
                })
                .builtBy(processResources)
        val generate = 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(generate, outputDir)
      }
    }
}
Copy the code

The above code, it is in the Android Plugin can register a registerJavaGeneratingTask Task. This Task can generate some Java classes we need during the compilation phase. Unlike Transform, this phase does not enter the JavaCompiler section, so we can generate Java classes through Javapoet, and we can refer to this class in the actual coding section.

Since the Extension of the Plugin knows that the current project needs to use several vendors, we can directly generate the policy class by generating code (previously, the policy class was implemented by the access party), and try not to let the development write the problems that can be solved by the code.

The above is a simple Java class THAT I have generated. The less it does for the access party, the less likely it is to cause problems, which is architecturally called high cohesion. Javapoet and KotlinPoet are two things that can be expanded into an article, so I won’t go too far here.

How do I change an AAR to a source dependency

The previous article solved the problem of difficult debugging of the Plugin, but there is a final small problem in this article, because the Plugin is available to other apps, so it directly uses maven dependencies. However, in the Demo development phase, the source code compilation method is more suitable for my development, so how to replace a group+name+version with a local Module?

Introduce git repo – before the project by means of setting when introducing other warehouse to the current engineering, then through configurations resolutionStrategy within the so-called resolution strategy to help solve the problem of more dependent on warehouse project.

So can we steal this logic? Hahaha.

val splite = pair.first.split(":")
val substitute = "${splite[0]}:${splite[1]}"
project.subprojects.forEach { it ->
    it.configurations.all { config ->
      config.resolutionStrategy {
            it.dependencySubstitution.substitute(
                  it.dependencySubstitution.module(
                      substitute
                  )
                  ).because("debug enable").with(it.dependencySubstitution.project(pair.second))
                        it.dependencySubstitution.project(pair.second)
        }
    }
}
Copy the code

First, in the policy, we have dependencySubstitution, where substitute is group+name, and then pair. Second is mapped to the local project. ResolutionStrategy will replace all group changes within the project, and we will complete the local mapping within the project.

TODO

There is one thing I would like to do, because the current push is designed to minimize dependencies, so there is no direct reference to OKHttp. Then introduce dependencies by introducing some of the Adapters like Retrofit based on this, and you can better improve the capabilities of the current repository.

conclusion

I’ve done some fun things since I came to Big B. About the industry to persuade android my personal opinion, although now the client position may be less than before, but does not mean that the client technology stack is very shallow ah. There are many things that Android can play with, such as Aop, Apt, Apm performance monitoring, debugging related, compiler optimization, CI/CD, static checking, network optimization, modularity, Gradle related, DSL and so on.

For a technology, the most important thing is to do in their own field is very deep, as long as you can do the so-called irreplaceability, so there is no client to persuade, and you big guys encourage it.