动态页面生成
编写页面的dart代码如下:
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文件:
{
"className": "Text",
"pa": [
"%(getHeight(100))"
],
"methodMap": {},
"digest": "fc6dd47c7b54434b70b954c981085540"
}
逻辑js文件:
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加载:
FairWidget(
name: "test1",
path: 'assets/fair/lib_fair_test_test2.fair.json',
)
解析第一步:
经过toWidget办法将json文件还原成widget
var widget = _convert(context, layout!, methodMap, data: data);
这一步layout数据便是json格局页面数据
经过遍历map
{
"className": "Text",
"pa": [
"%(getHeight(100))"
],
"methodMap": {},
"digest": "fc6dd47c7b54434b70b954c981085540"
}
tag值是className
var name = map[tag];
1、先在module中匹配
var module = bound?.modules?.moduleOf(name)?.call();
这个module的界说能够在FairApp初始化中装备
FairApp(
generated: AppGeneratedModule(),
modules: {},
delegate: {
},
child: MyApp(),
);
2、在办法和变量中匹配
mapper = bound?.functionOf(name) ?? bound?.valueOf(name);
这个bound经过FairApp获取,先获取FairApp,
var app = FairApp.of(context);
var bound = app?.bindData[page];
FairApp.of(context)这个办法是从 context 中 findAncestorWidgetOfExactType经过widget 树结构查找。
static FairApp? of(BuildContext? context, {bool rebuild = false}) {
return rebuild ? context?.dependOnInheritedWidgetOfExactType<FairApp>() : context?.findAncestorWidgetOfExactType<FairApp>();
}
咱们在main中经过FairApp在最外层包裹页面。
所以这儿对应app目标便是从main中初始化的FairApp
FairApp(
generated: AppGeneratedModule(),
modules: {},
delegate: {
},
child: MyApp(),
);
FairApp继承自 AppState
class FairApp extends InheritedWidget with AppState
bindData是AppState界说的特点,经过其register办法绑定
Future<dynamic> register(FairState state)
注册页面 的
bindData.putIfAbsent(
state.state2key,
() => BindingData(
modules,
functions: delegate.bindFunction(),
values: delegate.bindValue(),
),
);
这儿key名便是 页面姓名,即对应
FairWidget(
name: "test1",
path: 'assets/fair/lib_fair_test_test2.fair.json',
)
这个name 进行核算得到:
state2key = GlobalState.id(widget.name);
经过_counter大局的变量标识,防止同一个可能会运用多次而导致数据混用问题:
static String id(String? prefix) {
return '$prefix#${GlobalState._counter++}';
}
因此回到上面盯梢的进程:
mapper = bound?.functionOf(name) ?? bound?.valueOf(name);
这个办法便是在 delegate.bindFunction(), 和delegate.bindValue(),办法在FairApp
创建时分经过传入的delegate完成的:
FairApp(
generated: AppGeneratedModule(),
modules: {},
delegate: {
///界说办法和变量映射表
},
child: MyApp(),
);
3,在mapper = proxyMirror?.componentOf(name);
中查找
mapper = proxyMirror?.componentOf(name);
ProxyMirror 这个便是一个大map 叫做_provider目标办理,存放 字符串到dart办法的映射联系。
class ProxyMirror with P {
final _provider = BindingProvider();
}
分为3大部分:
首先是fair_version下面的,这是flutter framework 系统widget和办法的对应联系
BindingProvider 混入$BindingImpl类
class BindingProvider with $BindingImpl
$BindingImpl界说如下
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,
];}
一些特别映射表:
specialBinding =
SplayTreeMap.from({
...common.provider(),
...geometry.provider(),
...flow.provider(),
});
对应的是geometry.dart
common.dart
flow.dart 这3个文件中界说的映射联系,比方FairWidget Color等这些目标的映射对照表:
{
'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
void addGeneratedBinding(GeneratedModule? generated) {
if (generated == null) {
return;
}
_generatedMapping.addAll(generated.mapping());
_provider.binding.addAll(generated.components());
}
经过_proxy.addGeneratedBinding(generated);
办法添加。
GeneratedModule
FairApp(
generated: AppGeneratedModule(),
modules: {},
delegate: {
},
child: MyApp(),
)
AppGeneratedModule是一个经过@FairBinding经过fair compiler工具自动化生成的代码
class AppGeneratedModule extends GeneratedModule
GeneratedModule 类对应的便是需要供给办法:
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的映射查找联系。
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办法提取参数
var na = named(name, map['na'], methodMap, ctx, domain);
var pa = positioned(map['pa'], methodMap, ctx, domain);
看下之前转化的json 这个是一层,
{
"className": "Text",
"pa": [
"%(getHeight(100))"
],
"methodMap": {},
"digest": "fc6dd47c7b54434b70b954c981085540"
}
这个是多层widget嵌套样子:
{
"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往下遍历
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办法进行参数解析
var r = proxyMirror?.evaluate(context, bound, e, domain: domain);
有8种Expression正则表达式匹配,用来解析字符串对应提取特点值。是办法还是变量的调用。
abstract class Expression {
R onEvaluate(
ProxyMirror? proxy, BindingData? binding, Domain? domain, String? exp, String? pre);
/// fail-fast
bool hitTest(String? exp, String? pre);
}
这儿从源码中全部找出来如下:
final List<Expression> _expressions = [
ComponentExpression(),
InlineExpression(),
InlineObjectExpression(),
WidgetParamExpression(),
FunctionExpression(),
GestureExpression(),
PropValueExpression(),
ValueExpression(),
];
ValueExpression匹配放到最终,一切无法匹配的当作变量。
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格局:
{
"className": "Text",
"pa": [
"%(getHeight(100))"
],
"methodMap": {},
"digest": "fc6dd47c7b54434b70b954c981085540"
}
%(getHeight(100)) 这个匹配的是FunctionExpression
对应为一个函数办法的调用
分别会提取 getHeight 和 参数 100
完整的postion办法完成如下:
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办法。
重新创建一个稍微杂乱的运用情况:
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文件如下:
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如下:
{
"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有和没有在解析进程中有什么区别
参数办法履行
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)
办法进行办法调用的解析
提取办法姓名和办法参数:
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办法:
{
"pageName": "test1#3",
"type": "method",
"args":
{
"funcName": "getHeight",
"args":
[
"100"
]
}
}
js办法调用:
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);
}
成果回调数据格局:
{
"pageName": "test1#3",
"result":
{
"result": "100100"
}
}
这儿有一个问题,为什么result总是回来字符串类型的? 这样会导致在运用如果是需要double类型的当地,则会到界面转化失利。
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部分示例代码为:
defineModule(1, function(__mod__) {
with(__mod__.imports) {
inherit(ScreenUtilPlugin, IFairPlugin);
}
__mod__.exports.ScreenUtilPlugin = ScreenUtilPlugin;
}, []);```