In my years of study and growth, slowly realized that building a good Android development framework is a very difficult and painful thing, it not only needs to meet the growing business needs, but also to ensure that the framework itself is clean and extensible, which makes things very challenging, but we must do so, Because a robust Android development framework is the foundation of a great app.
In the early days of our development, we often don't need a framework, because the Android framework's good fault tolerance helps us avoid many problems, and even if you don't need to learn in depth, you can write a more complete APP, a few simple material Design The style interface plus some data that makes everyone a Android developer, but is that really enough?
Of course not!!
As our projects become larger and bigger, and problems ensue, chaotic data storage, access, and code that is not flexible enough will become the biggest obstacle to our project, and the consequence of its free development is that it leads to a messy project, and it's hard to add new features that can only be refactored or even overturned. Before we start programming, we should not underestimate the complexity of an application.
In addition, in the field of software engineering, there are always some principles that we should learn and abide by, such as: Single duty principle, dependence inversion principle, avoiding side effects and so on. The Android Framework does not force us to abide by these principles, or it does not impose any restrictions on us, think of those tightly coupled implementation classes that handle large amounts of business logic activity or Fragment, ubiquitous Eventbus, Hard-to-read data stream passing and confusing callbacks to hell and so on, though they won't cause the system to crash immediately, but as the project progresses, they will become difficult to maintain, and even difficult to add new code, which will undoubtedly become a formidable obstacle to business growth.
So it's important for developers to have a good architectural guide to their specifications.
Choice of Architecture
Now online about MVVM, MVP, MVC, Androidflux selection and analysis of the article has been very much, here I do not describe too much, interested students can see my Android Refactoring Tour: architecture, where we finally chose the MVP as our development framework, MVP Has a lot of benefits, but ultimately it makes us choose it because it's easy for the average developer to get started, and it can also make the business boundaries of our Activity clear.
Refused God Activity
During the years of development, it is often possible to see the Activity of thousands of lines of code:
Redefining the life cycle
Handling Intent
Data Update
Thread switching
Basic business logic
......
What's more, defining all the possible subclass variables in the baseactivity, and so on, it is now truly "God", a convenient and omnipotent God!
As the project grows, it's too big to continue adding code, so you've written a lot of help classes to help this god thin down:
My 10-year Android Refactoring Tour: Framework article
Inadvertently between that you've buried the black xxx
It seems that the business logic is solved by the help class digestion, the code in the baseactivity is reduced, no longer so "fat", help class to alleviate its pressure, but as the project grows, the business expands, and these help classes are slowly changing big, this time again according to business continue to split them, Maintenance costs seem to have increased, and the chaotic and hard-to-reuse programs are back, and our efforts seem to have been wasted.
Of course, some people will separate different abstract classes according to different business functions, but they are still omnipotent in the context of the business scenario.
Whatever the reason for this creation of the "God-like" approach should be avoided, we should not focus on writing those chatty classes, but instead devote our energies to writing low-coupling classes that are easy to maintain and test, if you can, it is best not to let business logic into the pure Android world, That's what I've been trying to achieve.
Clean architecture and the clean rule
This circular graph, which looks like the "crust", is clean Architecture, and the different colored rings represent different system structures, which make up the entire system and the arrows represent dependencies.
My 10-year Android Refactoring Tour: Framework article
We've chosen MVP as the framework for framing, and we're not going to go into the clean Architecture architecture, and some of the advantages of clean Architecture we'll rub into the framework, we should follow the following three principles when designing a framework:
Layering principles
Reliance principle
Abstract principles
Next I will explain separately my understanding of these principles and the reasons behind them.
Layering principles
First, the framework should not limit the specific layering of the application, but from a multi-person collaborative development perspective, I usually divide Android into three layers:
Outer layer: Event Guide layer (View)
Middle Layer: Interface adaptation layer (typically generated by Dagger2)
Inner layer: Business Logic Layer
Looking at the three layers above, it's easy to think of the MVP structure, and let me say what the three layers contain.
Event Guide Layer
A guide layer, which acts as another manifestation of the view layer in the framework, which is responsible for the direction of the view event, such as OnClick, OnTouch, Onrefresh, etc., which is responsible for passing events to the business logic layer.
Interface Adaptation Layer
The purpose of the interface adaptation layer is to connect the business logic with the framework-specific code, acting as a bridge between the outer and inner layers, which we typically use to generate Dagger2.
Business Logic Layer
The business logic layer is the most important part of the framework, where we solve all the business logic, which should not contain the code for event orientation, should be able to test independently using Espresso, which means that our business logic can be independently tested, developed and maintained, which is the main benefit of our framework architecture.
Dependency rules
The dependency rule is consistent with the clean Architecture arrow direction, the outer "dependent" inner layer, where the "dependency" is not referring to those Dependency statements you write in Gradle, it should be understood as "see" or "Know", the outer layers know the inner layer, In contrast, the inner layer does not know the outer layers, or the outer layers know how the inner layer defines the abstraction, but the inner layer does not know how to implement the outer layers. As mentioned earlier, the inner layer contains the business logic, the outer layers contain implementation details, and the dependency rule is that the business logic is neither visible nor aware of the implementation details.
For Project engineering, the exact way to rely depends entirely on you. You can put them into different packages, manage them through the package structure, and be careful not to use the external package code in the internal package. The use of packages for management is very simple, but also exposes a fatal problem, once someone does not know the dependency rules, it is possible to write the wrong code, because this kind of management can not prevent people to the rule of dependency destruction, so I prefer to generalize them into different Android module, adjust The dependencies between Module, so that the inner layer code can not know the existence of the outer layers.
Abstract principles
The so-called "abstract principle" refers to the extraction of common patterns from specific problems, and then the use of common solutions to deal with them.
For example, in our development often encounter switch no network, no data interface, we define a viewlayoutstate ' interface in the framework, on the one hand, the business logic layer can directly use it to switch interface, on the other hand we can also implement this interface in the View layer, to rewrite the different interface style, The business logic layer is just a notification interface, it is not clear implementation details, do not know how to implement, and even do not know the carrier is an Activity or a View.
This is a good example of how to use abstract principles, and when the abstraction and dependency are combined, you will find that the business logic using abstract notifications does not see or know the specific implementation of viewlayoutstate, which is what we want: The business logic does not notice the specifics of the implementation, and it does not know when it will change. Abstract principles are good for us to do this.
Build This library
The above introduces so many design criteria, now to introduce the library design, the library is divided into the following three modules:
Instance
Util
Base
My 10-year Android Refactoring Tour: Framework article
Util, Instance
Util, Instance are essentially tools, auxiliary classes, a static tool class for "go-to-walk", such as determining whether text is empty, and a "long-time use" form of Instance, such as Activity management stacks.
Base
Base main work is to give baseactivity and basefragment a lot of different abilities, above we mentioned to avoid creating "God", but in the project development process difficult to avoid this situation, in the Library we will baseview all the ability to extract , Baseactivity and Basefragment will only be responsible for the View display.
My 10-year Android Refactoring Tour: Framework article
Baseactivity
Baseactivity main functions are divided into:
ACTIVITYMVP provides context
ViewResult provides cross-interface refresh
Activitytoolbarbase provides top bar
Viewlayoutstate provides a switching interface
Lifecyclecallbackstrategy Life Cycle Callback Management
My 10-year Android Refactoring Tour: Framework article
What we can see here is that the full capabilities of baseactivity are related to View, which may be strange, isn't there a business capability to implement ViewResult cross-interface refresh? Let's see how it's implemented.
/* Global refresh /@Overridepublic void Resultall () {Presenter.resultall ();} / Partially refreshed * @param resultdata/@Overridepublic void result (map<string, string> resultdata) { Presenter.result (resultdata);}
As can be seen here, we have commissioned presenter to implement, ensuring that only View-related operations exist in baseactivity.
Baselistactivity
Public abstract class Activitylistbase extends Activitybase implements ACTIVITYRECYCLERMVP {private Recyclerview Rvindex Recycler = null; Private smartrefreshlayout Srlrefresh = null; Private Multitypeadapter adapter = null; Private Presenterlistbase presenter = null; @Override protected Final int getlayout () {return r.layout.activity_recycler_base;} @Override protected final void ONBEF Oreinit (Bundle savedinstancestate, Intent Intent) {presenter = Getpresenter (); Presenter.oncreate (savedinstancestate) ; } @Override protected final void Oninitcomponent () {Rvindexrecycler = Findviewbyid (r.id.rv_index_recycler); Srlrefresh = Findviewbyid (R.id.srl_index_refresh); Oninitrecycler (); Oninitlistcomponent (); } @Override protected final void Oninitviewlistener () {Oninitrefresh ();} @Override protected final void Onloadhttpdata () {Presenter.getdata (presenterlistbase.init);}/ Initialize Refresh layout /protected final void Oninitrefresh () {Srlrefresh.setonloadmorelistener (New Onloadmorelistener ( {@Override public void Onloadmore (Refreshlayout refreshlayout) {presenter.getdata (Presenterlistbase.load_more);}}); Srlrefresh.setonrefreshlistener (New Onrefreshlistener () {@Override public void Onrefresh (Refreshlayout refreshlayout {Srlrefresh.setenableloadmore (true); Srlrefresh.setnomoredata (false); Presenter.getdata ( Presenterlistbase.refresh); } }); } / Initialize recycler/protected final void Oninitrecycler () {Recyclerview.layoutmanager LayoutManager = Getlayoutmanager (); Rvindexrecycler . Setlayoutmanager (LayoutManager); Rvindexrecycler.sethasfixedsize (FALSE); adapter = new Multitypeadapter (Presenter.providerdata ()); Addrecycleritem (adapter); Rvindexrecycler.setadapter (adapter); }}
Presenterviewlistimpl
Public abstract class Presenterviewlistimpl<t extends respbase> implements Presenterlistbase {protected ACTIVITYRECYCLERMVP viewbase = null; Layout content protected list<object> data = null; Layout start protected int pagestart = 1; Load more protected final int pageSize = page_max_size; Load data type protected @LoadDataState int loadState; Public Presenterviewlistimpl (Activitylistbase activitylistbase) {viewbase = activitylistbase; data = new ArrayList<& gt; (); } @Override public void OnCreate (Bundle savedinstancestate) {} @Override public void result (map<string, string> Res Ultdata) {runtimeutil.runtimeexception ("The result interface is not implemented"), @Override public void Resultall () { Runtimeutil.runtimeexception ("Resultall Interface not implemented"); } @Override public void GetData (int.) {loadState = state, switch (loadState) {case INIT: {processpreinitdata (); BR Eak Case REFRESH: {pagestart = 1, Break,} case Load_more: {pagestart = Pagestart + 1; Load Network data loaddata (new onloaddatalistener<T> () {@Override public void Loaddatacomplete (T-t) {handleloaddata (loadState, t);} @Override public void Loaddataerro R (@StringRes int errorinfo) {handleloaddataerror (loadState, errorinfo);} @Override public void Loaddataend () {Handleloa Ddataend (); } }); } / Start load /protected final void Processpreinitdata () {pagestart = 1; viewbase.switchloadlayout ();}/ processing the data that is loaded to completion @param loadState@param t */protected void handleloaddata (int loadState, T t) {switch (loadState) {case INIT: {Viewbase.switchcontentla Yout (); Initview (t); Break Case REFRESH: {Viewbase.finishrefresh (); Initview (t), break,} case Load_more: {viewbase.finishrefreshloadmore (); Brea K } } } /* Handling a Load Error condition @param loadState@param errorinfo/protected void Handleloaddataerror (int loadState, int errorinfo) {switch (loadState) {case INIT: {Viewbase.switchrelo Adlayout (errorinfo); Break } case REFRESH: {toastutil.showtoast (Viewbase.getcontext (), Viewbase.getcontext (). getString (errorinfo)); Viewbase.finishrefresh (); Break } case Load_more: {pagestart = pageStart-1; Toastutil.showtoast (Viewbase.getcontext (), Viewbase.getcontext (). getString (errorinfo)); Viewbase.finishrefreshloadmore (); Break }}} protected void Handleloaddataend () {} @Override public void OnDestroy () {viewbase = null; data = NULL;} @Override Public list<?> Providerdata () {return data,} public abstract void LoadData (Onloaddatalistener loaddatalistener); public abstract void Initview (T t); public void Presenterloadmoredata (T-t) {} public interface Onloaddatalistener<q extends respbase> {public void Loa Ddatacomplete (q q); public void Loaddataerror (@StringRes int errorinfo); public void Loaddataend (); }}
Because of the limited space, students interested in this framework can come here to view.
Show Code
Let's try out the new framework for a simple list of data.
public class Informationlistactivity extends Baselistactivity {@Inject Informationactivitycontract.presenter Mpresenter; @Override public void Injectandinit () {//Interface Adaptation Layer Daggerinformationlistactivitycomponent.builder (). Activeinformationactivitymodule (New Informationmodule (this)). Build (). inject (this); } @Override Public Baselistpresenter Getbaselistpresenter () {return mpresenter;} @Override protected void Registeritem (M Ultitypeadapter adapter) {//Show multi-Recyclerview Adapter.register (activedetailinfo.class,new activealllistprovider ( mactivity)); Adapter.register (Nomoredatabean.class,new nomoredataprovider ()); }}
As you can see, we're very clean out of View, and then we'll see how Presenter is implemented.
public class Informationactivitypresenterimpl extends baselistpresenterimpl<responsebean<zoneactivebean> > Implements Informationactivitycontract.presenter {@Inject Informationactivitycontract.view mView; @Inject Zoneapiservice Mzoneapiservice; @Inject public Informationactivitypresenterimpl () {super (),} @Override public Observable getobservable (@ Constant.requesttype int RequestType) {return Mzoneapiservice.zoneactivedata (Mview.getuserid (), PageNo, pageSize);} @ Override public void Initview (responsebean<zoneactivebean> responsebean) {Zoneactivebean data = Responsebean.getdata (); if (data! = NULL && data.activityInfo.activityList! = null && data.activityInfo.activityList.size () > 0) {mdata.clear (); for (Activedetailinfo item:data.activityInfo.activityList) {mdata.add (item);} Mview.setloadmore ( Data.activityInfo.activityList.size () = = PageSize); pageno++; Mview.notifydatasetchanged (); } else {mview.setnodata ();}} @Override public void ProceSsloadmoredata (responsebean<zoneactivebean> responsebean) {Zoneactivebean data = Responsebean.getdata (); if ( Data! = NULL && data.activityInfo.activityList! = null && data.activityInfo.activityList.size () > 0) { for (Activedetailinfo item:data.activityInfo.activityList) {mdata.add (item);} if (mdata.size () = = DATA.ACTIVITYINFO.T Otal) {Mdata.add (new Nomoredatabean (false)); Mview.setloadmore (mdata.size () = = Data.activityInfo.total);} PageNo + +; }else{Mview.setloadmore (false); Mdata.add (new Nomoredatabean (false));} mview.notifydatasetchanged (); }}
As we have already specified, the event guide layer handles only View-related operations, so that our activity becomes very neat and activity is only a trend for data and events, and Presenter helps us deal with the specifics of the event.
Summarize
As a common development framework within the company, the selection of functions should be kept to a minimum, using only the necessary functions.
The architecture should maintain good extensibility.
I believe that you, like me, are faced with a variety of challenges in framing, learning from mistakes, optimizing code, adjusting dependencies, and even re-organizing the structure of your architecture, and all of the changes you make are to make architectures more robust, and we've always wanted applications to be easy to develop and maintain, This is the real benefit of the team.
I have to say that there are a variety of ways to build an application architecture, and I think that there is no omnipotent, once and for all architecture, it should be constantly iterative update, adapt to the business. So, you can try to build your application by combining business with the ideas provided in this article.
Finally, I hope this article can help you, if you have other better architectural ideas, welcome to share or communicate with me
My 10-year Android Refactoring Tour: Framework article