Skip to content

Latest commit

 

History

History
318 lines (249 loc) · 13.6 KB

README-en-US.md

File metadata and controls

318 lines (249 loc) · 13.6 KB

CC : Component Caller (Modularization Architecture for Android)

中文文档

Join the chat at https://gitter.im/billy_home/CC

Name CC cc-register
Version Download Download

demo download

Main App(contains demo and demo_component_a)

demo_component_b (Demo_B)

demo shows cc works on component in or not in main app. it looks like below via running both of above app on your device and launch demo app.

    Notice: calling across apps is only compat for develop time
    you need to turnon the permission 'auto start' for Demo_B to make it worked if the process of Demo_B is not alive. 

image

What`s different?

  • Easy to use, only 4 steps:

    • Add AutoRegister plug-in classpath in projectRoot/build.gradle
    • Add gradle file apply in module/build.gradle
    • Implements a component class for IComponent in the module
      • specified a name for this component in method: getNae()
      • call CC.sendCCResult(cc.getCallId, CCResule.success()) in method: onCall(cc).
    • Then you can call this component at everywhere in you app:
      • CC.obtainBuilder("component_name").build().call()
      • CC.obtainBuilder("component_name").build().callAsync()
  • Feature-rich

      1. Support inter-component invocation (not just Activity router, call&callback for almost all instructions)
      2. Support component invocation is associated with Activity and Fragment lifecycle (requires: android api level >= 14, support lib version >= 5.1.0)
      3. Support inter-app component invocation (component development/commissioning can be run separately as an app)
      4. Support to switch and permission Settings of the invocation between apps (Meets for the security requirements of different levels, default status: enabled and do not require permission).
      5. Support for synchronous/asynchronous invocation
      6. Supports synchronous/asynchronous implementation of components
      7. The invocation method is unrestricted by implementation (for example, asynchronous implementation of another component can be invoked synchronously. Note: do not use time-consuming operation in the main thread.
      8. Support for adding custom interceptors (executed in the order of addition)
      9. Support for timeout Settings (in milliseconds, 0: no timeout, synchronous invocation set as 1000 ms by default)
      10. Support manual cancellation
      11. Automatic registration of components (IComponent) at compile time without manually maintain the component registry (implemented by using ASM to modify bytecode)
      12. Support for dynamic registration/unregistration components (IDynamicComponent)
      13. Support for non-basic types of objects, such as passing fragments between components
      14. Try to solve the crash that is caused by incorrect usage:
          14.1 component invocation, callback, and component implementation crash are all caught within the cc framework
          14.2 The CCResult object of synchronous return or asynchronous callback must not be null to avoid null pointer
    
  • low cost to convert original code to CC

    Some guys worry about that it's too expensive to convert the code in the old project to a high degree of code coupling CC can only take 2 steps to solve this problem:

    1. Create a component class (IComponent implementation class) that provides functionality that was previously implemented by class dependencies
    2. Then change the direct class call to CC call mode
  • monitor the execution process log Developer can monitor execution process logs with Logcat CC disabled this function by default, enable it with code: CC.enableVerboseLog(true);

The directory structure

    //core
    - cc                            core library of CC framework
    - cc-settings.gradle            common gradle file for user
    
    //demos
    - demo                          demo main app module
    - demo_component_a              demo ComponentA 
    - demo_component_b              demo ComponentB
    - cc-settings-demo.gradle       actionProcessor自动注册的配置脚本demo
    - demo-debug.apk                demo apk(contains demo and demo_component_a)
    - demo_component_b-debug.apk    apk for demo_component_b only

How to Use

1. add classpath

buildscript {
    dependencies {
        classpath 'com.billy.android:autoregister:x.x.x'
    }
}

2. modify build.gradle for all component and main app modules:

apply plugin: 'com.android.library'
//or
apply plugin: 'com.android.application'

//replace to
apply from: cc-settings-2.gradle

see demo_component_a/build.gradle

module is setting as library by default. there are 2 ways to set as application for single launch apk:

2.1 modify local.properties

demo_component_b=true # run as application for module: demo_component_b

2.2 modify module/build.gradle: add ext.runAsApp = true before apply from: '...cc-settings.gradle',ext.runAsApp priority is higher than local.properties.

ext.runAsApp = true
apply from: cc-settings-2.gradle

3. Define a component (IComponent)

public class ComponentA implements IComponent {
    
    @Override
    public String getName() {
        // specify the name for this component
        return "demo.ComponentA";
    }

    @Override
    public boolean onCall(CC cc) {
        Context context = cc.getContext();
        Intent intent = new Intent(context, ActivityComponentA.class);
        if (!(context instanceof Activity)) {
            // context maybe an application object if caller dose not setContext 
            // or call across apps
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        context.startActivity(intent);
        //send result to caller
        CC.sendCCResult(cc.getCallId(), CCResult.success());
        // onCall return false if result is sent synchronization before this method returned
        return false;
    }
}

4. Call component by CC

// Synchronous call, get CCResult by method return
CCResult result = CC.obtainBuilder("demo.ComponentA").build().call();
// Asynchronous call, do not need result callback
String callId = CC.obtainBuilder("demo.ComponentA").build().callAsync();
// Asynchronous call, result callback in thread pool
String callId = CC.obtainBuilder("demo.ComponentA").build().callAsync(new IComponentCallback(){...});
//Asynchronous call, result callback on main thread
String callId = CC.obtainBuilder("demo.ComponentA").build().callAsyncCallbackOnMainThread(new IComponentCallback(){...});

More: Add dependencies in main app module for all component modules like below:

dependencies {
    addComponent 'demo_component_a' //default add dependency: project(':demo_component_a')
    addComponent 'demo_component_kt', project(':demo_component_kt')
    addComponent 'demo_component_b', 'com.billy.demo:demo_b:1.1.0'
}


## Advance usage

```java
// enable/disable debug log
CC.enableDebug(trueOrFalse);    
// enable/disable cc process detail log
CC.enableVerboseLog(trueOrFalse); 
// enable/disable caller across apps
CC.enableRemoteCC(trueOrFalse)  
// cancel a cc by callId
CC.cancel(callId)
// set context for cc
CC.obtainBuilder("demo.ComponentA")...setContext(context)...build().callAsync()
// cc will cancel automaticly on activity destroyed
CC.obtainBuilder("demo.ComponentA")...cancelOnDestroyWith(activity)...build().callAsync()
// cc will cancel automaticly on fragment destroyed
CC.obtainBuilder("demo.ComponentA")...cancelOnDestroyWith(fragment)...build().callAsync()
// set cc actionName
CC.obtainBuilder("demo.ComponentA")...setActionName(actionName)...build().callAsync()
// set cc timeout in milliseconds
CC.obtainBuilder("demo.ComponentA")...setTimeout(1000)...build().callAsync()
// add extenal params
CC.obtainBuilder("demo.ComponentA")...addParam("name", "billy").addParam("id", 12345)...build().callAsync()


// build a success CCResult
CCResult.success(key1, value1).addData(key2, value2)
// build a failed CCResult
CCResult.error(message).addData(key, value)
// send CCResult to caller (you should make sure this method called for each onCall(cc) invoked)
CC.sendCCResult(cc.getCallId(), ccResult)
// get cc result if success or not
ccResult.isSuccess()
// success code(0:success, <0: failed, 1:component reached but result is failed)
ccResult.getCode()
// get error message
ccResult.getErrorMessage()  
// get external data
Map<String, Object> data = ccResult.getDataMap();
if (data != null) {
    Object value = data.get(key)   
}

CCResult code list:

code error status
0 success
1 business failed in component
-1 default error. not used yet
-2 component name is empty
-3 CC.sendCCResult(callId, null) or interceptor returns null
-4 An exception was thrown during cc
-5 no component object found for the specified component_name
-6 context is null and get application failed by reflection
-7 connect failed during cc across apps
-8 cc is canceled
-9 cc is timeout
-10 component.onCall(cc) return false, bus no CCResult found
  • Custom interceptors

    1. Create a class that implements the ICCInterceptor interface
    2. Call the chain.proceed() method to keep the call chain down and not call to block the CC
    3. You can modify the parameters of the CC object before calling chain.proceed()
    4. After calling the chain.proceed() method, you can modify CCResult

    see demo: MissYouInterceptor.java

  • register/unregister dynamic component

Definition: Unlike the static component (IComponent), which is automatically registered to ComponentManager at compile time, dynamic components do not automatically register and work through manual registration/unregistration

    1. Dynamic components need to implement interfaces: IDynamicComponent
    2. It is necessary to call CC.registerComponent(component) manually, similar to the BroadcastReceiver dynamic registration
    3. It is necessary to call CC.unregisterComponent(component) manually, similar to the BroadcastReceiver dynamic unregistration
    4. Other usage are the same as static components
  • You can have multiple modules include in a module

      In a module, you can have multiple implementation classes for the IComponent interface (or IDynamicComponent interface)
      IComponents are automatically registered to the component management class ComponentManager at compile time
      IDynamicComponents are not
    
  • A component can process multiple actions

      In the onCall(CC cc) method, gets actions to handle separately via cc.getActionName()
    

    see:ComponentA

  • Auto register Custom ActionProcessor into component

    seeComponentB andcc-settings-demo.gradle

watch the sourcecode of demo, demo_component_a and demo_component_b for more details

More usage

You can easily do AOP work with CC, such as:

  1. Activity open requires user login:
  • check the user login status before startActivity
  • already login:
    • startActivity immediately and CC.sendCCResult(callId, CCResult.success());
  • not login:
    • start login activity and wait for result
    • login success: startActivity and CC.sendCCResult(callId, CCResult.success());
    • login failed: CC.sendCCResult(callId, CCResult.error("login failed"));

demo: seeLifecycleComponent.java

  1. Pre-load activity data before context.startActivity with PreLoader
  • define a component for open the activity
public class ComponentA implements IComponent {

    @Override
    public String getName() {
        return "demo.ComponentA";
    }

    @Override
    public boolean onCall(CC cc) {
        int preLoaderId = PreLoader.preLoad(new Loader());
        Intent intent = new Intent(this, PreLoadBeforeLaunchActivity.class);
        intent.putExtra("preLoaderId", preLoaderId);
        startActivity(intent);
        CC.sendCCResult(cc.getCallId(), CCResult.success());
        return false;
    }
}
  • call that component by CC to open activity
// pre-load is needless here, the logistic of component are all inside that component itself
CC.obtainBuilder("demo.ComponentA").build().call();

Proguard

none

Contact me

[email protected]