Modern Android development: the choice between componentization and modularization

Author: Guge E

At the beginning of the project, a layered architecture is generally used to access various frameworks, and then write business code. But if the project grows gradually, there will be many project management problems, such as:

1. Code reuse and abstraction issues
2. Compilation speed issues
3. Version iteration speed issues

Therefore, various technologies such as componentization, modularization, dynamicization, plug-inization, and cross-platform will be mentioned and valued.

Componentization

We have gone through the prehistoric period of Android development, because the official has given a very good Jetpack component library, so we are currently making more business-oriented components and abstractions to solve certain types of scenarios or everyone may use As a result, it is cumbersome and error-prone to write.

Writing components is not an easy task.

First of all, the development component must realize that some scenarios can be abstracted. If you program with the attitude that the code can run, it is basically impossible to think of abstraction, and it is also the easiest to fall into the code trap and cannot extricate yourself. Even for the simplest extraction, many times I would rather take the trouble of copying and pasting than extract a public method. Then after someone else has developed the component, there is no other feeling except for the real fragrance.

Secondly, the development component must design a useful and concise API. Many children's shoes, except for data entity classes and various Manager classes, basically do not define new data structures, let alone design interfaces for others to use.

Then, when developing components, you must think about the configuration of the components to improve the flexibility of the components. Products like to reuse the previous logic and add a little bit of difference. If it cannot be configured, the limitations of the components will be greatly reduced.

Finally, the development components have to handle edge cases and exceptions well. Components will be called by various people in various postures, so various unexpected scenarios may appear, which also tests the development and writing code to control the details.

So what I have always paid attention to is the ability to write components, which requires continuous training and continuous reading of excellent component libraries to master. The interview must ask Okhttp interceptor, can you really use something similar in your own components?

Best example? Suppose now we want to write a recording function using AudioRecord. In the beginning, it may be launched directly in the Activity:

private var isRecording = false;
private suspend fun startRecording() {
    audioRecord = AudioRecord(...)
        
    val data = ByteArray(bufferSize)
    audioRecord.startRecording()
    isRecording = true
    File(applicationContext.filesDir, "recording.pcm").outputStream().use {outputStream ->
        while (isRecording) {
            val bytesRead = audioRecord.read(data, 0, bufferSize)
            if (bytesRead > 0) {
                outputStream.write(data, 0, bytesRead)
            }
        }
    }
    audioRecord.stop()
    audioRecord.release()
}

Write it down in this way, once the process is cleared, then you feel that you are done.
Then the product logic changed: the audio data should be uploaded to the server in real time.

Change: Change the part of writing files to network transmission

After a while, the boss came again, and the recording failed when there was no network.

Change: judge the network environment, add if else.

After a while, the boss came again, and I started to have internet access, but the recording failed after entering the elevator, so I must save a copy to the file, and if I have internet, write a copy to the internet as well.

Change: Change from if else logic to double write logic.

Only for the above scenario, the overall process is quite stable, but the destination of writing is different, so why not abstract an AudioSink interface to represent the storage destination of writing?

interface AudioSink {
    suspend fun write(buf: ByteArray, offset: Int, len: Int)
    fun close()
}

Then the whole process is stable as:

private suspend fun startRecording(sink: AudioSink) {
    audioRecord = AudioRecord(...)
        
    val data = ByteArray(bufferSize)
    audioRecord.startRecording()
    isRecording = true
    while (isRecording) {
        val bytesRead = audioRecord.read(data, 0, bufferSize)
        if (bytesRead > 0) {
            sink.write(data, 0, bytesRead)
        }
    }
    audioRecord.stop()
    audioRecord.release()
    sink.close()
}

If you want to write a file, write an implementation:

class FileAudioSink(file: File): AudioSink {
    ...
}

If you want to write a network, write an implementation:

class NetworkAudioSink(api): AudioSink {
    ...
}

If you want to judge the local and network, write a proxy:

class DelegateAudioSink(
    val origin: AudioSink
): AudioSink {
    ...
}

If you want to write both local and network at the same time, it is double writing or multiple writing:

class MultiAudioSink(
    val list: List<AudioSink>
): AudioSink {
    ...
}

Once these classes are written, the specific logic is the change of the entry parameter. Of course, the actual situation should consider various abnormal situations. If you are experienced enough, you can take these scenarios into consideration at the beginning, and synchronize their usefulness and limitations to the product to help the product make the best choice at the beginning. This is what is called experience.

If you look back at the above examples, you will find that they are all familiar things, even design patterns. If the entire abstraction is done well, if it is switched from audio to video, can this piece be perfectly reused? Does that mean that the requirements can be fulfilled earlier? ——Then it can be cut earlier~

Componentization is the abstraction of business scenarios and functional scenarios. This is what most requires everyone to devote time to doing and training.

Modular

Some complex components can form modules one by one, and then maintain them separately. But when talking about modules, it is more about business modules. It is to put different businesses under different modules. This is of course meaningful for giant apps. After all, it may be a team maintaining a certain module. But for small apps, that doesn't mean much.

Because modularization brings a very difficult problem: the communication problem between modules. Both Arouter and TheRouter are largely aimed at solving communication problems between modules. Simple modularization generally considers the extraction of basic modules, but the definition of basic modules is always vague, and a giant basic module is extracted inadvertently. Just like many students who like to use the Base class, there is a huge Base class in the end.

Therefore, it is unnecessary to engage in modularization for small apps. Huge apps have problems with compilation speed and multi-team development, so major companies will produce and share their own multi-modular practices. Small companies tend to follow the trend and make the project hugely complicated, which is boring.

For example, large companies split multiple modules and then use aar packages to speed up the build. Correspondingly, there are private Maven warehouses and automatic packaging scripts, which are very complicated and necessary for them. For small companies, the ratio of input to output is better than spending some money to buy a better computer.

Dynamic, plug-in

As for dynamics and plug-ins, I can only sigh that the masters who wrote these frameworks are really strong in technology and are real hackers. Probably only the huge domestic apps and strange market review logic can create these demands.

If you have time, it is also very good to look at the source code of these libraries, which is very insightful. The important thing is to look at the problem scenarios encountered by the major frameworks, how to explore solutions, and how to solve them in the end.

However, the problem is always the maintenance problem. With the iteration of the Android version, it is always necessary to have new changes or to seek new solutions. However, the success of domestic frameworks and libraries is often related to the people who promote this project. There is no stable maintenance. Mechanisms are easily affected by personnel changes.

at last

No matter how complicated things are, they are slowly assembled from parts one by one. We have the blueprint, and we turn the screws to make it happen. Tighten more, and maybe have the opportunity to turn a bigger screw. I turned 20 screws on the emo component library, which is a lot, and it's not a small library. But at present, my source code has been transferred to private, and the screws to be screwed later are only left to small groups to enjoy themselves. Use actions to prove how unstable the open source libraries of domestic developers are.

Android study notes

Android Performance Optimization: https://qr18.cn/FVlo89
Android Vehicle: https://qr18.cn/F05ZCM
Android Reverse Security Study Notes: https://qr18.cn/CQ5TcL
Android Framework Principles: https://qr18.cn/AQpN4J
Android Audio and Video: https://qr18.cn/Ei3VPD
Jetpack (including Compose): https://qr18.cn/A0gajp
Kotlin: https://qr18.cn/CdjtAF
Gradle: https://qr18.cn/DzrmMB
OkHttp Source Code Analysis Notes: https://qr18.cn/Cw0pBD
Flutter: https://qr18.cn/DIvKma
Android Eight Knowledge Body: https://qr18.cn/CyxarU
Android Core Notes: https://qr21.cn/CaZQLo
Android Interview Questions from Previous Years: https://qr18.cn/CKV8OZ
Latest Android Interview Question Collection in 2023 https://qr18.cn/CgxrRy
Android Vehicle Development Job Interview Exercises: https://qr18.cn/FTlyCJ
Audio and Video Interview Questions:https://qr18.cn/AcV6Ap

Guess you like

Origin blog.csdn.net/weixin_61845324/article/details/131415491