动态页面生成
编写页面的dart代码如下:
scala
import 'package:fair/fair.dart';
import 'package:fairdemo/plugins/screen_util.dart';
import 'package:flutter/material.dart';
@FairPatch()
class Test2 extends StatefulWidget {
@override
State<Test2> createState() => _Test2State();
}
class _Test2State extends State<Test2> {
String getHeight(double height) {
return '${height + 100}';
}
@override
Widget build(BuildContext context) {
return Text(getHeight(100));
}
}
通过fair compiler编译生成逻辑js文件和json文件: 页面json文件:
json
{
"className": "Text",
"pa": [
"%(getHeight(100))"
],
"methodMap": {},
"digest": "fc6dd47c7b54434b70b954c981085540"
}
逻辑js文件:
javascript
GLOBAL['#FairKey#'] = (function(__initProps__) {
const __global__ = this;
return runCallback(function(__mod__) {
with(__mod__.imports) {
function _Test2State() {
const inner = _Test2State.__inner__;
if (this == __global__) {
return new _Test2State({
__args__: arguments
});
} else {
const args = arguments.length > 0 ? arguments[0].__args__ || arguments : [];
inner.apply(this, args);
_Test2State.prototype.ctor.apply(this, args);
return this;
}
}
_Test2State.__inner__ = function inner() {};
_Test2State.prototype = {
getHeight: function getHeight(height) {
const __thiz__ = this;
const __arg_ctx__ = {
height,
};
with(__thiz__) {
with(__arg_ctx__) {
return `${height + 100}`;
}
}
},
};
_Test2State.prototype.ctor = function() {};;
return _Test2State();
}
}, []);
})(convertObjectLiteralToSetOrMap(JSON.parse('#FairProps#')));
动态页面还原成dart页面渲染过程
通过FairWidget加载:
less
FairWidget(
name: "test1",
path: 'assets/fair/lib_fair_test_test2.fair.json',
)
解析第一步: 通过toWidget方法将json文件还原成widget
ini
var widget = _convert(context, layout!, methodMap, data: data);
这一步layout数据就是json格式页面数据
通过遍历map
json
{
"className": "Text",
"pa": [
"%(getHeight(100))"
],
"methodMap": {},
"digest": "fc6dd47c7b54434b70b954c981085540"
}
tag值是className
ini
var name = map[tag];
1、先在module中匹配
ini
var module = bound?.modules?.moduleOf(name)?.call();
这个module的定义可以在FairApp初始化中配置
less
FairApp(
generated: AppGeneratedModule(),
modules: {},
delegate: {
},
child: MyApp(),
);
2、在方法和变量中匹配
ini
mapper = bound?.functionOf(name) ?? bound?.valueOf(name);
这个bound通过FairApp获取,先获取FairApp,
ini
var app = FairApp.of(context);
var bound = app?.bindData[page];
FairApp.of(context)这个方法是从 context 中 findAncestorWidgetOfExactType通过widget 树结构查找。
javascript
static FairApp? of(BuildContext? context, {bool rebuild = false}) {
return rebuild ? context?.dependOnInheritedWidgetOfExactType<FairApp>() : context?.findAncestorWidgetOfExactType<FairApp>();
}
我们在main中通过FairApp在最外层包裹页面。 所以这里对应app对象就是从main中初始化的FairApp
less
FairApp(
generated: AppGeneratedModule(),
modules: {},
delegate: {
},
child: MyApp(),
);
FairApp继承自 AppState
scala
class FairApp extends InheritedWidget with AppState
bindData是AppState定义的属性,通过其register方法绑定
scss
Future<dynamic> register(FairState state)
注册页面 的
less
bindData.putIfAbsent(
state.state2key,
() => BindingData(
modules,
functions: delegate.bindFunction(),
values: delegate.bindValue(),
),
);
这里key名就是 页面名字,即对应
less
FairWidget(
name: "test1",
path: 'assets/fair/lib_fair_test_test2.fair.json',
)
这个name 进行计算得到:
ini
state2key = GlobalState.id(widget.name);
通过_counter全局的变量标识,避免同一个可能会使用多次而导致数据混用问题:
javascript
static String id(String? prefix) {
return '$prefix#${GlobalState._counter++}';
}
因此回到上面跟踪的过程:
ini
mapper = bound?.functionOf(name) ?? bound?.valueOf(name);
这个方法就是在 delegate.bindFunction(), 和delegate.bindValue(),方法在FairApp
创建时候通过传入的delegate实现的:
less
FairApp(
generated: AppGeneratedModule(),
modules: {},
delegate: {
///定义方法和变量映射表
},
child: MyApp(),
);
3,在mapper = proxyMirror?.componentOf(name);
中查找
mapper = proxyMirror?.componentOf(name); ProxyMirror 这个就是一个大map 叫做_provider对象管理,存放 字符串到dart方法的映射关系。
scala
class ProxyMirror with P {
final _provider = BindingProvider();
}
分为3大部分:
首先是fair_version下面的,这是flutter framework 系统widget和方法的对应关系
BindingProvider 混入$BindingImpl类
scala
class BindingProvider with $BindingImpl
$BindingImpl定义如下
javascript
import '\$\$c.dart' as $0;
import '\$\$w.dart' as $1;
import '\$\$p.dart' as $2;
import '\$\$m.dart' as $3;
import '\$\$r.dart' as $4;
import '\$\$a.dart' as $5;
mixin $BindingImpl {
final provider = [
$0.p,
$1.p,
$2.p,
$3.p,
$4.p,
$5.p,
];}
一些特殊映射表:
ini
specialBinding =
SplayTreeMap.from({
...common.provider(),
...geometry.provider(),
...flow.provider(),
});
对应的是geometry.dart common.dart flow.dart 这3个文件中定义的映射关系,比如FairWidget Color等这些对象的映射对照表:
css
{
'FairWidget': (props) => FairWidget(
name: props['name'],
path: props['path'],
data: props['data'],
),
'Color': (props) {
var color = pa0(props);
return color is String ? FairUtils.fromHex(color) : Color(color);
},
最后一部分就是GeneratedModule通过@FairBindging注解生成的第三方包中的方法和widget
ini
void addGeneratedBinding(GeneratedModule? generated) {
if (generated == null) {
return;
}
_generatedMapping.addAll(generated.mapping());
_provider.binding.addAll(generated.components());
}
通过_proxy.addGeneratedBinding(generated);
方法添加。
GeneratedModule
less
FairApp(
generated: AppGeneratedModule(),
modules: {},
delegate: {
},
child: MyApp(),
)
AppGeneratedModule是一个通过@FairBinding通过fair compiler工具自动化生成的代码
scala
class AppGeneratedModule extends GeneratedModule
GeneratedModule 类对应的就是需要提供方法:
csharp
abstract class GeneratedModule {
Map<String, dynamic> components();
Map<String, bool> mapping();
}
也是一个map
综合上面3个大map
就完成了 proxyMirror?.componentOf(name);这个组件的查找的逻辑。
在大map中找到匹配关系
调用return block(map, methodMap, context, domain, mapper, name, isWidget);
方法。
这个mapper对象则为fun ,就是 json 页面中name为key从大map找到的对应的实现,是一个方法或者是一个widget构造对象,这就完成了从json string name到widget的映射查找关系。
kotlin
dynamic block(
Map map,
Map? methodMap,
BuildContext ctx,
Domain? domain,
dynamic fun,
String name,
bool widget, {
bool forceApply = false,
})
通过named方法递归遍历json结构 并且返回当前层的context即dart三棵树的Element数BuildContext对象,通过positioned方法提取参数
ini
var na = named(name, map['na'], methodMap, ctx, domain);
var pa = positioned(map['pa'], methodMap, ctx, domain);
看下之前转化的json 这个是一层,
json
{
"className": "Text",
"pa": [
"%(getHeight(100))"
],
"methodMap": {},
"digest": "fc6dd47c7b54434b70b954c981085540"
}
这个是多层widget嵌套样子:
css
{
"className": "Scaffold",
"na": {
"appBar": {
"className": "AppBar",
"na": {
"title": {
"className": "Text",
"pa": [
"测试2131"
],
"na": {
"style": {
"className": "TextStyle",
"na": {
"color": "#(Colors.black)",
"fontSize": 20
}
}
}
}
}
}}
named方法递归遍历实现如下:通过 json中na往下遍历
dart
W<Map<String, dynamic>> named(
String tag,
dynamic naMap,
Map? methodMap,
BuildContext context,
Domain? domain,
) {
var na = <String, dynamic>{};
var needBinding = false;
if (naMap is Map) {
naMap.entries.forEach((e) {
if (e.value is Map) {
na[e.key] = namedMap(tag, naMap, methodMap, context, domain, e);
} else if (e.value is List) {
na[e.key] =
namedList(tag, naMap, methodMap, context, domain, e.value);
} else if (domain != null && domain.match(e)) {
na[e.key] = domain.bindValue(e as String);
} else if (domain != null && e is MapEntry && domain.match(e.value)) {
na[e.key] = domain.bindValue(e.value);
} else if (e.value is String) {
var w = namedString(tag, naMap, methodMap, context, domain, e.value);
needBinding = w.binding ?? false;
na[e.key] = w.data;
} else {
na[e.key] = e.value;
}
});
}
na['\$'] = context;
return W<Map<String, dynamic>>(na, needBinding);
}
通过positioned方法进行参数解析
ini
var r = proxyMirror?.evaluate(context, bound, e, domain: domain);
有8种Expression正则表达式匹配,用来解析字符串对应提取属性值。是方法还是变量的调用。
arduino
abstract class Expression {
R onEvaluate(
ProxyMirror? proxy, BindingData? binding, Domain? domain, String? exp, String? pre);
/// fail-fast
bool hitTest(String? exp, String? pre);
}
这里从源码中全部找出来如下:
ini
final List<Expression> _expressions = [
ComponentExpression(),
InlineExpression(),
InlineObjectExpression(),
WidgetParamExpression(),
FunctionExpression(),
GestureExpression(),
PropValueExpression(),
ValueExpression(),
];
ValueExpression匹配放到最后,所有无法匹配的当作变量。
python
ComponentExpression:
return RegExp('#\\(.+\\)', multiLine: true).hasMatch(exp ?? '');
InlineExpression:
return RegExp(r'\$\w+', multiLine: true).hasMatch(pre ?? '');
InlineObjectExpression:
return RegExp(r'\$\{\w.+\}', multiLine: true).hasMatch(pre ?? '');
WidgetParamExpression:
return RegExp('#\\(widget\..+\\)', multiLine: true).hasMatch(exp ?? '');
FunctionExpression:
return RegExp(r'\%\(.+\)', multiLine: false).hasMatch(exp ?? '');
GestureExpression:
return RegExp(r'\@\(.+\)', multiLine: false).hasMatch(exp ?? '');
PropValueExpression:
return RegExp(r'\^\(\w+\)', multiLine: false).hasMatch(exp ?? '');
所以回到本次例子中json格式:
json
{
"className": "Text",
"pa": [
"%(getHeight(100))"
],
"methodMap": {},
"digest": "fc6dd47c7b54434b70b954c981085540"
}
%(getHeight(100)) 这个匹配的是FunctionExpression
对应为一个函数方法的调用
分别会提取 getHeight 和 参数 100
完整的postion方法实现如下:
csharp
W<List> positioned(
dynamic paMap, Map? methodMap, BuildContext context, Domain? domain) {
var pa = [];
var needBinding = false;
if (paMap is List) {
paMap.forEach((e) {
if (e is Map) {
pa.add(convert(context, e, methodMap, domain: domain));
} else if (domain != null && domain.match(e)) {
pa.add(domain.bindValue(e));
} else if (domain != null && e is MapEntry && domain.match(e.value)) {
pa.add(domain.bindValue(e.value));
} else if (e is String) {
var r = proxyMirror?.evaluate(context, bound, e, domain: domain);
if (r?.binding == true) {
needBinding = true;
}
pa.add(r?.data);
} else {
pa.add(e);
}
});
}
return W<List>(pa, needBinding);
}
注意到这里有一个匹配methodMap方法。
重新创建一个稍微复杂的使用情况:
scss
import 'package:fair/fair.dart';
import 'package:fairdemo/plugins/custom_method.dart';
import 'package:fairdemo/plugins/custom_method2.dart';
import 'package:flutter/material.dart';
@FairPatch()
class Test2 extends StatefulWidget {
@override
State<Test2> createState() => _Test2State();
}
class _Test2State extends State<Test2> {
String getHeight(double height) {
return '${height + 100}';
}
String getHeight2(double height) {
return CustomMethod.getString('$height');
}
String getHeight3() {
return CustomMethod.getString('123123');
}
String getHeight4() {
getHeight2(23);
getHeight3();
CustomMethod2.getString2('123123');
return CustomMethod.getString('123123');
}
String getHeight5() {
getHeight2(23);
getHeight3();
CustomMethod.getString('123123');
return CustomMethod2.getString2('123123');
}
String getHeight6() {
getHeight2(23);
getHeight3();
return CustomMethod2.getString2('123123') +
CustomMethod.getString('123123');
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(getHeight(100)),
Text(getHeight2(100)),
Text(getHeight3()),
Text(getHeight4()),
Text(getHeight5()),
Text(getHeight6()),
],
);
}
}
js文件如下:
javascript
GLOBAL['#FairKey#'] = (function(__initProps__) {
const __global__ = this;
return runCallback(function(__mod__) {
with(__mod__.imports) {
function _Test2State() {
const inner = _Test2State.__inner__;
if (this == __global__) {
return new _Test2State({
__args__: arguments
});
} else {
const args = arguments.length > 0 ? arguments[0].__args__ || arguments : [];
inner.apply(this, args);
_Test2State.prototype.ctor.apply(this, args);
return this;
}
}
_Test2State.__inner__ = function inner() {};
_Test2State.prototype = {
getHeight: function getHeight(height) {
const __thiz__ = this;
const __arg_ctx__ = {
height,
};
with(__thiz__) {
with(__arg_ctx__) {
return `${height + 100}`;
}
}
},
getHeight2: function getHeight2(height) {
const __thiz__ = this;
const __arg_ctx__ = {
height,
};
with(__thiz__) {
with(__arg_ctx__) {
return CustomMethod.getString(`${height}`);
}
}
},
getHeight3: function getHeight3() {
const __thiz__ = this;
with(__thiz__) {
return CustomMethod.getString('123123');
}
},
getHeight4: function getHeight4() {
const __thiz__ = this;
with(__thiz__) {
getHeight2(23);
getHeight3();
CustomMethod2.getString2('123123');
return CustomMethod.getString('123123');
}
},
getHeight5: function getHeight5() {
const __thiz__ = this;
with(__thiz__) {
getHeight2(23);
getHeight3();
CustomMethod.getString('123123');
return CustomMethod2.getString2('123123');
}
},
getHeight6: function getHeight6() {
const __thiz__ = this;
with(__thiz__) {
getHeight2(23);
getHeight3();
return CustomMethod2.getString2('123123') + CustomMethod.getString('123123');
}
},
};
_Test2State.prototype.ctor = function() {};;
return _Test2State();
}
}, []);
})(convertObjectLiteralToSetOrMap(JSON.parse('#FairProps#')));
json如下:
bash
{
"className": "Column",
"na": {
"children": [
{
"className": "Text",
"pa": [
"%(getHeight(100))"
]
},
{
"className": "Text",
"pa": [
"%(getHeight2(100))"
]
},
{
"className": "Text",
"pa": [
"%(getHeight3)"
]
},
{
"className": "Text",
"pa": [
"%(getHeight4)"
]
},
{
"className": "Text",
"pa": [
"%(getHeight5)"
]
},
{
"className": "Text",
"pa": [
"%(getHeight6)"
]
}
]
},
"methodMap": {
"getHeight2": {
"className": "CustomMethod.getString",
"pa": [
"#($height)"
]
},
"getHeight3": {
"className": "CustomMethod.getString",
"pa": [
"123123"
]
},
"getHeight4": {
"className": "CustomMethod.getString",
"pa": [
"123123"
]
},
"getHeight5": {
"className": "CustomMethod2.getString2",
"pa": [
"123123"
]
}
},
"digest": "40c9bc448860777c9ff7bd08912d877c"
}
getHeight2方法和getHeight3方法因为方法内部调用了其他方法,所以在methodMap则有一个定义。 这个methodMap生成规则这里不研究。
主要methodMap有和没有在解析过程中有什么区别
参数方法执行
csharp
dynamic get value {
var extract;
matches
?.map((e) => {
'0': binding?.runFunctionOf(
e.group(0)!.substring(2, e.group(0)!.length - 1), proxyMirror, binding, domain),
'1': e.group(0)
})
.forEach((e) {
var first = e['0'] is ValueNotifier ? e['0'].value : e['0'];
if (first != null) {
extract = first; // extract.replaceFirst(e['1'], '$first');
} else {
extract = data;
}
});
return extract;
}
通过 binding?.runFunctionOf( e.group(0)!.substring(2, e.group(0)!.length - 1), proxyMirror, binding, domain)
方法进行方法调用的解析
提取方法名字和方法参数:
kotlin
dynamic runFunctionOf(String funcName, ProxyMirror? proxyMirror,
BindingData? bound, Domain? domain,
{String? exp}) {
if (_functions?[funcName] == null) {
var result;
if (RegExp(r'.+(.+)', multiLine: false).hasMatch(funcName)) { //提取方法名字
var rFuncName = funcName.substring(0, funcName.indexOf('('));
var params = funcName.substring(//提取参数
funcName.indexOf('(') + 1, funcName.lastIndexOf(')'));
var args = params.split(',').map((e) {
if (RegExp(r'^(index)', multiLine: false).hasMatch(e) &&
domain is IndexDomain?) {
return domain?.index;
} else if (domain != null && domain.match(e)) {
return domain.bindValue(e);
} else {
var r = proxyMirror?.evaluate(null, bound, e, domain: domain);
if (r?.data == null) {
return e;
} else {
return r?.data is ValueNotifier ? r?.data.value : r?.data;
}
}
}).toList();
//通过运行时 调用native通道执行js
result = _functions?['runtimeInvokeMethodSync']?.call(rFuncName, args);
} else {
//通过运行时 调用native通道执行js
result = _functions?['runtimeInvokeMethodSync']?.call(funcName);
}
try {
var value = jsonDecode(result);//返回结果用json解析
return value['result']['result'];//获取native js方法结果,返回的result字段则运算结果数据
} catch (e) {
throw RuntimeError(errorMsg: result);
}
} else {
return _functions?[funcName]?.call();
}
}
调用js方法:
json
{
"pageName": "test1#3",
"type": "method",
"args":
{
"funcName": "getHeight",
"args":
[
"100"
]
}
}
js方法调用:
ini
function _invokeMethod(par) {
let pageName = par['pageName'];
let funcName = par['args']['funcName'];
let args = par['args']['args'];
if ('getAllJSBindData' === funcName) {
return getAllJSBindData(par);
}
if ('releaseJS' === funcName) {
return _release(par);
}
let mClass = GLOBAL[pageName];
let func = mClass[funcName];
let methodResult;
if (isNull(func)) {
methodResult = '';
} else {
methodResult = func.apply(mClass, args);
}
let result = {
pageName: pageName,
result: {
result: methodResult
}
};
return JSON.stringify(result);
}
结果回调数据格式:
json
{
"pageName": "test1#3",
"result":
{
"result": "100100"
}
}
这里有一个问题,为什么result总是返回字符串类型的? 这样会导致在使用如果是需要double类型的地方,则会到界面转化失败。
ini
double getHeight(double height) {
return height;
}
//转化为js方法: %(getHeight(100))
{
"className": "Column",
"na": {
"children": [
{
"className": "Container",
"na": {
"height": "%(getHeight(100))"
}
}
]
},
"methodMap": {},
"digest": "4f6da55171a3ae0e9c8550464b7bf25e"
}
//js方法定义
GLOBAL['#FairKey#'] = (function(__initProps__) {
const __global__ = this;
return runCallback(function(__mod__) {
with(__mod__.imports) {
function _Test3State() {
const inner = _Test3State.__inner__;
if (this == __global__) {
return new _Test3State({
__args__: arguments
});
} else {
const args = arguments.length > 0 ? arguments[0].__args__ || arguments : [];
inner.apply(this, args);
_Test3State.prototype.ctor.apply(this, args);
return this;
}
}
_Test3State.__inner__ = function inner() {};
_Test3State.prototype = {
getHeight: function getHeight(height) {
const __thiz__ = this;
const __arg_ctx__ = {
height,
};
with(__thiz__) {
with(__arg_ctx__) {
return height; //这个height是什么类型
}
}
},
};
_Test3State.prototype.ctor = function() {};;
return _Test3State();
}
}, []);
})(convertObjectLiteralToSetOrMap(JSON.parse('#FairProps#')));
//"{\"pageName\":\"test1#0\",\"type\":\"method\",\"args\":{\"funcName\":\"getHeight\",\"args\":[\"100\"]}}"
//通过native调用fair_core.js中的方法:
function invokeJSFunc(parameter) {
if (parameter === null) {
return null;
}
let map = JSON.parse(parameter);
if ('method' === map['type']) {
return _invokeMethod(map);// 类型是method则调用名字为getHeight 的js方法
} else if ('variable' === map['type']) {
return _invokeVariable(map);
}
return null;
}
function _invokeMethod(par) {
let pageName = par['pageName'];
let funcName = par['args']['funcName'];
let args = par['args']['args'];
if ('getAllJSBindData' === funcName) {
return getAllJSBindData(par);
}
if ('releaseJS' === funcName) {
return _release(par);
}
let mClass = GLOBAL[pageName];
let func = mClass[funcName];
let methodResult;
if (isNull(func)) {
methodResult = '';
} else {
methodResult = func.apply(mClass, args);//调用js方法
}
let result = {
pageName: pageName,
result: {
result: methodResult//methodResult为什么总是string类型
}
};
return JSON.stringify(result);
}
通过native转化将结果结果通过 dartFFI函数返回dart端
//dartffi调用
const char *invokeJSCommonFuncSync(char *args) {
if ([FairDynamicFlutter sharedInstance].delegate &&
[[FairDynamicFlutter sharedInstance].delegate respondsToSelector:@selector(executeScriptSyncImpl:)]) {
return [[FairDynamicFlutter sharedInstance].delegate executeScriptSyncImpl:args];
}
return "";
}
//[[FairDynamicFlutter sharedInstance].delegate executeScriptSyncImpl:args];native真正的实现
- (const char *)executeScriptSyncImpl:(char *)args
{
JSValue *obj;
if (self.delegate && [self.delegate respondsToSelector:@selector(executeJSFunctionSync:params:)]) {
NSString *str = [NSString stringWithUTF8String:args];
obj = [self.delegate executeJSFunctionSync:FairExecuteJSFunction params:@[str]];
}
NSString *result = [NSString stringWithFormat:@"%@", obj.toString];
FairLog(@"result:%@", result);
if([result isEqualToString:@"undefined"]){
//取args中的funcName字段
//arg ===> "{\"pageName\":\"null#0\",\"type\":\"method\",\"args\":{\"funcName\":\"_getAuth\",\"args\":null}}"
NSString *str = [NSString stringWithUTF8String:args];
NSData *jsonData = [str dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSDictionary *args = dic[@"args"];
NSString *funcName = args[@"funcName"];
FairLog(@"invoke funcName:%@",funcName);
NSString *errorResult = [NSString stringWithFormat:@"Runtime error while invoke JavaScript method:%@()", funcName];
return errorResult.UTF8String;
}
return result.UTF8String;
}
总结
到这里基本,就简单分析了界面转化过程的源码实现部分。下一步继续分析:
- FairApp
Map<String, FairModuleBuilder>? modules
使用 - 自定义插件工作流程和使用
- 带参数函数调用
- json页面文件中方法被转化为在在methodMap和没有转化执行区别以及方法带参数时不能在methodMap中匹配执行区别
- 对于自定义dart代码
import 'package:fairdemo/fair_test/test_definemodule.dart';
和import 'test_definemodule.dart';
在@FairPatch()
页面中通过注解生成的js文件中 defineModule作用和生成规则,生成的defineModule js部分示例代码为:
scss
defineModule(1, function(__mod__) {
with(__mod__.imports) {
inherit(ScreenUtilPlugin, IFairPlugin);
}
__mod__.exports.ScreenUtilPlugin = ScreenUtilPlugin;
}, []);```