注册通用组件

模块化是 mPaaS 框架的设计原则之一,业务模块的低耦合与高内聚有利于业务的扩展和维护。

业务模块以 Bundle 的形式存在互不影响,但 Bundle 之间会存在一些关联性,比如跳转到另一个 Bundle 界面,调用另一个 Bundle 中的接口,或者 Bundle 中的一些操作需要在初始化的过程中完成等。

因此,mPaaS 设计了 metainfo 通用组件注册机制,各个 Bundle 将需要注册的组件在 metainfo.xml 中声明。

框架目前支持以下组件:

  • ActivityApplication(Application)

  • ExternalService(Service)

  • BroadcastReceiver

  • Pipeline

metainfo.xml 格式如下:

<?xml version="1.0" encoding="UTF-8"?>
<metainfo>
    <broadcastReceiver>
        <className>com.mpaas.demo.broadcastreceiver.TestBroadcastReceiver</className>
        <action>com.mpaas.demo.broadcastreceiver.ACTION_TEST</action>
    </broadcastReceiver>
    <application>
        <className>com.mpaas.demo.activityapplication.MicroAppEntry</className>
        <appId>33330007</appId>
    </application>
</metainfo>

Application 组件

ActivityApplication 是 mPaaS 框架设计的组件,起到 Activity 容器的角色。ActivityApplication 组件的主要作用在于管理和组织各个 Activity,专门用于解决跳转到另一个Bundle 界面的问题。因而,调用方只需关心业务方在框架中注册的 ActivityApplication 信息以及约定的参数。

关于此任务

ActivityApplication 的创建、销毁等一系列逻辑完全由 mPaaS 框架来管理。业务方只需要处理其收到的参数并管理自己业务下的 Activity,这样业务方和调用方之间被有效的隔离开来。业务方和调用方只需要协调调用的参数,使得依赖更加轻量。

基于 mPaaS 框架开发的 Android 客户端应用,Activity 须继承自 BaseActivity 或 BaseFragmentActivity,以便能够被 ActivityApplication 类所管理。

说明

您可以下载代码示例,示例中包含跳转到另一个 Bundle 的 Activity。有关下载地址、使用方法及注意事项,查看 获取代码示例

操作步骤

  1. 在工程的主 module 中创建 metainfo.xml 文件,放在如下图位置:

  2. metainfo.xml 中写入如下的配置,其中:

    • className 配置的类名用于提供跳转的类名称和定义各阶段的行为。具体类的定义,参见步骤 3 的代码。框架通过 className 定义的名称加载相应的类,所以该类一定不能被混淆,需要在混淆文件中保留。

    • appId:业务的唯一标识。业务方只需要知道该业务的 appId 就能完成跳转。appId 与 ActivityApplication 的映射由框架层处理。

      <?xml version="1.0" encoding="UTF-8"?>
      <metainfo>
        <application>
            <className>com.mpaas.demo.hotpatch.HotpatchMicroApp</className>
            <appId>33330002</appId>
        </application>
      </metainfo>
  3. 如果 metainfo 通过 className 指定的类只是完成简单的跳转,使用以下代码实现:

     /**
      * 场景一:
      * 若只可能会跳转到某一个Activity界面,那么需要重载getEntryClassName和onRestart,前者返回Activity的classname,后者需要调用getMicroApplicationContext().startActivity(this, getEntryClassName());
      * 场景二:
      * 若需要根据需求跳转到不同的Activity界面那么需要重载onStart和onRestart,根据bundle中的参数跳转到指定的界面
      * Created by mengfei on 2018/7/23.
      */
     public class MicroAppEntry extends ActivityApplication {
    
         @Override
         public String getEntryClassName() {
             //场景一:只可能跳转到某一个 Activity 在此返回 classname 即可
             //场景二:根据参数跳转到某一界面,需要返回 null
             return MainActivity.class.getName();
         }
    
         /**
          * Application被创建时被调用,实现类可以在这里做些初始化的工作
          *
          * @param bundle
          */
         @Override
         protected void onCreate(Bundle bundle) {
             doStartApp(bundle);
         }
    
         /**
          * 启动Application时被调用
          * 如果Application还没有被创建,会先去执行create方法,然后再执行onStart()回调
          */
         @Override
         protected void onStart() {
         }
    
         /**
          * 当Application被销毁时,调用此回调
          *
          * @param bundle
          */
         @Override
         protected void onDestroy(Bundle bundle) {
    
         }
    
         /**
          * 启动Application时,如果Application已经被start过了,则不调用onStart()而是调用onRestart()回调
          *
          * @param bundle
          */
         @Override
         protected void onRestart(Bundle bundle) {
         //针对场景一:需要在此调用getMicroApplicationContext().startActivity(this, getEntryClassName());
             doStartApp(bundle);
         }
    
         /**
          * 当一个新的Application被start时,当前的Application将被暂停,此方法被回调
          */
         @Override
         protected void onStop() {
    
         }
    
         private void doStartApp(Bundle bundle) {
             String dest = bundle.getString("dest");
             if ("main".equals(dest)) {
                 Context ctx = LauncherApplicationAgent.getInstance().getApplicationContext();
                 ctx.startActivity(new Intent(ctx, MainActivity.class));
             } else if ("second".equals(dest)) {
                 Context ctx = LauncherApplicationAgent.getInstance().getApplicationContext();
                 ctx.startActivity(new Intent(ctx, SecondActivity.class));
             }
         }
     }
  4. 作为调用者,您需要通过框架封装的 MicroApplicationContext 中提供的接口进行跳转。curId 参数也可以传 null:

     // 获取 MicroApplicationContext 对象:
     MicroApplicationContext context = MPFramework.getMicroApplicationContext();
     String curId = "";
     ActivityApplication curApp = context.getTopApplication();
      if (null != curApp) {
          curId = curApp.getAppId();
     }
     String appId = "目标ApplicationActivity的id";
     Bundle bundle = new Bundle(); // 附加参数,也可以不传
     context.startApp(curId, appId, bundle);

Service 组件

mPaaS 设计了 Service 组件解决跨 Bundle 调用接口。Service 组件用于将一些逻辑以服务的形式提供出来,供其他模块使用。

关于此任务

Service 组件的特点如下:

  • 没有用户界面的限制。

  • 在设计上,遵循接口与实现分离。

原则上只有接口类对调用者可见,所以接口类应定义在接口 module 中(在生成 Bundle project 时,默认会生成一个接口 module,名字是 api),实现定义在主 module 中。

外部调用都通过 MicroApplicationContextfindServiceByInterface 接口,通过interfaceName 获取相应的服务。对于 Bundle 使用来说,只暴露服务抽象接口类,即 interfaceName 中定义的类,抽象接口类会定义在接口包中。

说明

您可以下载代码示例,示例中包含调用另一个 Bundle 的接口示例。有关下载地址、使用方法及注意事项,查看 获取代码示例

操作步骤

通过以下步骤注册 Service 组件:

  1. 定义 metainfo.xml 位置,如下图所示:

  2. metainfo.xml 中写入如下的配置。框架将 interfaceName 作为 keyclassName 作为 value,记录两者的映射关系。其中,className 为具体接口的实现类, interfaceName 为抽象接口类:

    <metainfo>
     <service>
         <className>com.mpaas.cq.bundleb.MyServiceImpl</className>
         <interfaceName>com.mpaas.cq.bundleb.api.MyService</interfaceName>
         <isLazy>true</isLazy>
     </service>
    </metainfo>
    • 抽象接口类定义如下:

      public abstract class MyService extends ExternalService {
        public abstract String funA();
      }
    • 接口类实现定义如下:

      public class MyServiceImpl extends MyService {
        @Override
        public String funA() {
            return "这是 BundleB 提供的接口 by service";
        }
      
        @Override
        protected void onCreate(Bundle bundle) {
      
        }
      
        @Override
        protected void onDestroy(Bundle bundle) {
      
        }
      }
    • 外部调用方式如下:

      MyService myservice = LauncherApplicationAgent.getInstance().getMicroApplicationContext().findServiceByInterface(MyService.class.getName());
      myservice.funA();

BroadcastReceiver 组件

BroadcastReceiver 是 android.content.BroadcastReceiver 的封装,但区别在于 mPaaS 框架采用了 android.support.v4.content.LocalBroadcastManager 来注册和反注册 BroadcastReciever,因此,这些广播仅用于当前应用程序内部,除此之外,mPaas框架内部内置了一系列的广播事件,供使用者监听。

关于此任务

您可以下载包含该通用组件的代码示例。有关下载地址、使用方法及注意事项,查看 获取代码示例

mPaaS内置广播事件

mPaaS 定义了多种广播事件,主要用于监听当前应用的状态,注册监听与原生开发没有任何区别,但有一点需要特别注意,这些状态只有在主进程才能监听到。示例代码如下:示例代码内置的广播事件如下:

public interface MsgCodeConstants {
    String FRAMEWORK_ACTIVITY_CREATE = "com.alipay.mobile.framework.ACTIVITY_CREATE";
    String FRAMEWORK_ACTIVITY_RESUME = "com.alipay.mobile.framework.ACTIVITY_RESUME";
    String FRAMEWORK_ACTIVITY_PAUSE = "com.alipay.mobile.framework.ACTIVITY_PAUSE";
    // 用户离开的广播,压后台广播
    String FRAMEWORK_ACTIVITY_USERLEAVEHINT = "com.alipay.mobile.framework.USERLEAVEHINT";
    // 所有 Activity 全都 Stop 的广播,可能代表压后台,但目前没有用相同的判断逻辑
    String FRAMEWORK_ACTIVITY_ALL_STOPPED = "com.alipay.mobile.framework.ACTIVITY_ALL_STOPPED";
    String FRAMEWORK_WINDOW_FOCUS_CHANGED = "com.alipay.mobile.framework.WINDOW_FOCUS_CHANGED";
    String FRAMEWORK_ACTIVITY_DESTROY = "com.alipay.mobile.framework.ACTIVITY_DESTROY";
    String FRAMEWORK_ACTIVITY_START = "com.alipay.mobile.framework.ACTIVITY_START";
    String FRAMEWORK_ACTIVITY_DATA = "com.alipay.mobile.framework.ACTIVITY_DATA";
    String FRAMEWORK_APP_DATA = "com.alipay.mobile.framework.APP_DATA";
    String FRAMEWORK_IS_TINY_APP = "com.alipay.mobile.framework.IS_TINY_APP";
    String FRAMEWORK_IS_RN_APP = "com.alipay.mobile.framework.IS_RN_APP";
    // 用户回到前台的广播
    String FRAMEWORK_BROUGHT_TO_FOREGROUND = "com.alipay.mobile.framework.BROUGHT_TO_FOREGROUND";
}

自定义广播事件

  1. 定义 metainfo.xml 位置,如下图所示:

  2. metainfo.xml 中写入如下配置:

     <?xml version="1.0" encoding="UTF-8"?>
     <metainfo>
         <broadcastReceiver>
             <className>com.mpaas.demo.broadcastreceiver.TestBroadcastReceiver</className>
             <action>com.mpaas.demo.broadcastreceiver.ACTION_TEST</action>
         </broadcastReceiver>
     </metainfo>
    • 自定义 Receiver 实现

      public class TestBroadcastReceiver extends BroadcastReceiver {
        private static final String ACTION_TEST = "com.mpaas.demo.broadcastreceiver.ACTION_TEST";
      
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ACTION_TEST.equals(action)) {
                //TODO
            }
        }
      }
    • 发送广播

      LocalBroadcastManager.getInstance(LauncherApplicationAgent.getInstance().getApplicationContext()).sendBroadcast(new Intent("com.mpaas.demo.broadcastreceiver.ACTION_TEST"));

Pipeline 组件

mPaaS 框架有一个比较明显的启动过程,Pipeline 机制允许业务线将自己的运行逻辑封装成 Runnable 放到 Pipeline。框架在适当的阶段启动适当的 Pipeline。

以下为定义的 Pipeline 时机:

  • com.alipay.mobile.framework.INITED: 框架初始化完成。进程在后台启动,框架也会初始化。

  • com.alipay.mobile.client.STARTED: 客户端开始启动。必须等到界面出现,例如,欢迎界面。

  • com.alipay.mobile.TASK_SCHEDULE_SERVICE_IDLE_TASK:优先级最低,当没有其他高优先级的操作时才会得到执行

因为 Pipeline 的调用是由框架触发,使用者只需要在 metainfo 里指定相应的时机。

关于此任务

您可以下载包含该通用组件的代码示例。有关下载地址、使用方法及注意事项,查看 获取代码示例

操作步骤

  1. 定义 metainfo.xml 位置,如下图所示:

  2. metainfo.xml 中写入如下的配置:

     <?xml version="1.0" encoding="UTF-8"?>
     <metainfo>
         <valve>
             <className>com.mpaas.demo.pipeline.TestPipeLine</className>
             <!--pipelineName就是用于指定执行所在的阶段-->
             <pipelineName>com.alipay.mobile.client.STARTED</pipelineName>
             <threadName>com.mpaas.demo.pipeline.TestPipeLine</threadName>
             <!--weight指定了操作的优化级,值越小,代表越会优先得到执行-->
             <weight>10</weight>
         </valve>
     </metainfo>
  3. 实现 Pipeline:

     public class TestPipeLine implements Runnable {
         @Override
         public void run() {
             //....
         }
     }