此文用于描述组件化框架
基本概念
package com.tencent.qq;
public class QQHelloService implements IHelloService {
public String sayHello(){
print("Hello~");
}
}
服务的导出:服务的实现类被打包存储在组件中,若组件的开发者希望进一步方便其服务的使用,则需要先对服务进行导出。导出过程中需要填写两个字段, 一个是 服务的名称
, 另一个是 服务的实现类
。服务的导出信息存储在组件的描述文件中。
exports-services:
- name: HelloService # 服务的名称
class: com.tencent.qq.QQHelloService # 服务的实现类
服务的使用:服务由组件框架加载并管理,管理服务的具体实现类为ServiceManager。在使用服务时,需要先通过ServiceManager获取服务, 然后再调用服务的具体方法。
IHelloService helloService = g_ServiceManager.getService("HelloService"); // 获取服务
helloService.sayHello(); // 调用服务
组件的描述文件:因为组件是服务的集合,为了清晰的描述出组件中所包含的所有服务。同时描述出组件的基本信息,还需要 组件描述文件
。如下组件的描述文件中包含了组件的基本信息,同时组件的描述文件中还导出了 QQAccount 和 QQLoginService 两个服务。
apiVersion: com.raft.uas/v1alpha1
platform: Android
kind: Component
name: tencent/QQAccount # 非基础组件, 使用二级名称
version: 1.2.3
exports:
- name: QQAccount
class: com.tencent.qq.chatRoom.QQAccount
type: service
- name: QQLoginService
class: com.qq.account.QQLoginService
type: service #默认类型就是service, 后续会考虑 resource, config 等类型。
组件目录结构:组件的目录结构如下图所示, 包含组件的描述文件, 和组件的源代码(或二进制代码)。组件描述以文件形式伴随组件文件存在,但其并不强行入侵到组件的jar、dll包中(非入侵)。
└── QQAccount
├── raft.yml # 组件描述文件
└── src
├── QQAccount.java # 导出的服务
├── QQLoginService.java # 导出的服务
├── QQUserDao.java
└── QQUserItem.java
Application (应用程序)
应用程序是组件的集合,如下是一个应用程序的目录结构。 其中包含一个components目录。
同样每个应用也伴随有一个 raft.yml 文件,用于描述应用的基本信息和应用程序的版本。
此外, 在目录结构中也可以看出。 一个遵从raft标准的应用程序在components目录中,会存在一个名称为RaftFramework的特殊组件,即组件框架。
apiVersion: com.raft.uas/v1alpha1
platform: Android
kind: Application # Application 说明这是一个应用。
services:
- name: ChatRoomService
class: com.tencent.wx.ChatRoomService
- name: QQAccount
class: com.tencent.qq.chatRoom.QQAccount
- name: QQLoginService
class: com.qq.account.QQLoginService
components:
- name: tencent/WXChatRoom/1.0.1 # 使用WXChatRoom的1.0.1版本
- name: tencent/QQAccount/latest # 最新版本
- name: QQLog # 不加版本号默认为latest版本
public static void main(String []args){
// 读取描述文件,并加载描述文件中所描述的组件。
ServiceManager g_ServiceManager = new ServiceManager("raft.yml");
// 查询服务
// - name: QQAccount
// class: com.tencent.qq.chatRoom.QQAccount
QQAccount qqAccount = (QQAccount)g_ServiceManager.getService("QQAccount");
String userName = qqAccount.getUserName();
System.out.println("UserName:" + userName);
// 查询服务,并向上转型成对应的接口(IoC)
// - name: QQLoginService
// class: com.qq.account.QQLoginService
ILog log = (ILog)g_ServiceManager.getService("QQLogService");
log.e("Main", "error: this is a test information");
// 查询以类厂方式导出的组件
// - name: ChatRoomService
// factory-class: com.qq.WXChatRoomFactory # 制定创建服务的工厂类
// factory-method: getInstance # 这里使用工厂方法创建此类
WXChatRoomService chatroom = (WXChatRoomService)g_ServiceManager.getService("ChatRoomService");
chatroom.doSomeThing();
}
Application Framework (应用程序框架)
Service Interface (关键接口)
UIInterface
ServiceNameAware
InitializingBean
DisposableBean
BeanDefinitionRegistryPostProcessor
FactoryBean
BeanPostProcessor
class ApplicationServiceManager implements ServiceManager{
public ApplicationServiceManager(String appSpecFilePath) {
// STEP1: 构造一个 ServiceFactory
ServiceFactory serviceFactory = new ServiceFactory();
// STEP2: 加载app的描述文件
loadAppSpecFile(appSpecFilePath);
// STEP3: 初始化Services
initServices();
}
@override
public void startServices() {
Map lifecycleBeans = getLifecycleBeans();
for (Iterator it = new LinkedHashSet(lifecycleBeans.keySet()).iterator(); it.hasNext();) {
String beanName = (String) it.next();
doStart(lifecycleBeans, beanName);
}
publishEvent(new ContextStartedEvent(this));
}
private void doStart(Map lifecycleBeans, String beanName) {
Lifecycle bean = (Lifecycle) lifecycleBeans.get(beanName);
if (bean != null) {
String[] dependenciesForBean = getBeanFactory().getDependenciesForBean(beanName); //找到依赖的类
for (int i = 0; i < dependenciesForBean.length; i++) {
doStart(lifecycleBeans, dependenciesForBean[i]);//递归调用找到依赖的类
}
if (!bean.isRunning()) {
bean.start();
}
lifecycleBeans.remove(beanName);
}
}
}
Specification File (描述文件)
raft中通过 描述文件
准确的描述一个代码包的平台、版本、依赖的组件、导出服务等信息。描述文件采用YML格式, 并
关键字段
如下是一个组件的 描述文件
:
apiVersion: com.raft.uas/v1alpha1
platform: Android
kind: Component # 如果是一个组件则这里为Component
name: QQ/Account
version: 1.0.0
description: 腾讯QQ账号的组件。提供账号信息的查询、登录等服务。
services:
- name: AccountService
class: com.qq.account.AccountService
- name: LoginService
class: com.qq.account.LoginService
- name: OrderService
factory-class: com.qq.OrderServiceFactory # 制定创建服务的工厂类
factory-method: getInstance # 这里使用工厂方法创建此类
- name: ChatRoomService
class: com.qq.account.AccountService
config:
- name : ChatRoomServer
description: 聊天服务器的地址,可以是URL地址或者是IP。
type : string
default: https://qq.chatroom.com
- name : ChatRoomAppId
description: 聊天室与服务器建立连接所用的key。在聊天组件官网申请。
type : string
default: JJ_mas891jvZ
- name : ChatRoomTheme
description: 聊天室UI的皮肤。
type: string
default: DarkTheme
- name: SayHelloService
class: com.qq.account.sayHelloImple
scope: singleton
constructor-arg: # 这里使用构造方法传参
- name: who
description: mongo username
type: string
- name: times
defaultValue: 100
type: integer
components:
- name: tencent/WXChatRoom/1.0.1 # 依赖WXChatRoom的1.0.1版本
- name: tencent/QQAccount/latest # 依赖最新版本
- name: QQLog # 不加版本号默认为latest版本
- name: MyTestComponent # 也可以依赖其他平台上的组件
git: http://git.code.oa.com/raft-templates/generator-raft-android.git
组件化框架
组件的扫描与注册
配置加载
启动任务
在应用启动的时候,我们通常会有很多工作需要做,为了提高启动速度,我们会尽可能让这些工作并发进行。但这些工作之间可能存在前后依赖的关系,所以我们又需要想办法保证他们执行顺序的正确性。因此启动过程的设计应该着重考虑如下三个方面:
-
依赖关系:配置启动任务的先后依赖关系。即一个任务的前置依赖任务,和后续任务,前置依赖和后续任务可以是一个或多个。
-
并行能力:为了提升应用程序的启动速度,应该尽可能多的利用多核(多线程)能力加快启动速度。因此框架应该通过依赖关系和处理器核心数目,安排每个任务所处的线程。
-
特定线程:对于线程有特殊要求的任务,如:某些启动任务必须在主线程执行。框架应该有能力指定启动任务所在的的线程。
针对以上需求, raft框架为启动框架抽象出StartupTask类。其主要声明如下:
public abstract class Task{
// 任务名称
public String mTaskName;
// 前置任务和后继任务
private List mSuccessorList = new ArrayList();
protected Set mPredecessorSet = new HashSet();
// 是否在主线程执行
private boolean mInMainThread = false;
// 任务状态
public static final int STATE_IDLE = 0;
public static final int STATE_RUNNING = 1;
public static final int STATE_FINISHED = 2;
public static final int STATE_WAIT = 3;
private volatile int mCurrentState = STATE_IDLE;
public Task(String taskName) {
mTaskName= taskName;
}
// 抽象方法 用来填写task的具体实现。
public abstract void run();
}
public class TaskGraph extends Task{
// 构造器,以链式表达构造出TaskGraph对象
public static class Builder {
public Builder add(Task task){...}
public Builder after(Task task){...}
}
// 内置两个特殊节点,用保证单入单出。
public Task mStartTask, mFinishTask;
public void setTaskStateListener(TaskStateListenerlistener);
public void start();
public void stop();
}
interface TaskStateListener{
// 所有Task执行开始、结束时分别调用。
onTasksStart();
onTasksFinished();
// 某一个task开始、结束时分别调用。
onTaskStart(Task task);
onTaskFinished(Task task);
}
在一个应用程序中, 如果希望执行特定的操作,则需要进行如下操作:
-
先声明一个Task,在task中加入特定的执行代码。
-
将这个Task的实例化,并加入到TaskGraph中,加入时指定其依赖关系。
-
最后执行TaskGraph。
以下是这三个步骤的实例代码:
public class TaskA extends Task{
public TaskA() {
super("TaskA");
}
@Override
public void run() {
Log.d(TAG, "run TaskA");
}
}
Task a = new TaskA();
...
Task e = new TaskE();
e.mInMainThread = true; // 强制设定e在主线程中运行。
TaskListener listener = new TaskListener(){...};
TaskGraph.Builder builder = new TaskGraph.Builder();
builder.add(a); // 没有指定after则在mStartTask后执行
builder.add(b).after(a);
builder.add(c).after(a);
builder.add(d).after(b, c);
builder.add(e).after(a); // 因为e是最后一个节点,因此e之后为FinishTask。
TaskGraph group = builder.create();
group.setListener(listener)
group.start();
下图即上面代码的图形化表达。
由于参与到一个app启动过程中的任务会非常多,且开发者难以记住每个服务的名称。此时如果仅仅通过任务对象来简历依赖关系会非常麻烦。为此, raft框架提供了几个标准的初始化阶段(stage),用于简化依赖关系的配置。开发者只需要将task加入到对应的阶段中, 即可完成启动任务的依赖关系。这几个阶段分别是:
-
FrameworkInit:初始化Raft框架核心功能所的阶段。如Log,数据上报等。 一般Task不考虑放入此阶段。
-
BasicLibraryInit:初始化基础库的阶段,DB, Network等基础功能的初始化过程放入该阶段。
-
FunctionalComponentInit:初始化功能组件的阶段, 如视频播放出,图片查看器等功能性组件的处处华过程放入此阶段。
-
BusiniessComponentInit:业务组件初始化阶段, 如个人页,信息流等组件的初始化过程放入此阶段。
-
ReadyToStart:上述所有阶段执行完后的最后一个阶段。
// 框架会先初始化好这5个阶段。
TaskGraph.Builder builder = new TaskGraph.Builder();
builder.add(new Task("__FrameworkInit__"))
builder.add(new Task("__BasicLibraryInit__")).after("__FrameworkInit__");;
builder.add(new Task("__FunctionalComponentInit__")).after("__BasicLibraryInit__");;
builder.add(new Task("__BusiniessComponentInit__")).after("__FunctionalComponentInit__");;
builder.add(new Task("__ReadyToStart__")).after("__BusiniessComponentInit__");;
...
// 之后初始化任务可以直接用阶段的名称建立依赖关系。
builder.add(a).after("__FrameworkInit__");
builder.add(b).after("__BasicLibraryInit__");
builder.add(c).after("__BasicLibraryInit__");
builder.add(d).after("__FunctionalComponentInit__");
builder.add(e).after("__ReadyToStart__"); // 因为e是最后一个节点,因此e之后为FinishTask。
TaskGraph group = builder.create();
group.setListener(listener)
group.start();
框架的主要结构
Sample工程
Q&A
1、为什么一个组件需要暴露出多个服务?