本文是基於WMRouter,重新設計了我們公司雲教室APP業務模式
一、頁面路由架構
二、頁面路由規則(Page Router)
1、Activity頁面定義規則
1)頁面介面定義(頁面路由表)
在 Base Module (參考:Demo工程目錄結構)中 定義 頁面Path 和 頁面引數。
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)
1、服務定義規則
在 Base Module (參考:雲教室工程目錄結構)中 定義 服務介面類 和 服務路由表(服務key + 服務引數)。
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設計與使用文件