Android Component Development Case (Fusion of 10 Project Modules)

Keywords: Android xml Gradle Retrofit

stay Last article In this article, I will introduce some cases about Android component development in detail, in which 10 project modules are merged................................................................

Catalogue introduction

  • 1. Practical development cases

    • 1.1 Open Source Projects for Component Practice
    • 1.1 How to create modules
    • 1.2 How to Establish Dependency
    • 1.3 How to Unify Configuration Files
    • 1.4 Componentized Base Library
    • How to switch between 1.5 component mode and integration mode
    • 1.6 Componentization Solves Duplicate Dependencies
    • 1.7 Key Points for Componentization
    • Resource name conflict in 1.8 componentization
    • 1.9 Problems in Component Development
  • 2. Inter-component communication

    • 2.1 Select the Open Source Routing Library
    • 2.2 Basic Principles of Ali Arouter
    • 2.3 Notices for Using Arouter

0. Open source address of component development case

github.com/yangchong21...

1. Practical development cases

1.1 Open Source Projects for Componentized Practice

  • Some Thoughts on Component Development
    • There are many blogs about componentization on the Internet. They explain what componentization is, why componentization is needed, and the benefits of componentization. Most of the articles provide the idea of componentization, which provides a lot of convenience for me to start componentization development. Thank you for sharing! Although there are some gains, few articles can give an overall and effective solution, or a specific Demo.
    • But after all, blogging is also a preparation for practice. When we started to change the previous open source case into component case, there were a lot of problems and solved some problems.
    • Most companies start component development slowly. In small companies, some people try component development because they haven't done component development before. In large companies, some people just take charge of a module. Maybe at the beginning of component development, some people have already done it, so it's faster to learn and practice component development. Amateur practice, open source projects before the revision, wrote this blog, it took me a lot of time, if you have some help, then I am very happy. Because I'm a little man, so I can't write well, don't spray, welcome to make suggestions!
  • On Componentized Open Source Projects
    • The overall project architecture pattern is: component + MVP+Rx+Retrofit+design+Dagger2+VLayout+X5
    • Modules included: Wan Android [kotlin] + dry goods concentration camp + Zhizhi Daily + Tomato Todo + Selected News + Douban Music Movie Fiction + Novel Reading + Simple Notebook + Funny Video + Classic Games + More
    • This project belongs to the part-time training project, the interface data sources are from the network, if there is infringement, please inform the first time. This project is only for learning and communication. The ownership of API data content belongs to the original company. Please do not use it for other purposes.
    • About the project address of open source componentization: github.com/yangchong21...

1.1 How to create modules

  • according to Last article 3.3 Architecture Design Diagram
  • Main works:
    • There is no business code except for some global configuration and main Activity. Some are also called shell app s, which mainly rely on business components to run.
  • Business components:
    • At the top level, each component represents a complete line of business, independent of each other. In principle: there can be no direct dependence between business components! All business components need to be able to run independently. For testing, it is necessary to rely on the functions of multiple business components for integrated testing. app shells can be used to run multi-component dependency management.
    • This case is divided into: work camp, play Android, Zhizhi Daily, Wechat News, headlines, funny videos, Baidu music, my notebook, Douban music, reading movies, game components and so on.
  • Functional components:
    • This case is divided into, sharing component, comment feedback component, payment component, Gallery component and so on. Also note that there may be multiple business components that depend on a functional component!
  • Basic components:
    • Basic business services that support the operation of upper business components. This component provides basic functional support for upper business components.
    • In this case, there are network requests, picture loading, communication mechanism, tool class, sharing function, payment function and so on in the basic component library. Of course, I put some public third-party libraries into this basic component!

1.2 How to Establish Dependency

  • The component dependency structure diagram for engineering is shown below.
  • Complete Configuration Code under Business Module

    //Control Component Mode and Integration Mode
    if (rootProject.ext.isGankApplication) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
    
    android {
        compileSdkVersion rootProject.ext.android["compileSdkVersion"]
        buildToolsVersion rootProject.ext.android["buildToolsVersion"]
    
        defaultConfig {
            minSdkVersion rootProject.ext.android["minSdkVersion"]
            targetSdkVersion rootProject.ext.android["targetSdkVersion"]
            versionCode rootProject.ext.android["versionCode"]
            versionName rootProject.ext.android["versionName"]
    
            if (rootProject.ext.isGankApplication){
                //Setting up application Id in component mode
                applicationId "com.ycbjie.gank"
            }
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [AROUTER_MODULE_NAME: project.getName()]
                }
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    
        //jdk1.8
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    
        sourceSets {
            main {
                if (rootProject.ext.isGankApplication) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                }
                jniLibs.srcDirs = ['libs']
            }
        }
    
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation project(':library')
        annotationProcessor rootProject.ext.dependencies["router-compiler"]
    }
    

1.3 How to Unify Configuration Files

  • Because there are many modules in the practice of componentization, it is necessary to consider simplification when configuring gradle and adding dependency libraries. So how on earth?

  • The first step is to create a yc.gradle file in the project root directory. In actual development, you only need to change the version information in the file.

    • Most of the cases I see on the Internet are switching between component mode and integration mode through a switch control, but here I configure switches of several components to control the switching status of the corresponding components.
    ext {
    
        isApplication = false  //false: exists as a Lib component, true: exists as an application
        isAndroidApplication = false  //Play the Android module switch, false: exists as a Lib component, true: exists as an application
        isLoveApplication = false  //Love expression module switch, false: exists as Lib component, true: exists as application
        isVideoApplication = false  //Video module switch, false: exists as Lib component, true: exists as application
        isNoteApplication = false  //Notepad module switch, false: exists as a Lib component, true: exists as an application
        isBookApplication = false  //book module switch, false: exists as a Lib component, true: exists as an application
        isDouBanApplication = false  //Douban module switch, false: exists as a Lib component, true: exists as an application
        isGankApplication = false  //Dry cargo module switch, false: exists as Lib component, true: exists as application
        isMusicApplication = false  //Music module switch, false: exists as a Lib component, true: exists as an application
        isNewsApplication = false  //News module switch, false: exists as Lib component, true: exists as application
        isToDoApplication = false  //todo module switch, false: exists as a Lib component, true: exists as an application
        isZhiHuApplication = false  //Knowing the module switch, false: exists as a Lib component, true: exists as an application
        isOtherApplication = false  //Other module switches, false: exist as Lib components, true: exist as application s
    
        android = [
                   compileSdkVersion       : 28,
                   buildToolsVersion       : "28.0.3",
                   minSdkVersion           : 17,
                   targetSdkVersion        : 28,
                   versionCode             : 22,
                   versionName             : "1.8.2"    //It must be int or float, otherwise it will affect online upgrade
        ]
    
        version = [
                   androidSupportSdkVersion: "28.0.0",
                   retrofitSdkVersion      : "2.4.0",
                   glideSdkVersion         : "4.8.0",
                   canarySdkVersion        : "1.5.4",
                   constraintVersion       : "1.0.2"
        ]
    
        dependencies = [
                    //support
                    "appcompat-v7"             : "com.android.support:appcompat-v7:${version["androidSupportSdkVersion"]}",
                    "multidex"                 : "com.android.support:multidex:1.0.1",
                    //network
                    "retrofit"                 : "com.squareup.retrofit2:retrofit:${version["retrofitSdkVersion"]}",
                    "retrofit-converter-gson"  : "com.squareup.retrofit2:converter-gson:${version["retrofitSdkVersion"]}",
                    "retrofit-adapter-rxjava"  : "com.squareup.retrofit2:adapter-rxjava2:${version["retrofitSdkVersion"]}",
                    //A part of the code is omitted here.
            ]
    }
    
  • Step two, and then add the code to the lib in the project [notice here is build.gradle in the base component library], as shown below

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion rootProject.ext.android["compileSdkVersion"]
        buildToolsVersion rootProject.ext.android["buildToolsVersion"]
    
        defaultConfig {
            minSdkVersion rootProject.ext.android["minSdkVersion"]
            targetSdkVersion rootProject.ext.android["targetSdkVersion"]
            versionCode rootProject.ext.android["versionCode"]
            versionName rootProject.ext.android["versionName"]
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        api rootProject.ext.dependencies["appcompat-v7"]
        api rootProject.ext.dependencies["design"]
        api rootProject.ext.dependencies["palette"]
        api rootProject.ext.dependencies["glide"]
        api (rootProject.ext.dependencies["glide-transformations"]){
            exclude module: 'glide'
        }
        annotationProcessor rootProject.ext.dependencies["glide-compiler"]
        api files('libs/tbs_sdk_thirdapp_v3.2.0.jar')
        api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    
        //Omit part of the code
    }
    
  • The third step is to add dependencies to other model s

    • implementation project(':library') is enough.

1.4 Componentized Base Library

  • Base library component encapsulation
    • The basic library component encapsulation library mainly includes some frameworks commonly used for development. Can directly see my project more intuitive!
    • 1. Network request (using Retrofit + Rx Java framework), interceptor
    • 2. Picture loading (policy mode, switchable between Glide and Picasso)
    • 3. Communication Mechanism (RxBus), Routing ARouter Simple Packaging Tool Class (Communication between Different Models)
    • 4. mvp framework, common base classes, such as BaseActivity, BaseFragment, etc.
    • 5. General tools, such as cutting rounded corners, animation tools, etc.
    • 6. Custom view (including dialog box, ToolBar layout, circular picture, etc.)
    • 7. Shared shape, drawable, layout, color and other resource files
    • 8. Global initialization of asynchronous thread pool encapsulation library, each component can be used
  • Component initialization
    • For example, if you switch the news component in this case to an app that runs independently, it needs to be initialized because the news jump detail page needs to use x5 WebView. Initially, configure a separate application for each component that can be switched to app, and then initialize some tasks that the component needs to initialize. But it's a little bad to do so. It's not very convenient to manage. Later, after seeing the component-based practice scheme, the scheme proposed to develop a multi-threaded initialization framework. Each component only needs to create several new start-up Task classes and declare dependencies in Task. But how to use it in the middle and later stages of the code remains to be realized!
  • How to simplify people who are not familiar with componentization and quickly adapt to the independent operation of components
    • Set up multiple component switches and change that component if you need to switch it. If you set a switch, you can either cut all components into integration mode or all components into component mode, which is a bit easy to go wrong. More can be looked down!
  • Strict restrictions on the growth of public infrastructure components
    • As development continues, care should be taken not to add too much content to the underlying public components. It should be smaller! If the base component is too large, then the running component is relatively slow!

How to switch between 1.5 component mode and integration mode

  • Playing with the build.gradle file under the Android component, the other components are similar.

    • Switching this state is controlled by a switch. If module is a library, the com.android.library plug-in is used; if it is an application, the com.android.application plug-in is used.
    //Control Component Mode and Integration Mode
    if (rootProject.ext.isAndroidApplication) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
    
  • The integration pattern is as follows

    • First, you need to set isApplication=false in the yc.gradle file. After Sync, it was found that the component was library.
    ext {
        isAndroidApplication = false  //false: exists as a Lib component, true: exists as an application
    
  • The component pattern is shown below.

    • First, you need to set isApplication=true in the yc.gradle file. After Sync, it is found that the component is application, which can be run against the module.
    ext {
        isAndroidApplication = true  //false: exists as a Lib component, true: exists as an application
    
  • It's important to note that

    • First of all, look at the vast majority of online practices. Thank you very much for these Gods'selfless dedication! But I think multiple components can be controlled by one switch, but when switching to component app in sourceSets, you can reuse java and res files without the following troubles.
  • Next, let's look at my approach:
  • The following configuration is very important. That is to say, when the Android component is switched from library to application, because it can run as a stand-alone app, the application Id is set in order, and the configuration manifest file is shown below.
  • To switch between library and application, the manifest file also needs to provide two sets
    android {
        defaultConfig {
            if (rootProject.ext.isAndroidApplication){
                //Setting up application Id in component mode
                applicationId "com.ycbjie.android"
            }
        }
        sourceSets {
            main {
                if (rootProject.ext.isAndroidApplication) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                }
                jniLibs.srcDirs = ['libs']
            }
        }
    }
  • Specifically in the project as follows

4.6 Componentization Solves Duplicate Dependencies

  • Repeated dependency problem statement

    • The problem of duplicate dependency is often encountered in development, such as the project implementation of an A, then implementation of a B in the library, and then your project implementation of a same B, depending on two times.
    • By default, if it is an aar dependency, gradle will automatically help us find new versions of libraries and discard old versions of duplicate dependencies. But if you use project dependencies, gradle doesn't de-duplicate, and duplicate classes appear in the final packaging.
  • Solutions, for example

    api(rootProject.ext.dependencies["logger"]) { 
        exclude module: 'support-v4'//Exclude by component name 
        exclude group: 'android.support.v4'//Exclusion according to package name 
    }
    

1.7 Key Points for Componentization

  • Linkages between business components lead to serious coupling
    • For example, in actual development, shopping cart and home page goods are two components respectively. However, in the case of product requirements, such as a holiday activity, issuing a shopping voucher and so on, because the shopping cart and the product details page have activities, so components often interact. If the preparation is insufficient, as time goes on, the code boundary of each line of business will deteriorate as the main project before componentization, and the coupling will become more and more serious.
    • The first solution is to use sourceSets to put different business codes in different folders, but the problem with sourceSets is that it does not restrict references between different sourceSets, so this approach is not very friendly!
    • The second solution is to extract the requirement as a tool class and transfer the value of different components to achieve the invocation relationship, so that the requirement can be changed only by changing the tool class. But this is just the same as the requirement, but used in different module scenarios.
  • Database Separation in Component Development
    • For example, the video module I'm developing now wants to be used by others. Because of the need for databases such as caching, do we have to rely on a larger third-party database for this lib? But it is not convenient to use the system's native sql database. What should we do? For the time being, I haven't found a way to...

Resource name conflict in 1.8 componentization

  • What are resource name conflicts?
    • For example, color, shape, drawable, image resources, layout resources, or anim resources, etc., may cause resource name conflicts. This is why, sometimes people are responsible for different modules, if not named in accordance with the uniform specification, then occasionally this problem will arise.
    • Especially if string s, colors, dimens are distributed in every corner of the code, it is very tedious to disassemble them one by one. Actually, this is not necessary. Because Android builds resources merge and shrink. Every file under res/values (styles. XML should be noted) will only end up putting what is used in intermediate/res/merged/. / valus.xml should be noted And the useless will be automatically deleted. And finally we can use lint to delete automatically. So don't spend too much time in this place.
  • Solution
    • This is not a new problem. Third-party SDK s will almost encounter this problem, which can be avoided by setting up resourcePrefix. After setting this value, all of your resource names must be prefixed with the specified string, otherwise an error will be reported. However, the value of resourcePrefix can only limit the resources in xml, not the image resources. All the image resources still need you to modify the resource names manually.
  • Personal suggestion
    • Place color, shape, etc. in the base library component, because all business components depend on the base component library. In styles.xml, it should be noted that when writing attribute names, prefix qualifiers must be added. If not, it is possible that when packaged into aar and used by other modules, there will be conflicts of duplicate attribute names. Why? Because the name BezelImage View doesn't appear in intermediate / RES / merged /. / valus. XML at all, so don't think it's a qualifier for attributes!

1.9 Problems in Component Development

  • How to get global context from each component module
    • Scene reappearance
      • For example, if the initial online project is a single benefit created in the app master project, then componentization in lib or later division is unable to get the context in the application class of the master project. That's the time.
    • Solution
      • It's easy to write a Utils tool class in lib and initialize Utils.init(this) in the main engineering application, so that you can get the global context in lib and all business components [already dependent on the common base component library].
  • Use of butterKnife
    • Although there are many blogs on the Internet that say butterKnife can solve the problem of referencing between different components. However, in practical development, I encountered compilation errors when switching between component mode and integration mode. If the butterKnife reference problem is solved in componentization, please let me know. Thank you very much.
  • When componentization is lib
    • switch (R.id.xx) cannot be used, but if. else is needed instead.
  • Don't send bus messages indiscriminately
    • If Evetbus is used extensively in a project, you will see a class with a large number of onEventMainThread() methods, which are pleasant to write and painful to read.
    • Although it is very convenient and simple to use EventBus or RxBus to send messages to realize inter-component communication in the early stage, with the increase of business and the continuous updating in the later stage, some of them have been modified by several programmers before and after, which will reduce the amount of code reading. There are many places to send this Event and many places to receive it in the project. In the later stage, when we want to improve the component development and split the code, we dare not act rashly, fearing which events have not been accepted.
  • Problems with page jumps
    • If a page needs to be logged in before it can be viewed, then if (isLogin(){// jump page} else {// jump to login page} will be written, and these same logic will be written for each operation.
    • Native startActivity jump can not monitor the jump status, such as jump errors, success, abnormalities and so on.
    • Later, the background will control the jump from clicking the button to different pages. If the background configuration information is wrong or parameters are missing, then the jump may not succeed or lead to crash, which does not have a good processing mechanism.
    • Ali's open source framework, Arouter, can solve the problem of page jump, can add interception, or even if the background configuration parameters are wrong, when listening for jump abnormalities or jump errors, can jump to the home page by default directly. That's what I did in the open source case!
  • On Jump Parameters
    • Let's first look at this code writing, which is not a problem, but when many people develop, if someone wants to jump to a page of your development module, it's easy to mispass the value. It is suggested that the value of key be written as a static constant and put into a special class. Convenient to oneself and others.

      //Jump
      intent.setClass(this,CommentActivity.class);
      intent.putExtra("id",id);
      intent.putExtra("allNum",allNum);
      intent.putExtra("shortNum",shortNum);
      intent.putExtra("longNum",longNum);
      startActivity(intent);
      
      //Receive
      Intent intent = getIntent();
      int allNum = intent.getExtras().getInt("allNum");
      int shortNum = intent.getExtras().getInt("shortNum");
      int longNum = intent.getExtras().getInt("longNum");
      int id = intent.getExtras().getInt("id");
      

2. Inter-component communication

2.1 Select the Open Source Routing Library

  • The representative open source frameworks for component are DDComponent ForAndroid, Arouter Ali, Router Jumei and so on.
    • DDComponent ForAndroid: A complete and effective android componentization scheme, which supports the functions of component isolation, individual debugging, integrated debugging, component interaction, UI jump, dynamic loading and unloading.
    • Ali Arouter: The middleware that provides routing functions for pages and services is simple and easy to use. There are many introductory blogs on the Internet. In this component case, I just use this.
    • Router: A single-product, component and plug-in fully supported routing framework

2.2 Basic Principles of Ali Arouter

  • Here's just a basic idea.
    • The @Route annotation added to the code generates class files that store the mapping relationship between path and activityClass by apt at compilation time, and then gets these class files when the app process starts up, reads the data that saves these mapping relationships into memory (stored in map), and then passes in the route to the page by build() method when routing jumps. Address.

      • Add the @Route annotation and compile it to generate the class, then look at the class. As follows:
      /**
       * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
      public class ARouter$$Group$$video implements IRouteGroup {
        @Override
        public void loadInto(Map<String, RouteMeta> atlas) {
          atlas.put("/video/VideoActivity", RouteMeta.build(RouteType.ACTIVITY, VideoActivity.class, "/video/videoactivity", "video", null, -1, -2147483648));
        }
      }
      
    • ARouter finds the activity. class (activity. class = map. get (path) corresponding to the routing address through its own stored routing table, and then new Intent(), when it calls the withString() method of ARouter, it calls intent.putExtra(String name, String value) internally, and navigation() method internally, it calls startActivity(intent) to jump, which can be achieved. Two independent module s started each other's Activity smoothly.

      • Look at the _navigation method code in the _ARouter class, at 345 lines.
      private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
          final Context currentContext = null == context ? mContext : context;
      
          switch (postcard.getType()) {
              case ACTIVITY:
                  // Build intent
                  final Intent intent = new Intent(currentContext, postcard.getDestination());
                  intent.putExtras(postcard.getExtras());
      
                  // Set flags.
                  int flags = postcard.getFlags();
                  if (-1 != flags) {
                      intent.setFlags(flags);
                  } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                  }
      
                  // Set Actions
                  String action = postcard.getAction();
                  if (!TextUtils.isEmpty(action)) {
                      intent.setAction(action);
                  }
      
                  // Navigation in main looper.
                  runInMainThread(new Runnable() {
                      @Override
                      public void run() {
                          startActivity(requestCode, currentContext, intent, postcard, callback);
                      }
                  });
      
                  break;
              case PROVIDER:
                  //The code is omitted here.
              case BOARDCAST:
              case CONTENT_PROVIDER:
              case FRAGMENT:
                  //The code is omitted here.
              case METHOD:
              case SERVICE:
              default:
                  return null;
          }
          return null;
      }
      

2.3 Notices for Using Arouter

  • Use Ali Route Extraction Tool Class to facilitate later maintenance!
    • Let's first look at a way of writing on the Internet.

      //First, add the following code through annotations
      @Route(path = "/test/TestActivity")
      public class TestActivity extends BaseActivity {
      
      }
      
      //Jump
      ARouter.getInstance().inject("/test/TestActivity");
      
    • Optimized Writing

      • The following method is convenient for later maintenance.
      //Store all routing path constants
      public class ARouterConstant {
          //Jump to the video page
          public static final String ACTIVITY_VIDEO_VIDEO = "/video/VideoActivity";
          //Omit part of diamagnetic
      }
      
      //Store all routing jumps, tool classes
      public class ARouterUtils {
          /**
           * Simple jump pages
           * @param string                string Path Corresponding to Target Interface
           */
          public static void navigation(String string){
              if (string==null){
                  return;
              }
              ARouter.getInstance().build(string).navigation();
          }
      }
      
      //call
      @Route(path = ARouterConstant.ACTIVITY_VIDEO_VIDEO)
      public class VideoActivity extends BaseActivity {
      
      }
      ARouterUtils.navigation(ARouterConstant.ACTIVITY_VIDEO_VIDEO);
      
Last article: Android Component Development Practice

END

For Android programmers, I have collated some information for you, including not only advanced UI, performance optimization, architect courses, NDK, React Native + Weex, Wechat applet, Flutter and other advanced Android practices; I hope to help you, and save you time to learn online search information, but also share dynamic information to yourself. Study with your friends!

Data Acquisition: Free Acquisition of Tien Zan+Jia Qun Architecture Design of Android IOC

Additive group Architecture Design of Android IOC Receive advanced Android architecture information, source code, notes, videos. advanced UI, performance optimization, architect course, React Native + Weex all aspects of Android advanced practice technology, there are also technical bulls in the group to discuss and solve problems.

Posted by canabatz on Thu, 25 Apr 2019 09:18:35 -0700