全部產品
Search
文件中心

Mobile Platform as a Service:註冊萬用群組件

更新時間:Jul 13, 2024

模組化是 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 類所管理。

操作步驟

  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 中定義的類,抽象介面類會定義在介面包中。

操作步驟

通過以下步驟註冊 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() {
             //....
         }
     }