插件开发与热重载原理

Flutter的三方工具有两种,一种是插件(Plugin),一种是(Package)。这两种差别在于Plugin不仅包含Dart代码,还包含了iOS以及安卓的原生代码,比如常见的image_picker;而Package仅仅是Dart代码库。

支持空安全的库

package开发

下面我们开发一个支持空安全的库,就用之前的wechat_demo工程中通讯录页面右侧的IndexBar进行练习

  • 新建Package工程logic_package_demo,注意Project type选择Package
image.png
  • 查看工程目录,发现并不包含iOS安卓目录
image.png
  • 空安全版本相关配置依然在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账号授权。

image.png

有时就算是翻墙也并不能解决问题,因为我们还配置了相关的镜像。Flutter官方就建议过镜像的配置,所以我们在发布插件或者包的时候,就会因为镜像出现下面错误:

image.png

解决办法:指定服务器发布

$ flutter packages pub publish --server=https://pub.dartlang.org
  • 证书问题报错
image.png

发布包的一个版权问题,这个证书需要在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;
image.png

这个时候会有一个问题,点击Pub get的时候只是把代码引入到了wechat_demo项目,图片资源并没有引入,这个时候使用Package包就会报错,下面进行优化...

优化package

本地资源文件也上传到服务器,让使用者能够成功引入图片资源

  • images图片资源要放入lib目录下

    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 # 项目版本,进行版本升级,否则不能发布)
CHANGELOG.md文件也升级到0.0.2版本
  • 发布完成之后,wechat_demo工程进行引入使用
指定0.0.2版本
配置图片资源

这里的图片资源引用指定到了具体图片,不推荐这种方式。

  • 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网站查看提升包的分数进行优化
image.png
  • 添加项目描述
  • 添加example,使用Package包的示例
  • 增加API数量

下面主要是添加使用示例进行优化,提升包的评分

  • 新建package_example空工程,跟logic_package_demo放入同一目录下
  • pubspec.yaml文件进行配置,并点击Pub get进行引入
image.png
  • 使用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导致页面图片加载不出来
image.png
  • 我们不配置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工程删除下图中气泡图片进行测试
image.png
  • pubspec.yaml文件进行配置,重新运行工程
image.png
  • 这个时候通讯录页面头部的Cell图标还没有显示出来,我们修改package_example工程的main.dart文件
image.png
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,
  )),
), //图片
pubspec.yaml文件配置图片并运行package_example工程
  • 把示例代码导入logic_package_demo工程,重新发布Package
image.png

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,然后执行发布命令
pub.dev网站查看

plugin开发

新建logic_plugin_demo工程

新建Plugin工程

查看插件工程目录,自带example示例目录,其中iosandroid目录下并不包含工程,只是各个平台的代码

查看插件目录

pubspec.yaml文件配置homepage

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包的发布是一样的,注意配置证书(同上)

热重载挂载

热重载的本地Server服务器
热重载初探

Flutter是一套全新的跨平台方案,Flutter并不像React Native那样依赖原生应用的渲染,而是自己有一套渲染引擎,并使用Dart当作Flutter的开发语言。Flutter整体框架分为两层,底层是通过C++实现的引擎部分,SkiaFlutter的渲染引擎,负责跨平台的图形渲染。Dart作为Flutter的开发语言,在C++引擎上层是DartFramework

Flutter支持亚秒级热重载,Android StudioVSCode都支持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工程
  1. 进入~/flutter/packages/flutter_tools,把flutter_tools目录内容用Android Studio打开
Flutter SDK目录
  1. flutter_tools工程就相当于server端hotload_demo工程就相当于app端
  2. flutter_tools添加Dart Command Line App
添加Dart Command Line App
  1. 选择挂载的工程,并运行
配置挂载工程
配置Program arguments
  • 配置Dart SDK Path,选择Android Studio -> Preferences...
配置Dart SDK Path
  • 运行flutter_tools工程
image.png

server端在运行,实际上挂载的是hotload_demo工程;下面进行验证

  1. 修改hotload_demo工程页面title
image.png
  1. 执行运行命令查看title
image.png
image.png

实现了上面的热重载挂载,我们就可以在flutter_tools.dart文件中添加断点调试热重载的原理

你可能感兴趣的:(插件开发与热重载原理)