利用WMRouter 重新架构设计业务模式

753 阅读7分钟

本文是基于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设计与使用文档