Flutter Go 源码分析(一)
前言
在经过了一段时间的Flutter学习之后,感觉自己该来点项目实战了,在找了很多开源项目之后,最终决定学习阿里大佬们开发的Flutter Go项目,此文章以记录自己的学习过程,感谢大佬们的无私开源,项目下载地址。
目录
Flutter Go 学习之路(二)
Flutter Go 学习之路(三)
Flutter Go 学习之路(四)
Flutter Go 学习之路(五)
Flutter Go 学习之路(六)
我们从入口main()
函数开始学起,先说明一些初始化的这几个类:
void main() async {
final provider = new Provider();
await provider.init(true);//创建/打开 数据库
sp = await SpUtil.getInstance();//用来做shared_preferences的存储
new SearchHistoryList(sp);//工厂方法获取实例对象 获取本地搜索记录列表 单例
db = Provider.db;
runApp(new MyApp());
}
(1) Provider
它是对数据库操作相关的类,用于创建数据库的工具,首先我们来看一下初始化方法:
//初始化数据库
Future init(bool isCreate) async {
//Get a location using getDatabasesPath
String databasesPath = await getDatabasesPath();// 获取数据库路径 sqflite三方
String path = join(databasesPath, 'flutter.db');//拼接App数据库名称
print(path);
try {//尝试打开数据库 如果已经创建
db = await openDatabase(path);
} catch (e) {
print("Error $e");
}
bool tableIsRight = await this.checkTableIsRight();//检查数据库中表是否完整
if (!tableIsRight) {//如果不完整就删除重新创建
// 关闭上面打开的db,否则无法执行open
db.close();
// Delete the database
await deleteDatabase(path);
ByteData data = await rootBundle.load(join("assets", "app.db"));//读取assets app.db(创建表)的数据
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await new File(path).writeAsBytes(bytes);//将读取的数据写入路径
//打开数据库
db = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
print('db created version is $version');
}, onOpen: (Database db) async {
print('new db opened');
});
} else {
print("Opening existing database");
}
}
app.db表
init
方法总体实现思路就是
- 1)先通过
openDatabase(path);
尝试打开数据库。 - 2)再通过
checkTableIsRight
方法检查数据库是否存在或者完整:
// 检查数据库中, 表是否完整, 在部份android中, 会出现表丢失的情况
Future checkTableIsRight() async {
List<String> expectTables = ['cat', 'widget', 'collection'];
List<String> tables = await getTables();
for(int i = 0; i < expectTables.length; i++) {
if (!tables.contains(expectTables[i])) {
return false;
}
}
return true;
}
- 3)如果没有创建过或者表不完整就会删除重新创建数据表
await deleteDatabase(path);
,在通过本地assets 下的app.db按表名写入一个空的
ByteData data = await rootBundle.load(join("assets", "app.db"));
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await new File(path).writeAsBytes(bytes);
最后打开数据库。
(2) SpUtil
这个是用来做本地化存储的,主要代码:
static SharedPreferences _spf;
SpUtil._();
Future _init() async {
//SharedPreferences 数据本地化相关工具 NSUserDefaults (on iOS) and SharedPreferences (on Android)
_spf = await SharedPreferences.getInstance();
}
static Future<SpUtil> getInstance() async {
if (_instance == null) {
_instance = new SpUtil._();
await _instance._init();
}
return _instance;
}
这里我们看_spf
实例是一个SharedPreferences
,它就相当于iOS的NSUserDefaults
或者安卓的SharedPreferences
.剩下的其他一些方法都是些写入或者读取的方法,这里就不过多阐述了。
(3) SearchHistoryList
SearchHistoryList
是利用SpUtil
用来存取搜索记录的工具我们主要来看一下它的构造方法:
static SpUtil _sp;
static SearchHistoryList _instance;
static List<SearchHistory> _searchHistoryList = [];
static SearchHistoryList _getInstance(SpUtil sp) {
if (_instance == null) {
_sp = sp;
String json = sp.get(SharedPreferencesKeys.searchHistory);
_instance = new SearchHistoryList.fromJSON(json);//初始化方法
}
return _instance;
}
//工厂模式 实现
factory SearchHistoryList([SpUtil sp]) {
if (sp == null && _instance == null) {
print(new ArgumentError(
['SearchHistoryList need instantiatied SpUtil at first timte ']));
}
return _getInstance(sp);
}
// List<SearchHistory> _searchHistoryList = [];
// 存放的最大数量
int _count = 10;
SearchHistoryList.fromJSON(String jsonData) {
_searchHistoryList = [];//存储的是SearchHistory model
if (jsonData == null) {
return;
}
List jsonList = json.decode(jsonData);
jsonList.forEach((value) {
_searchHistoryList.add(SearchHistory(
name: value['name'], targetRouter: value['targetRouter']));
});
}
执行顺序是:factory SearchHistoryList([SpUtil sp])
-->static SearchHistoryList _getInstance(SpUtil sp)
-->SearchHistoryList.fromJSON(String jsonData)
并且在初始化的时候搜索记录列表就已经取出存到了_searchHistoryList
里面,其他的一些存取方法也不过多阐述。
(4) MyApp()
主程序框架
class MyApp extends StatelessWidget {
MyApp() {
final router = new Router();
Routes.configureRoutes(router);
Application.router = router;
}
showWelcomePage() {
// 暂时关掉欢迎介绍
return AppPage();
// bool showWelcome = sp.getBool(SharedPreferencesKeys.showWelcome);
// if (showWelcome == null || showWelcome == true) {
// return WelcomePage();
// } else {
// return AppPage();
// }
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'title',
theme: new ThemeData(
primaryColor: Color(ThemeColor),
backgroundColor: Color(0xFFEFEFEF),
accentColor: Color(0xFF888888),
textTheme: TextTheme(
//设置Material的默认字体样式
body1: TextStyle(color: Color(0xFF888888), fontSize: 16.0),
),
iconTheme: IconThemeData(
color: Color(ThemeColor),
size: 35.0,
),
),
home: new Scaffold(
body: showWelcomePage()
),
onGenerateRoute: Application.router.generator,
navigatorObservers: <NavigatorObserver>[Analytics.observer],
);
}
}
这里构造方法里面进行了Router的初始化和注册,用到了fluro库,具体用法大家自行去了解,不再做阐述。
在flutter应用中,一般一个应用都对应一个MaterialApp
这个组件是flutter应用程序的骨架:
this.navigatorKey, // 导航的key
this.home, // 主页
this.routes = const <String, WidgetBuilder>{},// 路由
this.initialRoute,//初始路由
this.onGenerateRoute,//生成路由
this.onUnknownRoute,//位置路由
this.navigatorObservers = const <NavigatorObserver>[],//导航的观察者
this.builder,//widget的构建
this.title = '',//设备用于识别用户的应用程序的单行描述。在Android上,标题显示在任务管理器的应用程序快照上方,当用户按下“最近的应用程序”按钮时会显示这些快照。 在iOS上,无法使用此值。 来自应用程序的`Info.plist`的`CFBundleDisplayName`在任何时候都会被引用,否则就会引用`CFBundleName`。要提供初始化的标题,可以用 onGenerateTitle。
this.onGenerateTitle,//每次在WidgetsApp构建时都会重新生成
this.color,//背景颜色
this.theme,//主题,用ThemeData
this.locale,//app语言支持
this.localizationsDelegates,//多语言代理
this.localeResolutionCallback,//
this.supportedLocales = const <Locale>[Locale('en', 'US')],//支持的多语言
this.debugShowMaterialGrid = false,//显示网格
this.showPerformanceOverlay = false,//打开性能监控,覆盖在屏幕最上面
this.checkerboardRasterCacheImages = false,
this.checkerboardOffscreenLayers = false,
this.showSemanticsDebugger = false,//打开一个覆盖图,显示框架报告的可访问性信息 显示边框
this.debugShowCheckedModeBanner = true,//右上角显示一个debug的图标
这里主要说一下onGenerateRoute: Application.router.generator,
这句代码,这里是配合fluro
库使用的,上面代码也可以注释掉:
home: new Scaffold(
body: showWelcomePage()
),
然后再注册的时候注册一下"/"
就好:
router.define("/", handler: homeHandler);
// app的首页
var homeHandler = new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return new AppPage();
},
);
其余属性自行去了解。