What is P2M?

P2M is a complete componentized tool that supports major functions such as separate compilation, separate running, and packaging to the warehouse. Services, events, and initiators of modules do not need to be submerged inside the module, and the module can be initialized safely according to module dependencies at run time. Attached is the project address.

When compiling in Gradle, P2M mainly upgrades Project state to Module state.

Simple comparison between Project state and Module state:

The Project state The Module state
include ‘:lib-login’ p2m {

        module(‘Login’) {

                include(‘:lib-login’)

        }

}

The Module state

A Module state corresponds to a Module. A Module contains the Api area and the Source code area. The Source code area can access the Api area of itself and its dependent modules.



Api area

The Api area contains the Launcher, Service, and Event, and the P2M annotation processor will participate in the compilation.

  • launcher– Initiator, associated annotations@Launcher, the same module can annotate multiple classes, P2M annotation processor will generate corresponding interface functions intolauncherCurrently supports annotation activities to be generatedfun newActivityIntentOfXXActivity(): IntentThe annotation Fragment will be generatedfun newFragmentOfXXFragment(): FragmentThe annotation Service will be generatedfun newServiceIntentOfXXService(): Intent;
  • service– Service, associated annotations@ServiceOnly one class can be annotated in a module. The P2M annotation handler extracts all public member functions of the annotated classservice, so that an external module can indirectly call the internal implementation of the module;
  • event– Event, associated annotations@EventOnly one class can be annotated in a module. The P2M annotation processor will extract all the annotated classes@EventFieldAnnotation member variables intoeventAnd based on the type of the variableSubscriptable event holding objectsFor sending events and subscribing to receive events,@EventFieldYou can specifySubscriptable event holding objectsWhether send events and subscribe to receive events occupy main thread resources.

The module opens a window, which is the Api area. If you find the window, you can find the launcher, Service and event of the module.



How do I access the Api area

When the Api area needs to be updated, we must compile the Api area first, which is the prerequisite to access the Api area. The area where you can write code inside the module belongs to the Source Code area. We access the Api area from the Source Code area:

val a = P2M.moduleApiOf<A>()          // Get the Api area of module A

val launcherOfA = a.launcher       // Launcher in the Api area
val serviceOfA = a.service         // Service in Api area
val eventOfA = a.event             // Event in the Api area
Copy the code

The Source code area

Source Code area refers to the area inside a module where code can be written and stored. The Source Code area of each module is hidden from the outside and contains the following contents:

  • Module init – Module initialization, association@ModuleInitializerAnnotation, a module can only annotate one class and must implement the ModuleInit interface. The P2M annotation handler generates proxy classes, which must be declared for each module and coded by the developer.
  • The specific Implementation area of implementation-API area is generated by P2M annotation processor, which is not perceived by developers.
  • Feature Code – Write the Feature code area, which is coded by the developer.

Modules need to be started up before they can be used by other modules. Starting up is in the Module init area, which is responsible for completing the necessary initialization of modules.

Module initialization has three phases:

  • onEvaluate– Evaluate its own phase, mainly used to register to complete its own initialization tasks, running in the background thread.
  • onExecute– Execution phase, here is to execute the registered task, running in the background thread.
  • onExecuted– Completed execution phase, the registered task has been completed, which means that the module has completed initialization, running on the main thread.

Module initialization works as follows:

  • Within a module, the order of execution must beonEvaluate > onExecute > onExecuted.
  • If module A depends on module B, the execution sequence must be that of module BonExecuted> of module AonExecute.
  • If module A depends on module B and module B depends on module C, the execution sequence must be that of module ConExecuted> of module AonExecute.

Knowing this, you must have some questions about how to design and use the Api area, what to do during the initialization phase, etc. Let’s start with an example.

The sample

An App has three interfaces: startup interface, login interface and main interface:

  • When you start an application, the startup screen is displayed. If you have not logged in to the application, the login screen is displayed. If not, the user information is displayed on the main screen.
  • After a successful login, the user information is displayed on the main screen.
  • Click Logout on the main screen to switch to the login screen.

Demand analysis

After analyzing the requirements, the file tree of the whole project is roughly as follows:

├ ─ ─ app / / app shell │ ├ ─ ─ the SRC │ │ └ ─ ─ the main/kotlin/package │ │ ├ ─ ─ MyApp. Kt / / custom Application │ │ └ ─ ─ SplashActivity. Kt / / Startup screen │ └ ─ ─ build. Gradle ├ ─ ─ the module - the account/account/module │ ├ ─ ─ the SRC │ │ ├ ─ ─ app/kotlin/package / / module as an independent run time of the app package │ │ └ ─ ─ Main /kotlin/package │ │ ├─ AccountModuleInit.kt // Module Account Initialization class │ │ ├─ AccountEvent Accountservice.kt // Module AccountService Class │ │ ├─ ├─ LoginUserInfo.kt // User Information Data Class │ │ ├─ UserDiskCache. Is responsible for reading the cache logged in, login information │ │ └ ─ ─ LoginActivity. Kt/a/login interface │ └ ─ ─ build. Gradle ├ ─ ─ the module - the main / / module main │ ├ ─ ─ the SRC │ │ ├ ─ ─ │ ├─ ├─ ├─ class ├─ ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ │ ├─ ├─ SRC │ ├─ ├─ TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT ├─ build.gradle ├─ settings.gradle // P2MCopy the code

Our entire application is divided into 2 modules (with 2 Api areas) + 1 APP shell:

  • Module Account:

    • Api area is mainly responsible for providing login status (subscribeable event holding object), login information (subscribeable event holding object), login success (subscribeable event holding object), login interface Intent (initiator), logout (service) externally.
    • The ModuleInit section of the Source Code section is responsible for declaring AccountModuleInit (initialization of the Module), which reads login status and login user information from the local cache and sends an event.
    • The Feature code area in the Source Code area is mainly responsible for the UI and logic of the login interface, the realization of login and logout, and the cache related to reading and writing login.
  • Main module:

    • Api area is mainly responsible for providing the main interface Intent (launcher) externally;
    • The ModuleInit section in the Source Code section is responsible for declaring MainModuleInit (the initialization of the Module) without any initialization logic;
    • The Feature code area in the Source code area is mainly responsible for the UI and logic of the main interface.
  • App shell:

    • Responsible for opening the P2M driver (start initializing all modules);
    • The login status of the subscription module Account. If the login succeeds, the MainActivity of the Main module will be jumped; otherwise, the LoginActivity of the Account module will be jumped.
    • Subscribe to the login status of the SplashActivity module. If the login is successful, skip to the MainActivity of the Main module; otherwise, skip to the LoginActivity of the Account module.

P2M project configuration

Settings. gradle under the root project:

// Be sure to add buildScript otherwise you won't find the plugin
buildscript {
    repositories {
        google()
        mavenCentral()
        mavenLocal()
        maven { url 'https://jitpack.io' }
    }

    dependencies {
        classpath 'com. Android. Tools. Build: gradle: 4.0.2'            AGP supports 3.4.0+ and Gradle 6.1.1+
        classpath 'com.github.wangdaqi77.P2M:p2m-plugin:last version'
    }
}

apply plugin: "p2m-android"                     / / P2M plug-in

p2m {
    app {                                       // Declare the app shell
        include(":app")                         / / declare the project
        dependencies {                          // Declare the dependency module, here represents the app shell dependency module Account and module Main
            module("Account")
            module("Main")
        }
    }

    module("Account") {                         // Declare the module Account
        include(":module-account")              / / declare the project
    }

    module("Main") {                            // Declare the module Account
        include(":module-main")                 / / declare the project
        dependencies {                          // Declare the dependent module
            module("Account")}}}Copy the code

The whole project structure is shown in the figure below:



Module Account

  • The Api area is an open door, so let’s consider how to design the Api area first:

    • Launcher – An Intent that provides an external login interface, so it is defined in the Source code section:

      @Launcher
      class LoginActivity : Activity(a)// See sample source code for specific implementation
      Copy the code

      When compiled, the following code is generated in the Api area:

      /** * A launcher class of Account module. * Use `P2M.moduleApiOf
                
                 ().launcher` to get the instance. * * Use [newActivityIntentOfLoginActivity] to launch [com.p2m.example.account.pre_api.LoginActivity]. */
                
      public interface AccountModuleLauncher : ModuleLauncher {
        public fun newActivityIntentOfLoginActivity(context: Context): Intent
      }
      Copy the code
    • Service – Provides the service method for logging out externally, so it is defined in the Source code area:

      @Service
      class AccountService { // See sample source code for specific implementation
          fun logout(a){}}Copy the code

      When compiled, the following code is generated in the Api area:

      /**
       * A service class of Account module.
       * Use `P2M.moduleApiOf<Account>().service` to get the instance.
       *
       * @see com.p2m.example.account.pre_api.AccountService - origin.
       */
      public interface AccountModuleService : ModuleService {
        public fun logout(a): Unit
      }
      Copy the code
    • Event – Provides login status, login user information, etc., so it is defined in the Source code area:

      @Event
      interface AccountEvent{
          @EventField(eventOn = EventOn.MAIN, mutableFromExternal = false)        // Send and subscribe to receive events in the main thread
          val loginInfo: LoginUserInfo?                                           // Login user information
      
          @EventField                                                             EventField(eventOn = eventon. MAIN, mutableFromExternal = false);
          val loginState: Boolean                                                 // Login status
      
          @EventField(eventOn = EventOn.BACKGROUND, mutableFromExternal = false)  // Send, subscribe and receive events do not occupy main thread resources
          val loginSuccess: Boolean                                               // Login succeeded
          
          @EventField(eventOn = EventOn.MAIN, mutableFromExternal = true)         // mutableFromExternal = true, which indicates that external modules can setValue and postValue. This setting is not recommended to ensure event security
          val testMutableEventFromExternal: Int                                   // Test external variability
          
          val testAPT:Int     // This field is not annotated, so it will be filtered
      }
      Copy the code

      When compiled, the following code is generated in the Api area:

      /**
       * A event class of Account module.
       * Use `P2M.moduleApiOf<Account>().event` to get the instance.
       *
       * @see com.p2m.example.account.pre_api.AccountEvent - origin.
       */
      public interface AccountModuleEvent : ModuleEvent {
        public valloginInfo: LiveEvent<LoginUserInfo? >public val loginState: LiveEvent<Boolean>
        public val loginSuccess: BackgroundLiveEvent<Unit>
        public val testMutableEventFromExternal: MutableLiveEvent<Int>}Copy the code
  • The Module init area is where a Module starts up and designs the necessary initialization according to the requirements and responsibilities of the Module.

    @ModuleInitializer
    class AccountModuleInit : ModuleInit {
    
        // Run in the child thread, used to register tasks in the module, organize task dependencies
        override fun onEvaluate(taskRegister: TaskRegister) {
            // Register the task to read the login status
            taskRegister.register(LoadLoginStateTask::class.java, "input 1")
    
            // Register the task to read the information of the logged-in user
            taskRegister
                .register(LoadLastUserTask::class.java, "input 2")
                .dependOn(LoadLoginStateTask::class.java) / / execution sequence must be for LoadLoginStateTask onExecute () > LoadLastUserTask. OnExecute ()
        }
    
        // Run on the main thread, called when all dependent modules have started up and their own modules have completed their tasks
        override fun onExecuted(taskOutputProvider: TaskOutputProvider, moduleProvider: SafeModuleProvider) {
            val loginState = taskOutputProvider.getOutputOf(LoadLoginStateTask::class.java) // Get the login status
            val loginInfo = taskOutputProvider.getOutputOf(LoadLastUserTask::class.java)    // Get user information
    
            val account = moduleProvider.moduleApiOf(Account::class.java)  P2m. moduleApiOf() cannot be called in Module initaccount.event.mutable().loginState.setValue(loginState ? :false)      // Save to the event holder for use by the dependent module
            account.event.mutable().loginInfo.setValue(loginInfo)                 // Save to the event holder for use by the dependent module}}// Read login status task, input:String output:Boolean
    class LoadLoginStateTask: Task<String, Boolean>() {
    
        // Run on a child thread when all dependent modules have started up and their dependent tasks are completed
        override fun onExecute(taskOutputProvider: TaskOutputProvider, moduleProvider: SafeModuleProvider) {
            val input = input // Input is 1
            Log.i("LoadLoginStateTask"."onExecute input: $input")
    
            val userDiskCache = UserDiskCache(moduleProvider.context)
            output = userDiskCache.readLoginState()
        }
    }
    
    Input :String Output :LoginUserInfo
    class LoadLastUserTask: Task<String, LoginUserInfo>() {
    
        // Run on a child thread when all dependent modules have started up and their dependent tasks are completed
        override fun onExecute(taskOutputProvider: TaskOutputProvider, moduleProvider: SafeModuleProvider) {
            val loginState = taskOutputProvider.getOutputOf(LoadLoginStateTask::class.java)
    
            // Query user information
            if (loginState == true) {
                val input = input // Input is 2
                Log.i("LoadLastUserTask"."onExecute input: $input")
    
                val userDiskCache = UserDiskCache(moduleProvider.context)
                output = userDiskCache.readLoginUserInfo()
            }
        }
    }
    Copy the code

See all the sample source code for more implementations.

Q&A

How do I publish data classes to the Api area?

First add the @APIuse annotation to the data class, and finally compile the Api area. Such as LoginUserInfo in the example

How do I compile the Api area?

If the annotations used in the Api area (@launcher, @Service, @Event, @Eventfield, @APIuse) are added, deleted, or changed in your code, To compile the Project, click Build > Make Module in Android Studio or Build > Make Project.

How do I run modules separately?

Enable and run app and applicationId:

  1. Add runApp = true and useRepo = false to the declaration module code block, settings.gradle at the root of the project:

    p2m {
        module("YourModuleName") {
            // ...
            runApp = true               // true indicates that the app can be run
            useRepo = false             // False means use source code, true means rely on repository AAR}}Copy the code
  2. Build. gradle in the module folder declares:

    // This configuration applies only when the Module sets' runApp=true ', and must be placed at the bottom of the file to override the above configured values.
    p2mRunAppBuildGradle {
        android {
            defaultConfig{
                applicationId "your.application.package"
            }
    
            sourceSets {
                main {
                    java.srcDirs += 'src/app/java'                      // We need a custom Application to open the P2M driver
                    manifest.srcFile 'src/app/AndroidManifest.xml'      // You need to specify a custom Application here}}}}Copy the code
  3. sync project

How to publish a module’s AAR and other components to the repository?

You need to configure the warehouse information before publishing:

  1. Add the following configuration to the declaration module code block, settings.gradle under the root project:

    p2m {
        module("YourModuleName") {
            // ...
            groupId = "your.repo.groupId"       // group, mainly used to determine the published group, the default module name
            versionName = "0.0.1"               // Version, which determines the released version. The default value is Unspecified
            useRepo = false                     // False means use source code, true means rely on repository AAR
        }
    
        p2mMavenRepository {                    // Declare a Maven repository for publishing and retrieving remote AArs. Default is mavenLocal()
            url = "your maven repository url"   // Warehouse address
            credentials {                       // The user who logs in to the warehouse
                username = "your user name"
                password = "your password"}}}Copy the code
  2. Execute commands issued to the warehouse

    • Under the Linux/MAC:
    ./gradlew publicYourModule // For publishing a single module./gradlew publicAllModule // for publishing all modulesCopy the code
    • Windows:
    Bat publicYourModule // Is used to publish a single module. Gradlew. Bat publicAllModule // Is used to publish all modulesCopy the code

How do I use a warehouse AAR?

If the module has been packaged into the repository, then:

  1. Add the following configuration to the declaration module code block, settings.gradle under the root project:
    p2m {
        module("YourModuleName") {
            // ...
            groupId = "your.repo.groupId"       // group, mainly used to determine the published group, the default module name
            versionName = "0.0.1"               // Version, which determines the released version. The default value is Unspecified
            useRepo = true                      // False means use source code, true means rely on repository AAR
        }
    
        p2mMavenRepository {                    // Declare a Maven repository for publishing and retrieving remote AArs. Default is mavenLocal()
            url = "your maven repository url"   // Warehouse address
            credentials {                       // The user who logs in to the warehouse
                username = "your user name"
                password = "your password"}}}Copy the code
  2. sync project