From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App…)

Time:2021-12-31

Meituan cat’s eye movie Android modular actual combat — probably the most detailed modular actual combat

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

The original intention of writing this blog

First, I want to summarize in one sentence: I want to record what I have done in recent months, and hope to be as detailed as possible. I hope readers can know what problems may be encountered in the modularization of the project, the transformation of the project into the business framework, and what to do in each step, rather than a general understanding.

Now many people are talking about modularity. There are a lot of blog practices on the Internet. A lot of talk is about decoupling between modules, and most talk about decoupling through router routing. Others don’t talk much and talk in general. However, an app can be truly decoupled and run. There is much more to be done than decoupling. Business architecture, inter process communication, resource processing and decoupling methods need to be solved. Just for the implementation of the whole process of cat’s eye modularization, I have analyzed and solved various problems from beginning to end for several months. The historical version of the cat’s eye app is a highly coupled project. From such a historical version to the final, each business module can run independently and communicate between processes, which will involve decoupling of all aspects and some other things. I’ll take this app as an example today (other apps may encounter different problems in decoupling. Please pay attention to this),Complete the whole process of cat’s eye modularization。 Every aspectInstead of copying some practices of the network, we analyze and compare them and adopt a better design method. For example, decoupling uses serviceloader instead of routing; For example, the architecture uses a MVP variant with a life cycle that is more suitable for our business。 I’ll also say something specificSpend time and some experienceIn this way, we will know when we do the module in the future. (note that there are many things involved in the modularization process besides those mentioned in the article. Some are not mentioned because they have been completed before. For example, the cache of the network library is from database – > text. Readers should pay attention to this. If there are still omissions, you can communicate ~).

primary coverage: serviceloader decoupling, MVP variant framework, module communication, lib running independently, multi terminal reuse.

2 why modularization

First of all, one thing to say: modularization is not to show off technology. If there are no business scenario requirements, it is not recommended to do so.
There are many reasons for modularization on the Internet. Here, let me briefly explain why cat’s eye does it:

  • Cat eye needs to be quickly transplanted to other apps (meituan, comments…).
  • Decouple the front page to reduce the cold start time.
  • Reduce build time and code responsibility during development.
  • Service quick replace

3. To what extent?

First of all, what is modularity? Everyone must be familiar with this: it can separate different businesses into different lib modules. So after modularization, what functions does a business lib have? I think it is:

To sum up:No communication cost, fast, fool like running on any app.Specifically, this lib is not coupled with the service of the specific app and the activity of the specific app. Just give me an app (or fake app shell), and through its baseactivity and their services, I can run this lib on that app very quickly. Stop! You may say what this service is. Let me talk about it in detail~

3.1 various services can be configured without intrusion

We know that each app will provide account information, device information, network services, image loading services, dotting services, pull-down refresh styles, error status, etc. These services of each app may be different. For example, the network service used by meituan is okhttp, while the comment uses a long connection. Therefore, our business logic lib cannot couple these specific services. Only interfaces abstracted from services can be coupled. When the specific app is used, we provide the app service to the lib. So how do you give these services? If I leave a pass parameter gap when I need services, I need to plug the app services into the required places in lib one by one. It’s too expensive. I don’t want to be so troublesome. I want to directly put the service implementation as TXT text in a folder of the app, and your lib can run it for me. So I hardly care what’s in lib. You just give me a business lib and I add a TXT text to run it.

3.2 lib is fast and convenient for multi terminal use

Say uncoupled activity. We know that each app has its own baseativity, which performs statistics, exception handling, initialization of some libraries and other functions. In addition, the actionbar of each app is also different, and the schema of the manifest of each page in the unused app is also different. Therefore, if the business in lib is a business, we can’t write it directly as an activity, but a view / fragment. In this way, for any app, we can directly create an activity, and then put the page in lib into that activity. Similarly, considering the cost of collaboration, I don’t want to deal with many other things, such as data loading, when I put this page. I hope you can give me this business page pager (actually a view). I can put it in setcontentview () of activity oncreate(), and it can run. Don’t let me do other things to deal with life cycle, data binding, destruction, etc. that’s what you need to do inside pager.

3.3 demo example

The above two points may be confused. Recently, I wrote a cat’s Eye Q & a requirement, involving 5 pages, so I made it into a lib. Then, let’s explain it in combination with the Lib I recently wrote.

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

This lib is the question and answer service lib. This does not couple specific services, only service interfaces. The page inside (under the page package) is not an activity, but a view.
Well, at this time, another colleague wants to use this lib on the cat’s eye app. How?

  • In the build. Of the cat’s eye app Add the Lib dependency under gradle:

     compile 'com.maoyan.android.business:movie:1.0.2.3'
    
    
  • Add a service configuration required by Lib in the cat’s eye host app: the TXT text of the service implementation (because it is the host app, it already exists before).

    From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

    Txt is:

    From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

  • Create an activity in the host app, place the page in lib, and fill in the manifest, for example (you may need to write the interaction logic of actionbar in it sometimes)

    From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

That’s it. That’s it. Various services used, such as pull-down refresh, are provided by this app. Is not fast, no communication writing, fool.
If we want to test this app, it’s also very simple. Just create an app shell, create an activity, and put the page in lib. Then add the required service implementation TXT text (because it is a test, the service implementation can be free and configured at will),
It’s done. This way to fix bugs and adjust the UI saves a lot of time than starting the host app to modify the code.
Let’s see. I wrote an app to test lib:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

We can see that the pull-down refresh and status service are different from those in the cat’s eye app, and can be customized. If it’s all written in this way, in fact, we can quickly, fool, and customize all modules into appsIs this decoupling better

If it feels good, let’s get to work~


4 start the modular journey

4.1 coupling structure of the original project

To start the modularization work, I first have to show you the highly coupled cat’s eye app when there is no module before. Let’s take the movie details page as an example to see its coupling:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

The movie details page is built on a layer of base classes, which are coupled with various services such as specific network loading. Because the details page has editable statuses such as want to see, score and like, it is also coupled with the grendao database (in the past, the network load was also coupled with this database, and later replaced with retrofit + rxjava, so this layer of coupling is replaced. Thank God). Because this page needs to interact with other pages (such as jump, score synchronization, etc.), it is also coupled with classes of other pages. In addition, there are utils, view, model, etc. If you want to pull out the movie details page, all these coupling should be stripped off. The specific problems to be solved are as follows:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

4.2 preparation

4.2.1 workload evaluation

First, let’s talk about the preparations for decoupling. Because these works are the basis of decoupling and splitting. There are two things to do, as follows:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

First of all, it’s not that I like playing five stars. Indeed, this part of the workload is relatively large~~~

4.2.2 splitting of public resources, models, utils, etc

4.2.2.1 example of coupling

The first point is the splitting of public resources, models, utils, etc. Although these things do not need to consider too many things, they are very cumbersome. This place takes a lot of time to do modularization. A large part of the reason is that the code of the previous cat’s eye historical version is not standardized and is not sensitive to code coupling. Take a few examples:

  • Our previous utils are basically written in a class movieutils. This class is like a big dye vat. Put everything in. The parameters passed in are not standardized, and even business codes such as maoyanbasefragment are passed in as parameters. It makes this thing extremely difficult to disassemble.
  • The utils method does not pass context. It was easy for predecessors to write. A static context was added to the project, resulting in almost all utils not passing in the context. As a result, these tools and methods directly enter the host app.
  • The common view written before is not independent enough. Since you want to write a common view, try to make the view independent, do not couple with other third-party libraries, and try to use the official Android library.
4.2.2.2 resource splitting experience

The splitting of resources is actually very cumbersome. Especially if the resources such as string, color and dimensions are distributed in every corner of the code, it is very cumbersome to disassemble them one by one. You don’t have to. Because Android will merge and shrink resources during build. Each file under RES / values (styles. XML, please note) will only put what is used in intermediate / RES / merged // valus. XML, useless will be deleted automatically. And finally, we can use lint to delete it automatically. So don’t spend too much time in this place. Just now, styles XML needs attention. So what should we pay attention to? This thing is written like this:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

When we write attribute names, we must add prefix qualifiers. If you don’t add it, when your lib is packaged into AAR and used by other apps, there will be a conflict of duplicate attribute names. Why? Because the name bezelimageview will never appear in intermediate / RES / merged // valus. XML, so don’t think this is a qualifier of attributes!

4.2.3 integrated vs. combined (optional)

Earlier, we talked about the splitting of resource utils. Next, we will talk about the second point, the processing of base classes. We see that the movie details page is based on a pile of base classes. The base class of each layer does something. (it was written for the rapid development of the page at that time) if we want to separate the movie details page, we need to package these base classes into an AAR and sink them into the basic library for all pages. However, our previous base class coupled many cat’s-eye things, such as pull-down refresh, page status and so on. If I need to write a page, I need to inherit a lot of fragments. Of course, this change can also be transplanted. However, it is certainly not good for future code iterations (modifying and adding business). Because it has poor flexibility. For example, if the comment app needs a part of a page instead of the whole page, the original change is not very convenient. What I want is that these pages are views, not fragments. And it is not this way of inheritance, but the way of combination. That is, if I want a list view with drop-down refresh, I can directly build such a view, set what configuration is required, and it can be used. This view can be put into any view, and the fragment can be combined with other views. Namely:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

The moviercpagepulltorrefreshstatusblock is a view that can be used to combine views on any page.

4.2.3.1 pluggable and combined design of components

In fact, my approach is bolder, or more “lazy”. I hope that after the moviercpagepulltorrefreshstatusblock is successfully built, it can be displayed and run on the page and automatically load data. Just like playing with building blocks as a child, components and components are plug and play. As for how the block loads data, the user does not need a relationship. Users only need to get the block, and then set what they need when building. Put it on the page and you can run it. You can refer to this as an example:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

We can see this page. I just built two views and put them into this page. I don’t care about data loading. Data loading is completed in this block. Then, as mentioned earlier, the page can be run by putting it into the activation of an app. Pluggable and stupid thinking, maybe I am more “lazy”~~

How to implement this architecture?, Next, let’s take a rough look at the general implementation idea of this framework (see this article I wrote for details)Android’s official MVP framework Optimization: lifecycle MVP, which writes pages in combination like the front end)。 In fact, this framework is generally the idea of MVP framework, but it also solves some problems in business scenarios, such as life cycle, portability, communication cost, ease of use, etc. Since we want to talk about the realization ideas, it is a summary for ourselves from the beginning, which may be of some help to readers. First, let’s talk about the meaning of MVP framework:

4.2.3.2 meaning of MVP framework

MVP framework is generally applicable to Android scenarios. M stands for model and provides data; V is view, which provides view related methods called by presenter; P is the presenter, which provides the logical method to trigger the action in the page.

4.2.3.3 shortcomings of official MVP framework

There are many MVP frameworks online, and the official has also recommended the MVP framework. The difference from the general is that contract is used to host the interface definitions of view and presenter. Use fragment to implement the view interface. However, the official use of fragment to implement view also has its helplessness. Why is it helpless? For the view layer interface, fragment is used to implement it, mainly because fragment has a life cycle. But fragment is too bulky. Imagine that I have a page with four or five pieces of content. In order to move, remove and transplant each block of content in the future, I hope that each block of content will be made into MVP form, and there is no coupling between blocks. Then the official MVP framework is not applicable. Because you can’t write five fragments on one page. It is not recommended to write so many fragments in Android activities. The typical usage scenario of fragments is viewpager.

4.2.3.4 general modifications

The view layer of five pieces of content is no longer implemented with fragments, but just ordinary views. The response of each view to listening events is still carried out in the view (call their respective presenter methods). For the initialization loading or pull-down refresh loading of the whole page, the five pieces of content share a fragment. The presenter method corresponding to the five pieces of content is loaded in the onstart() of the fragment and the listening callback of pull-down refresh. Then fill in the view of five pieces of content in oncreateview () of the fragment. Communication and data exchange may also be required between the five pieces of content, which are carried out in the fragment with the help of the presenter.

4.2.3.5 MVP with life cycle: lifecycle MVP

There is no problem with doing that above, and the above practice also exists in our project. But through several versions of iteration, I found some problems: the presenter is too messy and scattered. Fragment needs to hold all presenters and load () data when OnStart (). Each view also needs to have its own presenter. And the view and presenter need to set () each other. You also need to manage the presenter in the ondetroy () method of activity or fragment. Overall, it makes people feel very confused. In particular, if your component needs to be used by others, or other apps need to be used for component use, other people get your component. You should care about two things: view and presenter. He needs to know the methods in these two things, and he needs to associate them and call some methods in the life cycle of activity / fragment. Um. There must be a lot of communication costs in this process~
That’s why I thought of the build method mentioned earlier to instantiate components, and then use pager to combine components. The features are (see below for details)Android’s official MVP framework Optimization: lifecycle MVP, which writes pages in combination like the front end):

  • The lifecycle component is used to provide the lifecycle.
  • The presenter is held inside the view layer and is not exposed to the outside.
  • When build creates a view instance, typefactory is provided for business extension.
  • Business code layering.
    Using this MVP variant framework to rewrite the original / new business of the project can make the page easier to transplant and expand, and the modules in the page can also be moved and changed. Of course, this framework is based on our business. The framework still needs to be based on the project. There is no best, but more suitable~

4.3 withdrawal of interface

The preparation for modularization has been described earlier. What do we need to do next? According to the original project coupling structure introduced earlier, we know that our previous projects directly relied on the specific implementation of various services. What we need to do next is to split these specific service implementations with interfaces:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

4.3.1 use servieloader to decouple — call the service implementation class non explicitly

4.3.1.1 official serviceloader

As can be seen from the figure, our implementation classes are replaced by corresponding interfaces. But as far as this step itself is concerned, it is not too difficult: find the place where the service was called before, and then replace it with an interface call. It’s just that some services use more, and it’s cumbersome to change. But now we need to consider a question: how do we give service implementation? The first thought is that we leave a parameter to pass in. But this method will lead to too much communication cost when using Lib in the future: you need to tell others where and what type of parameters I need to pass in. Otherwise, your lib won’t work. I don’t want others to check what your code is and how to pass parameters when using your lib. I hope that when others use it, lib is as transparent as possible to them. You don’t need to know what is written inside lib. You only need to configure a TXT text externally to run lib! What should we do?
In fact, Java has provided this similar function for a long time: place the provider configuration file in the resource directory meta-inf / services, and then encounter serviceloader. When the app is running When loading (xxxinterface. Class), you will look for the full pathname of the implementation class corresponding to this interface in the meta-inf / services configuration file, and then use reflection to generate a parameterless instance.
Our general usage is also based on this function provided by Java:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

4.3.1.2 transformation of official serviceloader
4.3.1.2.1 defects of official serviceloader

From the above description, there are at least three areas that need to be improved in the official JAVA serviceloader.

  • Serviceloader has no caching function. For services, most of us need to use the singleton pattern instead of generating new instances frequently.
  • Serviceloader uses a parameterless construction method to build instances. Needless to say, this must be improved. Whose service does not need to pass in parameters when building?
  • The serviceloader does not have problems such as pre inspection. Because at runtime, you need to find the implementation class name corresponding to the interface in the configuration file. Then you will definitely encounter wrong interface name, wrong class name, wrong configuration mode, unable to find interface implementation class, etc. these errors cannot be found in the compiler. At the same time, using serviceloader is a non explicit way to call service implementation classes. If these implementation classes are not protected in Proguard, they will certainly be shrunk. In addition to the Proguard problem, the configuration file is written in the resource directory meta-inf / services, which also has compatibility problems for some mobile phones (Samsung). Finally, considering the disadvantage of manual registration of service configuration files, serviceloader needs to provide automatic registration function.

For the above three cases, the first point is easy to solve. Just provide a cache. I won’t say much.

4.3.1.2.2 serviceloader construction example

The second point we solve is this: we let all interfaces that use serviceloader to load services implement the iprovider interface. The iprovider interface provides an init (context) method. In this way, all service implementation classes need to implement the init (context) method and do the initialization logic in the original construction method. Therefore, when we call serviceloader to load the service, it is similar to this:

          ImageLoader  imageLoader = MovieServiceLoader.getService(context, ImageLoader.class);

Inside movieserviceloader, the generated instance will call the init (context) method. So we solved the second problem. There may also be some questions from some friends here (for example, children’s shoes on the meituan platform discussed this matter): why only pass in the context parameter. What if a service implementation class needs other parameters? As far as our services and are concerned, I think we only need to pass in the context. Basically, most of the parameters of Android can be obtained through the context. And for the service, since it is a kind of service, it is reasonable not to rely on a specific component of your project. Therefore, I think it is enough to pass in the context instead of the object parameter in an indefinite format:

    MovieServiceLoader.getService(Object... params, ImageLoader.class);

This approach can certainly solve all problems. However, this design idea has violated the concept of isolation between interface and implementation. For example, I want to use the image loading service. Normally, I only need to call it

    imageLoader = MovieServiceLoader.getService(context, ImageLoader.class);

Just OK. Don’t let me know whether your specific service is Picasso or glass. I don’t want to know. If the second scheme is used, do I still need to know what parameters your specific service needs and then pass them in? It feels so unfriendly. Another advantage of using iprovider is that we only need to add:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

That’s it. In other places, you don’t need to consider Proguard when using or creating a new service interface.

4.3.1.2.3 serviceloader preprocessing — gradle plug-in

The third problem to be solved is the pre check of serviceloader. The solution is to write a gradle plug-in. The general flow of the plug-in is

  • We get all the compiled class files (folders) and jar packages at a certain stage of build.
  • Use javassit to determine which classes are modified by @ autoservice. If they do not exist in the configuration file, add them.
  • Check whether the format in the serviceconfig configuration file is correct.
  • Use javassit to determine whether the classes in the serviceconfig configuration file exist in the project and whether the interface class implements the iprovider interface.
4.3.1.2.4 required knowledge: build process, javassit, groovy

I didn’t want to say much here, but considering these three, many readers may not be familiar with them. Go directly to Google on the Internet. These three things alone may need to be learned. Then I’d better write down some of my experience (in order to get to the point, I won’t expand it in detail). Readers can refer to it and get twice the result with half the effort.
Because you need to get the compiled class file and jar package, you need to know the general process of build, what the input and output of each task is, whether in the form of folder or jar package.
For example, when taking all classes, you can get all classes from the input folder / jar package of the DEX task when the assemblyxxx task is completed. Similarly, the input of javac task is OK. However, the output of javac task is not allowed, because the intermediate / class folder of javac task output only contains the class files in the project, not the class files in the intermediate / expanded AAR file corresponding to AAR. Of course, transform is also an implementation. The input and output file path of transform has been given, and the input classes are all classes.
In addition to the build process, you may also use groovy to write plug-in logic. However, if you really don’t want to use groovy, you can also use Java. The two are compatible, but many groovy features such as loops can’t be used. Here’s a little experience: when you write goovy, IDE can’t well prompt errors. For example, you use a variable or a method. If the method is used incorrectly, the variable is not defined. It won’t give you a hint that you can’t find it. So it’s better to write first Java, and then move to Inside groovy.
Finally, you also need to know some knowledge of javassit, which is a tool for processing class files. It is very powerful. It is very similar to Java. Most of its use will end with the use of ctclass. So it’s best to be familiar with this class. Here’s a little experience: sometimes you need to convert ctclass – > class. Remember to use static variables to store this class object, otherwise you will report an exception that the classloader loads the same path multiple times.
OK, the principle, improvement and benefits of using serviceloader for decoupling have been finished.

4.3.2 serviceloader decoupling vs routing mode decoupling

Most of the online blogs about modularization use the way of routing. What is the decoupling of routing?

4.3.2.1 description of route mode decoupling

Let’s take a look at the general routing framework, which is intercepted in
Android componentized communication

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

So how does this routing framework work? The action here is a service, and the provider is a map collection that holds all (action name: action instance) key value pairs in a lib. Register the provider of each Lib in the host app. In this way, when module a requests the service of module B, it passes(Code source):

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

That is, by providing the name of the provider, the name of the action, the parameter name and the value, the corresponding action instance is found in the registered map, and then the corresponding method is called. The core is to use the string to match the corresponding instance for decoupling.

4.3.2.1.1 advantages of route decoupling

The biggest advantage of this method is that when creating a new service, there is no need to write an interface. All services are marked and matched with strings. There is no need to couple anything between the two models, or even the interface declaration. If there are many services in a lib that need to be called by the outside world, and the number of calls is not large, or I not only decouple the services, this routing method is very good because there is no need to write interfaces.

4.3.2.1.2 discussion on inapplicability of routing mode in service decoupling

But why not choose this decoupling method? Because of this way, I still put forward the following concerns about the overall service decoupling of Android (it only represents my own point of view, which may be vulgar, not that other people’s projects are not good enough):

  • For large-scale decoupling, most of the services in the app domain must be decoupled. It is characterized by extensive use. At this time, I write several interfaces and sink to the base library, which is harmless. In this way, the benefits of serviceloader come out when I use the service: when using the service, I don’t need to care about the class name of the implementation class, what the package name is, what parameters need to be passed in, and what the name of the called method is. If I use the routing interface, I need to care about more things. If I need to care about so many things, it should not be called a service. What if another lib changes its name without your knowledge? Moreover, when the code is transplanted to other apps or run independently, the configuration method is not friendly enough. Serviceloader only needs to write a configuration TXT file and put it in APK, and each lib service can be written to its own servicecinfig, which does not need the care of the host app. Using the routing method, even if the action can be registered automatically, some registration needs to be handled in the application.
  • In essence, the routing service framework and service loader can not communicate between modules in a real sense. The popular point of view is that the routing framework can do that: B lib can get new out of an instance of a class in a (or new ahead of time) without depending on the a lib project, and then invoke the method of that instance. This is not communication, just the ability to call methods from other repositories. Communication refers to listening status and callback. The serviceloader also can’t communicate in the real sense. Communication between modules can only be carried out through non explicit monitoring mechanisms, such as eventbus, broadcast, content provider, etc. Why say that? Because I see many modular blogs talking about using routing framework for inter module communication. However, the routing framework mentioned above can not do the real communication between modules.
    OK, serviceloader decoupling vs routing decoupling, that’s all.

4.4 other work on decoupling

4.4.1 job evaluation

In the previous large space, we talked about using serviceloader to decouple services. So besides this, what else need to be done? Here, I’ll make a general summary first, and then elaborate one by one:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

4.4.2 separation of service realization

The second half of the first point needs to be noted: if you want all modules to be packaged and run independently, you need to separate all service implementations. If you don’t want to run independently but just want to decouple, you can still stay in the host app. Although it’s easy to say so. However, it takes a lot of time to pull away from a service implementation, because a service may be coupled with a lot of things, which is not easy to dismantle if you are not careful. Readers should have a number in mind. But if you can pull it away, try to pull it away, not just the independent operation of lib. It also has great benefits for the replacement of later services. For example, the network load library used to use retrofi + okhttp, and later upgraded to retrofit + long connection. When replacing, it is only a matter of changing a sentence in the service configuration file. If you plan to separate, you should pay attention to the definition of the interface and do not couple the classes of a specific library. The consideration should be comprehensive and the design should be reasonable. For example, in INET library, the interface is defined as:

         public interface INetService extends IProvider {
      <T> T create(final Class<T> service, String getdataPolicy, String        cacheTime);
       }

Although retrofit is a great library, the interface does not couple this library. Maybe it will be replaced one day.

4.4.3 extraction of database

Second, it’s painful to say. It’s really troublesome to pull away the database. I don’t know which version started. Cat eye is coupled with grendao. The database itself is excellent, but it’s too big! If I want to use a lib for others, do I have to couple this lib with a large third-party database?!! Because modularization has not been considered before, almost all network data and sensitive data have been saved by grrendao. So when I see daosion, I am shocked by the tiger’s body. Network data is stored in files and transparent to business codes. Sensitive data is stored in the database, but isolated by the interface, and the official database SQLite or room is recommended for the database.

4.4.4 say goodbye to butterknife

The third point means that if you want to modularize the business code independently, you have to say goodbye to the view injection function of the butterknife framework. Since the R resource in the library is no longer of final type since Android adt14, you cannot use r.id.xx in the library. Instead, you need to use findviewbyid(); You can’t use switch (r.id.xx), you need to use if Else instead.
The fourth point is the follow-up work of the first point. There is not much work.

4.4.5 page Jump

4.4.5.1 what to do for page Jump

Page Jump is also an important thing in app, because it is a modular portal, which involves the communication between pages and between other apps and version I pages. Although it looks simple, if the design is unreasonable, the code elegance, the number of crashes, page degradation, operation cooperation and other aspects of the modular entry will be affected.
For jump between pages, our general practice:

  • If this class page does not have an implicit jump function:

    • Then directly on other pages first
      Get intent (getcontext()), targetActivity Class), and then add the parameter intent.
      Finally, staractivity (getcontext(), intent).
    • Getintent() in oncreate() of the target activity GetString (xx_key, DefaultValue) and other parameters;
    • If XX_ The value corresponding to key is illegal or parsing error, such as movieid = 0 or equal to ”. Then you should jump to another page or the jump fails.
  • If this page is configured with implicit jump function:

    • Then on other pages, you must first create a utils method of createxxxactivityintent(), and pass in the path, parameter key and parameter value of the landing page.
    • Declare in manifest.
    • Getintent() in oncreate() of the target activity getData(). parseBoolean(xx_key,defaultValue)… Wait to get parameters
    • If XX_ The value corresponding to key is illegal or parsing error, such as movieid = 0 or equal to ”. Then you should jump to another page or the jump fails.
4.4.5.2 problems in Android native page Jump

Let’s talk about the problems of using native PAGE jump~

  • When obtaining parameters, you need to write a large amount of intent Get (XX). If this page contains both implicit jump and display jump, the above process must be required, so it will be very messy in oncreate(). To perform if else
  • If you want to perform implicit jump, you need to register intent filter in manifest. First, it’s troublesome. Second, I need to configure an activity in another place, which is inconvenient to manage.
  • You need to write another utils to get the implicit intent.
  • There is no degradation strategy. If the operation configuration is wrong, you can only go to the error page and cannot take a remedial measure, such as entering the version I page.
  • When developers or the background configure wrong parameters, we need to write the bottom logic. Each page parsing needs to write the same logic.
  • If a page needs the permission of the login user to open, we often write if (islogin()) {/ / jump to the page} else {/ / jump to the login page}, and write these same logic for each operation.

If you think there are not so many requirements in this regard, for the jump between pages, in order not to couple the classes of other modules, all pages can adopt the implicit jump mechanism. This can basically meet the situation. But I still want to talk about the open source framework arouter launched by Ali. It has the interception function, so that the jump failure can be degraded (such as presenting version I page), so that the page can be opened by the login user; Uniform parameter acquisition methods, etc. It’s still pretty good. The above problems have been basically solved. The details will not be expanded. You can see the detailsOpen source best practice: Android platform page routing framework arouter

4.5 communication between modules / pages

4.5.0 using ViewModel to share data between pages

This paragraph is a new addition. I think it’s better to put it here. ViewModel is a class in the lifecycle component newly launched by Google. The official document describes that using ViewModel can solve the problem of data saving when page rotation and other configuration changes. After thinking about it, I think it can also play a role in decoupling the data sharing in the page.

Take an example I have encountered before: when a page is finished, PM asks me to be the embedding point of the page. The buried point needs the movieid information of the page, but there is no movieid in the block that needs to be buried. And my block level is very deep. If I want to get the movieid, I need to transfer it layer by layer from the activity page level to my block, which is inevitable for the coupling of intermediate levels and the creation of methods. I thought it was a big deal. At that time, it was necessary to have something like eventbus in the form of event monitoring. I just need to put the data into the bus, and then it can be easily obtained anywhere on this page. To sum up:To put it bluntly, when the data / view of the other party needs to be used between page blocks / fragments, there is no need for rigid reference between them. Only the context parameter of activity can obtain the data / view of the other party, so as to exchange data and access views. The context of the page is system type and easy to obtain, and there is no coupling.
For specific use, please refer to an article I wrote beforeUse ViewModel to share data within a page: activitydatabus

4.5.1 why remove eventbus and use broadcast

If you have reached this step, then generally one page has been pulled out, and the rest is the interaction with other modules and other pages.
As mentioned earlier, neither serviceloader nor routing method can do these things. Our first thought is to use eventbus to do these things. The premise of using eventbus is that some event events need to be defined. For example:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

But if you modularize the business code, there is an embarrassing question: where are the events? Because many libraries need to listen to this event, they can only sink the event to the basic library. As a result, the basic library is getting larger and larger and cannot be split. About this
Wechat Android modular architecture reconstruction practiceAlso mentioned this matter, and creatively used a method called “. API” to solve this matter. The principle is to sink the common interface to the basic library for use by other modules during compilation, and the maintenance of this code is still placed in the non basic library. This base library will not expand, and the responsibility system for code maintenance is clearer, which is very good. Unfortunately, I don’t have so much time to write this gradle plug-in recently. I don’t know which readers have the time and interest to implement this plug-in. It is still of great significance. The code of the basic library will not expand more and more. In addition to expanding the basic library, eventbus also has a problem that process communication between apps cannot be carried out.
We use broadcast instead of eventbus. The local broadcast implementation mechanism launched by Android is simply a looper handler and maintains a global map. Similar to eventbus in performance, string is used instead of event model to match events. If we use
An interface is used to wrap the broadcastmanager, so we can use intra domain broadcasting inside the app. For the modular lib, we can use extradomain broadcasting to communicate between apps.

4.5.2 do not broadcast indiscriminately

If you use eventbus a lot in your project, you will see a large number of oneventmainthread () methods in a class. It’s cool to write and painful to read. If there are many places to send this event in the project, there are also many places to receive this event. When splitting the code, you dare not act rashly, for fear of which events are not received. Broadcast is similar to eventbus. If there are a large number of sending and receiving of the same event in the project, the readability and maintainability of the project will become quite poor. This situation is particularly prominent in the synchronization of sensitive data:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

In fact, for the synchronization of sensitive data, it is not necessary to send broadcast or eventbus for synchronization. You can use the database to localize the data you want to see to complete the synchronization. The general idea is that the data we get from the network are synchronized to the database. When filling the view with sensitive data, the data used comes from the database. When the page returns, if the page does not trigger the logic to fill in the sensitive data view, call it manually in onresume(), that is:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

Then the communication between modules / pages is generally finished. There is not much work to be done here:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

4.6 lib running independently

4.6.1 why do I need to communicate with the host app process

At this step, a business module can be placed in the host app as a library or run independently as an application. As a library, it is easy to understand. As described in the Q & a module earlier in the article, add several shells of activity in the host app, then add the page in lib, and then register in the manifest, that is:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

Of course, if you need to do some interaction with the actionbar, you need to write the corresponding logic in the host activity. The framework diagram of the whole app is as follows:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

When the business lib needs debugging, we need to make the Lib run independently, as shown in the Q & a business module demo earlier in the article. At this time, there is a problem. When lib runs independently, where does the account data come from, and how do we get the data related to the app’s geographical location, city, etc? Readers may say that these are not services? For services, shouldn’t you use serviceloader to load services like network loading and image loading services? It’s reasonable to say so, but the service implementation class of some information such as account is not so easy to be extracted from the host app, because the service implementation class needs to be initialized in the application and many other things need to be considered. Therefore, the real account information is not so easy to obtain through the previous way. What should we do? The simplest way is to create fake data, such as the information of my own account, which can be used as a service implementation class. However, in this case, the account information can only be owned by one person, the modification of the account information is not feasible, and the account cannot log out. So we have to find a new way.

4.6.2 communication process with host app process

Finally, it is found that if we independently run lib can monitor and synchronize the account, location, city, login type, device and other information of the host app, then these information in the independently run lib is real and dynamic. When the host app exits login, there is no login status in lib. The specific operations are:

  • In the host app, we provide some ContentProviders. The content provided by each method is the real account and other data of the host app. When the host app account and other information are changed, notify the listener of the ContentProvider, such as:

            public void onEventMainThread(LoginEvent loginEvent) {
      getContext().getContentResolver().notifyChange(Uri.parse("content://com.maoyan.android.runenv/loginsession"),null);
       }
    
    
  • In the independent app, it acts as the listener of ContentProvider:

           mContentResolver.registerContentObserver(Uri.parse("content://com.maoyan.android.runenv/devicesession"), false, new ContentObserver(null) {
          @Override
          public void onChange(boolean selfChange) {
              super.onChange(selfChange);
              reloadEnviroment();
          }
      });
    
    

In this way, the data such as accounts in lib will be consistent with the data of the host app. We use the service interface package layer, so that the usage is consistent with the previous service usage.
The general schematic diagram is as follows:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

When the host app logs out, there is no login status in lib. Let’s see the demo below:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

Finally, according to the Convention, when a module is to run independently, what needs to be done is evaluated:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

5 last words

The whole process is finally over. I hope readers will have an overall understanding of modularity and have a general understanding of what needs to be done and how much time it takes in each step. Modularity is not
To show off your skills and show how powerful you are. If that’s all, you don’t have to. Because modularization is tedious, boring and time-consuming, you have done a lot of work, but in
On the surface, bosses may not see it. It’s better to spend a little time introducing a third-party library. A large part of the workload is to pay for the previously under designed code logic. I also do this event for business services, because cat’s eye movies need a lot of clients. It is shown that business decoupling and business modularization are inevitable. After modularization, the code can be easily transplanted to other ends, and the adjustment of pages in the app becomes simple.
Finally, in the whole process of modularization, there are some experiences and insights that can be shared with you. The reason is very simple, and more importantly, the implementation:

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

In the open source project:https://github.com/Android-Alvin/Android-LearningNotesIt has included the most complete open source project of Android component (App, App, Alipay App, WeChat App, mogujie.com App, APP). Wait, resources are continuously updated

From novice to architect, one is enough! Android component is the most open source project (USA App, get App, Alipay App, WeChat App, mogujie.com App...)

Advanced Android componentization and enhanced actual combat (with source code)

Please indicate the source for Reprint:
Meituan cat’s eye movie Android modular actual combat — probably the most detailed modular actual combat
Address:http://www.jianshu.com/p/d372cc6802e5

Recommended Today

Could not get a resource from the pool when the springboot project starts redis; nested exception is io. lettuce. core.

resolvent: Find your redis installation path: Start redis server Exe After successful startup: Restart project resolution. ———————————————————————->Here’s the point:<——————————————————————- Here, if you close the redis command window, the project console will report an error. If you restart the project, the same error will be reported at the beginning, The reason is: It is inconvenient to […]