利用WMRouter 重新架構設計業務模式

代運超521發表於2020-04-03

本文是基於WMRouter,重新設計了我們公司雲教室APP業務模式

一、頁面路由架構

利用WMRouter 重新架構設計業務模式



二、頁面路由規則(Page Router)

利用WMRouter 重新架構設計業務模式

1、Activity頁面定義規則

1)頁面介面定義(頁面路由表)

在 Base Module (參考:Demo工程目錄結構)中 定義 頁面Path 和 頁面引數。

利用WMRouter 重新架構設計業務模式

利用WMRouter 重新架構設計業務模式

a)頁面路由表命名規則

[modle] + PageRouter

如支付元件:PayPageRouter.java

b)path宣告

  • page path 字串規則:/[model]/[page]

i. 必須以 "/" 開頭
ii. 必須以 "第一段必須是元件名",如支付元件: "/pay/***"

  • 引數註釋: 每個 path 必須詳細說明需要的引數, 及其型別。

c)引數宣告

  • 在內部類 XXXPageRouter.ParamsKey 中定義Page引數
  • 命名規則: [page]_[引數KEY]_[引數型別] = "key",如:public static final String PAGE1_TIME_INT = "time";


頁面路由表模板檔案:

package com.yimi.router.lib1base;
/**
* Page Path and Params key fo Lib1
* <p>
* <strong> 注意 </strong>
* <pre>
* 1、page path 字串規則:
* 1) 必須以 "/" 開頭
* 2)必須以 "第一段必須是元件名", 如支付元件: "/pay/***"
*
* 2、引數註釋: 每個 path 必須詳細說明需要的引數, 及其型別。
* </pre>
*/
public class Lib1PageRouter {
// ------- pages --------
/**
* 開啟 /lib1/page1
* <pre>
* 引數:
* 1. int {@link ParamsKey#PAGE1_PARAMS1_INT}
* 2. String {@link ParamsKey#PAGE1_PARAMS2_STRING}
*/
public static final String PAGE1 = "/lib1/page1";
/**
* 開啟 /lib1/page1
* <pre>
* 引數:
* 1. String {@link ParamsKey#PAGE2_PARAMS1_STRING}
*/
public static final String PAGE2 = "/lib1/page2";
// ------ page params keys -------
/**
* page 跳轉需要的引數Key
* <per>
* 命名規則: [page]_[引數KEY]_[引數型別] = "key"
* 如:public static final String PAGE1_TIME_INT = "time";
*/
public static class ParamsKey {
// lib1/page1
public static final String PAGE1_PARAMS1_INT = "params1";
public static final String PAGE1_PARAMS2_STRING = "params2";
// lib1/page2
public static final String PAGE2_PARAMS1_STRING = "params1";
}
}

2)頁面註解

為模組中,需要對外暴露的Activity頁面新增 註解:

@RouterUri(path = Lib1PageRouter.PAGE1)複製程式碼
public class Page1Activity extends AppCompatActivity {複製程式碼
}複製程式碼

2、Activity跳轉Activity

使用 Router.startUri(context, path) new DefaultUriRequest(context, path).start()

1)最基本的方式

Router.startUri(context, Constant.PTAH_APP_PAGE1);複製程式碼

2)複雜跳轉(帶資料、動畫、回撥等)

new DefaultUriRequest(context, Lib1PageRouter.PAGE1)
.putExtra(Lib1PageRouter.ParamsKey.PAGE1_PARAMS1_INT, 1)
.putExtra(Lib1PageRouter.ParamsKey.PAGE1_PARAMS2_STRING, "str")
.overridePendingTransition(R.anim.enter_activity, R.anim.exit_activity)
.onComplete(new OnCompleteListener() {
@Override
public void onSuccess(@NonNull UriRequest request) {
Toast.makeText(context, "跳轉成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(@NonNull UriRequest request, int resultCode) {
}
})
.start();

3)startActivityForResult

使用 new DefaultUriRequest(context, path).activityRequestCode(code).start()

/**
* 測試 startActivityForResult
*/
public void startForResult(View view) {
new DefaultUriRequest(context, Lib1PageRouter.PAGE2)
.activityRequestCode(1234)
.putExtra(Lib2PageRouter.ParamsKey.FRAGMENT_ACTIVITY1_PARAMS2_STRING, "str")
.start();
}


3、Fragment跳轉Activity

使用 new FragmentUriRequest(fragment, path).start()

/**
* 測試 Fragment 跳轉 外部Activity
*/
private void jumpFromFragment2Activity() {
new FragmentUriRequest(FragmentPage1.this, Lib1PageRouter.PAGE1)
.putExtra(Lib1PageRouter.ParamsKey.PAGE1_PARAMS1_INT, 1)
.putExtra(Lib1PageRouter.ParamsKey.PAGE1_PARAMS2_STRING, "str from lib2 fragment")
.overridePendingTransition(R.anim.enter_activity, R.anim.exit_activity)
.onComplete(new OnCompleteListener() {
@Override
public void onSuccess(@NonNull UriRequest request) {
Toast.makeText(FragmentPage1.this.getActivity(), "跳轉成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(@NonNull UriRequest request, int resultCode) {
}
})
.start();
}


4、Activity載入Fragment(模組內呼叫,可選)

1)Fragment頁面註解

與 Activity頁面註解使用 @RouterUri 不同的是,Fragment頁面註解需要使用:@RouterPage

如:

@RouterPage(path = Constant.FRAGMENT1)public class FragmentPage1 extends Fragment {複製程式碼

}

2)Activity載入Fragment頁面

使用 new FragmentTransactionUriRequest(activity, PageAnnotationHandler.SCHEME_HOST + path).add(R.id.fragment_container).start

/**
* 測試 Activity 載入 Fragment
*/
private void launchFragment(int msg1, String msg2) {
// 傳統方案
/*
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container,
new FragmentPage1()).commitAllowingStateLoss();
*/
// Router方案
new FragmentTransactionUriRequest(this, PageAnnotationHandler.SCHEME_HOST + Constant.FRAGMENT1)
.add(R.id.fragment_container)
.allowingStateLoss()
.putExtra("params1", msg1)
.putExtra("params2", msg2)
.start();
}


5、Fragment切換Fragment(模組內呼叫,可選)

1)Fragment頁面註解

@RouterPage(path = Constant.FRAGMENT2)public class FragmentPage2 extends Fragment {複製程式碼

}

2)Fragment切換Fragment頁面

使用 new FragmentTransactionUriRequest(FragmentPage1.this.getActivity(), PageAnnotationHandler.SCHEME_HOST + path).replace(R.id.fragment_container).start()

/**
* 測試 Fragment1 切換 Fragment2
*/
private void jumpFromFragment2Fragment() {
new FragmentTransactionUriRequest(FragmentPage1.this.getActivity(), PageAnnotationHandler.SCHEME_HOST + Constant.FRAGMENT2)
.replace(R.id.fragment_container)
.putExtra("params1", 1)
.putExtra("params2", "str")
.onComplete(new OnCompleteListener() {
@Override
public void onSuccess(@NonNull UriRequest request) {
Toast.makeText(FragmentPage1.this.getActivity(), "跳轉成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(@NonNull UriRequest request, int resultCode) {
}
})
.start();
}


三、元件通訊架構


四、元件通訊方案(Service Router)

利用WMRouter 重新架構設計業務模式

1、服務定義規則

在 Base Module (參考:雲教室工程目錄結構)中 定義 服務介面類 和 服務路由表(服務key + 服務引數)。

利用WMRouter 重新架構設計業務模式


1)服務介面類

定義需要對外暴露的 Sevice interface 檔案,命名必須以”I" 開頭,如:IAudioService.java

package com.yimi.router.lib1base;
public interface IAudioService {
void startAudio(String audioPath);
void stopAudio(String audioPath);
}

2)服務介面定義(服務路由表)

a)服務路由表命名規則

[modle] + ServiceRouter

如支付元件:PayServiceRouter.java

b)服務Key的定義

(i)每種Service至少註冊一個預設實現,如:public static final @Default String VIDEO_DEFAULT = "default";
(ii)引數註釋: 每個 path 必須詳細說明需要的引數,可先以下三種:

    • 無引數

--> 可使用 Router.getService(IService.class, "key1"); 獲取Service

    • 一個Context引數

--> 可使用 Router.getService(IService.class, "key1", context); 獲取Service

    • 其他不定引數,必須在此指定 params (一個{@link IFactory}的實現), 詳見Demo

--> 可使用 Router.getService(IService.class, "key1", params); 獲取Service

c)服務引數的定義

對於自定義服務引數,需要在 ”服務路由表中“ 定義好引數。該引數其實是一個實現了 IFactory 介面的 服務工廠類。

public static final class XXServiceParams implements IFactory {}複製程式碼


服務路由表模板:

package com.yimi.router.lib1base;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.StringDef;
import com.sankuai.waimai.router.service.IFactory;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Service 路由介面資訊
* <p>
* 注意:
* <pre>
* 1、每種Service至少註冊一個預設實現,如:public static final @Default String VIDEO_DEFAULT = DEFAULT;
* 2、引數註釋: 每個 path 必須詳細說明需要的引數,可先以下三種:
* 1) 無引數
* --> 可使用 Router.getService(IService.class, "key1"); 獲取Service
* 2) 一個Context引數
* --> 可使用 Router.getService(IService.class, "key1", context); 獲取Service
* 3) 其他不定引數,必須在此指定 params (一個{@link IFactory}的實現), 詳見Demo
* --> 可使用 Router.getService(IService.class, "key1", params); 獲取Service
* </pre>
*/
public class Lib1ServiceRouter {
/**
* Service實現的預設版本
* <p>
* 該預設實現需要使用該變數和@{@link RouterService}註解來標識,
* 如:@RouterService(interfaces = IService.class, key = Lib2ServiceKey.DEFAULT)
*/
public static final String DEFAULT = "default";
@Retention(RetentionPolicy.SOURCE)
@StringDef({DEFAULT})
public @interface Default {
}
// -------- Service Key ---------
// video service 只有一個預設實現版本
/**
* Video Service 的預設實現
* <p>
* 引數: 無引數
*/
@Default
public static final String VIDEO_DEFAULT = DEFAULT;
// audio service 有多個實現版本,至少包含一個預設實現版本
/**
* Audio Service 的預設實現
* <p>
* 引數: 一個Context
*/
@Default
public static final String AUDIO_DEFAULT = DEFAULT;
/**
* Audio Service 的 type2 實現
* <p>
* 引數: {@link AudioParams}
*/
public static final String AUDIO_TYPE2 = "audio_type2";
// -------- Service 自定義Params ---------
/**
* params of {@link #AUDIO_TYPE2}
*/
public static final class AudioParams implements IFactory {
private Context context;
private boolean debug;
public AudioParams(Context context, boolean debug) {
this.context = context;
this.debug = debug;
}
@NonNull
@Override
public <T> T create(@NonNull Class<T> clazz) throws Exception {
return clazz.getConstructor(Context.class, boolean.class).newInstance(context, debug);
}
}
}

2)服務註解

使用 @RouterService

package com.yimi.router.lib1.service;
import android.util.Log;
import com.sankuai.waimai.router.annotation.RouterService;
import com.yimi.router.lib1base.IVideoService;
import com.yimi.router.lib1base.Lib1ServiceRouter;
/**
* VideoService 預設版本 - 無參
* <p>
* router service key: {@link Lib1ServiceRouter#VIDEO_DEFAULT}
* <pre>
* 注意:
* 1、@RouterService 中必須指定 interfaces、key
* 2、預設實現版本的 key 必須都等於 {@link Lib1ServiceRouter#DEFAULT}
* 3、單例模式 需指定: singleton = true. (建議同時使用 單例模式 + @RouterProvider註解)
*/
@RouterService(interfaces = IVideoService.class, key = Lib1ServiceRouter.VIDEO_DEFAULT)
public class VideoService implements IVideoService {
private static final String TAG = "VideoService";
public VideoService() {
}
@Override
public void startVideo(String videoPath) {
Log.i(TAG, "startVideo: ");
}
@Override
public void stopVideo(String videoPath) {
Log.i(TAG, "stopVideo: ");
}
}


2、單例服務定義

一般路由註解的服務預設是”非單例“的,也就是,每次呼叫 Router.getService() 獲取的Service例項是不同的物件。反之,則是 ”單例“ 服務。

對於單例服務的定義,需要注意下面三點:

  • 在 RouterService 註解中指定 singleton 屬性為 true。

如: @RouterService(interfaces = ISocketService.class, key = ”default“, singleton = true)

  • Service實現本身也應該同時使用”單例模式“。
  • 在Service單例模式的 getInstance() 方法上 新增 @RouterProvider註解。

@RouterProvider 註解僅能新增在 無參的靜態方法上,該註解會使得 Router 在例項化無參服務時,優先呼叫該註解標記的方法。

package com.yimi.router.lib2.service;
import android.util.Log;
import com.sankuai.waimai.router.annotation.RouterProvider;
import com.sankuai.waimai.router.annotation.RouterService;
import com.yimi.router.lib2base.ISocketService;
import com.yimi.router.lib2base.Lib2ServiceRouter;
/**
* SocketService 預設版本 - 無參、單例
* <p>
* router service key: {@link Lib2ServiceRouter#SOCKET_DEFAULT}
* <pre>
* 注意:
* 1、@RouterService 中必須指定 interfaces、key
* 2、預設實現版本的 key 必須都等於 {@link Lib2ServiceRouter#DEFAULT}
* 3、單例模式 需指定: singleton = true. (建議同時使用 單例模式 + @RouterProvider註解)
*/
@RouterService(interfaces = ISocketService.class, key = Lib2ServiceRouter.SOCKET_DEFAULT, singleton = true)
public class SocketService implements ISocketService {
private static final String TAG = "SocketService";
// ----- 單例模式 + @RouterProvider註解 ----->>
private static volatile SocketService sInstance;
private SocketService() {
// init
}
@RouterProvider
public static SocketService getInstance() {
if (sInstance == null) {
synchronized (SocketService.class) {
if (sInstance == null) {
sInstance = new SocketService();
}
}
}
return sInstance;
}
// <<----- 單例模式 + @RouterProvider註解 -----
@Override
public boolean start() {
Log.i(TAG, "start: ");
return false;
}
@Override
public void send(String msg) {
Log.i(TAG, "send: ");
}
@Override
public boolean stop() {
Log.i(TAG, "stop: ");
return false;
}
}


3、服務呼叫

使用 Router.getService()

下面分表展示了 無引數的Service引數為一個Context的Service自定義引數的Service單例 Service 的呼叫方法:

// ------- Service Router Test --------
/**
* 測試 呼叫 無引數的Service (in lib1)
* <p>
* 非單例,每次生成不同的物件。
*/
public void callNoParamsService(View view) {
IVideoService video = Router.getService(IVideoService.class, Lib1ServiceRouter.VIDEO_DEFAULT);
video.startVideo("");
updateServiceInfo(video);
}
/**
* 測試 呼叫 引數為一個Context的Service(in lib1)
*/
public void callAContextParamsService(View view) {
IAudioService audio = Router.getService(IAudioService.class, Lib1ServiceRouter.AUDIO_DEFAULT, context);
audio.startAudio("");
updateServiceInfo(audio);
}
/**
* 測試 呼叫 自定義引數的Service(in lib1)
*/
public void callCustomParamsService(View view) {
Lib1ServiceRouter.AudioParams params = new Lib1ServiceRouter.AudioParams(context, true);
IAudioService audio = Router.getService(IAudioService.class, Lib1ServiceRouter.AUDIO_TYPE2, params);
audio.startAudio("");
updateServiceInfo(audio);
}
/**
* 測試 呼叫 無引數的 單例 Service(in lib2)
* <p>
* 單例,每次生成同一個物件。
*/
public void callSingletonParamsService(View view) {
ISocketService socket = Router.getService(ISocketService.class, Lib2ServiceRouter.SOCKET_DEFAULT);
socket.send("");
updateServiceInfo(socket);
}

五、WMRouter 接入配置

1、配置 gradle 外掛

1)在專案根 build.gradle 中新增:router plugin 依賴

buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
// 新增WMRouter外掛
classpath "com.sankuai.waimai.router:plugin:1.2.0"
}
}

2)在APP build.gradle 中 應用 router plugin

apply plugin: 'com.android.application'// 應用WMRouter外掛apply plugin: 'WMRouter'複製程式碼

2、配置 WMRouter 依賴

在使用Router的 lib、app 的 build.gradle 中新增 WMRouter 依賴

// routerimplementation 'com.sankuai.waimai.router:router:1.2.0'annotationProcessor 'com.sankuai.waimai.router:compiler:1.2.0'複製程式碼

3、混淆配置

# WMRouter

# 使用了RouterService註解的實現類,需要避免Proguard把構造方法、方法等成員移除(shrink)或混淆(obfuscate),導致無法反射呼叫。實現類的類名可以混淆。-keepclassmembers @com.sankuai.waimai.router.annotation.RouterService class * { *; }複製程式碼

六、Demo 與 參考

Router Demo: android-router

更多 WMRouter 使用方法,參考:WMRouter設計與使用文件






相關文章