• Writing in the front
  • 1. Divide components
    • architecture
      • Host shell, debugging shell
      • Component layer
      • Base layer
      • How do MVC, MVP, MVVM sink
    • Utils specification: Use Kotlin
      • A static method
      • The singleton pattern
    • Res specification: clear naming
      • string.xml
    • asset
    • Special components
      • CommonResource
      • CommonData
      • CommonBase
      • CommonSDK
  • 2. Create a component
    • A new component that is split from the original code
    • Create new components for new functionality
      • Determination of new functions
      • Identification of new component architectures
      • The creation of a new component architecture is silly
  • 3. Component development
    • Component debugging is independent
    • Multicomponent debugging
    • Component development
    • Component coupling
      • Data transfer between components
      • Intercomponent function calls
      • Interface jump between components * build standard routing requests * process routing requests * ARouter can’t handle the situation
      • UI mixing between components
  • 4. Maintain components
    • Components to confuse
    • Volume optimization
      • Assets to optimize
      • Res optimization
      • Lib directory optimization
      • Clear unnecessary resource files
      • Clear unused alternate resources
      • Sort code
    • Compilation acceleration (Gradle optimization)
    • Depend on the control
    • Release the aar
    • Common mistake: already present
  • 5. Component publishing
  • 6. Remove components
  • Write in the back
    • Outlook: Component sharing
    • The end of the

Writing in the front

Demo will be updated on Github when free, welcome to follow. A COMMIT corresponds to a specification. So it will not be soon, you can first star collection for reference.

This article was written while I was componentizing my personal project, Time Cat (experience welcome). Stepped in a lot of holes, but also constantly thinking of elegant solutions. Since it’s a personal project, it should be instructive to be able to make waves and publish details without reservation.

I started with the most popular componentization solution, but in the process of refactoring it became clear that the most popular was not necessarily the best. Popular solutions, for example, have the Common base library that each component relies on. That’s actually bad. The lack of clarity about common’s responsibilities leads to bloatiness, and any code feels like it’s sinking into the Common library. This can greatly affect the compile time of the component. In fact, when I first componentized it, it didn’t make much of a difference. It was even slower. It may be the pot calling the kettle black, but 10 minutes is really uncomfortable. Then there is the fact that components can switch between standalone mode and library mode. It was amazing at first, but the experience was terrible. Originally, THERE was a ButterKnife in my project, and it was troublesome to change it greatly every time I switched, because the usage of ButterKnife in component library mode and independent mode was not uniform. The independent mode used R.I.D.xx, and the library mode used R2.id. XXX, which was too intrusive. There are many other practices, such as two copies of Androidmanifest.xml for each component, publishing components as aArs, and so on, some of which are not as efficient as the blog claims, and some of which are not necessary at all. Finally, I standardized the componentization process and defined the content and boundary of componentization according to my idea. After componentation of my project Timecat, full compilation of a single large component (3W + lines of code) took 10 minutes, incremental compilation (debugging once) took 1 minute, full compilation of a single component (1W – lines of code) took less than 3 minutes, and incremental compilation took less than 30 seconds, meeting the efficiency requirements of development. Of course, second to compile second to execute is also possible. If you had the energy to refine the base layer a bit more, the compilation efficiency could have been improved.

In the process of realizing componentization, there may be different technical paths to solve the same problem, but the main problems to be solved are as follows:

  1. Component debugging independent: Each component can become a releasable whole, so the component development process should meet the requirements of running and debugging separately, which can also improve the compilation speed of the project during development.

  2. Intercomponent data passing and function calls: Data passing and intercomponent method calls are another issue that we mentioned above that must be addressed.

  3. Component interface hopping: Different components not only transfer data, but also jump to each other’s pages. How to realize inter-jump without interdependence in componentized development process?

  4. Intercomponent UI mixing: How do I get an instance of a Fragment in a component and add it to the interface of the main project when the main project does not directly access the specific classes in the component? The View?

  5. Component on demand dependencies: After component development is complete, how is the full integration achieved? There is also in the debugging stage, just want to rely on a few components for development debugging, how to achieve?

  6. Component versus code isolation: The goal of component decoupling and how to achieve code isolation. Not only are components isolated from each other, but modules can dynamically add and remove components when they depend on them. In this way, modules do not operate on specific classes in the component, so completely isolating module use of classes in the component makes the decoupling more complete and the program more robust.

  7. Component obfuscation and volume optimization: Componentization means refactoring. If you have to add a lot of code to maintain componentization after refactoring, it will be destroyed.

  8. Special component standardization: Clearly define certain fixed responsibilities and boundaries in the expectation that you can throw code into special components without thinking about becoming a garbage dump.

In fact, the above 8 questions can not cover all cases, large and small holes, not systematic.

Now, I propose to put them into the componentized life cycle and initially build a systematic componentized guide.

Why start with the life cycle?

First, a clear component lifecycle can reduce hidden time costs due to changing requirements during development. For example, the boss wants to add a live broadcast function to the current video app. Should you create a new Git branch and write in the original video component, or create a new component to write? Ok, now the boss is crazy, you just made the demo, the boss said not to do it? ! What do you do? We have to scrap this branch. Ok, after two months, the video component developed, the previous interface and the current interface is different, now the boss suddenly said to add a live broadcast. At this time, the demo written before is still in, but if it is directly written in the video component, it cannot be used now, and it is absolutely difficult to modify it; If you’re creating a new component, you can just import it as if it were a third-party library. Which is easier?

In this article’s definition, components and third-party libraries are similar.

For the third party library, we need to introduce the third party library, use the third party library, if it is not good to remove the third party library. Most of these processes are done unconsciously, usually by looking at the document, the document is written in a mess. And often, the removal of third-party libraries is not explained. When removing, not only according to the introduction of third-party library instructions, delete the introduced part, but also depending on the project where the library is used, delete the library will crash, etc., very troublesome.

Components go through a similar life cycle, but the component life cycle is simpler and easier to manage because components are decoupled at intervals. Each component can have its own database, can have its own architecture, etc., basically a one-stop service, and write an app is not different (if your component can not become a separate app, then the component may not need to do a separate).

Second, componentization doesn’t happen all at once; it takes a long time. This is where component partitioning is particularly important. When will this feature be taken out? When do you come in? When will the new feature be added? How to add new features? How to ensure componentization while normal business running? Why don’t you waste time on componentization? How do you determine that this is a relatively good way to partition components? The problem is, things are constantly evolving, and the current architecture may work, but in a month or two it will be a garbage dump. So component partitioning is an important part of the life cycle.

Lifecycle oriented component design requires a set of operational specifications for component creation, component development, component release, component removal, component partitioning and component maintenance. The following is a detailed analysis of the reasons and solutions for this system.

1. Divide components

Componentization and modularization compared, componentization is more fine-grained, but must grasp the degree of component division, not too fine, not too thick.

Too fine division will lead to too many components and maintenance difficulties (of course, if there are many hands, the implementation of maintainer component responsibility system can be perfectly solved, so we prefer fine granularity division)

Too thick a partition will result in slow compilation and a component that looks like a dummy.

architecture

It is divided into three layers: host shell and debugging shell, component layer, base layer. Dependencies between layers:

  • The host shell and debug shell depend on all components with runtimeOnly, not the base layer.
  • Component layers must not depend on each other. The component layer depends on the various libraries of the base layer as needed. The dependencies of all components on the base layer are on demand and may be independent of the base layer if necessary.
  • The base layers shall not depend on each other. Base layer module for third party dependencies, at most can use API long dependencies on third party, do not use implementation short dependencies. (Of course, self-written modules and special components can even depend on nothing)

Part of time Cat architecture is shown as follows:

The contents of each layer are as follows.

Host shell, debugging shell

You can’t have any Java code, just build. Gradle with dependencies on each component, androidmanifest.xml with permissions, entries, variables, and summary activities.

A host shell and a debug shell are essentially the same. A host shell is equivalent to a debug shell that debugs all components simultaneously.

Component layer

Each component should run independently. This means that new data structures can be declared and corresponding tables and databases created within components.

There is no need to sink the Model into the base layer. If you need to share the same model, it doesn’t make sense to either split components or get fragments from other components by ARouter.

If you really want to sink the model into the base layer so that components can share the data model, you have to create a new library in the base layer (named CommonData) that is not allowed in the CommonSDK, because you want to make sure that the base layer libraries are all subject to a single responsibility.

Base layer

Try not to sink code into the base layer! Try not to sink code into the base layer! Try not to sink code into the base layer!

The setting of the base layer is to make components rely on the base layer and reuse the code of the base layer, so as to achieve the purpose of efficient development. So don’t let the base layer become your code trash can. There are two requirements for the base layer, internal and external. External, the second to understand the name. So when writing the business code for the component, think about the code in the base layer, reuse is more important than building the wheel. Internally, the classification should be clear. Don’t let a base layer’s library be filled with xxutils.java junk. It’s not about not writing, it’s about writing less, and categorizing clearly. Recommend AndroidUtilCode and learn how Util is classified. Second, code that sinks to the base layer may not be used by other components, so go through code review before sinking the code.

The base layer includes its own CommonSDK, its own CommonBase, CommonResource, third-party SDK, third-party UI, and third-party code-level libraries.

  1. CommonSDK containing

    • Your Utils
    • Events entity
  2. CommonBase containing

    • Your own widget
    • BaseActivity BaseFragment etc
    • base_layout
    • Res: base_style. XML
  3. CommonData this is the base layer special library, including

    • Shared Database
    • ARouter routing table
  4. CommonResource containing

    • resources
  5. The third-party SDK refers to the SDK of IFlytek SDK, Tencent Bugly SDK, Bmob SDK and other specialized solutions that require registration and application. This part of the dependency is in the base layer. Note:

    1. Configure dynamic dependencies by component to reduce compile time. Because not all components use all SDKS, the reality is that components use only part of the SDK.
    2. A new module must be created for each third-party SDK to contain the SDK. Components depend on demand.
  6. Third party code-level libraries refer to libraries that annotate, generate code and define a code style such as ButterKnife, Dagger, ARouter, Glide, RxJava, ArmsMVP, etc. This part of the dependency is placed in CommonSDK. This means that each component relies on all third-party code-level libraries. The code level libraries are listed below:

    • ButterKnife: binding the view
    • Dagger: Object injection
    • ARouter: Cross-component routing framework
    • Glide: Image loading frame
    • rxjava,rxAndroid: asynchronous
    • ArmsMVP: The above libraries are integrated here, and the library also defines a set of MVP specifications. There are templates for one-click armSMVP-style MVPS, instead of creating individual files manually.
  7. Third-party UIs refer to UI widget libraries such as SmartRefreshLayout, QMUI, and Android-Skin-Support. This part of the dependency in their own CommonBase. For larger libraries, prioritize in-component dependencies and think twice before sinking to CommonBase to avoid letting other components passively attach unnecessary compile time.

  8. Other third-party libraries, depending on the size of the library, should be placed in the component first, and try not to sink into the base layer.

How do MVC, MVP, MVVM sink

You can only sink M to the base layer at most. V and C in MVC, V and P in MVP, V and VM in MVVM can only be placed in the component layer.

Why is that?

M is the data layer, and if you want to do anything to share data between components, sink M. Note that it is not recommended that all M’s sink into the base layer. There are two reasons:

  1. Sinking M will result in a bloated base layer, forcing other components to compile, forcing time costs
  2. M is placed inside components for easier maintenance

V, C, P, and VM are all businesses, and businesses should remain within components.

Keep in mind that only good code can sink to the base layer.

Utils specification: Use Kotlin

Why Kotlin? Because Kotlin makes it easy to extend a certain class of util (using extension functions).

Mandatory: must comment!!

Suggestion: Put it in the CommonSDK of the base layer. (It can also be a separate module, but it is recommended to put it in CommonSDK)

A static method

The original:

/ / declare
final class Utils {
    public static boolean foo(a) {
        return false; }}/ / use
final boolean test = Utils.foo();
Copy the code

After the transformation:

/ / define
object Utils {
    @JvmStatic
    fun foo(a): Boolean = true
}
// Kotlin
val test = Utils.foo()
// Use in Java
final boolean test = Utils.foo()
Copy the code

The singleton pattern

Use lazy, because Utils is initialized when it is used to prevent performance impacts when it is not used.

  1. The hungry type

Kotlin introduced the object type, which makes it easy to declare singleton patterns.

Object declarations are singletons. The Object DataProviderManager can be understood as creating the class DataProviderManager and implementing the singleton pattern.

object Singleton { ... } // call singleton.xx () in Kotlin // call singleton.instance.xx () in JavaCopy the code

This approach is the same as the Java singleton, but with much less implementation code than in Java, which is a syntactic sugar. After decompiling the generated class file, it looks like this:

public final class Singleton {
    public static final Singleton INSTANCE = null;

    static {
        Singleton singleton = new Singleton();
    }

    private Singleton() { INSTANCE = this; }}Copy the code
  1. LanHanShi

Object is instantiated before it is used. How to initialize it on the first call? The delay attribute Lazy in Kotlin is just right for this scenario.

// Kotlin declares class Singleton privateconstructor() { companion object { val instance: Singleton by lazy {Singleton()}}} // call Singleton.instance.xx() // call Singleton from Java Singleton.Companion.getInstance().xx()Copy the code

Res specification: clear naming

All:

  • The use of plug-inAndroid File Grouping PluginIt can group files by name without changing the file directory structure.
    • Grouping rule: use the underscore “_” as the separator in the name, and make the items before the underscore as a group
    • Grouping does not move files
    • Grouping also does not actually create directories

Res within the component:

  • ResourcePrefix “Component name _” // Adds prefixes to resource names in the Module to avoid resource name conflicts

Res within the base layer:

  • ResourcePrefix “base_” // component also wants to use itbase_If so, yesComponent name _base_

The component scoped RES are specified below

string.xml

Classification of the string

  • The tag typestring_short.xml, such as:comfirm.cancel.ok.delete.app_nameAnd so on short string
  • Lengthy help document typestring_long.xml: such as the FAQ
  • Formatting typestring_format.xml, such as:It ranks the first %d in the country
  • String arrayarray_string.xml
  • Number of Plurals
  • Magic stringstring_important.xml, such as:This guy has a goddess name as base signal, mixed into the user edited text stored in the database, can not be translated, do not move. Movement means two flowers.

Note: 0. The project is not big, so it is recommended not to translate

  1. Translate what is shown to the user, not the rest
  2. The string resource in the component sinks to the base layer. If you don’t want to sort it out for the time being, you can create a new oneString_ < component name >.xml. But it’s not recommended, because sooner or later you’ll have to turn into a trash can.

Welcome to add the above.

asset

Avoid collisions by placing that component in the asset/< component name > folder. Do not sink into the base layer (unless it is the base layer’s own asset).

Apk is automatically merged when compiled.

Special components

As we know from specifications 5 (relying on only a few components) and 6 (component isolation, code isolation), you can’t just sink code into the base layer.

But sharing between components is a hard requirement, and it’s not agile to think about it every time you sink.

Is there a solution that can just sink and share without thinking?

A simple and easy way to do this is to define special components that can be sunk as long as they meet the requirements of a particular component.

CommonResource

CommonResource contains and only the following types of resources

  • drawable-xxx.mipmap-xxx
  • color
  • anim
  • integer.bool
  • raw.asset: Files are not compressed or checked for completeness when packaged, that is, 100% packaged into APK.
  • string

Note:

  • There was only one sentence: exceptattr.xmlnamely<declare-styleable... />External all resources. because<declare-styleable... />Is bound to widget and style, so put it in the component that defines the widget<declare-styleable... />Add style to CommonResource<declare-styleable... />.
  • When using a theme color in a vector, consider sinking the style along with it. For example:android:fillColor="? icon_color"Among them? icon_colorIt’s defined in the theme. A little brain is needed here… You might make a mistakeDrawable com.time.cat:drawable/ic_notes has unresolved theme attributes! Consider using Resources.getDrawable(int, Theme) or Context.getDrawable(int).Solution:https://stackoverflow.com/questions/9469174/set-theme-for-a-fragmentIt turned out to beAndroid-skin-supportInternal conflict in this library, hack!
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportHeight="24.0"
        android:viewportWidth="24.0">
    <path
        android:fillColor="? icon_color"
        android:pathData="M3, 18 h3v2zm0 h18v - 2, 5 h18v - 2 h3v2zm0, 7 v2h18v6h3z"/>
</vector>
Copy the code

CommonData

Data model shared library.

  • If you use Greendao, the library relies directly on Greendao to manage the generated data model classes
  • You use BMob (a cloud database saas) and the library relies on it directly
  • You use Room, the library depends on Room directly
  • .

It’s all data model related, defined here.

Advantages: Easy data sharing between components Disadvantages: A component depends on the library, but it only wants to use one database, so it is forced to compile other data, resulting in slow compilation.

Recommended practice: CommonData should still be used, but it should only be used for data models shared by multiple components, such as the user model, for login components, account components, and active components. If only one component uses the database, define and use the database within the component and don’t sink into CommonData.

CommonBase

Base library, requires extreme reuse. containing

  • java : BaseFragment.java.BaseActivity.java.BasePresenter.java, etc.
  • Kotlin: Extention (also availableBaseFragment.kt.BaseActivity.kt.BasePresenter.ktEtc.)
  • res > layout : base_toolbar.xml.base_progressbar.xml, etc.

Here I advocate “over-encapsulation”, the more Base pairs, the more efficiency will be improved in the future. In my project

base_rv.xml
base_rv_stateful.xml
base_rv_stateful_trans.xml
base_refresh_rv.xml
base_refresh_rv_stateful.xml
base_refresh_rv_stateful_trans.xml
base_toolbar_refresh_rv.xml
base_toolbar_refresh_rv_stateful.xml
base_toolbar_refresh_rv_stateful_trans.xml

BaseFragment.java
BaseSupportFragment.java
BaseAdapterSupportFragment.java
BaseRefreshAdapterSupportFragment.java
BaseToolBarFragment.java
BaseToolBarRefreshAdapterSupportFragment.java
BaseToolbarAdapterSupportFragment.java
BaseToolbarSupportFragment.java
Copy the code

PS: Don’t ask me why I use this method, because it’s simple, clear, and easy for others to take over.

CommonSDK

Own SDK and third party SDK. containing

  • ArmsMVP (rX series, Retrofit series and other code style level libraries)
  • bugly
  • xxUtils.kt
  • Log Log Tool
  • Toast and other tools
  • Set up reporting tools
  • Key-value storage tools such as SharePreference and MMKV
  • RouterHub (ARouter’s routing table)

Note:

  • Third-party libraries provide singletons that can be called directly,Don’t use it directly. Take ARouter, for example. ARouter providesARouter.getInstance()...Please don’t be lazy, you have to wrap another layer:
public class NAV {
    public static void go(String path) {
        ARouter.getInstance().build(path).navigation();
    }

    public static void go(String path, Bundle bundle) {
        ARouter.getInstance().build(path).with(bundle).navigation();
    }

    public static void go(String path, int resultCode) {
        ARouter.getInstance().build(path).withFlags(resultCode).navigation();
    }

    public static void go(String path, Bundle bundle, int resultCode) {
        ARouter.getInstance().build(path).with(bundle).withFlags(resultCode).navigation();
    }

    public static void go(Context context, String path) {
        ARouter.getInstance().build(path).navigation(context);
    }

    public static void go(Context context, String path, Bundle bundle) {
        ARouter.getInstance().build(path).with(bundle).navigation(context);
    }

    public static void go(Context context, String path, int resultCode) {
        ARouter.getInstance().build(path).withFlags(resultCode).navigation(context);
    }

    public static void go(Context context, String path, Bundle bundle, int resultCode) {
        ARouter.getInstance().build(path).with(bundle).withFlags(resultCode).navigation(context);
    }

    public static void go(Context context, String path, int enterAnim, int exitAnim) {
        ARouter.getInstance().build(path)
                .withTransition(enterAnim, exitAnim).navigation(context);
    }

    public static void go(Context context, String path, Bundle bundle, int enterAnim, int exitAnim) {
        ARouter.getInstance().build(path)
                .with(bundle)
                .withTransition(enterAnim, exitAnim).navigation(context);
    }

    public static void go(Context context, String path, int resultCode, int enterAnim, int exitAnim) {
        ARouter.getInstance().build(path)
                .withFlags(resultCode)
                .withTransition(enterAnim, exitAnim).navigation(context);
    }

    public static void go(Context context, String path, Bundle bundle, int resultCode, int enterAnim, int exitAnim) { ARouter.getInstance().build(path) .with(bundle).withFlags(resultCode) .withTransition(enterAnim, exitAnim).navigation(context); }... }Copy the code

Take EventBus again. EventBus provides eventbus.getDefault ()… Please don’t be lazy, you have to wrap another layer:

public class MyEventBus {
    public static void post(Object obj) { EventBus.getDefault().post(obj); }... }Copy the code

Similarly, a layer of key-value libraries such as MMKV can be built.

Why an extra layer?

These third-party libraries can be replaced at any time in the future. EventBus, for example, is extremely intrusive if you want to switch to AndroidEventBus with tag. Using our own MyEventBus will have less impact on the function calls (but not without impact, because annotations and so on will need to be changed, just let you change a little less).

2. Create a component

Create a component with two sources. A new component is created from the split, and a new component is created for the new functionality.

A new component that is split from the original code

There are these scenarios:

  1. It used to be that all the code was written inapp/To do this, first pull out all the code and create a new componentcomponentsStep by step processing of the specification.
  2. Previously the component partition is not standard, now repartition new components.
  3. Previously, the component sink was not standardized, and now the code is re-claimed from the base layer to form a new component.

So there are three cases,

  1. The old code is in the shellapply plugin: 'com.android.application'
  2. The old code is in the component layerapply plugin: 'com.android.library'
  3. The old code is in the base layerapply plugin: 'com.android.library'

These three cases deal with old code by component specification. Then create a new component as described below for creating new components for new functionality.

Create new components for new functionality

Identify two parts: What is the new component functionality? What is the architecture of the new component?

Determination of new functions

First, the functionality of a new component cannot be 100% the same as the functionality of an existing component, or it is not new. Second, new component functionality can interact with, but not overlap with, existing component functionality. Finally, the division of new component functionality is consistent in its granularity. One Activity per component is allowed, as long as the granularity of the partition is consistent with that of the other components. So, analogous to chemistry, you divide the elements, hydrogen, helium, lithium, beryllium, boron, you don’t divide any electrons, you don’t divide any oxygen molecules, you keep the grain size in the element layer. However, a lightweight component of an Activity like this is recommended to be published as an AAR.

Identification of new component architectures

There are three commonly used architectures: MVC, MVP, and MVVM. Components are independent and different architectures are allowed.

If you come from the front end, I recommend MVVM, if you are traditional Android development, I recommend MVP.

The creation of a new component architecture is silly

Use a template or AS plug-in.

JessYanCoding’s MVPArms framework, combined with ArmsComponent, is recommended. MVPArms 1 provides the armSComponent-template (Module level one-click Template) to build the entire component architecture. MVPArmsTemplate (page-level one-click template) one-click generation of MVP and Dagger2-related classes required for each business page. You can use the template tool to unify the architecture. Don’t waste too much time on the maintenance of the architecture during the new process, just press MVP mang

I’m not aware of any other architecture that provides templating tools, but what if I didn’t? Welcome to add!

PS: The official MVVM architecture should be able to come out with the entire template…

PPS: JessYanCoding’s MVPArms framework integrates common third-party libraries, recommended for wall cracking!

3. Component development

Component debugging is independent

!!!!!!!!! Do not switch to standalone mode! Components only allow library mode!

Componentization is now a popular practice to divide components into library patterns and standalone patterns, using standalone patterns for development and library patterns for release. For example, in gradle.properties, define a constant value isPlugin (whether it is a standalone mode, true for yes, false for no) and write it in each component’s build.gradle:

if (isPlugin.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
Copy the code

But that’s not necessary.

There are at least four reasons not to switch.

  1. Reliability issues. Note that the components developers face are different at development time than at release time. Dependency conflicts often occur when multiple components are packaged together, which is different from development and wastes time.
  2. Switching costs. To debug communication between two components at the same time, it is often necessary to switch between library mode and standalone mode frequently, each timeGradle SyncOnce, at great cost.
  3. Third-party libraries. Such asButterKnife, switching components into library mode and standalone mode,ButterKnifeThe switch may fail because library mode is usedR2And standalone modeR, extremely invasive.
  4. Maintenance cost. Because there are two modes, two copies need to be maintainedAndroidManifest.xmlThe details vary greatly.

The original intent of these two patterns was component decoupling and agile development, which is a joke.

To solve this problem, the following specifications have been developed:

  • Components only allow library mode!

  • An empty shell is set on the base of library pattern to realize the independent pattern of components indirectly.

  • The third party library is based on the library mode. The ButterKnife component uses R2, not R. If R is used, CTRL + Shift +R is used to replace R2 with regular expression.

  • The shell consists of only two files. Build. Gradle and AndroidManifest. XML

    Androidmanifest.xml declaration entry Activity (other Activity declarations as long as they are declared in the component, they will be merged in the build)

    In fact, the shell can write a template folder that can be copied and pasted as needed.

    You can declare androidmanifest.xml in build.gradle to be placed in the same level of directory without creating extra folders.

Benefits:

  1. Reliability issues. Developers are faced with only the library pattern and end up packaging the components of the library pattern. (onlyapply plugin: 'com.android.library')
  2. Switching costs. With the shell indirectly realize independent mode to debug components, very flexible.
    • You can have a shell for one component, a shell for two components, and cram as many components as you want into one shell.
    • The process of switching shells is not requiredGradle Sync, directly specify which shell is directBuildjust
  3. ButterKnife. The old program must have been used extensivelyButterKnifeIn componentization, a modification is permanent (onlyRtoR2), unlike the previous two modes need to be switched simultaneouslyRandR2
  4. Maintenance cost. Only components in library mode are maintained. Feel free to maintain the shell, there is no Java or Kotlin code.

The bad:

  1. Each shell requires disk space to build, so this is essentially trading space for time.
  2. We haven’t found any other disadvantages yet.

Multicomponent debugging

Because components only allow library patterns, you need to create empty shells that indirectly implement stand-alone patterns for debugging purposes.

The shell consists of only two files. Build. Gradle and AndroidManifest. XML. Androidmanifest.xml declaration entry for activities (all other Activity declarations are merged into builds as long as they are declared in components)

In fact, the shell can write a template folder that can be copied and pasted as needed. You can declare androidmanifest.xml in build.gradle to be placed in the same level of directory without creating extra folders.

You can have a shell for one component, a shell for two components, and cram as many components as you want into one shell. Gradle Sync is not required to switch shells, just specify which shells to Build directly

Component development

Components are complete and independent entities that allow you to build your own database without sinking into the base layer.

Try to reuse the basic layer, such as inherit the Base series layout, include Base series layout and so on.

Use gradle Dependencies to check the dependency tree to see if it conflicts with other component dependencies.

Allow multiple architectures to coexist. Like this component MVC, that component MVP, that component MVVM. Still suggest unification however. I currently have all 3 components in my personal project, and it seems to be easy to maintain at the same time… However, the company generally has personnel changes, and a unified structure is more conducive to communication.

Life cycle injection within a component: The component needs to initialize the database when the Application is created. How can it be uniformly injected into the Application life cycle? MVPArms is recommended for Application, Activity, and Fragment lifecycle injection. You can also implement it yourself:

public class GlobalConfiguration implements ConfigModule {
    @Override
    public void applyOptions(Context context, GlobalConfigModule.Builder builder) {
     // Use Builder to configure some configuration information for the framework
    }

    @Override
    public void injectAppLifecycle(Context context, List<AppLifecycles> lifecycles) {
     // Inject some custom logic into the Application lifecycle
    }

    @Override
    public void injectActivityLifecycle(Context context, List<Application.ActivityLifecycleCallbacks> lifecycles) {
    // Inject some custom logic into the Activity lifecycle
    }

    @Override
    public void injectFragmentLifecycle(Context context, List<FragmentManager.FragmentLifecycleCallbacks> lifecycles) {
    // Inject some custom logic into the Fragment lifecycle}}Copy the code

The injection here implements part of AOP (aspect oriented programming).

By injecting life cycles, more elegant solutions can be implemented, such as:

I don’t write a line of code to implement the Toolbar! And you’re still wrapping BaseActivity?

Component coupling

Components should not depend on each other and should be relatively independent.

Communication between components must allow for no response. That is, when debugging a component separately, you cannot crash even if you access another component that does not exist.

As long as you handle the non-response, you have the boundaries of the components and can easily decouple them.

Data transfer between components

Use the event bus. By default, Greendao EventBus is used, and events are managed by subcontracting.

  • If the Event is used only in the component, the Event class is placed in the component’score/eventUnified management under directories
  • If it is a cross-component Event, the Event class is placed in the base layer CommonSDK.
  • Recommended EventBus event jump plug-in: event navigation dedicated to the EventBus

Comparison between AndroidEventBus and EventBus and Otto: Greendao EventBus is used for efficiency, AndroidEventBus is used for clear event classification and management, and Otto is not recommended.

The name of the Whether a subscription function can be executed on another thread The characteristics of
The EventBus greenrobot is The name Pattern mode is efficient, but inconvenient to use.
Square’s otto no Annotations are easy to use, but not as efficient as EventBus.
AndroidEventBus is Annotations are easy to use, but not as efficient as EventBus. The subscription function supports tags (Action like broadcast receivers), making the delivery of events more accurate and adaptable to a wide range of usage scenarios.

Intercomponent function calls

Declare the called function into an interface, called a service.

Implement it with ARouter.

  • HelloServiceThe input and output data types of the function in the base CommonSDK support the basic data type and any class in CommonSDK, but do not support the component, because the class declaration in the two components must be different.
  • HelloServiceImplPut it where it’s needed, usually in a component. The implementation of special services, such as JSON serialization services, degradation policies, etc., is placed in the base layer CommonSDK.
  1. Expose services (declare service interfaces, implement service interfaces)
// Declare the interface through which other components invoke the service
public interface HelloService extends IProvider {
    String sayHello(String name);
}

// Implement the interface
@Route(path = "/yourservicegroupname/hello", name = Test Services)
public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String name) {
    return "hello, " + name;
    }

    @Override
    public void init(Context context) {}}Copy the code
  1. Discover service, use@Autowired(Using the service)
public class Test {
    @Autowired
    HelloService helloService;

    @Autowired(name = "/yourservicegroupname/hello")
    HelloService helloService2;

    HelloService helloService3;

    HelloService helloService4;

    public Test(a) {
    ARouter.getInstance().inject(this);
    }

    public void testService(a) {
        if (helloService == null) return;/ /!!!!!! Nullity is important when using a service in a component, because components are independent and the service may not be retrieved.

        // 1. (recommended) Use dependency injection to discover services. Annotate fields and use them
        // The Autowired annotation will use byName to inject the corresponding field, without setting the name attribute, and will use byType to discover the service by default (when there are multiple implementations of the same interface, the service must be discovered by byName).
        helloService.sayHello("Vergil");
        helloService2.sayHello("Vergil");

        // 2. Use the dependency lookup method to discover and use the service. The following two methods are byName and byType respectively
        helloService3 = ARouter.getInstance().navigation(HelloService.class);
        helloService4 = (HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation();
        helloService3.sayHello("Vergil");
        helloService4.sayHello("Vergil"); }}Copy the code

The definition, implementation, and usage of these services are similar to functions in C language. C language is also the first declaration of the function, then the implementation of the function, finally can call the implementation of the function.

The interface between components jumps

Implement it with ARouter.

ARouter has a feature where the classes under path can be placed anywhere. You only need to declare path in the CommonSDK of the base layer, and other components can jump from path without worrying about the contents of the classes under Path.

Build standard routing requests
/ / 1. Basis
ARouter.getInstance().build("/home/main").navigation();

// 2. Specify a group
ARouter.getInstance().build("/home/main"."ap").navigation();

// 3. The Uri is resolved directly
Uri uri;
ARouter.getInstance().build(uri).navigation();

// 4. startActivityForResult
// The first parameter of navigation must be Activity and the second parameter must be RequestCode
ARouter.getInstance().build("/home/main"."ap").navigation(this.5);

// 5. Pass Bundle directly
Bundle params = new Bundle();
ARouter.getInstance()
    .build("/home/main")
    .with(params)
    .navigation();

// 6. Specify a Flag
ARouter.getInstance()
    .build("/home/main")
    .withFlags();
    .navigation();


// 7. Pass objects
ARouter.getInstance()
    .withObject("key".new TestObj("Jack"."Rose"))
    .navigation();
// If there are not enough interfaces, you can simply take out the Bundle and assign values
ARouter.getInstance()
        .build("/home/main")
        .getExtra();

// 8. Transition animation (normal mode)
ARouter.getInstance()
    .build("/test/activity2")
    .withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
    .navigation(this);

// 9. Transition animation (API16+)
ActivityOptionsCompat compat = ActivityOptionsCompat
    .makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2.0.0);
    / / note: makeSceneTransitionAnimation using Shared elements, need to pass into the current Activity in the navigation method
ARouter.getInstance()
    .build("/test/activity2")
    .withOptionsCompat(compat)
    .navigation();
Copy the code
Processing routing requests

Intercept, green channel, downgrade, external arousal.

// Interceptors are executed between jumps, // Interceptor with lower priority @interceptor (priority = 8, Public class TestInterceptor implements IInterceptor {@override public void Process (Postcard,  InterceptorCallback callback) { ... callback.onContinue(postcard); // Callback. OnInterrupt (new RuntimeException(" I feel something unusual ")); } @override public void init(Context Context) {// When the interceptor is initialized, this method is called when the SDK initializes. }} // handles the jump result only once to see if it is blocked // uses the navigation method with two parameters, Arouter.getinstance ().build("/test/1").navigation(this, new NavigationCallback() { @Override public void onFound(Postcard postcard) { ... } @Override public void onLost(Postcard postcard) { ... }}); // 2. GreenChannel (skip all interceptors) ARouter.getinstance ().build("/home/main").greenchannel ().navigation(); 3. Degrade (interceptor does not exist, but the destination path is lost) // Customize a global degrade policy // Achieve survival of eservice. @Route(Path = "/ XXX/XXX ") public class DegradeServiceImpl implements eservice {@override public void onLost(Context context, Postcard postcard) { // do something. } @Override public void init(Context context) { } } // 4. Public Class SchameFilterActivity extends Activity {public class SchameFilterActivity extends Activity {public class SchameFilterActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Uri uri = getIntent().getData(); ARouter.getInstance().build(uri).navigation(); finish(); } } // AndroidManifest.xml <activity android:name=".activity.SchameFilterActivity"> <! -- Schame --> <intent-filter> <data android:host="m.aliyun.com" android:scheme="arouter"/> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> </intent-filter> </activity>Copy the code
A situation ARouter can’t handle

Notifications, widgets, and so on need to build PendingIntent scenarios for RemoteView.

Save the country with the transition Activity curve.

Pass the route path to the forwarding Activity as an action, which then opens the corresponding Activity based on the action.

There is no need for setContentView in the transition Activity, just finish().

As follows:

RouterHub = {
    MainActivity = "/app/main",
    OtherActivity = "/app/other". } public class RouterActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState); Intent intent = getIntent();if (RouterHub.MainActivity.equals(intent.getAction())) {
            ARouter.getInstance().build(RouterHub.MASTER_MainActivity).navigation(this);
            finish();
        } else if(RouterHub.OtherActivity.equals(intent.getAction())) { ARouter.getInstance().build(RouterHub.MASTER_InfoOperationActivity).navigation(this); finish(); } finish(); Intent2Add = new Intent(context, routerActivity.class); intent2Add.setAction(RouterHub.MASTER_InfoOperationActivity); intent2Add.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent pendingIntent2Add = PendingIntent.getActivity(context, <requestCode>, intent2Add, <flag>);Copy the code

UI mixing between components

UI coupling between components is a routine operation, such as when an aggregation page needs to display UI cards inside individual components.

How do I get the internal UI of another component while it remains separate?

  1. Get component Fragment: get component Fragment with ARouter

    / / get fragments
    Fragment fragment = (Fragment) ARouter.getInstance()
          .build("/test/fragment")
          .navigation();
    Copy the code

    Fragment interaction with activities, etc. Component Development > Data Transfer between Components 2. Component Development > Function Call between Components 2. Component Development > Method of interface hopping between components.

  2. Get component View: Using ARouter, encapsulate component View into service and get View by discovering service. 2. Component Development > Data transfer between components, 2. Component Development > Function call between components, 2. Component Development > Method of interface hopping between components. PS: Cross-component UIs are not recommended because of concerns about empty fetching views.

4. Maintain components

Components to confuse

  1. It’s only confused in release. For higher security requirements, Proguard may be out of its depth. DexGuard, which provides more security, can be found here

    buildTypes {
        release {
            minifyEnabled true   // Turn on obfuscation
            zipAlignEnabled true  // Compression optimization
            shrinkResources true  // Remove unwanted resources
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' // The default obfuscation file and the obfuscation file we specified}}Copy the code
  2. Each component maintains its own obfuscated configuration separately and implements it in build.gradle.

    buildTypes {
        release {
            consumerProguardFiles 'proguard-rules.pro'}}Copy the code
  3. Confusion of third-party libraries: Third-party libraries may require certain classes not to be confused. Please write the corresponding confusion configuration according to the official requirements, and summarize all third-party confusion configuration into a file. Therefore, a unified third-party obfuscation configuration is maintained globally.

Note:

  1. The obfuscate switch configuration of the host shell directly affects the component, which means that if your host shell obfuscate is turned on, the component will still be obfuscate even if it is turned off.

  2. Component obtruse files are specified using the consumerProguardFiles property, not the proguardFiles property, and we don’t need to configure any other options, just the consumerProguardFiles property. This property means that the code will be obfuscated automatically by looking for the obfuscation file we specified in this Module when packaging. If we want to obfuscate the library code by distributing it to others, we can also configure this property.

Volume optimization

Reference: https://blog.csdn.net/cchp1234/article/details/77750428

Cause analysis:

  1. Screen adaptation problem, add a variety of resource files and pictures
  2. Model adaptation problem
  3. The Android version is incompatible
  4. Various development frameworks, third-party lib introduced
  5. Cool UI, UE effect
  6. Redundant code, etc

The following are specific optimizations.

Assets to optimize

  1. Font files: You can use the font resource file editor Glyphs to compress and remove unwanted characters to reduce the size of APK.

  2. WEB pages: Consider using the 7zip compression tool to compress the file and decompress it for use

  3. Some images: You can use Tinypng to compress images. Currently, Tinypng supports PNG and JPG images, and.9 images

  4. Put the cache file to the server and download it from the network. We can use the dynamic loading form of resources in the project, such as: expression, language, offline library and other resources dynamic loading, reduce the size of APK

Res optimization

  1. For some unnecessary equipment size, not all (mainly depends on product demand);

  2. Resource files, mainly image resources for compression, compression tool is ImageOptim;

  3. Some UI effects can be rendered using code instead of image resources;

  4. Reuse of resource files, such as two pages of background images the same background color is different, you can reuse background images, set different background color;

  5. Replace the image with a VectorDrawable and SVG image. If you upgrade minSdkVersion, it will be better to use vector images directly after minSdkVersion 21, instead of generating additional.png images for compatibility with earlier versions. SVG has no screen fit and is very small;

  6. If you have audio files in the RAW folder, try not to use lossless audio formats such as WAV. Consider the Opus audio format, which has the same quality but smaller files than MP3.

  7. If you don’t need an image, you don’t need an image. You can use shape.

  8. Using WEBP. Convert larger PNG and JPG files to WebP files. This AS comes with images (single) or folders containing images (batch). Right-click and select Convert to WebP. Webp lossless images are 26% smaller than PNG images. Webp lossy images are 25-34% smaller than JPEG at the same SSIM (structurally similar) quality. Lossless Webp supports transparency (transparent channels) with only 22% extra bytes. If lossy RGB compression is acceptable, lossy Webp also supports transparency and is usually 3 times smaller than PNG file size.

  9. Highly recommended: Using the AndResGuard resource compression and packaging tool, shortening resource names can also confuse resources.

Lib directory optimization

  1. Reduce unnecessary.so files. For example, some third-party SDKS require the introduction of. So files, often quite large, such as speech recognition (.so is usually used for local recognition, but not always necessary) and should not use these SDKS if rest apis are available.

  2. RenderScript librssupport. so is a very large library.

  3. Select arm64-v8A, armeabi-v7a, armeabi, x86 so file from arm64-v8A

    • mips / mips64: rarely used in mobile phones can be ignored
    • x86 / x86_64: x86The architecture of the mobile phone will include the Instruction set provided by Intel called Houdini dynamic transcoding tool, to achievearm.soCompatible, then considerx86Less than 1% of the market share,x86The relevant.soIt’s also negligible
    • armeabi: ARM v5This was a fairly old version that lacked hardware support for floating-point calculations and had performance bottlenecks when a large number of calculations were required
    • armeabi-v7a: ARM v7Current mainstream version
    • arm64-v8a: 64-bit support. Note:arm64-v8aIt’s backwards compatible, but only if it’s not in your projectarm64-v8a“Folder. If you have two foldersarmeabiandarm64-v8aTwo folders,armeabiThere is aa.sob.so.arm64-v8aThere was onlya.so, thenarm64-v8aI’m using my cell phonebI found that there werearm64-v8a“Folder, found there is nob.so, is an error, so delete this timearm64-v8aFolder, this time the mobile phone found no adaptationarm64-v8a“They go straight to itarmeabiSo either you don’t add itarm64-v8a, orarmeabiThere’s a so library in there,arm64-v8aIt has to be in there.

    Final solution: Use only Armeabi, and delete the rest like MIPS, x86, Armeabi-V7A, arm64-V8A. When the phone finds no arm64-V8A, it goes straight to Armeabi’s SO library.

     buildTypes {
         release {
             ndk {
                 abiFilters "armeabi"// Only keep armeabi schema to reduce volume}}}Copy the code

Clear unnecessary resource files

Resource Shrinking: Needs to be used with Code shrinking. After deleting all unused code from the code, Resource Circles can determine which resources are still being used by the APK program. You must delete the unused code before the Resource becomes useless and can be erased.

Clear unused alternate resources

Gradle Resource Shrinker only removes resources you haven’t used in your code, which means it doesn’t remove alternative resources for different device configurations. If necessary, you can use the ResConfigs property of the Android Gradle Plugin to delete the alternative resource files.

For example: our project is compatible with 10 national languages, and the project relies on v7, V4 and other support packages containing 20 national languages. Then we can use Resconfigs to delete the remaining alternative resource files, which can reduce the size of our APK.

The following code shows how to limit your language resources, just English and French:

android {
    defaultConfig {
        ...
        resConfigs "en"."fr"}}Copy the code

The language specified by the resconfig property as above. Any resources for an unspecified language are deleted.

Sort code

  1. Reduce unnecessary dependencies. Recommend the use of com. Making. Znyang: library – reports generated depend on the trees and depend on the volume analysis plug-ins, merger, eliminate unnecessary library according to the report.

  2. Delete the code. Write more functions, write more functions, write more functions! Always write functions over 30 lines! Write classes if necessary! Once copy, paste everywhere a piece of code. Clean code is recommended

  3. Regular code review, which is a very important work, can help team members get familiar with each other’s code, at the same time, can find some bugs that they can’t see and reduce some useless code;

  4. Do some code-Lint work. Android Studio already provides this functionality, and it’s a good habit to do code-Lint on a regular basis;

  5. Cloud functions: Logic that can be processed on the server is processed on the server, thereby reducing code and improving fault tolerance (the server can update in a timely manner)

Compilation acceleration (Gradle optimization)

See Gradle configuration and build optimization for Android

  1. Use the latest Android Gradle plugin
  2. Avoid using multidex. With Multidex, compilation time increases significantly when minSdkVersion is below 21 (excluding 21).
  3. Reduce packaged resource files. referenceVolume optimizationsection
  4. PNG compression is disabled.
     android {
         ...
         if (project. HasProperty (' devBuild)) {aaptOptions cruncherEnabled =false}... }Copy the code
  5. Use Instant Run. Instant Run has been greatly optimized since Android Studio 3.0, and the previous version had updated code that didn’t run on the phone, so it was turned off. You can try it now.
  6. Do not use dynamically dependent versions.
    // This is not the case
    implementation 'com.appadhoc:abtest:latest'
    implementation 'com.android.support:appcompat-v7:27+'
    Copy the code
  7. Allocation of the maximum heap size for Gradle daemon processes
  8. Use Gradle caching

Gradle builds one of the time Cat debug shells for your reference. More build.gradle will be released in the demo:

apply plugin: 'com.android.application'
apply plugin: 'idea'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.alibaba.arouter'

static def releaseTime() {
    return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))}def keystorePSW = ' '
def keystoreAlias = ' '
def keystoreAliasPSW = ' '
// default keystore file, PLZ config file path in local.properties
def keyfile = file('C:/Users/dlink/Application/key/dlinking.jks')

Properties properties = new Properties()
// local.properties file in the root director
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def keystoreFilepath = properties.getProperty("keystore.path")
def BILIBILI_APPKEY = properties.getProperty('BILIBILI_APPKEY')
def ali_feedback_key = properties.getProperty('ali_feedback_key')
def ali_feedback_key_secret = properties.getProperty('ali_feedback_key_secret')

if (keystoreFilepath) {
    keystorePSW = properties.getProperty("keystore.password")
    keystoreAlias = properties.getProperty("keystore.alias")
    keystoreAliasPSW = properties.getProperty("keystore.alias_password")
    keyfile = file(keystoreFilepath)
}

android {
    signingConfigs {
        config {
            storeFile keyfile
            storePassword keystorePSW
            keyAlias keystoreAlias
            keyPassword keystoreAliasPSW
            println("====== signingConfigs.debug ======")
        }
    }
    compileSdkVersion rootProject.ext.android["compileSdkVersion"]
    buildToolsVersion rootProject.ext.android["buildToolsVersion"]

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    dexOptions {
        / / the precompiled
        preDexLibraries true
        // Support big projects
        jumboMode = true
        / / the number of threads
        threadCount = 16
        //dex memory, formula: dex memory + 1G < Gradle memory
        javaMaxHeapSize "4g"
        additionalParameters = [
                '--multi-dex'./ / subcontract
                '--set-max-idx-number=60000'// Maximum number of methods per package
        ]
    }
    lintOptions {
        checkReleaseBuilds false
        disable 'InvalidPackage'
        disable "ResourceType"
        // Or, if you prefer, you can continue to check for errors in release builds,
        // but continue the build even when errors are found:
        abortOnError false
    }

    defaultConfig {
        applicationId "com.time.cat"
        minSdkVersion rootProject.ext.android["minSdkVersion"]
        targetSdkVersion rootProject.ext.android["targetSdkVersion"]
        versionCode rootProject.ext.android["versionCode"]
        versionName rootProject.ext.android["versionName"]

        manifestPlaceholders = [UMENG_CHANNEL_VALUE: "for_test"]
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        multiDexEnabled true

        // compatible with Android6.0
        useLibrary 'org.apache.http.legacy'

        // Enable RS support
        renderscriptTargetApi 25
        renderscriptSupportModeEnabled false

        // ButterKnife
        javaCompileOptions {
            annotationProcessorOptions {
                includeCompileClasspath = true
                arguments = [moduleName: project.getName()]
            }
        }
    }
    packagingOptions {
        exclude 'META-INF/rxjava.properties'
    }
    buildTypes {
        debug {
            minifyEnabled false
            shrinkResources false
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "for_test"]
            debuggable true
            signingConfig signingConfigs.config
        }
        release {
            minifyEnabled true
            shrinkResources true
            zipAlignEnabled true
            useProguard true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'.file(rootProject.ext.path["globalProguardFilesPath"])
            applicationVariants.all { variant ->
                variant.outputs.all {
                    def apkName = "${defaultConfig.applicationId}_${defaultConfig.versionName}_${releaseTime()}.apk"
                    outputFileName = apkName
                }
            }
            ndk {
                abiFilters "armeabi"
            }
            signingConfig signingConfigs.config
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            multiDexKeepProguard file('multidex-config.pro')
        }
    }
}
idea {
    module {
        downloadJavadoc = true
        downloadSources = true}}dependencies {
    //region project
    runtimeOnly rootProject.ext.dependencies["module-reader"]

    runtimeOnly project(':modules:module-rawtext-editor')
    runtimeOnly project(":modules:module-controller")
    //endregion
}

apply plugin: 'com.zen.lib.analysis'

libReport {
    output = [
            "txt"."html" // default
    ]
    ignore = [
            "com.android.support:support-v4"]}Copy the code

Highly recommended: use //region project and //endregion for code folding, which can clearly organize the class structure and is easy to use

//region collapses to show meI can be folded, no matter how long//endregion
Copy the code

Depend on the control

Upgrade Gradle to 3.0+. Use Implementation, API, runtimeOnly, compileOnly for dependency control.

  • implementation: Short dependency. My dependence dependence, not my dependence.
  • api: Long dependency. My dependence, and the dependence of dependence, is my dependence.
  • runtimeOnly: Unsocial dependence. It is not involved in writing code or compiling, but is packaged in when APK is generated.
  • compileOnly: Pretend to rely on. Only for code writing and compilation, not for packaging.

Specification of use:

  • implementation: used for component-wide dependencies that are not shared with other components. (Scope is within the component)
  • api: For base layer dependencies, to penetrate the base layer, exposed to the component layer. (Scope is all)
  • runtimeOnlyDependencies for the app host shell, components are completely isolated from each other, invisible at compile time, but participate in packaging. (No scope)
  • compileOnly: used for high-frequency dependency to prevent already-present errors. Open source libraries are usually used to rely on support libraries to prevent conflicts with customer support library versions.

Every component can introduce dependencies at any time during development, so when do you introduce dependencies only to components, and when should you sink down to the base layer?

The dependency introduction principle: the least dependency principle.

The dependence of base layer must be as little as possible, do not sink to rely on at will. A dependency can sink if and only if a library in the base layer needs the dependency. If a dependency is only used by a component, it is not required by the base library. Even if there are dozens or hundreds of components that require this dependency, it must be added to the build.gradle of each component. Even if different components in the component layer have the same dependencies, there are no conflicts in packaging (Gradle works really well).

Release the aar

RuntimeOnly supports aar dependency, so once a component is stable, package it as an AAR and publish it to Github or Maven. In the local setting. Gradle comment out the component declaration and replace it with arR. Can greatly reduce compile time and run – storage footprint.

Run gradle uploadArchives after sync to automatically package arR to the specified folder.


//////// Package publishing configuration start ////////
apply plugin: 'maven'
ext {
    // Clone the local address of the project from Github
    GITHUB_REPO_PATH = "C:\Users\dlink\Documents\GitHub\mempool\TimeCatMaven"       // The path specified here is the local path of the clone
    PUBLISH_GROUP_ID = 'com.timecat.widget'
    PUBLISH_ARTIFACT_ID = 'widget-calendar'
    PUBLISH_VERSION = '1.0.0'
}
uploadArchives {
    repositories.mavenDeployer {
        def deployPath = file(project.GITHUB_REPO_PATH)
        repository(url: "file://${deployPath.absolutePath}")
        pom.project {
            groupId project.PUBLISH_GROUP_ID
            artifactId project.PUBLISH_ARTIFACT_ID
            version project.PUBLISH_VERSION
            / / use:
            / / implementation of "com. Timecat. Widget: widgets - calendar: 1.0.0"}}}//////// Package publishing configuration end ////////

Copy the code

Pure widgets (dependencies that are at most existing in the base layer, such as the support library) can also be packaged and distributed as ARRs, reducing compilation time.

Note:

  • There is a chance that an AAR is not packaged with the generated files, such as a component containing ARouter that generates routing tables and helper classes. If the generated files are not packaged with the AAR, the route will not be found at the time of the actual publication. To ensure that the generated classes are actually packaged into an AAR, it is best to run Gradle Assemble to build the components completely before publishing Gradle uploadArchives to Marven.

  • Android Bundle Support can be used to analyze jar or AAR files in the same way as an APK.

Common mistake: already present

After introducing and upgrading an AAR, it is possible for a class already present error to cause a Build to fail during recompilation. There are only two solutions. The analysis is as follows:

  • If theBuildThere was an AAR upgrade along the way, and while sync was successful, the older version of the AAR may have been partially subcontracted and mergedBuildIt’s in cachebuild/intermediates/debugDirectory), and then the new version of the AAR is built in, causing the caches to be mergedalready presentError. Solutions:Build > Clean > RebuildStill can’t solve, delete< component name >/buildFolder, againBuild
  • After the above method is tried, if there is stillalready presentError, that must depend on repetition. About why repeat? There are two possibilities: 1. Relying on multiple versions of a library simultaneously
    1. A depends on B, B depends on C, and if A also depends on C, then the class in C might report this error. Solution: Analyze the dependencies of the library where the error class resides. Double-click Shift to enter the duplicate class usedgradle dependenciesLook at the dependency tree to determine whether there are duplicate dependencies and keep only one.
    ./ / tip Error: the Program type already present: android. Arch. Lifecycle. LiveData
        api('com. Making. Applikeysolutions: cosmocalendar: 1.0.4') {
          exclude group: "android.arch.lifecycle"
          exclude module: "android.arch.lifecycle"}...Copy the code

    aboutmoduleandgroupPlease draw inferences from the release of ARR

      PUBLISH_GROUP_ID = 'com.timecat.widget' // group
      PUBLISH_ARTIFACT_ID = 'widget-calendar' // module
    Copy the code

5. Component publishing

One of two ways

  1. Publish as an AAR, and pass dependenciesruntimeOnly rootProject.ext.dependencies["module-book-reader"].
  2. Copy and paste, depend on and passruntimeOnly project(':modules:module-book-reader')

PS: AAR does not seem to improve compilation efficiency very much… There is no significant reduction in full compilation time. To be fastidious, welcome to add!

Note: There is a trap that says “package the aar with its dependencies”. Gradle does not automatically package the dependencies. However, it is not recommended to package dependencies together, as this can lead to already-present errors. It is recommended to keep the API provided by the base layer relatively stable.

6. Remove components

!!!!!!!!! It is not recommended to remove components from the shell.

Why is that?

Although component removal is simple and has few conflicts, it is simply not recommended. The standard practice is to copy the shell, remove the dependencies in the copy, and become the new shell. Because you might want to use it back after you remove it? True fragrance warning! Want to use back to select the original shell can be compiled. Keeping the original shell benefits with reduced compilation time. It’s essentially trading space for time.

Component Removal: Comment out the corresponding component in the build.gradle of the shell.

Because components are absolutely independent, like standalone apps, but made into libraries, removing components is as easy as removing a library that doesn’t matter.

// runtimeOnly project(':modules:module-rawtext-editor')
        runtimeOnly project(":modules:module-master")
// runtimeOnly project(":modules:module-controller")
        runtimeOnly project(":modules:module-github")
Copy the code

Remember runtimeOnly? Non-group dependencies, not involved at compile time, but available at run time. That is, even the app shell cannot access any of the classes in the component at compile time. To the shell, the components of runtimeOnly are virtual.

You may remove the android. Intent. The category. The LAUNCHER Activity corresponding component… This is to start the Activity, just change it.

Write in the back

Outlook:Component sharing

Component sharing: Share your components at work or on Github to give everyone some hair.

People can freely share components, by choosing a component written by someone else, building a shell and compiling it, a new APP is created!

Wonderful ~

The end of the

Congratulations to see the end, demo will be updated on Github when free, welcome to follow.

A song “My code without Insects” for everyone.

Create a blank document and press Enter. Confusingly cut keys crisscrossing, instructions such as spring, stack fine silent.

Link compilation execution, code to success, test easy. Bili bili printing void, program passed through the ages, bit is eternal.