nonsense
2020 is still a big year for me. I’m a 90-year old Android, and I’ve been having a bit of a midlife crisis for the last few years. Since I have been working in a small company, I was limited by the development vision, so I could only study some technologies I saw. I fell into the bottleneck of technology, and felt the so-called ceiling. It was very difficult to get started on something new, especially the Gradle plug-in.
Last year, I left for Hello, and then I learned about the technology stack and development model of some big companies, as well as some technology related to the chassis. In my spare time, I carefully read the functions implemented by others, strengthened my knowledge of Gradle and some simple Apm, and expanded my understanding of componentization.
Say some own opinion, now can go to a big company or try to go to a big company, and a group of excellent people to work together, the speed of progress will be faster. For example, my current group of several leaders, are particularly strong, in their side or learned a lot of technology completely did not understand before. It would have saved you a lot of detours if you had someone who could give you one.
The body of the
At the beginning of the text, I will first send out the two project addresses introduced in this article to you. In fact, with the project and Demo to read this article, you should have a different understanding of some strange posture points.
I wrote the toy routing component myself
More rational multi-repository compilation of plug-ins
Coroutines and responses
I want to start by talking about reactive programming, which is my personal understanding that an input value has a valid output. When I call a method, the method will give me the result directly instead of telling me the result through a callback. Because in the case of asynchrony, it’s simply not straightforward enough.
And hang coroutines is through the function, namely the callback function by suspending and recovery mechanism, into a return value method, when I call this method without a return value, the program is in a pending state development does not need to care about, and returns a value, we can go on into a downward.
Writing this way allows us to take the originally complex and tedious asynchronous programming, and the most terrible callback hell, and turn it into linear, synchronous, sequential execution of the code, and facilitate the iterative maintenance of subsequent code.
That’s why I prefer coroutines to Rx, because there’s no escaping the law of fragrance.
From the startActivityForResult
I’m sure you’ve all used startActivityForResult, but there are a few scenarios that are particularly nasty. For example, if I use this in a list page, I first throw the Click event to the Activity and then process the result in the onActivityResult method. And the real use of the adapter is really uncomfortable.
Aside from Google’s recent framework, is it possible to optimize a more nuanced way of writing existing code or routing?
In my toy routing framework, I borrowed from the principle of Premission permission request library, passed parameters and target page to the Fragment through a proxy Fragment, and bound the Fragment to the outer Activity. Then, the Fragment initiates the page jump logic, accepts the return value of the page jump and the Callback parameter, and informs the route of the current hop result through the Callback method.
class FragmentForResult : Fragment() {
var onSuccess: () -> Unit = {}
var onFail: () -> Unit = {}
var clazz: Class<out Any>? = null
private var code by Delegates.notNull<Int> ()override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState) code = arguments? .getInt("requestCode".0) ?: 0
valintent = Intent() clazz? .let { intent.setClass(requireContext(), it) }// Remember that the fragment must belong to the fragment, not the context
startActivityForResult(intent, code)
}
override fun onActivityResult(requestCode: Int, resultCode: Int.data: Intent?). {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == code) {
if (resultCode == Activity.RESULT_OK) {
onSuccess.invoke()
} else {
onFail.invoke()
}
}
}
}
//
fun AppCompatActivity.startForResult(code: Int, clazz: Class<out Any>, bundle: Bundle? = null,
onSuccess: () -> Unit = {}, onFail: () -> Unit = {}) {
var fragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as FragmentForResult?
if (fragment == null) {
fragment = FragmentForResult()
}
fragment.onSuccess = onSuccess
fragment.onFail = onFail
fragment.clazz = clazz
valmBundle = Bundle() bundle? .apply { mBundle.putAll(this)
}
mBundle.putInt("requestCode", code)
fragment.arguments = bundle
supportFragmentManager.beginTransaction().apply {
if (fragment.isAdded) {
remove(fragment)
}
add(fragment, FRAGMENT_TAG)
}.commitNowAllowingStateLoss()
}
private const val FRAGMENT_TAG = "FragmentForResult"
Copy the code
It’s important to use startActivityForResult inside the Fragment, not the context.
Although this method has solved some of my pain points, I can use the result directly in the non-activity, but I personally think it can be more dishonest?
If I can get the actual result as soon as I call the method, how sweet!! Wouldn’t it be cool if, within my SDK, the user could get the results directly using the suspend restore mechanism?
suspend fun KRequest.dispatcher(context: Context): Boolean {
return withContext(Dispatchers.Main) {
val result = await(context)
result
}
}
suspend fun KRequest.await(context: Context): Boolean {
return suspendCancellableCoroutine { continuation ->
onSuccess = {
continuation.resume(true)
}
onFail = {
continuation.resume(false)
}
start(context)
}
}
Copy the code
First, I don’t refer to coroutines directly in the routed Module, but provide coroutine support as a separate repository for users. This way, if the current project does not use the coroutine, it does not need to rely directly on the coroutine repository.
All right, let’s go ahead and make some changes. First we wrap an asynchronous wrap suspend by suspending the function and finally throw the result back to the user. Here I only use within the coroutines suspendCancellableCoroutine, put all the callbak into a suspended function, before my article introduced, Rxjava Emitor is a similar role. Since the startActivity operation must be operated on the main thread, an additional thread schedule can be wrapped around it.
And finally what happens to the user?
Before the change
try {
val request = KRequest("https://www.baidu.com/test", onSuccess = {
Log.i("KRequest"."onSuccess")
}, onFail = {
Log.i("KRequest"."onFail")
}).apply {
activityResultCode = 12345
}.start(this)}catch (e: Exception) {
e.printStackTrace()
}
Copy the code
The modified
GlobalScope.launch {
val request = KRequest("https://www.baidu.com/test").apply {
activityResultCode = 12345
addValue("1234"."1234")
}.dispatcher(this@MainActivity)
Log.i(""."")}Copy the code
From my point of view, at least this is more linear, and it’s a little easier for me to write if I have consequence logic to follow.
Modular optimization techniques
If this is the only content of the article, I feel a little bit of water ah, yes I am the headline party, the core purpose of fooling you in or let you see my recent gradle plugin, for the modular installation force tips.
The following will be analyzed from several small problems respectively.
The AAR that publishes jCenter is weird
Do not know each development classmate is written in actual project encounter some trouble. Take my toy Routing component project as an example. Below is what my Project looks like.
RouterLib depends on RouterAnatation. When I pushed aar to Jcenter, I often encountered the problem that the poM of RouterLib could not find the dependency of RouterAnatation.
First, gradle uniquely identifies a JAR by group, name (ID), and version, similar to Maven
Our Module build.gradle also has group, version, and name (ID) is our ModuleName. I have been confused before, when the foreign bosses push Aar, do you want to bother so much, one by one after the modification push it, it is too not smart.
Gradle upload for ButterKnife
apply plugin: 'maven'
apply plugin: 'signing'
version = VERSION_NAME
group = GROUP.Copy the code
In fact, because they have defined group+version in each module, they do not need to care about this part of the dependency when uploading jCenter, the plug-in will replace these local repositories with group+ ID +version when generating Pom files.
Use it wisely
Here is a very simple technique. You can write group+ ID +version directly, using simple Gradle syntax, and replace them directly to our local repository.
Let’s take a look at this part of the code.
// Add configuration to all projects
allprojects {
configurations.all { Configuration c ->
// All dependencies are demoted
c.resolutionStrategy {
dependencySubstitution {
all { DependencySubstitution dependency ->
if (dependency.requested instanceof ModuleComponentSelector) {
// If the group+name in the project is equal to the group+name in the remote project, then compile the local project directly
def p = rootProject.allprojects.find { p -> p.group == dependency.requested.group && p.name == dependency.requested.module }
if(p ! =null) {
dependency.useTarget(project(p.path), 'selected local project')}}}}}}}Copy the code
You u need to declare this code in the build.gradle of the project root node, define the dependency policy of configurations, and replace all remote dependencies with local dependencies. I simply added a few comments to the above code so that you can understand what the code does.
In this way, all of our implementations in modules can be written as remote dependency addresses that we upload to jCenter, even if they don’t exist on the remote side. During development, the local repository source code is compiled, rather than the remote version.
Gradle – Repo upgrade version
For example, if this Module refers to the configuration of the current Project global, it can be used to create a new gradle-repo plugin. If this Module refers to the configuration of the current Project global, it can be used to create a new gradle-repo plugin. This will cause compilation failure problems.
From the Gradle documentation: A composite build is just a build that contains other builds. In many ways, composite builds are similar to Gradle multi-project builds, except that they include full builds rather than individual projects
Combining builds that are typically developed independently, for example, breaking large multi-project builds into smaller, more isolated chunks that can work independently or together as needed while trying to fix bugs in libraries used by the application.
This problem can also be improved in the same way as mentioned above in ComposeBuilding, because each Project is compiled independently, so there is no specific content missing for the Project.
I improved the plugin address before the big Boss is GradleTask.
The routing of the Plugin
Routing component Apt is mainly used to help Module to generate routing table, in which the Apt plug-in in the Module in fact, there are a lot of small hidden dangers and small problems, the following to expand a little popular science how to optimize.
Small upgrade
The route contains a Plugin that collects classes from each Jar package or source code and dynamically registers them with a registered class through ASM instrumentations, as described in my previous article. I have a bold scheme to speed up the ARouter and WMRouter compilation.
If you’ve ever used ARouter, you need to add a block of code to each ‘com.android.library’.
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
Copy the code
This is because APT is not executed in a fixed order, and Javapoet can only generate new Java classes, and has no way to modify an original class. This results in the need for each module to generate a routing table class with a distinct name. If the developer misses this code, the route will be lost due to duplicate classes in the routing table.
I felt that this was still a little weak to document, so I added it to my current Plugin.
class AutoRegisterPlugin implements Plugin<Project> {
private static final String EXT_NAME = "AutoRegister";
@Override
void apply(Project project) {
project.getExtensions().create(EXT_NAME, AutoRegisterConfig.class);
if (project.plugins.hasPlugin('com.android.application')) {
project.android.registerTransform(new NewAutoRegisterTransform())
project.afterEvaluate(new Action<Project>() {
@Override
void execute(Project newProject) {
AutoRegisterConfig config = (AutoRegisterConfig) newProject.getExtensions().findByName(EXT_NAME);
if (config == null) {
config = new AutoRegisterConfig()
}
config.transform()
}
})
}
project.afterEvaluate {
if (project.plugins.hasPlugin('com.android.library')) {
def android = project.extensions.getByName('android')
android.defaultConfig.javaCompileOptions.annotationProcessorOptions {
arguments = [ROUTER_MODULE_NAME: project.getName()]
}
if (project.plugins.hasPlugin('kotlin-kapt')) {
def kapt = project.extensions.getByName('kapt')
kapt.arguments {
arg("ROUTER_MODULE_NAME", project.getName())
}
}
}
}
}
}
Copy the code
If ‘com.android.library’ also introduces the router-register plugin, it is possible to refer to apt and route dependencies directly to the current module, but I have to add TODO for the time being.
No Gradle Plguin does not use Gradle plugin tips
If you are currently developing based on ARouter or similar routing components, and you need to write arguments in your Module, you can also copy the code to the root directory of build.gradle if you find it too cumbersome to write gradle plugins.gradle. This will do the same thing.
allprojects {
project.afterEvaluate {
if (project.plugins.hasPlugin('com.android.library')) {
def android = project.extensions.getByName('android')
android.defaultConfig.javaCompileOptions.annotationProcessorOptions {
arguments = [ROUTER_MODULE_NAME: project.getName()]
}
if (project.plugins.hasPlugin('kotlin-kapt')) {
def kapt = project.extensions.getByName('kapt')
kapt.arguments {
arg("ROUTER_MODULE_NAME", project.getName())
}
}
}
}
}
Copy the code
Compile plug-ins on the spot
Gradle plugin development has always had two pain points.
1. Plug-ins can’t be referenced by the project on the spot. Each change takes a long time to make.
2. Debugging is difficult, plug-ins for beginners, you do not have a person to teach, basically want to learn to debug plug-ins is hell.
Here’s a more interesting way to play it. It’s based on ComposeBuilding, but it relies on another Gradle plugin to make two things that are not connected to each other.
Before, I used to write gradle plugin using buildSrc+ Settings. But at the end of the year, someone translated the Gradle team’s documentation, and they now suggest using a method called Composed Build to compile plug-ins.
Goodbye buildSrc, embrace Composing Builds to improve Android build speed
In fact, my routing project also has this new version of the plugin way. Those of you who are interested in developing plug-ins can consider this approach.
We need to create a new Project under the current module. This Project(” Plugin “) is where our Plugin is stored. We need to create a new Project under the original Project settings.gradle. Add an includeBuild(‘./Plugin’). This will compile the Plugin project when we run the current project.
After that we just create our Plugin Module (autoRegister) under the Plugin project.
apply plugin: 'groovy'
apply plugin: 'java-gradle-plugin'
buildscript {
repositories {
jcenter()
maven {
url 'https://maven.aliyun.com/repository/central/'
}
maven {
url 'https://dl.bintray.com/leifzhang/maven'
}
jcenter()
}
dependencies {
// Add the Kotlin plugin because you need to use Kotlin
classpath "Org. Jetbrains. Kotlin: kotlin - gradle - plugin: 1.3.72"}}repositories {
maven {
url 'https://maven.aliyun.com/repository/central/'
}
maven {
url 'https://dl.bintray.com/leifzhang/maven'
}
jcenter()
google()
}
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation 'com. Kronos. Plugin: BasePlugin: 0.2.0'
implementation 'com. Android. Tools. Build: gradle: 4.0.0'
implementation 'Commons - IO: the Commons - IO: 2.6'
implementation 'org. Javassist: javassist: 3.20.0 - GA'
}
gradlePlugin {
plugins {
version {
// You need to refer to this plugin by id in the app module
id = 'router-register'
// The path to the class that implements the plug-in
implementationClass = 'com.kronos.autoregister.AutoRegisterPlugin'}}}Copy the code
The java-gradle-plugin plugin is the most important one, and the Extension below is the core to establish the plug-in dependency of the two projects. The Project can directly find our autoRegister plugin by using this plugin. And any subsequent code changes can take effect on the spot.
// Build. Gradle in the root directory
plugins {
// The id apply false defined in the build.gradle file in the folder is not referenced by gradle
id "router-register" apply false
}
Copy the code
Finally, we just need to declare the Plugin under build.gradle of our Project, so that our Plugin can be referenced by the Project.
conclusion
2020 is a year of gratitude. In fact, I am very grateful to the big guy around me, because I can make faster progress with the big guy, and I can reduce many detours. I’ve written about 30 articles in nuggets this year. Thank you for reading my articles. Your reading actually satisfied my vanity.
In fact, writing an article at the same time is also a promotion of their own, not only can improve their own document ability, and when you write an article, more you will go to this related information for a more systematic grasp, and then you dare to boast with the big guy ah.
Denver annual essay | 2020 technical way with me The campaign is under way…