本篇文章给大家谈谈flutter离线通知,以及flutter 通知对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
今天给各位分享flutter离线通知的知识,其中也会对flutter 通知进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!
本文目录一览:
Flutter—手机消息推送(notification)
在flutter中使用notifacation,目前可以使用官方的插件 Flutter Local Notifications Plugin 来实现。
在pubspec.yaml中添加
使用该插件的思路大致为:在android中设置权限管理——引入依赖——初始化。
安卓权限管理设置
引入依赖
接下来就可以在项目中初始化使用了,首先在initState()中初始化设置

阿里IM技术分享(六):闲鱼亿级IM消息系统的离线推送到达率优化
本文由阿里闲鱼技术团队逸昂分享,原题“消息链路优化之弱感知链路优化”,有修订和改动,感谢作者
flutter离线通知的分享。
闲鱼的IM消息系统作为买家与卖家的沟通工具,增进理解、促进信任,对闲鱼的商品成交有重要的价值,是提升用户体验最关键的环节。
然而,随着业务体量的快速增长,当前这套消息系统正面临着诸多急待解决的问题。
以下几个问题典型最为典型:
1) 在线消息的体验提升
flutter离线通知;
2) 离线推送的到达率;
3) 消息玩法与消息底层系统的耦合过强。
经过评估,
flutter离线通知我们认为现阶段离线推送的到达率问题最为关键,对用户体验影响较大。
本文将要分享的是闲鱼IM消息在解决离线推送的到达率方面的技术实践,内容包括问题分析和技术优化思路等 ,希望能带给你启发。
(本文已同步发布于: http://im.net/thread-3748-1-1.html )
本文是系列文章的第6篇,总目录如下:
《 阿里IM技术分享(一):企业级IM王者——钉钉在后端架构上的过人之处 》
《 阿里IM技术分享(二):闲鱼IM基于Flutter的移动端跨端改造实践 》
《 阿里IM技术分享(三):闲鱼亿级IM消息系统的架构演进之路 》
《 阿里IM技术分享(四):闲鱼亿级IM消息系统的可靠投递优化实践 》
《 阿里IM技术分享(五):闲鱼亿级IM消息系统的及时性优化实践 》
《 阿里IM技术分享(六):闲鱼亿级IM消息系统的离线推送到达率优化 》(* 本文)
从数据通信链接的技术角度,我们根据闲鱼客户端是否在线,将整体消息链路大致分为强感知链路和弱感知链路。
强感知链路由以下子系统或模块:
1) 发送方客户端;
2) idleapi-message(闲鱼的消息网关);
3) heracles(闲鱼的消息底层服务);
4) accs(阿里自研的长连接通道);
5) 接收方客户端组成。
整条链路的核心指标在于端到端延迟和消息到达率。
强感知链路中的双方都是在线的,消息到达客户端就可以保证接收方感知到。强感知链路的主要痛点在消息的端到端延迟。
弱感知链路与强感知链路的主要不同在于: 弱感知链路的接收方是离线的,需要依赖离线推送这样的方式送达。
因此弱感知链路的用户感知度不强,其核心指标在于消息的到达率,而非延迟。
所以当前阶段,优化弱感知链路的重点也就是提升离线消息的到达率。换句话说, 提升离线消息到达率问题,也就是优化弱感知链路本身 。
下图一张整个IM消息系统的架构图,感受下整体链路:
如上图所示,各主要组件和子系统分工如下:
1) HSF是一个远程服务框架,是dubbo的内部版本;
2) tair是阿里自研的分布式缓存框架,支持 memcached、Redis、LevelDB 等不同存储引擎;
3) agoo是阿里的离线推送中台,负责整合不同厂商的离线推送通道,向集团用户提供一个统一的离线推送服务;
4) accs是阿里自研的长连接通道,为客户端、服务端的实时双向交互提供便利;
5) lindorm是阿里自研的NoSQL产品,与HBase有异曲同工之妙;
6) 域环是闲鱼消息优化性能的核心结构,用来存储用户最新的若干条消息。
强感知链路和弱感知链路在通道选择上是不同的:
1) 强感知链路使用accs这个在线通道;
2) 弱感知链路使用agoo这个离线通道。
通俗了说,弱感知链路指的就是离线消息推送系统。
相比较于在线消息和端内推送(也就是上面说的强感知链路),离线推送难以确保被用户感知到。
典型的情况包括:
1) 未发送到用户设备:即推送未送达用户设备,这种情况可以从通道的返回分析;
2) 发送到用户设备但没有展示到系统通知栏:闲鱼曾遇到通道返回成功,但是用户未看到推送的案例;
3) 展示到通知栏,并被系统折叠:不同安卓厂商对推送的折叠策略不同,被折叠后,需用户主动展开才能看到内容,触达效果明显变差;
4) 展示到通知栏,并被用户忽略:离线推送的点击率相比于在线推送更低。
针对“1)未发送到用户设备”,原因有:
1) 离线通道的token失效;
2) 参数错误;
3) 用户关闭应用通知;
4) 用户已卸载等。
针对“3)展示到通知栏,并被系统折叠”,原因有:
1) 通知的点击率;
2) 应用在厂商处的权重;
3) 推送的数量等。
针对“4)展示到通知栏,并被用户忽略”,原因有:
1) 用户不愿意查看推送;
2) 用户看到了推送,但是对内容不感兴趣;
3) 用户在忙别的事,无暇处理。
总之: 以上这些离线消息推送场景,对于用户来说感知度不高,我们也便称之为弱感知链路。
我们的弱感知链路分为3部分,即:
1) 系统;
2) 通道;
3) 用户。
共包含了Hermes、agoo、厂商、设备、用户、承接页这几个环节。具体如下图所示。
从推送的产生到用户最终进入APP,共分为如下几个步骤:
步骤1 :Hermes是闲鱼的用户触达系统,负责人群管理、内容管理、时机把控,是整个弱感知链路的起点。;
步骤2 :agoo是阿里内部承接离线推送的中台,是闲鱼离线推送能力的基础;
步骤3 :agoo实现离线推送依靠的是厂商的推送通道(如:苹果的 apns通道 、Google的fcm通道、及 国内各厂商的自建通道 。;
步骤4 :通过厂商的通道,推送最终出现在用户的设备上,这是用户能感知到推送的前提条件;
步骤5 :如果用户刚巧看到这条推送,推送的内容也很有趣,在用户的主动点击下会唤起APP,打开承接页,进而给用户展示个性化的商品。
经过以上5个步骤,至此弱感知链路就完成了使命。
弱感知链路的核心问题在于:
1) 推送的消息是否投递给了用户;
2) 已投递到的消息用户是否有感知。
这对应推送的两个阶段:
1) 推送消息是否已到达设备;
2) 用户是否查看推送并点击。
其中: 到达设备这个阶段是最基础的,也是本次优化的核心。
我们可以将每一步的消息处理量依次平铺,展开为一张漏斗图,从而直观的查看链路的瓶颈。
漏斗图斜率最大的地方是优化的重点,差异小的地方不需要优化:
通过分析以上漏斗图,弱感知链路的优化重点在三个方面:
1) agoo受理率:是指我们发送推送请到的数量到可以通过agoo(阿里承接离线推送的中台)转发到厂商通道的数量之间的漏斗;
2) 厂商受理率:是指agoo中台受理的量到厂商返回成功的量之间的漏斗;
3) Push点击率:也就通过以上通道最终已送到到用户终端的消息,是否最终转化为用户的主动“点击”。
有了优化方向,我们来看看优化手段吧。
跟随推送的视角,顺着链路看一下我们是如何进行优化的。
用户的推送,从 Hermes 站点搭乘“班车”,驶向下一站: agoo 。
这是推送经历的第一站。到站一看,傻眼了,只有不到一半的推送到站下车了。这是咋回事嘞?
这就要先说说 agoo 了,调用 agoo 有两种方式:
1) 指定设备和客户端,agoo直接将推送投递到相应的设备;
2) 指定用户和客户端,agoo根据内部的转换表,找到用户对应的设备,再进行投递。
我们的系统不保存用户的设备信息。因此,是按照用户来调用agoo的。
同时: 由于没有用户的设备信息,并不知道用户是 iOS 客户端还是 Android 客户端。工程侧不得不向 iOS 和 Android 都发送一遍推送。虽然保证了到达,但是,一半的调用都是无效的。
为了解这个问题: 我们使用了agoo的设备信息。将用户转换设备这一阶段提前到了调用 agoo 之前,先明确用户对应的设备,再指定设备调用 agoo,从而避免无效调用。
agoo调用方式优化后,立刻剔除了无效调用,agoo受理率有了明显提升。
至此: 我们总算能对 agoo 受理失败的真正原因做一个高大上的分析了。
根据统计: 推送被 agoo 拒绝的主要原因是——用户关闭了通知权限。同时,我们对 agoo 调用数据的进一步分析发现——有部分用户找不到对应的设备。 优化到此,我们猛然发现多了两个问题。
那就继续优化呗:
1) 通知体验优化,引导打开通知权限;
2) 与agoo共建设备库,解决设备转换失败的问题。
这两个优化方向又是一片新天地,我们择日再聊。
推送到达 agoo ,分机型搭乘厂商“专列”,驶向下一站:用户设备。
这是推送经历的第二站。出站查票,发现竟然超员了。
于是乎: 我们每天有大量推送因为超过厂商设定的限额被拦截。
为什么会这样呢?
实际上: 提供推送通道的厂商(没错, 各手机厂商的自家推送通道良莠不齐 ),为了保证用户体验,会对每个应用能够推送的消息总量进行限制。
对于厂商而言,这个限制会根据推送的类型和应用的用户规模设定——推送主要分为产品类的推送和营销类的推送。
厂商推送通道对于不同类型消息的限制是:
1) 对于产品类推送,厂商会保证到达;
2) 对于营销类推送,厂商会进行额度限制;
3) 未标记的推送,默认作为营销类推送对待。
我们刚好没有对推送进行标记,因此触发了厂商的推送限制。
这对我们的用户来说,会带来困扰。闲鱼的交易,很依赖买卖家之间的消息互动。这部分消息是需要确保到达的。
同样: 订单类的消息、用户的关注,也需要保证推送给用户。
根据主流厂商的接口协议,我们将推送的消息分为以下几类,并进行相应标记:
1) 即时通讯消息;
2) 订单状态变化;
3) 用户关注内容;
4) 营销消息这几类。
同时,在业务上,我们也进行了推送的治理——将用户关注度不高的消息,取消推送,避免打扰。
经过这些优化,因为超过厂商限额而被拦截的推送实现了清零。
通过优化agoo受理率、厂商受理率,我们解决了推送到达量的瓶颈。但即使消息被最终送达,用户到底点击了没有?这才是消息推送的根本意义所在。
于是,在日常的开发测试过程中,我们发现了推送的两个体验问题:
1) 用户点击Push有开屏广告;
2) 营销Push也有权限校验,更换用户登陆后无法点击。
对于开屏广告功能,我们增加了Push点击跳过广告的能力。
针对Push的权限校验功能,闲鱼根据场景做了细分:
1) 涉及个人隐私的推送,保持权限校验不变;
2) 营销类的推送,放开权限校验。
以上是点击体验的优化,我们还需要考虑用户的点击意愿。
用户点击量与推送的曝光量、推送素材的有趣程度相关。推送的曝光量又和推送的到达量、推送的到达时机有关。
具体的优化手段是:
1) 在推送内容上:我们需要优化的是推送的时机和相应的素材;
2) 在推送时机上:算法会根据用户的偏好和个性化行为数据,计算每个用户的个性化推送时间,在用户空闲的时间推送(避免在不合适的时间打扰用户,同时也能提升用户看到推送的可能性)。
3) 在推送素材上:算法会根据素材的实时点击反馈,对素材做实时赛马。只发用户感兴趣的素材,提高用户点击意愿。
通过以上我们的分析和技术优化手段,整体弱推送链路链路有了不错的提升,离线消息的到达率相对提升了两位数。
本篇主要和大家聊的是只是IM消息系统链路中的一环——弱感知链路的优化,落地到到具体的业务也就是离线消息送达率问题。
整体IM消息系统,还是一个比较复杂的领域。
我们在消息系统的发展过程中,面临着如下问题:
1) 如何进行消息的链路追踪;
2) 如何保证IM消息的快速到达(见《 闲鱼亿级IM消息系统的及时性优化实践 》);
3) 如何将消息的玩法和底层能力分离;
4) 离线推送中如何通过用户找到对应的设备。
这些问题,我们在以前的文章中有所分享,以后也会陆续分享更多,敬请期待。
[1] Android P正式版即将到来:后台应用保活、消息推送的真正噩梦
[2] 一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践
[3] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等
[4] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等
[5] 从新手到专家:如何设计一套亿级消息量的分布式IM系统
[6] 企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等
[7] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制
[8] 移动端IM中大规模群消息的推送如何保证效率、实时性?
[9] 现代IM系统中聊天消息的同步和存储方案探讨
[10] 新手入门一篇就够:从零开发移动端IM
[11] 移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”
[12] 移动端IM开发者必读(二):史上最全移动弱网络优化方法总结
[13] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递
[14] IM消息送达保证机制实现(二):保证离线消息的可靠投递
[15] 零基础IM开发入门(一):什么是IM系统?
[16] 零基础IM开发入门(二):什么是IM系统的实时性?
[17] 零基础IM开发入门(三):什么是IM系统的可靠性?
[18] 零基础IM开发入门(四):什么是IM系统的消息时序一致性?
(本文已同步发布于: http://im.net/thread-3748-1-1.html )
Flutter注册iOS推送
####总结:
Flutter在iOS中AppDelegate继承自FlutterAppDelegate
flutter离线通知,所以很多方法必须重写父类中
flutter离线通知的方法。iOS的推送注册流程还是一样的。不一样的是需要给推送设置别名或者将设备的deviceToken上传到推送服务器,这一步可以原生实现也可以flutter实现,但是还是需要和flutter进行交互,这是就需要注册一个通道实现这个。通道也可以增加别的一些例如:信息处理等。
正文:
在进行iOS上开发,发现Flutter创建的项目不走didRegisterForRemoteNotificationsWithDeviceToken,起初以为是没有设置UNUserNotificationCenterDelegate,后来发现AppDelegate是继承于FlutterAppDelegate的,
也就是将原生的UIApplicationDelegate的方法都会被FlutterAppDelegate拦截,即使我们不实现didRegisterForRemoteNotificationsWithDeviceToken,我觉得应该有两种方法可以实现:第一种是需要重写父类的推送方法。第二种就是在dart文件中监听系统代理,通过通道回调appdelegate来实现,
下面是百度云推送,重写父类代理的实现:
在didFinishLaunchingWithOptions launchOptions: 中注册原生交互channel
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? ) - Bool {
//Flutter Plugin插件注册
GeneratedPluginRegistrant.register(with: self);
//调用appdelegate 的 设置、注册远程推送
self.requestAuthorization(application: application);
//Flutter原生交互通道
self.BPushChannel();
//注册BPush通道
BPush.registerChannel(launchOptions, apiKey: BPushKey, pushMode: BPushMode.development, withFirstAction: "打开", withSecondAction: "关闭", withCategory: nil, useBehaviorTextInput: true, isDebug: true);
//禁用地理位置信息推送
BPush.disableLbs();
return super.application(application, didFinishLaunchingWithOptions: launchOptions);
}
//MARK:注册远程推送通知
func requestAuthorization(application: UIApplication) {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate;
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in
if granted == true {
DispatchQueue.main.async {
application.registerForRemoteNotifications()
}
}
}
} else if #available(iOS 8.0, *) {
let types:UIUserNotificationType = [.badge , .alert , .sound]
let settings:UIUserNotificationSettings = UIUserNotificationSettings(types: types, categories: nil)
application.registerUserNotificationSettings(settings)
} else {
let types:UIRemoteNotificationType = [UIRemoteNotificationType.alert, UIRemoteNotificationType.badge, .sound]
application.registerForRemoteNotifications(matching: types)
}
}
//百度推送通道
func BPushChannel() - Void {
//获取系统的跟控制器
let controller = self.window.rootViewController
//建立rootViewController和Flutter的通信通道
let pushChannel = FlutterMethodChannel.init(name: channelNameForPush, binaryMessenger:controller as! FlutterBinaryMessenger)
//设置Method回调 FlutterMethodCall包含
flutter离线通知了method的Name,ID等信息, FlutterResult是给Native和Flutter的通信回调
pushChannel.setMethodCallHandler { (FlutterMethodCall, FlutterResult) in
print("pushChannel");
}
//绑定channelId到服务器
let pushBind = FlutterMethodChannel.init(name:channelNameForPushBind, binaryMessenger: controller as! FlutterBinaryMessenger)
pushBind.setMethodCallHandler { (FlutterMethodCall, FlutterResult) in
//FlutterResult();结果回调,回调的结果只能为string类型
if(self.channelId.isEmpty){
FlutterResult(FlutterMethodNotImplemented);
} else{
print("channelId",self.channelId);
let dic : Dictionary<String,String = ["channelId":self.channelId];
let data = try? JSONSerialization.data(withJSONObject: dic, options: [])
let encodingStr = String(data: data!, encoding: String.Encoding.utf8)!
//将信息传到Flutter,
FlutterResult(encodingStr);
}
}
}
// 重写远程推送通知 注册成功
override func application(_ application: UIApplication , didRegisterForRemoteNotificationsWithDeviceToken deviceToken:Data) {
// 向云推送注册 device token
print("deviceToken = %@", deviceToken);
BPush.registerDeviceToken(deviceToken as Data)
// 绑定channel.将会在回调中看获得channnelid appid userid 等
BPush.bindChannel(completeHandler: { (result, error) - Void in
if ((result) != nil){
self.channelId = BPush.getChannelId();
BPush.setTag("MyTag", withCompleteHandler: { (result, error) - Void in
if ((result) != nil){
}
})
}
})
super.application(application, didRegisterForRemoteNotificationsWithDeviceToken:deviceToken)
}
// 重写注册失败
override func application(_ application: UIApplication , didFailToRegisterForRemoteNotificationsWithError error: Error ) {
print("deviceToken = %@", error)
super.application(application, didFailToRegisterForRemoteNotificationsWithError: error)
}
**如果解决了你的问题,点个赞呗!**
Flutter入门这一篇效率文章就够了
本文面向 Flutter 初学者flutter离线通知,旨在用易懂的方式带大家入门。除了 Flutter 代码,还会介绍到语法、原理、特性等基础知识。相信本文能帮助你学习和理解 Flutter。
flutter离线通知我们先看一下目前的一些跨平台方案,从前端渲染的角度来分类的话,大致可以分为以下几种方案。
WebView 渲染
这种方案就很好理解,现在很多项目都会嵌入 H5 的页面。就是用 JavaScript 等前端技术进行开发,在客户端上用 WebView 来进行渲染。微信小程序目前使用的就是这种方案。
它的优点很明显,使用成熟的前端技术进行开发,学习成本低,开发效率高,并且支持动态发布代码。
但缺点也很明显,在性能体验上,和原生还是存在较大差距的。
原生控件渲染
既然 WebView 的性能不够好,于是就有了使用原生控件进行渲染的方案。这种方案,同样也是使用 JavaScript 开发,区别是它最终是调用原生控件进行渲染的。这种方案的代表是 Facebook 的 React Native。
由于使用原生控件进行渲染,性能体验也会更接近原生。但也只是更接近,和原生还是有差距的,因为它需要频繁的进行 JavaScript 和原生之间的通信,这个通信效率是比较低的。
另外,由于需要适配各个平台的控件,那就有可能出现,系统控件更新了,而框架本身还没有更新,由此产生了一些问题。换句话说,这种方案是受到原生控件限制的。
绘图引擎渲染
接下来就是主角了。
在前端,如果完全不使用原生控件,我们可以通过系统的绘图 API 绘制出一个用户界面。从这个角度出发,可以在各个平台使用一个统一接口的绘图引擎来进行界面绘制,这个引擎最终调用的是系统的 API 绘制的。这样的话,它的性能可以做到接近原生,并且又不受原生控件的限制,在不同平台上能够做到 UI 统一。
Flutter 就是这样的一个开发框架。
一个跨平台 UI 解决方案
Flutter 是由 Google 开发的,一个跨平台 UI 解决方案。换句话说,它原则上只管 UI 的问题,如果涉及到平台本身的一些功能,比如调用蓝牙、摄像头,一般还是需要原生代码去操作。但现在也会有一些第三方库帮我们解决这些问题。
绘图引擎 Skia
Flutter 使用 Skia 作为它的绘图引擎。Skia 已经被 Google 收购,目前很多 Google 旗下的产品都是用 Skia 绘制的,包括 Android。
Android 内置了 Skia,但 iOS 没有,所以在打 iOS 安装包的时候,会把 Skia 一起打进去。这就导致了,用同一份 Flutter 代码打包之后,iOS 的包要比 Android 的包大一些。
开发语言 Dart
Flutter 使用的开发语言,叫 Dart。Dart 也是 Google 自家的,它是一门面向对象的语言,从它身上会看到一些其flutter离线通知他开发语言的影子。学习起来难度不大的。
前面讲跨平台方案的时候,可以发现别的方案基本都是用 JavaScript 作为开发语言的,但为什么 Flutter 不用?就因为 Dart 是谷歌自家的吗?这个问题先留着,我们后面会提到。
这里部分就简单点带过了,具体的搭建流程可以在官网查看:
https://flutter-io.cn/docs/get-started/install
主要的搭建步骤如下:
下载 Flutter SDK
官网下载地址flutter离线通知:
https://flutter.dev/docs/development/tools/sdk/releases
由于在国内访问可能受限,官方为中国开发者搭建了镜像:
https://flutter.dev/community/china
更新环境变量
解压后,将 flutter\bin 的全路径添加到环境变量 PATH 中。
安装开发工具
理论上,任何文本编辑器都可以用来开发 Flutter 应用,但推荐的开发工具是 Android Studio、IntelliJ 以及 VS Code。因为在这些开发工具上,可以安装官方的 Flutter 和 Dart 插件,得到更好的开发体验。文章里使用 Android Studio 来演示。
如果你打算开发 iOS 应用,则还需要安装 Xcode。
安装插件
在开发工具的插件设置中,安装上面说到的 Flutter 和 Dart 插件。Flutter 插件用于支持 Flutter 的运行、调试、热重载等功能,而 Dart 插件则提供了代码的输入校验、代码补全等功能。
万物始于 Hello World,我们先来创建一个显示 Hello World 的 Flutter 项目。
在 Android Studio 的欢迎页面选择 Start a new Flutter project ,或者通过菜单栏的 File New New Flutter Project ,创建一个新的 Flutter 项目。
创建好的项目里面包含了 android 和 ios 两个文件夹,它们是标准的 Android 和 iOS 项目。我们的 Flutter 代码,存放在 lib 文件夹里。项目创建好后,会默认带一个计数器的示例,我们不管它,把 main.dart 的代码改成 Hello World:
启动一个模拟器,或者连上真机,点击 Run 运行一下,就能看这样一个界面了:
具体代码先混个眼熟就好,具体的后面会再讲到。
在写 Flutter 之前,还要先跟大家简单介绍一下 Dart 的语法。如果你有 Java 或 JavaScript 的开发经验,以及面向对象的编程思想,学起来是很快的。
我们可以在 test 文件夹下新建一个 dart 文件,用来写测试代码。
指定类型
var
但和 JavaScript 不同的是,以下代码在 JavaScript 是不会报错的,但在 Dart 里会报错:
Object
如果非要上面这样写,那也可以。把 var 换成 Object 就不报错了:
和 Java 类似,Object 是所有对象的根基类。但是这样的话,如果想打印一下 num 的字符串长度,是会报错的:
因为 length 是属于 String 的,但系统只知道 num 是一个对象,并不知道它是一个 String。
dynamic
如果还是非要这样写,那也可以。Dart 有一个特有的关键字 dynamic,把 Object 改成 dynamic 就不报错了:
我们运行一下这个文件,可以在控制台看到正确打印出了字符串长度。
函数
dynamic
在 Dart 里,函数也是可以不写返回类型的,不写的话会被当做 dynamic 来处理。这样的话,函数的类型就是 return 的类型,如果没有 return 则是 void 类型。比如可以这样:
运行之后是能正确打印出字符串长度的。
用于传参
Dart 里的函数也是一个对象,所以可以把函数作为参数来传递,比如:
可选参数
在 Dart 的函数传参里,有一个叫可选参数的概念,我们以文字控件 Text 为例,在源码里可以看到 Text 的构造函数是这样的:
首先,在参数里有一个 data,它是要显示的文字内容,是一个必填项。而 data 后面的一堆参数,是用一个大括号括起来的,这些参数就叫做可选参数,意思是这些参数可传可不传。
假如我们要显示一个比较长的文字,又想限制它最多显示两行,就可以这样来创建一个 Text:
可选参数,在 Flutter 里面用的非常多。
异步
Future
在 Dart 里使用 Future 来处理异步任务,比如我们现在延时一秒打印 666,代码如下:
Future 的语法和 Promise 非常像。任务执行成功会调用 then,执行失败会调用 catchError,而无论成功还是失败,都会调用 whenComplete。
async/await
如果你不喜欢上面那种写法,或者是想把异步转成同步,就可以用 async 和 await 这两个关键字来转换。
我们把上面的代码转换一下,写一个 getString 方法,返回的类型是 Future,它会延时返回一个字符串。在 main 函数后面加上 async 关键字,在 getString() 前面加上 await,代码如下:
运行之后可以看到,能正常延时一秒后,把字符串打印出来。这里 getString() 返回的类型是 Future,而 await getString() 则是返回了延时之后返回的字符串。await 要在 async 的函数里面才能使用。
async 和 await 其实是一个语法糖,它最终也是转换成 Future 调用链的形式执行的。
接下来回到 Flutter,Flutter 里最重要的一个概念是 Widget(下面翻译作控件)。
在原生开发里面,我们可能会在界面上区分,这是一个 View,这是一个 Layout,这是一个 View Controller。但在 Flutter 里面,它们全都属于一个统一的模型 Widget。可以说,在 Flutter 界面里,所有东西都是 Widget。
以前学面向对象的时候,我们都听过一句话,叫万物皆对象。我这里套用一下,在 Flutter 里, 万物皆控件 。
具体有哪些控件,我做了一下简单的分类。
根控件
所有的控件都属于 StatefulWidget 或 StatelessWidget 。它们的区别是,StatefulWidget 拥有状态 State ,而 StatelessWidget 没有。
StatefulWidget
当一个控件是可变的时候,就要使用 StatefulWidget 来构建。StatefulWidget 本身不可变,但它持有的状态 State 是可变的。
StatelessWidget
当一个控件状态是固定不可变的时候,就可以使用 StatelessWidget。前面我们写的 Hello World 就是使用 StatelessWidget。
容器控件
容器类控件一般是将某些属性或配置,作用在它的子控件上,比如控件所在的宽高、背景、位置等。
常用的容器控件有 Container、Center、Padding 等。
布局控件
布局控件可以类比作原生开发中的 Layout,通常它会拥有一个 children 的属性,用于接收一个控件数组,对这些控件进行特定的排版。
常用的布局控件有 Row、Column、Stack、Flex 等。
基础控件
基础控件就是常用的文字、按钮、图片等控件。
常用的基础控件有 Text、TextField、Button、Image 等。
功能控件
在 Flutter 里还有一类控件,它们不影响 UI 布局,但带有一些特定的功能,比如页面跳转、事件监听、定义主题等。我们把这一类控件称作功能控件。
常用的功能控件有 Navigator、NotificationListener、Theme 等。
开始写 Flutter 代码了。还记不记得,在 Flutter 项目创建之后,是自带一个计数器 demo 的,现在我们用自己的代码实现一遍。代码修改成如下:
运行之后,就可以看到这样的界面了:
按钮每点击一次,数字就会加一。下面我们来分析一下这段代码,看下里面用到的一些 Widget。
StatefulWidget
由于页面中的数字是跟随状态变化的,所以该页面改用 StatefulWidget。StatefulWidget 并不会直接返回一个 Widget,而是返回状态 State,在 State 里再返回 Widget。
Scaffold
Scaffold 是一个标准的 Material Design 页面,它包含了标题栏、浮动按钮、侧滑菜单、底部导航栏等配置。我们这里用到了标题栏 appBar、页面内容 body、浮动按钮 floatingActionButton。
AppBar
AppBar 就是标题栏,通过查看控件的构造方法,我们可以知道它可配置的属性。
AppBar 的可选参数除了标题 title,还可以配置标题前的内容 leading,右侧的操作按钮 anctions,控件垂直高度 elevation 等。我们只传了 title,其他属性都用默认值。
Center
Center 是一个容器类控件,它的作用就是让它的子控件居中显示。
FloatingActionButton
熟悉安卓开发的应该对这个控件比较熟悉,它就是页面右下角一个特定样式的 Button,参数里面的 onPressed 是一个必填项,要传一个点击之后的回调函数。
根据这个例子,下面给大家介绍一下 Flutter 两个比较重要的特性。
点击 Button 之后,我们把 num 变量加一,并使用 setState 通知状态发生了改变,Flutter 会根据新的状态更新 UI。如果有接触过小程序开发,setState 就和小程序的 setData 类似。
在 Flutter 里面我们不需要用 set 方法来更新 UI,可变控件是和状态绑定的,这就是 Flutter 的响应式 UI 编程。
在 Android Q 和 iOS 13 里都加入了暗黑模式,我们也换一个暗黑主题来玩一下。MaterialApp 里有一个 theme 的属性,我们把它配置一下:
这次改完之后不点 Run 了,我们点一下闪电图标 Flutter Hot Reload ,就能看到界面发生了变化:
这就是 Flutter 的 热重载 ,在修改完代码之后,通过热重载就能马上在设备上看到修改结果,可以很大程度上增加开发效率。
下面再给大家介绍几个 Flutter 里的常见操作。
在 Flutter 里,使用 Navigator 来管理页面跳转,比如要跳转到一个 NewPage 可以这样写:
进栈使用 push,出栈则是 pop。
使用 MaterialPageRoute 会模拟出 Android 上页面跳转的过场效果。
我们来看看怎么显示一张本地图片。
先在根目录新建一个存放图片的文件夹,比如叫 images,把图片 picture.png 放进去。
找到根目录下的 pubspec.yaml 文件,这个便是 Flutter 依赖配置文件,我们需要在这里配置一下刚才的图片:
这样,我们就能使用 Image 控件把这张图片显示出来了:
和 node 的 npm 以及 Android 的 jcenter 类似,Flutter 也拥有一个公共仓库 pub.dev。pub.dev 是 Google 官方的 Dart 仓库,在上面可以找到我们需要的包和插件。
Flutter 本身没有 Toast,我们来接入一个。在 pub.dev 上搜索后,我决定使用 fluttertoast:
按照说明,在 pubspec.yaml 文件里的 dependencies 下配置:
点一下 Android Studio 右上角的 Packages get 同步之后就可以使用了:
我们上面使用的都是 Material Design 的控件,它们都是在 flutter/material.dart 包里面的。如果要使用 iOS 风格的控件,则要用到 flutter/cupertino.dart 包:
iOS 风格的控件,基本都以 Cupertino 开头。我们把计时器页面里的控件替换一下:
效果如下:
代码的部分就到这里了,接下来跟大家聊一下编译方式,编程语言的编译方式有两种。
关于它们孰优孰劣,就要看从哪个角度去对比了。JIT 的话,它的一大特点就是支持动态发布代码,也就是支持热更新。但要是从性能的角度考虑,AOT 会更好,因为在运行的时候不用再进行编译的操作的,运行的效率会更高一些。
回到我们一开始的时候留下的问题,为什么别的跨平台方案都是用 JavaScript,而 Flutter 要用 Dart 来开发。JavaScript 的编译方式是 JIT 的,它不支持 AOT。而 Dart 同时支持 JIT 和 AOT。
Flutter 在开发阶段使用 JIT,让我们用上了热重载,增加了开发效率。在打包时改用 AOT,保证了正式版应用的性能。
最后讲一下大家比较关心的一个东西,Flutter 是否支持热更新?前面说到 Dart 支持 JIT,所以从技术层面它是支持的。但是目前是不支持的,在官方的计划文档中,可以看到:
至于原因,官方在这里进行了说明。总的来说,是由于政策的限制,以及出于对性能和安全性的考虑,暂时不支持了。
到这就结束啦。由于想把 Flutter 基础在一篇内讲完,没有涉及太多细节,如果要写 Flutter 代码还需要深入学习。但相信理解之后再学,会轻松很多。
最近公司做技术分享写的文章的demo
Flutter中的InheritedWidget状态管理
1.InheritedWidget是什么?
InheritedWidget是Flutter中非常重要的一个功能型组件,它提供了一种数据在widget树中从上到下传递、共享的方式,比如我们在应用的根widget中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget中来获取该共享的数据!这个特性在一些需要在widget树中共享数据的场景中非常方便!如Flutter SDK中正是通过InheritedWidget来共享应用主题(Theme)和Locale (当前语言环境)信息的。
InheritedWidget和React中的context功能类似,和逐级传递数据相比,它们能实现组件跨级传递数据。InheritedWidget的在widget树中数据传递方向是从上到下的,这和通知Notification的传递方向正好相反。
2.源码分析
InheritedWidget
先来看下InheritedWidget的源码:
abstract class InheritedWidget extends ProxyWidget { const InheritedWidget({ Key key, Widget child }): super(key: key, child: child); @override InheritedElement createElement() =InheritedElement(this); @protected bool updateShouldNotify(covariant InheritedWidget oldWidget);}
它继承自ProxyWidget:
abstract class ProxyWidget extends Widget { const ProxyWidget({ Key key, @required this.child }) : super(key: key); final Widget child;}
可以看出Widget内除了实现了createElement方法外没有其他操作了,它的实现关键一定就是InheritedElement了。
InheritedElement 来看下InheritedElement源码
class InheritedElement extends ProxyElement { InheritedElement(InheritedWidget widget) : super(widget); @override InheritedWidget get widget = super.widget; // 这个Set记录了所有依赖的Elementfinal Map<Element, Object _dependents = HashMap<Element, Object();
//该方法会在Element mount和activate方法中调用,_inheritedWidgets为基类Element中的成员,用于提高Widget查找父节点中的InheritedWidget的效率,它使用HashMap缓存了该节点的父节点中所有相关的InheritedElement,因此查找的时间复杂度为o(1) @override void _updateInheritance() {final Map<Type, InheritedElement incomingWidgets = _parent?._inheritedWidgets;if (incomingWidgets != null) _inheritedWidgets = HashMap<Type, InheritedElement.from(incomingWidgets); else _inheritedWidgets = HashMap<Type, InheritedElement(); _inheritedWidgets[widget.runtimeType] = this; }
//该方法在父类ProxyElement中调用,看名字就知道是通知依赖方该进行更新了,这里首先会调用重写的updateShouldNotify方法是否需要进行更新,然后遍历_dependents列表并调用didChangeDependencies方法,该方法内会调用mardNeedsBuild,于是在下一帧绘制流程中,对应的Widget就会进行rebuild,界面也就进行了更新 @override void notifyClients(InheritedWidget oldWidget) { assert(_debugCheckOwnerBuildTargetExists('notifyClients'));for (Element dependent in _dependents.keys) { notifyDependent(oldWidget, dependent); } }
其中_updateInheritance方法在基类Element中的实现如下:
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
总结来说就是Element在mount的过程中,如果不是InheritedElement,就简单的将缓存指向父节点的缓存,如果是InheritedElement,就创建一个缓存的副本,然后将自身添加到该副本中,这样做会有两个值得注意的点:
InheritedElement的父节点们是无法查找到自己的,即InheritedWidget的数据只能由父节点向子节点传递,反之不能。
如果某节点的父节点有不止一个同一类型的InheritedWidget,调用inheritFromWidgetOfExactType获取到的是离自身最近的该类型的InheritedWidget。
看到这里似乎还有一个问题没有解决,依赖它的Widget是在何时被添加到_dependents这个列表中的呢?
回忆一下从InheritedWidget中取数据的过程,对于InheritedWidget有一个通用的约定就是添加static的of方法,该方法中通过inheritFromWidgetOfExactType找到parent中对应类型的的InheritedWidget的实例并返回,与此同时,也将自己注册到了依赖列表中,该方法的实现位于Element类,实现如下:
@overrideT dependOnInheritedWidgetOfExactType
// 这里通过上述mount过程中建立的HashMap缓存找到对应类型的InheritedElement final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];if (ancestor != null) { assert(ancestor is InheritedElement);return dependOnInheritedElement(ancestor, aspect: aspect); } _hadUnsatisfiedDependencies = true; return null;}
@overrideInheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) { assert(ancestor != null);
// 这个列表记录了当前Element依赖的所有InheritedElement,用于在当前Element deactivate时,将自己从InheritedElement的_dependents列表中移除,避免不必要的更新操作 _dependencies ??= HashSet<InheritedElement(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect);return ancestor.widget;}
3.如何使用InheritedWidget
1)、创建一个类继承自Inheritedwidget
class InheritedContext extends InheritedWidget{ final InheritedTestModel inheritedTestModel; InheritedContext({ Key key, @required this.inheritedTestModel, @required Widget child}): super(key: key, child: child);static InheritedContext of (BuildContext context) { return context.dependOnInheritedWidgetOfExactType<InheritedContext(); } @override bool updateShouldNotify(InheritedContext oldWidget) { return inheritedTestModel != oldWidget.inheritedTestModel; }}
2)、InheritedTestModel类为数据容器(这里定义了一个List<int数据源)
class InheritedTestModel{ final List _list; InheritedTestModel(this._list); List getList(){ return _list; }}
class ArrayListData{ static List _list ;static List getListData (){ _list = new List(); _list .add(1); _list .add(2); _list .add(3); _list .add(4);return _list ; }}
3)、定义一个Widget 使用 InheritedContext类的数据 InheritedTestModel
class ListDemo extends StatefulWidget{ @override State createState() { return new ListDemoState(); }}class ListDemoState extends State<ListDemo{List _list; InheritedTestModel _inheritedTestModel; Timer _timer; Duration oneSec = const Duration(seconds: 1); @override void initState() { _list = ArrayListData. getListData (); _inheritedTestModel = new InheritedTestModel(_list); _timer = Timer.periodic(oneSec, (timer) { _doTimer(); }); } void _doTimer() { for(int i = 0; i < _list.length; i++){ _list[i] = _list[i]+ 1; } setState(() { _inheritedTestModel = new InheritedTestModel(_list); }); }Widget _buildBody() { return Container(child: ListDemo2(), ); } @override Widget build(BuildContext context) { return InheritedContext(inheritedTestModel: _inheritedTestModel, child: Scaffold(appBar: AppBar(title: Text("ListDemo"), actions: <Widget[ IconButton(icon: Icon(Icons. add ), ) ],), body: _buildBody(), ), ); } @override void dispose() { super.dispose();if (_timer != null) { _timer.cancel(); } }}
4)、在ListDemo中通过Timer更新InheritedTestModel 中的数据,然后再下一个Widget中获取更新的数据作为展示
class ListDemo2 extends StatefulWidget{ @override State createState() { return new ListDemoState2(); }}class ListDemoState2 extends State<ListDemo2{InheritedTestModel _inheritedTestModel; Widget _buildListItem(BuildContext context,int index) { return Container(height: 50, width: 100, alignment: Alignment. center , child: Text(_inheritedTestModel.getList()[index].toString()), ); }Widget _buildBody() { _inheritedTestModel = InheritedContext. of (context).inheritedTestModel;return Container(child: ListView.builder(itemBuilder:(context, index)=_buildListItem(context,index),itemCount: _inheritedTestModel.getList().length,), ); } @override Widget build(BuildContext context) { return _buildBody(); }}
这样就可以在父widget中更新数据,子View不需任何操作直接从数据容器InheritedTestModel 中获取到更新后的新数据
这是一个数据共享的简单的例子,基本的流程,大致就是A去更新B的数据,A和B有一个共同的父类,实现数据的共享
4.上面说了原理和基本的使用,但是在实际项目当中,我当然不建议这样来使用,Google 已经为我们封装好了功能更加强大的插件Provider,其内部原理就是基于InheritedWidget来实现的,我们理解了基本原理,可以更好的在项目中运用Provider
关于flutter离线通知和flutter 通知的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。
flutter离线通知的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于flutter 通知、flutter离线通知的信息别忘了在本站进行查找喔。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。