Reprint please indicate the source: Meituan cat eye film android modular of actual combat, is perhaps the most detailed modular real address: www.jianshu.com/p/d372cc680…

directory


1. The original intention of writing this blog

First of all, I want to make a summary: I want to record what I have done in these months, and I hope to be as detailed as possible. I hope readers can know what problems they may encounter when the project is modularized and the business framework of the project is changed, and what to do in each step, rather than a general understanding.

There’s a lot of talk about modularity right now, and there’s a whole bunch of blog practices on the web that talk about it. A lot of this talk is about module to module decoupling, and most of it is about router routing, but not much and not a lot of generalities. But to really decouple an app and run it. There is much more to solve than decoupling. Business architecture, interprocess communication, resource processing, decoupling, etc., all need to be addressed. Just for the implementation of the whole process of cat-eye modularization, from beginning to end, ANALYZING and solving various problems, I did it one by one for several months. The historical version of cat’s Eye app is a highly coupled project. Moving from such a historical version to the final business module being able to run independently and communicate between processes involves various aspects of decoupling and other things. Today, I will take this app as an example (other apps may encounter different problems in decoupling, which should be noted) to describe the whole process of cat-eye modularization. Each aspect did not copy some of the network’s practices, but analysis and comparison, the use of better design. For example, decoupling is done using serviceloader rather than routing. For example, the architecture uses a lifecycle MVP variant that is more appropriate for our business. I’m also going to give you a little bit of time and experience so that you know what you’re doing when you’re doing modules. (As a reminder, there’s a lot more to the modularity process than the article mentioned. Some are not mentioned because they have been done before, such as the caching of the network library by database -> text, which the reader should note. If there is something missing, please exchange it.

Main contents: Serviceloader decoupling, MVP variant framework, module communication, lib independent running, multiplex.

2 why do modularity

First things first: Modularity is not about showmanship. If there are no service scenarios, do not perform this operation. There are a lot of reasons on the Internet why we should do modularity. Here I say a simple cat eye why to do:

  • Maoyan needs to be quickly transplanted to other apps (Meituan, comments..) .
  • Decouple the home page to reduce cold start time.
  • Reduce build time and code responsibility during development.
  • Quick service replacement

To what extent is it decoupled?

First of all, what exactly is modularity? The ability to separate different businesses into separate Lib Modules is familiar. So with modularization done, what are the capabilities of one of our business lib’s? I think it is:

Bottom line: no communication costs, fast, dumb running on any app. The lib is not coupled to the service of the specific app, and the activity of the specific app is not coupled. Just give me an app (or fake app shell), and with its baseActivity and their services, I can run this lib on that app very quickly. Stop! You may say what is this service, let me elaborate on it

3.1 Non-invasive configuration of various services

We know that each app will provide account information, device information, network services, picture loading services, registration services, drop-down refresh style, error status and so on. These services may vary from app to app. For example, Meituan uses okhttp, while Dianping uses a long connection. So our business logic lib cannot couple these concrete services. You can only couple interfaces that the service abstracts from. When the specific app is used, we will provide the service of app to this lib. So how to give these services? If I leave a pass-through slot when I need services, I will need to stuff the services of app to the places needed in lib one by one. It costs too much. I don’t want such trouble, I want to directly put the service implementation as TXT text in a folder of app, your lib can run for me. So I hardly have to care what’s in lib. You just give me a business lib, I’ll add a TXT text, and it will run.

3.2 Lib is quick and easy to use

Talk about decoupled activities. We know that each app has its own baseAtivity, which does stats, handles exceptions, initializes certain libraries, and so on. In addition, the actionBar of each app is different, and the manifest schema of each page in different app is also different. So the business in lib, if it is a business, we can not write it as an Activity, but should be a view/fragment, so for any app, we can directly create an Activty, and then put the page in lib into that Activity. Again, considering the cost of collaboration, I don’t want to have to deal with a lot of other things like data loading while I’m putting this page up. I want you to give me the business page pager (which is actually a view) and I’ll just put it in the setContentView () of the activity onCreate () and it’ll run. Don’t ask me to do anything else with the life cycle, data binding, destruction, etc., that’s all you need to do internally with pager.

3.3 the demo sample

The previous two points may be in the clouds. Recently I wrote a cat’s eye q&A requirement that covered 5 pages, so it was made into a lib. So let’s illustrate this with my recent book Lib.


This lib is the business lib for q&A. This is not coupled to specific services, only to service interfaces. Inside the page (under the page package) is not an activity, but a view. So, at this time another colleague wants to use this lib on the cat’s eye app. So how do we do that?

  • Add this lib dependency to build.gradle of cat Eye app:

    The compile 'com. Maoyan. Android. Business: movie: 1.0.2.3'Copy the code
  • Add a service configuration required by lib to the cat-eye host app: service implementation TXT (as the host app, it already exists).



    TXT contains:

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

That’s done. That’s run. The various services used, such as drop-down refresh, are provided by this app. Is not fast, no communication writing, stupid. If we want to test the app, it’s easy. Create a random app shell, create a new activity, and put pages in the lib. Then add the required service implementation TXT text (because it is a test, so the service implementation can be free, can be configured at will), and you are done. This way to fix bugs and tune UI, save a lot of time than starting the host APP to modify the code. Let’s see I wrote a random app to test lib:


We can see that the pull-down refresh, status service and so on are different from the Cat eye app, and can be customized. If I write it this way, all of these modules we can quickly, stupid, customizable into an app wouldn’t that be a better degree of decoupling

If it feels good, let’s get to work


4 Start the modularity 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 before the module. Let’s take the movie details page as an example to see how it is coupled:


The movie detail page is built on top of layers of base classes that are coupled to specific services such as network loading. Because the details page has editable states like, rate, like, etc., it is also coupled to the Greendao database (which was also coupled to the network loading database before it was replaced with Retrofit + RxJava, thankfully). Because the page needs to interact with other pages (such as jump, score synchronization, etc.), it also couples the classes of other pages. In addition, there are utils, View, Model, etc. If you want to separate the movie details page, all of these couplings have to be stripped away. Specific problems to be solved are as follows:




4.2 Preparations

4.2.1 Workload assessment

First, let’s talk about what you need to do to prepare for decoupling. Because that’s the basis of decoupling and uncoupling. There are two things that need to be done, as follows:


First of all, it’s not that I like five stars. It is true that this part of the workload is relatively heavy ~~~

4.2.2 Split common resources, Model, utils, etc

4.2.2.1 Coupling Example

The first is the split of common resources, model, utils, etc. Although these things do not need to consider too many things, but very tedious. This place took a lot of time when it came to modularity. A big part of the problem is that the previous cat-eye historical version of the code was not canonical enough to be sensitive to things like code coupling. Here are a few examples:

  • So our previous utils was pretty much inside of a class MovieUtils. This class is like a big dye VAT. Put everything in there. It is also not standardized in terms of the parameters passed in. Even business code such as Maoyanbase fragments are passed in as parameters. Which makes this thing incredibly difficult to dismantle.
  • Utils methods do not pass context. As a result, almost all utils are not passed in the context. As a result, these tool methods directly feed into the host APP.
  • The Common View I wrote earlier wasn’t independent enough. If you want to write a Common View, try to make it as independent as possible and use the official Android library instead of coupling it to other third-party libraries.
4.2.2.2 Resource Splitting Experience

The splitting of resources is actually very tedious. Especially if string, color and Dimens resources are distributed in various corners of the code, it is very tedious to remove them one by one. You don’t have to do that. Because Android merges and shrinks resources during build. Res/values under the various documents (styles. Should pay attention to XML) eventually will only be put to use in intermediate/res/merged /.. /valus.xml, all useless will be deleted automatically. And finally we can use Lint to automatically delete. So this place doesn’t take too much time. As I said, styles.xml is something to watch out for. So what do you need to watch out for? This is what it says:


When we write property names, we must add a prefix qualifier. If you do not add it, there will be a conflict of property names when your lib is packaged as an AAR and used by other apps. Why? Because the name BezelImageView simply won’t appear in the intermediate/res/merged /.. /valus.xml, so don’t assume this is a qualifier for the attribute!

4.2.3 Integrated vs Combined (Optional)

The second point is the handling of base classes. We see that the movie details page is built on top of a bunch of base classes. The base class at each level does something. If we wanted to separate the movie detail pages, we would need to package these base classes into a single AAR and sink them into the base library for all pages to use. But our old base class coupled a lot of cat-eye stuff, like pull-down refreshes, page states and so on, and if I needed to write a page, I had to inherit a bunch of fragments. Of course, this change can also be transplanted. But it’s definitely bad for future iterations of code (modify, add business). Because it’s not flexible. For example, if the review app needs a part of a cat eye page instead of the whole page, it is not very convenient to change the original. I want these pages to be views, not fragments. And it’s not inheritance, it’s composition. That is, if I want a list view with a drop-down refresh, I just build that view, set whatever configuration I need, and it works. This view you can put into any view, fragment and combine with other views. That is:


The MovieRcPagePullToRefreshStatusBlock is a view, can be used in any page in the view of combination.

4.2.3.1 Plug-in type and combined design of components

I was bolder, or lazier. I hope I am the MovieRcPagePullToRefreshStatusBlock build successful, on the page can show run, automatic loading data. Just like playing with building blocks as a kid, components are plug-and-play. There is no relationship between the user and how the block loads the data. All the user needs to do is take the block and set it to whatever they want when they build it. Put it on a page and it’s ready to run. Consider this as an example:


As you can see from this page, I just built two views and put them in this page. I didn’t care about the data loading, the data loading was done inside this block. Then the page can be put into the Activty of an app as mentioned above. Plug and pull type, fool type thought, may I this person is more “lazy” ~~

So how does this architecture work? Lifecycle MVP, write pages like the front end, will be lifecycle MVP (lifecycle MVP). In fact, this framework is basically the same idea as the MVP framework, but it also solves some business scenarios, such as life cycle, portability, communication cost, ease of use, etc. Since to say the realization of the train of thought, so from the beginning, is a summary of their own, the readers may have some help. Here’s what the MVP framework means:

4.2.3.2 Meaning of MVP Framework

The MVP framework is generally applicable to android scenarios. M stands for model, providing data; V, for view, provides view-related methods that are called by presenter. P, for presenter, provides a logical method for triggering actions on a page.

4.2.3.3 Shortcomings of the official MVP framework

MVP frameworks there are many online, and they are officially recommended. A contract is used to host the view and Presenter interface definitions. Implement the View interface with fragments. However, the official use of fragments to implement views has its own frustrations. Why is it helpless? For interfaces in the View layer, fragments are used for implementation, mainly because fragments have a life cycle. But fragments are too bulky. Imagine I have a page with four or five pieces of content. For the sake of moving, removing and porting each piece of content in the future, I want each piece of content to be in MVP form, decoupled from each other. Then the official MVP framework doesn’t apply. Because you can’t write 5 fragments on a page. It is not recommended to write so many fragments in An Android activity. The typical fragment use scenario is ViewPager.

4.2.3.4 General Workarounds

For a workaround, the 5-piece view layer is no longer implemented as a fragment, but as a normal view, and each view listens to events in response to the view (calling its own Presenter method). For an initial load or pull-down refresh load of the entire page, all five pieces of content share a single fragment. In the fragment’s onStart () and pull-down refresh listening callback, the presenter methods for all five pieces of content are loaded. Then fill the fragment’s onCreateView () view with 5 pieces of content. There may also be communication and data exchange between the five pieces of content, which can be done in fragments with presenters.

4.2.3.5 MVP with A Lifecycle: Lifecycle – MVP

There is no problem doing that above, and the above practice also exists in our project. But after iterating through several versions, I found a few problems: Presenters are too messy and scattered. Fragment needs to hold all presenter, load () data on onStart (). Each view also needs to hold its own presenter. And the view and presenter need to set () each other. You also need to manage presenter in the onDetroy () method of activty or Fragment. The general feeling is very messy. Especially if your component needs to be used by someone else, or if your component needs to be used by another app, and someone else gets your component, you need to care about two things: View and Presenter. They need to know the methods in these two things, and they need to associate them and call some methods in the Activty/Fragment life cycle. Well. There must be a lot of communication cost in this process ~ hence the idea of instantiating components in the build style described above and then composing components with pager. Lifecycle MVP features (see android’s official MVP framework optimizations) :

  • Lifecycle is provided using the lifecycle-component.
  • Presenter is held inside the View layer and is not exposed outside.
  • When build creates a View instance, it provides a TypeFactory for business extension.
  • Business code layering. Using this MVP variant framework to rewrite the original project/write new business makes the page more portable, extensible, and the modules within the page can be moved and changed. Of course, this framework is built on the basis of our business, the framework or needs to be due to the project, there is no best, only more suitable ~

4.3 Removing an Interface

Having described the preparation for modularity, what do we need to do next? From the original project coupling structure described earlier, we know that our previous projects relied directly on concrete implementations of various services. What we are going to do is to strip out these concrete service implementations as interfaces:


4.3.1 Decoupling with Servieloader – non-explicit invocation of service implementation classes

4.3.1.1 official serviceloader

As you can see from the diagram, our implementation classes have been replaced with corresponding interfaces. But in itself, this step is not too difficult: find where the service was called before, and then replace it with an interface call. No more than some services with more, change a few cumbersome. But now we need to consider a question: how do we give the implementation of the service? The first thing that comes to mind is, let’s just pass in a parameter. However, this approach leads to too much communication cost when using Lib in the future: you need to tell people where and what type of arguments I need to pass in. Otherwise your lib won’t run. I don’t want people using your lib to have to look internally to see what your code is and how it should be passed. I want lib to be as transparent to others as possible when they use it. No need to know what is written inside lib, just configure a TXT text outside to run lib! So how do you do that? Java has long provided similar functionality: Place the provider configuration file in the resource directory meta-INF /services, and when the app runs, when it encounters Serviceloader.load(xxxinterface.class), The meta-INF /services configuration file looks for the implementation class full path name for this interface and uses reflection to generate an instance with no arguments. Our general approach is based on the functionality provided by Java:


4.3.1.2 Transform the official Serviceloader
4.3.1.2.1 Official Serviceloader Defects

There are at least three areas where Java’s official Serviceloader needs improvement, based on the foregoing.

  • The Serviceloader has no caching capability. For most services, we need to use the singleton pattern and not generate new instances frequently.
  • The Serviceloader builds instances using a constructor with no arguments. Needless to say, it definitely needs to be improved. Whose service is built without passing in parameters?
  • Serviceloader has no pre-check issues. At runtime, you need to look for the implementation class name of the interface in the configuration file. You’ll definitely get the wrong interface name, the wrong class name, the wrong configuration, the missing interface implementation class, etc., and these errors are not found in the compiler. Also, using serviceloader is a non-explicit way to invoke service implementation classes, and if you do not protect these implementation classes in ProGuard, you will be guaranteed to shrink. In addition to proGuard issues, configuration files written in the resource directory meta-INF /services also have compatibility issues with some phones (Samsung). Finally, considering the disadvantages of manual registration of servic profiles, serviceloader needs to provide automatic registration.

For the three cases above, the first point is easy to solve. Just provide a cache, without further ado.

4.3.1.2.2 Constructing a serviceloader instance

The second solution is to make all interfaces that use Serviceloader to load services implement the Iprovider interface. The Iprovider interface provides a Context Context (init) method. Thus, all service implementation classes need to implement init (Context), which does the initialization logic required in the original constructor. So we call the Serviceloader to load the service like this:

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

Inside MovieServiceLoader, the generated instance calls the init (Context) method. That solves the second problem. There may also be some questions (such as the discussion with Meituan platform children shoes) : why only the context parameter is passed in. What if a service implementation class needs additional parameters? As far as our services and services are concerned, I think we just pass in the context and basically get most of the parameters of Android from the context. And for a service, since it is a service, it should not depend on a specific component of your project. So I thought it would be enough to pass the context, rather than the variable object argument:

    MovieServiceLoader.getService(Object... params, ImageLoader.class);
Copy the code

This way of course can solve all problems. But the idea of this kind of design violates the separation of interface and implementation. For example, if I want to use the image loading service, I should just call it

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

Don’t let me know whether your specific service is Picasso or Glide. I don’t want to know. If I use the second option, do I need to know what parameters you need for this particular service and pass them in? This feels so unfriendly. Another advantage of using Iprovider is that we only need to add proGuard in the MovieServiceLoader repository:


That’s it. Elsewhere, there is no need to consider ProGuard when using or creating service interfaces.

4.3.1.2.3 Serviceloader preprocessing — Gradle plug-in

The third issue that needs to be addressed is pre-checking of serviceloader, etc. The solution is to write a Gradle plugin. The general flow of plug-ins is

  • We get all the compiled class files (folders) and JARS at some point in the build.
  • Use Javassit to determine which classes are decorated by @AutoService and add them if they don’t exist in the configuration file.
  • Check that the format in the serviceConfig configuration file is correct.
  • Use Javassit to determine if the class in the serviceConfig configuration file exists in the project and if the interface class implements the Iprovider interface.
4.3.1.2.4 Knowledge needed: build process, Javassit, groovy

I don’t want to say too much here, but considering these three things many readers may not be familiar with. Go directly to the Internet to Google these three things, just these things, may also need to learn a study. So I still write down some of my experience (for the sake of relevance, not to expand in detail), readers can refer to the reference, some can get twice the result with half the effort. Since you need to get the compiled class file and jar package, you need to know the general build process, what the input and output of each task are, whether it is in the form of folder or jar package. For example, to get all classes, you can get all classes from the input folder /jar package of the Dex Task when assembleXxx task is finished. The same goes for javac Task input. The output of a Javac task cannot, because the Intermediate /class folder of the output of a Javac task contains only the class files in the project, not the class files in the corresponding Intermediate /exploded-aar file. Of course transform is another way to do this. The input to transform, the output file path is already given, and the input classes are all classes. In addition to the build process, you might also use Groovy to write plug-in logic. But if you really don’t want to use Groovy, you can use Java. It’s compatible, but many of Groovy’s features, like loops, don’t work. Here’s a little lesson: When writing Goovy, the IDE doesn’t do a very good job of picking up errors, such as using a variable or a method. If the method is used incorrectly, the variable is not defined. It doesn’t give you a hint that you can’t find it. So it’s better to write to.java first and then move to.Groovy. Finally, you need to know something about Javassit, a tool for handling class files. Very powerful, and much like Java, most of the use will end with ctClass. So this class is good to be familiar with. As a rule of thumb, if you need to convert ctClass->class, remember to use static variables to store the class object. Otherwise, you will get an exception when the classloader loads the same path multiple times. Ok, that’s all about the principles, improvements, and benefits of using serviceloader for decoupling.

4.3.2 Serviceloader decoupling vs Routing mode decoupling

Most of the modularity blogs on the web use decoupled routing. What’s the decoupling of routing?

4.3.2.1 Describes the decoupling of routing modes

Let’s take a look at the general routing framework, captured from Android componentized communication:


So how does this routing framework work? The action is a service, and the provider is a map that holds all the key-value pairs (action name: Action instance) in a lib. Register providers for each lib in the host app. So module A requests moduleB’s services via (source code) :


That is, by providing the provider name, action name, parameter name, value, to the registered map to find the corresponding action instance, and then call its corresponding method. The core is decoupling using strings to match corresponding instances.

4.3.2.1.1 Advantages of decoupling routing modes

The biggest benefit of this approach is that when creating a new service, there is no interface to write, everything is marked and matched with strings, and nothing needs to be coupled between the two models, not even interface declarations. If a lib has a lot of services that need to be called from the outside and don’t call them very often, or IF I’m decoupled from more than just the services, then routing this way is good because you don’t have to write interfaces.

4.3.2.1.2 Discussion on the inapplicability of routing in service decoupling

But why not choose this decoupling approach? Because of this approach, I still raise the following concerns about service decoupling for Android as a whole (speaking for myself, it may be crude, not to say their project is not good enough) :

  • For a large area of decoupling, most services in the APP sector must be decoupled. Feature is a lot of use, this time I write a few interfaces, sink into the base library, no harm. The serviceloader benefits are highlighted when I use the service: WHEN I use the service, I don’t need to care about the class name of the implementation class, what the package name is, what parameters I pass in, and what the method name is. If I’m using a routed interface, I have a lot more things to care about, and if I have that many things to care about, it shouldn’t be called a service. What if the other Lib changes its name without your knowledge? Configuration is also not friendly when the code is ported to other apps or run independently. Serviceloader only needs to write a configuration TXT file in apK, and each lib service can write its own serviceCinfig, do not need to host app care. With routing, even though actions can be automatically registered, you need to handle some registration in the application.
  • Routing, a service framework, and serviceloader, by their nature, do not really communicate between modules. In layman’s terms, a routing framework can do this: b lib can new an instance of a class in A (or new it in advance) and call the methods of that instance without relying on a lib project. This is not communication, just a method that can call another repository. And communication refers to listening state, callbacks. Serviceloader doesn’t really communicate either. Communication between modules can only be done through non-explicit listening mechanisms such as EventBus, broadcast, ContentProvider, etc. Why do you say that? Because I see a lot of modular blogs talking about using routing frameworks for module to module communication. However, the routing framework mentioned above can’t really communicate between modules. Ok, serviceloader decoupling vs routing decoupling ends there.

4.4 Other work on decoupling

4.4.1 Job evaluation

A large section has been devoted to decoupling services using Serviceloader. So what else do I need to do? Here I first summarize the general, and then elaborate one by one:


4.4.2 Withdrawal of service implementation

Note the second part of the first point: if you want all modules to be packaged separately, you need to separate out all service implementations as well. If you don’t want to run it on its own, but just want to decouple it, stay in the host app. Although it’s easy to say that. But taking a service implementation apart and actually implementing it can take a lot of time, because a service can be coupled with a lot of things that are not easy to unplug. Readers should have a number in mind. But pull as far away as you can, not just lib’s standalone run. The replacement of subsequent services also has significant benefits. For example, the network-loaded library, which used to use Retrofi + OKHTTP, has been upgraded to RetroFIT + Long connections. Substitution is simply a matter of changing a sentence in the service configuration file. If you are going to pull away, pay attention to the definition of the interface, don’t couple the classes of a specific library, and be thoughtful and well-designed. For example, the INet library, the interface is defined as:

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

While Retrofit is a great library, the interface is not coupled to the library either. It could be replaced one day.

4.4.3 Database withdrawal

The second point, which is painful to say, is that pulling out the database is really troublesome. In unknown version, Cat’s eye is coupled to Greendao. The database itself is good, but it’s too big! If I want to give a lib to someone else, do I have to couple the lib to a large third-party database? !!!!! Because modularization was not considered before, basically all network data and sensitive data were saved in GrrenDAO. So every time I saw daossion when DECOUPLING, I was shocked. Network data is stored using files and is transparent to business code. Sensitive data is stored in a database, but isolated by interfaces, and the official database SQLite or Room is recommended for the database.

4.4.4 Say goodbye to butterKnife

The third point is that if you want to isolate and modularize your business code, say goodbye to view injection features like the Butterknife framework. Since Android ADT14, library R resources are no longer final, so you can’t use R.idx in library. Instead, use findViewById(); Also cannot use switch (R.id.x), should use if.. Else instead. The fourth point is a follow-up to the first point. There is not much work.

4.4.5 Page Hopping

4.4.5.1 Things to do for page Hopping

Page jump is also a thing that needs to be paid attention to in APP, because it is a modular portal, involving the communication between pages, other apps, version I to the page. It may seem simple, but if it is not properly designed, it can affect the code elegance of modular entry, crash count, page degradation, operational collaboration, and more. For the jump between pages, our general approach is:

  • If the class page does not have an implicit jump:

    • Get the intent (getContext (), targetActivity.class) and add parameters to the intent. Finally, starActivity (getContext (), intent).
    • GetIntent ().getString(xx_key,defaultValue) in onCreate () of activty;
    • If the value corresponding to xx_key is invalid or parsed incorrectly, such as movieId=0, or equal to “”. It should jump to another page or the jump fails.
  • If the page is configured with implicit jump:

    • On other pages you first create a createXxxActivityIntent () utils method that passes in the path, key, and value of the landing page.
    • Declared in the manifest.
    • In onCreate () of target Activty getIntent ().getData ().parseBoolean(xx_key,defaultValue)… Equal access parameter
    • If the value corresponding to xx_key is invalid or parsed incorrectly, such as movieId=0, or equal to “”. It should jump to another page or the jump fails.
4.4.5.2 Problems of Android native page hopping

Here are some of the problems with using native page jumps

  • Get (xx) with a bunch of intent.get(xx) parameters. If the page has both an implicit and a display jump, then it’s going to be messy in onCreate (). I’m going to do if else
  • If you want to make an implicit jump, you need to register the intent-filter in the manifest. First, it is troublesome. Second, I need to configure an activity in another place, which is inconvenient to manage.
  • You need to write another utils to get an implicit intent.
  • There is no downgrading strategy, and if the operation is mismatched, you can only go to the error page instead of taking a remedial action, such as going to the version I page.
  • When the developer or background configures wrong parameters, we need to write the back-pocket logic. Each page parse requires the same piece of logic to be written.
  • If a page requires permissions that only login users can open, we often write if (isLogin()) {// jump to page}else{// jump to login page}, the same logic for each operation.

If this is not so demanding, all pages can be implicitly jumped in order to decouple other module classes from page to page. That’s pretty much enough for the situation. But I want to mention Arouter, the open source framework that Alibaba launched. It has interception function, so that the jump failure can be degraded processing (such as rendering I version of the page), so that the page has login users can open permissions; Unified method of obtaining parameters, etc. It’s pretty good. Basically solved the above problems. We won’t expand on this, but look at open source best practice: ARouter, the Page routing framework for the Android platform

4.5 Inter-module or Inter-page Communication

4.5.0 Use ViewModel to share data between pages

This paragraph is a new addition. I think that’s a good place to put it. ViewModel is a class in Google’s new Lifecycle – Component that can be used to save data for configuration changes such as page rotation. I thought about it and thought it would work in the case of data sharing within decoupled pages.

Take an example I have encountered before: a page finished, PM asked me to do the page buried point. A burrow requires the page’s movieId information, but the block that needs a burrow doesn’t have a movieId in it. And I have this block hierarchy deep. So if I want to get the movieId, I need to pass it from the activity page level to my block, so there’s some coupling and method creation in the middle. At the time, I thought it was really embarrassing. There was a great need for something like an eventBus in the form of an event listener. I could just put the data in the Bus and it could be easily accessed anywhere on the page. To sum up: to put it simply, when a page block/fragment needs to use each other’s data /view, there is no need for rigid reference between them. Only the context parameter of the activity can obtain the data /view of each other, so as to carry out data exchange and view access. The context of the page is of system type and is easily retrieved, and there is no coupling. See the article I wrote earlier about using ViewModel to share data within a page: ActivityDataBus

4.5.1 Why Remove EventBus and use broadcast

If you’ve reached this point, then one page has basically been removed, and all that is left is interaction with other modules and other pages. As mentioned earlier, neither serviceloader nor routing can do this. Our first thought was to use EventBus to do this. To use EventBus, you need to define some events. Such as:


But if you modularize your business code individually, an awkward question arises: Where do you put the events? Since many libraries need to listen to this Event, you have to sink the Event into the base library. The result is that the base library gets bigger and bigger and can’t be broken down. About this wechat Android modular architecture reconstruction practice also mentioned this matter, and the use of a creative way called “. API “to solve this matter. The idea is that at compile time the common interface is sunk into the base library for use by other Modules, while the maintenance of the code remains in the non-base library. The base doesn’t swell, and the responsibility for code maintenance is much clearer. Unfortunately, I don’t have much time to write the Gradle plugin these days. I wonder which reader has the time or interest to implement this plug-in. It makes a lot of sense that the code in the base library doesn’t get bigger and bigger. In addition to ballooning the base, EventBus also has the problem of not being able to process communication between apps. Instead of EventBus, we use broadcast. The LocalBroadcast implementation for android is simply looper-handler and maintains a global map. Similar in performance to EventBus, events are matched using strings instead of Event Models. We can wrap BroadcastManager with an interface that uses in-domain broadcast inside the app, and modularized Lib that uses out-of-domain broadcast to communicate between apps.

4.5.2 Do not broadcast randomly

If you use EventBus a lot in your project, you’ll see a class with a lot of onEventMainThread () methods that are fun to write and painful to read. If there are many places in the project where the Event is sent, there are many places in the project where it is received. When splitting code, you are afraid to do anything for fear of what events are not received. Broadcast is similar to EventBus in that the readability and maintainability of a project can become quite poor if there are a large number of sends and receives of the same event. This is especially true for sensitive data synchronization:


There is no need to send broadcast or EventBus to synchronize sensitive data. Synchronization can be accomplished by localizing the data you want to view with the help of a database. The general idea is that all the data we get from the network is synchronized to the database. When you populate a view with sensitive data, the data comes from the database. On page return, if the page does not trigger the logic to populate the sensitive data view, then call it manually in onResume (), i.e. :


So that’s it for module/page communication in general, there’s not much work to do here:


4.6 Lib runs independently

4.6.1 Why do I need to communicate with the host APP process

At this point, a business module can either be placed in the host app as a library or run independently as an application. As explained in the q&A module in the previous article, add several activty shells to the host app, then add pages in lib, and register them in the manifest, that is:







Of course, if some actionBar interactions are needed, corresponding logic needs to be written in the host Activty. The framework diagram of the whole APP is as follows:







4.6.2 Communication with the host APP process

Finally, we found that if the independently run lib could monitor and synchronize the host app’s account, location, city, login type, device and other information, the information in the independently run lib would be real and dynamic. When the host app logs out, there is no login state in lib. The specific operation is as follows:

  • In the host app, we provide some contentProviders, and the content provided by each method is the real account and other data of the host app. Notify the listener of the contentProvider when changes are made to the host app account, for example:

            public void onEventMainThread(LoginEvent loginEvent) {
      getContext().getContentResolver().notifyChange(Uri.parse("content://com.maoyan.android.runenv/loginsession"),null);
       }
    Copy the code
  • In a standalone app, it acts as a listener for the 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(); }});Copy the code

This way, the account data in the lib is consistent with the data in 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:


When the host app is logged out, there is no login state in lib. Let’s take a look at the demo:


Finally, by convention, evaluate what needs to be done when a module is to run on its own:


5 Last Words

The whole process is finally over, and I hope readers will have an overall understanding of modularization after reading it, and have a general understanding of what each step needs to do and how much time it takes. Modularity is not about showing off how good you are. If that’s all it is, you don’t have to do it. Because modularity is tedious, boring, and time-consuming, you’re doing a lot of work, but in terms of surface functionality, bosses may not see it. Might as well take the time to introduce a third-party library that looks fancy. A large part of the effort is paying for previously poorly designed code logic. I do this event for business service, because Cat’s Eye needs to serve a lot of clients. As shown to do business decoupling, business modularization is inevitable. After modularization, it is very convenient to transplant the code to other ends, and the adjustment of the page inside the APP becomes simple. Finally, in the whole process of modularization, there are some experiences to share with you, the truth is very simple, more important is to implement:


6 Reference Materials

  • Practice of wechat Android modular architecture reconstruction
  • Open source best practice: ARouter, the page routing framework for the Android platform
  • Android componentized communication
  • ServiceLoader
  • AutoService
  • Android Architecture Components
  • Some thoughts on the componentization of Android business
  • New Android project 11 – Componentization practice from scratch