Flutter
的三方工具有两种,一种是插件
(Plugin),一种是包
(Package)。这两种差别在于Plugin
不仅包含Dart
代码,还包含了iOS
以及安卓
的原生代码,比如常见的image_picker
;而Package
仅仅是Dart
代码库。
package开发
下面我们开发一个支持空安全
的库,就用之前的wechat_demo
工程中通讯录页面
右侧的IndexBar
进行练习
- 新建
Package
工程logic_package_demo
,注意Project type
选择Package
- 查看
工程目录
,发现并不包含iOS
、安卓
目录
-
空安全版本
相关配置依然在pubspec.yaml
文件中 -
Package
工程引用资源图片,New -> Directory
新建images
目录,把图片资源放入目录
-
pubspec.yaml
文件进行配置,引入图片资源
- 单元测试
test
目录下的报错先删掉
-
logic_package_demo.dart
文件内容
library logic_package_demo;
import 'package:flutter/material.dart';
class IndexBar extends StatefulWidget {
// 对外提供回调,告诉friends_page当前选中的是标识
final void Function(String str) indexBarCallBack;
const IndexBar({required this.indexBarCallBack, Key? key}) : super(key: key);
@override
State createState() => _IndexBarState();
}
// 获取选中的item的字符!!
// context就是widget中的Element
int getIndex(BuildContext context, Offset globalPosition) {
// 获取当前小部件的盒子,类型强转RenderBox
RenderBox box = context.findRenderObject() as RenderBox;
// 获取y值 globalToLocald当前位置距离部件原点(左上角)的位置
double y = box.globalToLocal(globalPosition).dy;
// 算出字符高度
var itemHeight = screenHeigth(context) / 2 / INDEX_WORDS.length;
// 算出第几个item, clamp约束index范围值
int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length - 1);
return index;
}
class _IndexBarState extends State {
// 索引条选中 背景色与文字颜色
Color _bkColor = Color.fromRGBO(1, 1, 1, 0.0);
Color _textColor = Colors.black;
// 添加图片指示器Y值,显示索引标识,是否显示等属性
double _indicatorY = 0.0;
String _indicatorText = 'A';
bool _indicatorHidden = true;
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
// 与界面相关的数据要放入build中
final List words = [];
for (int i = 0; i < INDEX_WORDS.length; i++) {
words.add(Expanded(
child: Text(
INDEX_WORDS[i],
style: TextStyle(fontSize: 10, color: _textColor),
)));
}
// TODO: implement build
return Positioned(
right: 0.0,
top: screenHeigth(context) / 8,
height: screenHeigth(context) / 2,
width: 120,
// 添加手势
child: Row(
children: [
Container(
alignment: Alignment(0, _indicatorY),
width: 100,
// color: Colors.red,
child: _indicatorHidden
? null
: Stack(
// 让气泡居中
alignment: Alignment(-0.2, 0),
children: [
Image(
image: AssetImage('images/气泡.png'),
width: 60,
),
Text(
_indicatorText,
style: TextStyle(fontSize: 35, color: Colors.white),
)
],
),
), //指示器
GestureDetector(
// 拖拽手势
onVerticalDragUpdate: (DragUpdateDetails details) {
// String str = getIndex(context, details.globalPosition);
// print('选中的是$str');
int index = getIndex(context, details.globalPosition);
widget.indexBarCallBack(INDEX_WORDS[index]);
setState(() {
_indicatorY = 2.2 / INDEX_WORDS.length * index - 1.1;
_indicatorText = INDEX_WORDS[index];
_indicatorHidden = false;
});
},
// 点击索引
onVerticalDragDown: (DragDownDetails details) {
int index = getIndex(context, details.globalPosition);
widget.indexBarCallBack(INDEX_WORDS[index]);
setState(() {
_indicatorY = 2.2 / INDEX_WORDS.length * index - 1.1;
_indicatorText = INDEX_WORDS[index];
_indicatorHidden = false;
_bkColor = Color.fromRGBO(1, 1, 1, 0.5);
_textColor = Colors.white;
});
},
// 点击结束,颜色值还原
onVerticalDragEnd: (DragEndDetails details) {
setState(() {
_indicatorHidden = true;
_bkColor = Color.fromRGBO(1, 1, 1, 0.0);
_textColor = Colors.black;
});
},
child: Container(
// 设置宽度,让右侧索引居中
width: 20,
color: _bkColor,
child: Column(
children: words,
),
),
), //索引条
],
),
);
}
}
//主题色
//const修饰的常量,名称首字母可以是大写
const Color WeChatThemeColor = Color.fromRGBO(220, 220, 220, 1.0);
//屏幕宽度
//屏幕宽度是通过计算来获取,不能定义成常量;首字母需要小写
double screenWidth(BuildContext context) => MediaQuery.of(context).size.width;
double screenHeigth(BuildContext context) => MediaQuery.of(context).size.height;
const INDEX_WORDS = [
'',
'☆',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z'
];
-
Package
包发布前配置
-
Package
包进行发布
// 进入logic_package_demo工程目录
$ cd /Users/wn/Desktop/logic_package_demo
// 检查Package包
$ flutter packages pub publish --dry-run
比如pubspec.yaml
文件中的homepage
没有配置,检查的时候就会报错。
// Package包进行发布
$ flutter packages pub publish
注意:目前发布插件和包都需要Google
账号,同时需要翻墙。
当出现下面提示,需要你使用浏览器访问提示中的链接,用你的Google
账号授权。
有时就算是翻墙也并不能解决问题,因为我们还配置了相关的镜像。Flutter
官方就建议过镜像的配置,所以我们在发布插件或者包的时候,就会因为镜像出现下面错误:
解决办法:指定服务器发布
$ flutter packages pub publish --server=https://pub.dartlang.org
证书问题报错
发布包的一个版权问题,这个证书需要在Github
仓库中创建。
-
Github
获取证书
把证书文件内容复制到LICENSE
文件。
- 解决完上面证书问题,再次发布
等待20 ~ 25分钟
,pub.dev网站就能搜索到我们发布的logic_package_demo
包。
wechat_demo使用我们发布的包
-
pubspec.yaml
文件进行配置,点击Pub get
引入
-
friends_page.dart
文件进行引入使用
// 导入头文件
import 'package:login_package_demo/login_package_demo.dart' as login;
这个时候会有一个问题,点击Pub get
的时候只是把代码引入到了wechat_demo
项目,图片资源并没有引入,这个时候使用Package
包就会报错,下面进行优化...
优化package
把本地资源
文件也上传到服务器,让使用者能够成功引入图片资源
-
images
图片资源要放入lib
目录下
使用图片资源的时候指定
Package
包名
// 去logic_package_demo包中找到图片进行加载
const AssetImage('images/bubble.png', package: 'logic_package_demo'),
- 重新发布
logic_package_demo
包(pubspec.yaml文件要把version: 0.0.1 更改为 0.0.2 # 项目版本,进行版本升级,否则不能发布)
- 发布完成之后,
wechat_demo
工程进行引入使用
这里的图片资源引用指定到了具体图片
,不推荐这种方式。
-
logic_package_demo.dart
中的IndexBar
进行扩展,允许外面传入图片
class IndexBar extends StatefulWidget {
// 对外提供回调,告诉friends_page当前选中的是标识
final void Function(String str) indexBarCallBack;
final ImageProvider? image;
const IndexBar({required this.indexBarCallBack, this.image, Key? key}) : super(key: key);
......
// 让气泡居中
alignment: Alignment(-0.2, 0),
children: [
Image(
// widget.image使用外面传入的image
image: widget.image ?? AssetImage('images/气泡.png', package: 'logic_package_demo'),
width: 60,
),
Text(
_indicatorText,
style: TextStyle(fontSize: 35, color: Colors.white),
)
],
Pub.dev
网站查看提升包的分数进行优化
- 添加项目描述
- 添加
example
,使用Package
包的示例 - 增加
API
数量
下面主要是添加使用示例进行优化,提升包的评分
- 新建
package_example
空工程,跟logic_package_demo
放入同一目录下 -
pubspec.yaml
文件进行配置,并点击Pub get
进行引入
- 使用
wechat_demo
中的FriendsPage
页面,并进行修改
import 'package:flutter/material.dart';
import 'package:logic_package_demo/logic_package_demo.dart' as logic;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FriendsPage(),
);
}
}
class _FriendCell extends StatelessWidget {
const _FriendCell(
{this.imageUrl, this.name, this.groupTitle, this.imageAssets});
final String? imageUrl;
final String? name;
final String? groupTitle;
final String? imageAssets;
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 10),
height: groupTitle != null ? 30 : 0,
// 使用logic_package_demo库中配置的常量
color: logic.WeChatThemeColor,
child: groupTitle != null
? Text(
groupTitle!,
style: const TextStyle(color: Colors.grey),
)
: null,
), //头部
Container(
color: Colors.white,
child: Row(
children: [
Container(
margin: EdgeInsets.all(10),
width: 34,
height: 34,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
image: DecorationImage(
image: imageUrl != null
? NetworkImage(imageUrl!)
: AssetImage(imageAssets!,
package: 'logic_package_demo') as ImageProvider,
)),
), //图片
Container(
// color: Colors.red,
width: logic.screenWidth(context) - 54,
child: Column(
children: [
Container(
alignment: Alignment.centerLeft,
height: 54,
child: Text(
name!,
style: TextStyle(fontSize: 18),
),
),
Container(
height: 0.5,
color: logic.WeChatThemeColor,
), //下划线
],
),
), //昵称+下划线
],
),
), //Cell的内容
],
);
}
}
class FriendsPage extends StatefulWidget {
@override
_FriendsPageState createState() => _FriendsPageState();
}
class _FriendsPageState extends State
with AutomaticKeepAliveClientMixin {
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
double _cellHeight = 54.5;
double _groupHeight = 30.0;
//字典,里面放item和高度的对应的数据。
final Map _groupOffsetMap = {
logic.INDEX_WORDS[0]: 0.0,
logic.INDEX_WORDS[1]: 0.0,
};
final List _headerData = [
Friends(imageAssets: 'images/新的朋友.png', name: '新的朋友'),
Friends(imageAssets: 'images/群聊.png', name: '群聊'),
Friends(imageAssets: 'images/标签.png', name: '标签'),
Friends(imageAssets: 'images/公众号.png', name: '公众号'),
];
final List _listDatas = [];
// 懒加载
late ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController();
//创建数据
_listDatas
..addAll(datas)
..addAll(datas);
//排序
_listDatas.sort((Friends a, Friends b) {
return a.indexLetter!.compareTo(b.indexLetter!);
});
var _groupOffset = _cellHeight * _headerData.length;
//进过循环计算,将每一个头的位置算出来。放入字典
for (int i = 0; i < _listDatas.length; i++) {
if (i < 1) {
//第一个cell一定有头!
_groupOffsetMap.addAll({_listDatas[i].indexLetter: _groupOffset});
//保存完了再加_groupOffset
_groupOffset += _cellHeight + _groupHeight;
} else if (_listDatas[i].indexLetter == _listDatas[i - 1].indexLetter) {
//不同存,只需要加Cell的高度
_groupOffset += _cellHeight;
} else {
_groupOffsetMap.addAll({_listDatas[i].indexLetter: _groupOffset});
//保存完了再加_groupOffset
_groupOffset += _cellHeight + _groupHeight;
}
}
}
Widget _itemForRow(BuildContext context, int index) {
//显示头部4个Cell
if (index < _headerData.length) {
return _FriendCell(
imageAssets: _headerData[index].imageAssets,
name: _headerData[index].name,
);
}
//是否显示组名字!
bool _hiddenIndexLetter = (index - 4 > 0 &&
_listDatas[index - 4].indexLetter == _listDatas[index - 5].indexLetter);
return _FriendCell(
imageUrl: _listDatas[index - 4].imageUrl,
name: _listDatas[index - 4].name,
groupTitle: _hiddenIndexLetter ? null : _listDatas[index - 4].indexLetter,
);
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(
backgroundColor: logic.WeChatThemeColor,
title: Text('通讯录'),
),
body: Stack(
children: [
Container(
color: logic.WeChatThemeColor,
child: ListView.builder(
controller: _scrollController,
itemBuilder: _itemForRow,
itemCount: _listDatas.length + _headerData.length,
),
), //列表
logic.IndexBar(
indexBarCallBack: (String str) {
if (_groupOffsetMap[str] != null) {
_scrollController.animateTo(_groupOffsetMap[str],
duration: Duration(microseconds: 100),
curve: Curves.easeIn);
}
},
), //悬浮的索引条
],
));
}
}
class Friends {
Friends({this.imageUrl, this.name, this.indexLetter, this.imageAssets});
final String? imageAssets;
final String? imageUrl;
final String? name;
final String? indexLetter;
}
List datas = [
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/27.jpg',
name: 'Lina',
indexLetter: 'L'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/17.jpg',
name: '菲儿',
indexLetter: 'F'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/16.jpg',
name: '安莉',
indexLetter: 'A'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/31.jpg',
name: '阿贵',
indexLetter: 'A'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/22.jpg',
name: '贝拉',
indexLetter: 'B'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/37.jpg',
name: 'Lina',
indexLetter: 'L'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/18.jpg',
name: 'Nancy',
indexLetter: 'N'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/47.jpg',
name: '扣扣',
indexLetter: 'K'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/3.jpg',
name: 'Jack',
indexLetter: 'J'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/5.jpg',
name: 'Emma',
indexLetter: 'E'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/24.jpg',
name: 'Abby',
indexLetter: 'A'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/15.jpg',
name: 'Betty',
indexLetter: 'B'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/13.jpg',
name: 'Tony',
indexLetter: 'T'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/26.jpg',
name: 'Jerry',
indexLetter: 'J'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/36.jpg',
name: 'Colin',
indexLetter: 'C'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/12.jpg',
name: 'Haha',
indexLetter: 'H'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/11.jpg',
name: 'Ketty',
indexLetter: 'K'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/13.jpg',
name: 'Lina',
indexLetter: 'L'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/23.jpg',
name: 'Lina',
indexLetter: 'L'),
];
- 由于
package_example
工程没有配置assets
导致页面图片加载不出来
- 我们不配置
package_example
工程的pubspec.yaml
文件,我们优化logic_package_demo
库
// image与icon都设置成可选的
class IndexBar extends StatefulWidget {
final void Function(String str) indexBarCallBack;
final ImageProvider? image;
final Icon? icon;
const IndexBar(
{required this.indexBarCallBack, this.image, this.icon, Key? key})
: super(key: key);
......
alignment: const Alignment(-0.2, 0),
children: [
// 传icon就显示icon,传image就显示image,都不传的话显示铺底图片
widget.icon ??
Image(
image: widget.image ??
const AssetImage('images/bubble.png',
package: 'logic_package_demo'),
width: 60,
),
Text(
_indicatorText,
style:
const TextStyle(fontSize: 35, color: Colors.white),
)
],
-
package_example
工程重新Pub get
导入logic_package_demo
库 -
package_example
工程删除下图中气泡图片
进行测试
-
pubspec.yaml
文件进行配置,重新运行工程
- 这个时候
通讯录页面
头部的Cell
图标还没有显示出来,我们修改package_example
工程的main.dart
文件
class _FriendCell extends StatelessWidget {
......
Container(
margin: EdgeInsets.all(10),
width: 34,
height: 34,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
image: DecorationImage(
image: imageUrl != null
? NetworkImage(imageUrl!)
// 本地图片指定到logic_package_demo包
: AssetImage(imageAssets!,
package: 'logic_package_demo') as ImageProvider,
)),
), //图片
- 把示例代码导入
logic_package_demo
工程,重新发布Package
包
logic_package_demo
工程新建example
目录,把package_example
示例工程中的main.dart
文件导入。
-
logic_package_demo.dart
文件中代码太多,我们进行拆分对库进行优化
新建index_bar.dart
文件,把logic_package_demo.dart
文件中代码抽取出来
library logic_package_demo;
// 库中导入即可
import 'package:flutter/material.dart';
part 'index_bar.dart';
// 指定属于哪个库
part of 'logic_package_demo.dart';
class IndexBar extends StatefulWidget {
final void Function(String str) indexBarCallBack;
final ImageProvider? image;
final Icon? icon;
const IndexBar(
{required this.indexBarCallBack, this.image, this.icon, Key? key})
: super(key: key);
@override
_IndexBarState createState() => _IndexBarState();
}
//获取选中的Item的字符!!
int getIndex(BuildContext context, Offset globalPosition) {
//拿到点前小部件的盒子
RenderBox box = context.findRenderObject() as RenderBox;
//拿到y值,globalToLocal当前位置我部件的原点(小部件左上角)的距离(x,y)
double y = box.globalToLocal(globalPosition).dy;
//算出字符高度
var itemHeight = screenHeight(context) / 2 / INDEX_WORDS.length;
//算出第几个item
int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length - 1);
return index;
}
class _IndexBarState extends State {
Color _bkColor = Color.fromRGBO(1, 1, 1, 0.0);
Color _textColor = Colors.black;
double _indicatorY = 0.0;
String _indicatorText = 'A';
bool _indicatorHidden = true;
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
final List words = [];
for (int i = 0; i < INDEX_WORDS.length; i++) {
words.add(Expanded(
child: Text(
INDEX_WORDS[i],
style: TextStyle(fontSize: 10, color: _textColor),
)));
}
return Positioned(
right: 0.0,
top: screenHeight(context) / 8,
height: screenHeight(context) / 2,
width: 120,
child: Row(
children: [
Container(
alignment: Alignment(0, _indicatorY),
width: 100,
// color: Colors.red,
child: _indicatorHidden
? null
: Stack(
alignment: const Alignment(-0.2, 0),
children: [
widget.icon ??
Image(
image: widget.image ??
const AssetImage('images/bubble.png',
package: 'logic_package_demo'),
width: 60,
),
Text(
_indicatorText,
style:
const TextStyle(fontSize: 35, color: Colors.white),
)
],
),
), //指示器
GestureDetector(
onVerticalDragUpdate: (DragUpdateDetails details) {
int index = getIndex(context, details.globalPosition);
widget.indexBarCallBack(INDEX_WORDS[index]);
setState(() {
_indicatorY = 2.2 / INDEX_WORDS.length * index - 1.1;
_indicatorText = INDEX_WORDS[index];
_indicatorHidden = false;
});
},
onVerticalDragDown: (DragDownDetails details) {
int index = getIndex(context, details.globalPosition);
widget.indexBarCallBack(INDEX_WORDS[index]);
setState(() {
_indicatorY = 2.2 / INDEX_WORDS.length * index - 1.1;
_indicatorText = INDEX_WORDS[index];
_indicatorHidden = false;
_bkColor = Color.fromRGBO(1, 1, 1, 0.5);
_textColor = Colors.white;
});
},
onVerticalDragEnd: (DragEndDetails details) {
setState(() {
_indicatorHidden = true;
_bkColor = Color.fromRGBO(1, 1, 1, 0.0);
_textColor = Colors.black;
});
},
child: Container(
width: 20,
color: _bkColor,
child: Column(
children: words,
),
),
), //索引条
],
),
);
}
}
//主题色
const Color WeChatThemeColor = Color.fromRGBO(220, 220, 220, 1.0);
//屏幕宽高
double screenWidth(BuildContext context) => MediaQuery.of(context).size.width;
double screenHeight(BuildContext context) => MediaQuery.of(context).size.height;
const INDEX_WORDS = [
'',
'☆',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z'
];
- 更改
pubspec.yaml
文件与CHANGELOG.md
文件中的版本号为1.0.0
,然后执行发布命令
plugin开发
新建logic_plugin_demo
工程
查看插件工程
目录,自带example
示例目录,其中ios
、android
目录下并不包含工程,只是各个平台的代码
pubspec.yaml
文件配置homepage
Plugin插件开发
-
logic_plugin_demo.dart
文件编写代码如下
import 'dart:async';
import 'package:flutter/services.dart';
class LogicPluginDemo {
static const MethodChannel _channel =
const MethodChannel('logic_plugin_demo');
static Future get platformVersion async {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
// 获取电池电量
static Future get platformBatteryLevel async {
int batteryLevel = await _channel.invokeMethod('getPlatformBatteryLevel');
return batteryLevel.toString();
}
}
-
ios/Classes
目录下的LogicPluginDemoPlugin.m
文件修改代码如下,以接收Flutter
发送过来的消息
#import "LogicPluginDemoPlugin.h"
@implementation LogicPluginDemoPlugin
+ (void)registerWithRegistrar:(NSObject*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"logic_plugin_demo"
binaryMessenger:[registrar messenger]];
LogicPluginDemoPlugin* instance = [[LogicPluginDemoPlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"getPlatformVersion" isEqualToString:call.method]) {
result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
// 接收Flutter发送过来的获取电池电量消息
} else if([@"getPlatformBatteryLevel" isEqualToString:call.method]){
int batteryLevel = [self getBatteryLevel];
// 把值包装成对象返回出去
result(@(batteryLevel));
}else{
result(FlutterMethodNotImplemented);
}
}
-(int)getBatteryLevel{
UIDevice * device = UIDevice.currentDevice;
device.batteryMonitoringEnabled = YES;
if (device.batteryLevel == UIDeviceOrientationUnknown) {
return -1;
}else{
return (int)(device.batteryState * 100);
}
}
@end
- 上面代码编写完成之后,在
example/lib/main.dart
文件中进行调试
- 打开
example/ios
目录下的Runner
工程,使用真机查看获取电量情况
-
logic_plugin_demo
插件进行发布
// 检查Plugin包
$ flutter packages pub publish --dry-run
// 指定服务器发布
$ flutter packages pub publish --server=https://pub.dartlang.org
Plugin
插件的发布与Package
包的发布是一样的,注意配置证书(同上)
。
热重载挂载
热重载初探
Flutter
是一套全新的跨平台方案,Flutter
并不像React Native
那样依赖原生应用的渲染,而是自己有一套渲染引擎,并使用Dart
当作Flutter
的开发语言。Flutter
整体框架分为两层,底层是通过C++
实现的引擎部分,Skia
是Flutter
的渲染引擎,负责跨平台的图形渲染。Dart
作为Flutter
的开发语言,在C++
引擎上层是Dart
的Framework
。
Flutter
支持亚秒级热重载,Android Studio
和VSCode
都支持Hot Reload
的特性;但需要区分的是热重载和热更新是两个不同的概念
-
热重载
是在运行调试状态下,将新代码直接更新到执行中的二进制 -
热更新
是在上线后,通过Runtime
或其他方式,改变现有执行逻辑
热重载的搭建是可以做到热更新的
AOT 、JIT
Flutter
支持AOT
(Ahead of time)和JIT
(Just in time)两种编译模式
-
JIT模式
支持在运行过程中进行Hot Reload
,刷新过程是一个增量的过程,由系统对本次和上次的代码做一次snapshot
,将新的代码注入到DartVM
中进行刷新,但有时会不能进行Hot Reload
,此时进行一次全量的Hot Reload
即可 -
AOT模式
则是在运行前预先编译好,这样在每次运行过程中就不需要进行分析、编译、此模式的运行速度是最快的。
Flutter
同时采用了两种方案,在开发阶段采用JIT模式
进行开发;在release阶段
采用AOT模式
,将代码打包为二进制进行发布。
在开发原生应用时,每次修改代码后都需要重新编译,而且运行到硬件设备上;由于Flutter
支持Hot Reload
,可以进行热重载,对项目的开发效率有很大的提升。
由于Flutter
实现机制支持JIT
的原因,理论上来说是支持热更新以及服务器下发代码的;但是由于这样会使性能变差,而且还有审核问题,所以Flutter
没有采用这种方案。
热重载
本质就是通讯,server端
与Dart端
之间进行通讯,接收到Dart端
发生了变化,服务端会告诉Flutter.framework
去进行渲染
- 新建
App
类型工程hotload_demo
- 将
Server端
挂载到hotload_demo
工程
- 进入
~/flutter/packages/flutter_tools
,把flutter_tools
目录内容用Android Studio
打开
-
flutter_tools
工程就相当于server端
,hotload_demo
工程就相当于app端
; -
flutter_tools
添加Dart Command Line App
- 选择挂载的工程,并运行
- 配置
Dart SDK Path
,选择Android Studio
->Preferences...
- 运行
flutter_tools
工程
以server端
在运行,实际上挂载的是hotload_demo
工程;下面进行验证
- 修改
hotload_demo
工程页面title
- 执行
运行命令
查看title
实现了上面的热重载挂载
,我们就可以在flutter_tools.dart
文件中添加断点调试热重载的原理
。