美文网首页
插件开发与热重载原理

插件开发与热重载原理

作者: 浅墨入画 | 来源:发表于2021-12-28 16:55 被阅读0次

    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<StatefulWidget> 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<IndexBar> {
      // 索引条选中 背景色与文字颜色
      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<Widget> 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页面,并进行修改
    <!-- package_example工程的main.dart文件 -->
    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<FriendsPage>
        with AutomaticKeepAliveClientMixin<FriendsPage> {
      @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<Friends> _headerData = [
        Friends(imageAssets: 'images/新的朋友.png', name: '新的朋友'),
        Friends(imageAssets: 'images/群聊.png', name: '群聊'),
        Friends(imageAssets: 'images/标签.png', name: '标签'),
        Friends(imageAssets: 'images/公众号.png', name: '公众号'),
      ];
      final List<Friends> _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<Friends> 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文件中代码抽取出来
    <!-- logic_package_demo.dart文件 -->
    library logic_package_demo;
    // 库中导入即可
    import 'package:flutter/material.dart';
    part 'index_bar.dart';
    
    <!-- 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<IndexBar> {
      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<Widget> 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<String?> get platformVersion async {
        final String? version = await _channel.invokeMethod('getPlatformVersion');
        return version;
      }
      // 获取电池电量
      static Future<String?> 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<FlutterPluginRegistrar>*)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文件中添加断点调试热重载的原理

    相关文章

      网友评论

          本文标题:插件开发与热重载原理

          本文链接:https://www.haomeiwen.com/subject/vigsqrtx.html