Flutter帧率监控 | 由浅入深,详解获取帧率的那些事

前言

做线上帧率监控上报时,少不了需要弄明白如何通过代码获取实时帧率的需求,这篇文章通过图解配合Flutter性能调试工具的方式一步步通俗易懂地让你明白获取帧率的基础知识,以后再也不愁看不懂调试工具上指标了。

说说 List<FrameTiming>

Flutter 中通过如下方式监听帧率,addTimingsCallback 涉及到帧调度知识,感兴趣可以看看这篇Flutter 帧调度过程。

这里重点说说 List<FrameTiming>。

List<FrameTiming>从哪里来

addTimingsCallback 定义:

List<FrameTiming>可简单理解成:引擎层到框架层的帧数据流。

List<FrameTiming>何时有值

List<FrameTiming>则表示一系列实时帧信息。

如点击屏幕按钮,引擎将传递系列帧信息到框架层:“框架层,屏幕发送了变化,准备回调数据更新了!”。如果用户未操作,addTimesCallback 则不会回调。

因此 ,addTimesCallback(List<FrameTiming>)只有用户操作界面时参数才有值

List<FrameTiming>中帧存储顺序

List<FrameTiming>中 0 的位置是第一帧,last 是最新一帧。 最新的帧永远在最后面

再说说 FrameTiming

通过这个单词不难猜测 Frame 表示帧,加上 Timing 可以理解成实时变化的帧。FrameTiming 是一个用来存储实时帧信息的数据结构

FrameTiming 定义:

这里列了下我认为最重要的几个属性:

前置知识简单说明

理解上述属性前需了解渲染相关知识,不清楚的可以看看Vsync 机制 和 卡顿产生原因 。

核心思想
图像内容展示到屏幕的过程需要 CPU 和 GPU 共同参与。CPU 负责计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。之后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。

FrameTiming 在帧中的表示

当在应用中操作时候,就会产生连续的帧,如图:

每两个柱形一起表示一帧:ui 表示 cpu 耗时,raster 表示 gpu 耗时。

每帧细化后如下图,其中标注 ①②③④ 对应 FrameTiming 中的四个主要属性。而其中:

  • ui 在 FrameTiming 中有对应衍生变量叫 buildDuration 。
  • Raster 在 FrameTiming 中用 RasterDuration 表示。

同时可推导出 FrameTiming 中相关衍生变量与上述重点关注属性关系:

④-① = totalSpan:同步信号开始到栅格化时间

②-① = vsyncOverhead:同步信号接受后到 ui 构建之间延迟。

③-② = buildDuration:ui 构建过程总时间。

④-③ = rasterDuration:栅格化过程总时间。

totalSpan 与 buildDuration+rasterDuration 关系

通过代码验证 Flutter 调试工具 PerformanceOverlay 中 Timing 每帧 ui 值和 ration 值与 vsyncstart、buildstart、buildFinish、rasterStart、rasterFinish 关系。

输出:

代码中,11 行是 ui 构建 + 栅格化时间,17 行是 totalSpan 时间, 22 行中是 vsyncOverhead + ui 构建 + 栅格化时间 这个值最终和才等于 totalSpan 值。

这里有个误区, 网上很少人关注 totalSpan 与 buildDuration+rasterDuration 关系,好像默认就是相等的。其实,totalSpan 不等于 Timing 中 ui + raster 值而是 Vsync 信号接受后构建之前延迟 vsyncOverhead+cpu 构建耗时 + gpu 耗时

通过上述案例和 totalSpan 定义很容易佐证这点:

如何获取帧率

核心思路

  1. 将原始帧数据 List降噪保留最新关注帧数。
  2. 通过公式 FPS≈ REFRESH_RATE * 实际绘制帧数 / 理论绘制帧数 。

如何降噪

  • 从原生数据中筛查最新关注帧数,其他都干掉。

    如下,通过栈方式调换了存储方式更容易操作,然后将栈中老的干掉只保留最新的关注 100 条。

  • 将位于不同帧的无效数据过滤掉。

    如下,以刷新率为 60 举例,如果一帧之间的时间 > 16.6 *2,该帧就位于不同帧中,因为一帧最大时间也就是 16.6ms。

如何计算

代码如下:

这里拆解下其中逻辑,方便理解。

有 5 帧,其中在实际绘制过程中 f① 和 f② 都是在正常时间范围内绘制,f③ 则会绘制耗时,跨越 2 帧。

假设 f①,f②,f③ 绘制总耗时为 P1, P2, P3 则:

  1. 理论绘制帧数 = (P1 / 16.6)+ 1 + (P2 / 16.6) + 1 + (P3 / 16.6) + 1 图中明显可以看到 P1 和 P2 < 16.6, 而 P3 > 16.6 *2 ,所有理论绘制帧数 = 0 +1 + 0 + 1 + 2 + 1 = 5。

  2. 实际绘制帧数 = 3 。

  3. 本来正常应该绘制 5 帧,但是实际绘制 3 帧,取比值表示实际绘制能力,根据 FPS≈ REFRESH*RATE * 实际绘制帧数 / 理论绘制帧数 。 即 FPS = 3 _ 60 / 5。

完整代码

效果展示

这就结束了?

上面代码在刷新率为 60HZ 的手机上每秒绘制帧时间为 16.6 是没有问题的,但是如果在其他帧率的手机上,比如 90HZ(OnePlus 7 Pro), 120HZ(Redmi K30)上就会存在问题。

  1. 代码中写死了 REFRESH_RATE = 60 。
  2. maxframes = 100 也有问题,如果在 60HZ 手机上取 100 帧绰绰有余,在 120HZ 手机上的话,每秒绘制 120 帧显然不够。

如何获取帧率(改进版)

思路:通过通道获取各系统提供的刷新率获取方式,然后更新上述代码中的刷新率。

获取各系统帧率

在 Android 和 ios 平台提供了获取帧率的方法。

  • 对于 Android 通过 WindowManager 获取刷新率:

  • 对于 iOS 从  CADisplayLink获取刷新率:

定义统一获取接口并实现(以安卓为例)

定义接口

安卓中获取方式

最终修改点

  1. 最大帧率数修改成 120。
  2. fpsHZ 这个值通过插件动态获取。
  3. 时间间隔也同步修改下,也就是 16.6(60hz 的时候)。
  4. 最后 fps 计算公式中的刷新率同步修改成 fpsHZ。

总结

本文重点讲解了 FrameTiming 结构在帧显示过程中的对应关系,图解获取准确帧的算法,最后完善了获取帧的逻辑。

总体来说网上能搜到的我这里都有,在学习过程中遇到 FrameTiming 结构和帧率计算方法这两个点觉得不好理解,不够系统,就重点介绍争取深入浅出表达出来。不足之处还望各位大佬指出,谢谢!

如果觉得文章对你有帮助,点赞、收藏、关注、评论,一键四连支持,你的支持就是我创作最大的动力。

❤️ 本文原创听蝉 公众号:码里特别有禅 欢迎关注原创技术文章第一时间推送 ❤️

PS: 文中所有源码获取方式:公众号后台回复 “fps”

参考链接

如何代码获取 Flutter APP 的 FPS - Yrom's

Flutter 如何更加准确地获取 FPS | 区长

Flutter 性能计算之流畅性 fps 计算 - 简书

allenymt/flutter_fps: flutter Fps 的两种监听方案

如果觉得文章对你有帮助,点赞、收藏、关注、评论,一键四连支持,你的支持就是我创作最大的动力。

❤️ 本文原创听蝉 公众号:码里特别有禅 欢迎关注原创技术文章第一时间推送 ❤️

本文转载于网络 如有侵权请联系删除

相关文章

  • 制作OpenResty-1-19-9-1的RPM包原创

    源码包有源码包的灵活,RPM包有RPM包的方便,一些比较常用的生产工具包,打成RPM包还是比较方便的。#1,物料openresty官网(opensnewwindow)openresty-packaging(opensnewwindow):官方维护的RPM构建的基础文件。#2,我的官方提供的包大多给的是默认配置,不大适合在生产直接使用,因此借鉴官方的包进行了一些简单的调整改造,并将基础文件整理成了项目:rpmbuild工作中常用的RPM构建spec-name:rpmbuild desc:工作中常用的RPM构建spec avatar:https://avatars2.githubusercontent.com/u/416130?s=460&u=8753e86600e300a9811cdc539aa158deec2e2724&v=4#可选 link:https://github.com/eryajf/rpmbuild#可选 bgColor:'#FBDE4B'#可选,默认var(--bodyBg)。颜色值有#号时请添加单引号 textColor:'

  • R语言中基于混合数据抽样(MIDAS)回归的HAR-RV模型预测GDP增长

    原文链接:http://tecdat.cn/?p=12292预测GDP增长我们复制了Ghysels(2013)中提供的示例。我们进行了MIDAS回归分析,来预测季度GDP增长以及每月非农就业人数的增长。预测公式如下其中yt是按季度季节性调整后的实际GDP的对数增长,x3t是月度总就业非农业工资的对数增长。首先,我们加载数据并执行转换。R>y<-window(USqgdp,end=c(2011,2)) R>x<-window(USpayems,end=c(2011,7)) R>yg<-diff(log(y))*100 R>xg<-diff(log(x))*100复制最后两行用于均衡样本大小,样本大小在原始数据中有所不同。我们只需在数据的开头和结尾添加其他NA值即可。数据的图形表示如图所示。要指定midas_r函数的模型,我们以下等效形式重写它:就像在Ghysels(2013)中一样,我们将估算样本限制在1985年第一季度到2009年第一季度之间。我们使用Beta多项式,非零Beta和U-MIDAS权重来评估模型。R>coef(beta

  • k3d入门指南:在Docker中运行K3s

    在本文中,我们将简单了解k3d,这是一款可让您在安装了Docker的任何地方运行一次性Kubernetes集群的工具,此外在本文中我们还将探讨在使用k3d中可能会出现的一切问题。什么是k3d?k3d是一个小型程序,用于在Docker中运行K3s集群。K3s是经过CNCF认证的轻量级Kubernetes发行和沙箱项目。它是为资源有限环境设计的,被打包为单个二进制文件,所需RAM小于512MB。要了解有关K3s的更多信息,请查看我们之前的公众号文章及B站上的视频。 k3d借助从K3s仓库构建的Docker镜像在安装了Docker的任何机器上的Docker容器中启动多个K3s节点。这样,一台物理(或虚拟)机(称为DockerHost)可以运行多个K3s集群,每个集群同时有多个server和agent节点。k3d能做什么?2021年1月,发布k3dv4.0.0,包含以下功能:创建/停止/启动/删除/扩大/缩小K3s集群(和单个节点)通过命令行标志通过配置文件管理可与集群一起使用的容器镜像仓库并与之交互管理集群的Kubeconfigs将本地Dockerdaemon中的镜像导入集群中运行的容器运行

  • 线性表概述

    定义特征是唯一的"第一个数据元素",又称表头元素.是唯一一个"最后一个元素"又称表尾元素.除第一个元素外,每个元素有且仅有一个直接前驱.除最后一个元素外,每个元素有且仅有一个直接后继.元素之间为一对一的线性关系.特点表中的元素个数有限表中元素具有逻辑上的顺序性,表中的元素有先后次序表中元素都是数据元素且每个元素都是单个元素表中元素的数据类型都相同(这意味着每个元素占有相同大小的存储空间)线性表抽象数据类型描述一个数据结构最基本操作是指最核心、最基本的操作。(其他复杂的操作可通过调用其基本操作来实现)InitList(&L)初始化表(构造空的线性表L)Length(L)求表长(即表L中数据元素的个数)LocateElem(L,e)按值查找操作GetElem(L,i)按位查找操作(查找表L中第i个位置的元素)ListInsert(&L,i,e)插入操作(在表L中第i位置插入e元素)ListDelete(&L,i,&e)删除操作PrintList(L)输出操作(按照前后顺序输出表L中的数据元素)Empty(L)判空操作(

  • ​组复制常规操作-网络分区&混合使用IPV6与IPV4 | 全方位认识 MySQL 8.0 Group Replication

    网络分区对于常规事务而言,每当组内有事务数据需要被复制时,组内的成员需要达成共识(要么都提交,要么都回滚)。对于组成员资格的变更也和保持组数据一致性的内部消息传递一样,也需要组内的成员达成共识。共识要求多数组成员同意一个给定决策。当组中的大多数成员失联时,由于无法确保多数或足够的仲裁人数(需要多数仲裁人数才能够确保执行有效的仲裁决议),组将无法接收新的写请求(发生写阻塞,因为此时组内无法达成共识)。当组中的多个成员出现意外失联(Unreachable)时,组可能会失去足够的仲裁人数(失联的成员数量超过组总成员的半数)。例如,在一个由5个成员组成的组中,如果其中3个成员同时处于静默状态(无任何响应),则此时就无法实现仲裁。因为剩下的两个成员无法判断其他3个成员是否崩溃了,或者是否发生了网络分区导致这2个成员被单独隔离了,因此不能自动执行重新配置组。另一方面,如果成员自愿退出组(正常退出),则它会告知组执行重新配置。在实践中,这意味着正在离开组的成员会通知组内的其他成员它正在离开组。这样其他成员就可以正确地重新配置组,以维护成员的一致性,并重新计算大多数成员(仲裁人数)。例如,在上面的场景

  • 基于线性链表的书籍检索系统-数组顺序存储方式

    书籍检索系统,其根本在于书,按数学的观点而言,是一个数的集合。介绍因此,首先应分析基于书的各种信息,众所周知,书的信息基本包括:书号、书名、作者、出版社、定价等。显然我们可以建立一个基于书的数据结构:structbookinfor{char*ID;char*Name;char*Author;char*Publisher;floatPrice;};书的顺序存储链表结构:structbook{structbookinforbook;intlength;intsize;};基于书籍的操作:添加、删除、查找、赋值、读取、书籍集合中书的数量等等鉴于时间关系,不再进行详细的文字描述,下面给出具体的程序实现。//基本程序头内容#include<stdio.h>#include<conio.h>#include<string.h>#defineBookSize100#defineTRUE1#defineFALSE-1//代码使用结构/*对应一本书的信息*/structbookinfor{charID[20];charName[100];charAuthor[50];

  • python连接Linux命令行

    #!/usr/bin/python #-*-coding:utf-8-*- '''https://www.ibm.com/developerworks/cn/linux/l-cn-pexpect2/index.html''' importpexpect importtypes username="root" ip="192.168.***.***" password="****" pex=pexpect.spawn('ssh%s@%s'%(username,ip)) def_check(pattern,timeout=120): i=pex.expect(pattern,timeout=timeout) returni defsendcr(cmd): ifpex==None: return0 n=pex.send("%s\r"%cmd) returnn defgetexec(cmd): child=pexp

  • 阻塞队列与非阻塞队列

    Java提供很多线程安全的容器,为开发人员在并发编程场景下使用,通常我们会更加关注业务实现,而不关心底层结构。但我们应该理解这些容器的原理和使用场景,以方便我们的开发和遇到问题的分析,并且有时候也能借鉴一下大神们的实现思想。使用线程安全队列的场景有很多,Java在实现同步机制时,多线程对竞争资源进行操作时,同一时刻只能有一个线程可以操作,其他线程进行阻塞等待,这时,需要使用一种容器队列来装载等待的线程,在入队和出队时候保证线程的安全性,也就保证同步机制下的线程可以安全的执行。Java提供两种方式来实现阻塞式和非阻塞式,阻塞式使用锁实现,非阻塞式使用CAS方式实现。使用阻塞队列和非阻塞队列的场景还有很多,比较常用的就是我们常说的生产者\消费者模型。非阻塞队列 ConcurrentLinkedQueue——无界非阻塞队列查看类图和实现的方法,可以看出ConcurrentLinkedQueue实现了队列操作add、offer、poll、peek和几个集合的属性操作isEmpty、size、contains、remove、iterator等。还包含volatile修饰的头结点head和尾节点t

  • 查找js文件中隐藏的子域名工具 – SubDomainizer

    +前言SubDomainizer是一款用于查找隐藏在页面的内联和引用Javascript文件中子域的工具。除此之外,它还可以为我们从这些JS文件中检索到S3bucket,云端URL等等。这些对你的渗透测试可能有非常大的帮助,例如具有可读写权限的S3bucket或是子域接管等。云存储服务支持SubDomainizer可以为我们找到以下云存储服务的URL:1.AmazonAWSservices(cloudfrontandS3buckets) 2.Digitaloceanspaces 3.MicrosoftAzure 4.GoogleCloudServices 5.Dreamhost 6.RackCDN使用截图安装从git克隆SubDomainzer:git clone https://github.com/nsonaniya2010/SubDomainizer.git复制更改目录:cd SubDomainizer复制安装依赖项:sudo apt-get update sudo apt-get install python3-termcolor python3-bs4 python3-req

  • C/C++编码规范

    《C++高级进阶教程》就编码规范作了如下叙述。1.编码规范的作用对于变成人员,良好的编程风格是提高程序可靠性和效率非常重要的手段。而编码规范就是对编程风格最好的约束保障。 严格遵守编码规范方便代码的交流和维护,利于提高代码的简洁性,稳定性和效率。2.可供参考的C++编码规范C++额编码规范设计到程序设计的方方面面,而不是三言两语就可以描述清楚的。下面给出一些具体的编码规范,仅供参考,它说明了编码规范所可能拥有的形式。2.1命名原则<1>减少匿名命名空间级标识符 <2>命名时避免使用国际组织占用的格式 <3>名字要本着清楚、简单的原则 <4>尽量用可发音的名字 <5>尽量用英文命名 <6>尽量选择通用词汇并贯穿始终 <7>避免用模棱两可、晦涩或不标准的缩写 <8>避免使用会引起误解的词汇 <9>减少名字中的冗余信息 <10>建议起名尽量通俗,太专一会限制以后的扩展 <11>名字最好尽可能精确地表达其内容 <12>避免名字中出现形状混淆的字母或数字

  • 史上第一!我国将公布首个无人驾驶技术标准

    日前,中国汽车工程学会理事长付于武在国家智能互联汽车试点示范区封闭测试区开园仪式上表示,国内无人驾驶的技术路线图已经存在,将于两个月之内发布,这是中国无人驾驶领域公布的首个技术标准,也是世界无人驾驶领域的首个技术标准!从实际上看,目前亮相的无人驾驶汽车基本上都不是真正意义上的无人驾驶,而是需要司机从旁辅助的自动驾驶。根据国际著名调研机构IHS报告,使用智能驾驶系统的汽车数量将从2015年的700万增加到2025年的12.2亿。并且,在未来5年内,自动驾驶这项新技术也将得到越来越广泛的应用,包括语音和手势识别、虚拟助理和语言界面等信息娱乐系统。除此之外,BusinessInsider的调研团队也表示,到2020年,每年汽车上装载的智能设备将出现134%的增长,而全球自动驾驶汽车出厂量到2020年根式预计增长2400%。面对如此巨大的市场,各大汽车品牌早已按捺不住,纷纷投入到人工智能驾驶系统的研究与开发,哪怕上周被爆出世上首起自动驾驶汽车致人死亡的丑闻,也不能磨灭他们对未来市场份额的蓬勃野心。就在最近,英特尔携手以色列无人驾驶技术厂商Mobileye为宝马开发无人驾驶汽车技术,而在国内,4

  • uva----(100)The 3n + 1 problem

    The3n+1problemBackgroundProblemsinComputerScienceareoftenclassifiedasbelongingtoacertainclassofproblems(e.g.,NP,Unsolvable,Recursive).Inthisproblemyouwillbeanalyzingapropertyofanalgorithmwhoseclassificationisnotknownforallpossibleinputs.TheProblemConsiderthefollowingalgorithm: 1. inputn复制2.printn3.ifn=1thenSTOP4.ifnisoddthen5.else6.GOTO2Giventheinput22,thefollowingsequenceofnumberswillbeprinted221134175226134020105168421Itisconjecturedthatthealgorithmabovewillterminate(whena1isprinted)foranyin

  • 【C++小白成长撸】--矩阵乘法程序

    矩阵乘法是大学矩阵课程中,相比矩阵加减法比较困难的部分。 矩阵乘法的原理: 矩阵乘法在代码中实现 得到目标矩阵的一个元素,涉及两个求和符号,一个求和符号一个for循环,两个求和符号两个for循环,再加上是二维数组,再加一个for循环   以下呈现出代码 /*程序的版权和版本声明部分:**Copyright(c)2017,电子科技大学本科生三年级学生**Allrightsreserved.**文件名:矩阵乘法**程序作用:矩阵乘法**作者:Amoshen**完成日期:2016.10.26更新日期:2017.11.29**版本号:V2.0*/#include<iostream> usingnamespacestd;#defineMAX_SIZE10 intmain(void){ inti=0,j=0,m=0,NumOfRowA,NumOfColA,NumOfColB,s=0;//i为第一矩阵(A)的行变量,j为第一矩阵的列变量,同时为第二矩阵的列变量,m为第二矩阵(B)的列变量 inta[MAX_SIZE][MAX_SIZE],b[MAX_SIZE][MAX_SI

  • 浅谈Spring Boot自动装配

    引言 当我们从SpringMVC的配置地狱走出来怀抱SpringBoot,最吸引我们的就是它的自动装配,开箱即用 学习SpringMVC我们最需要掌握的就是DispatcherServlet这个前端控制器,他就是网页项目的核心 同为servlet,相比于Struts2,DispatcherServlet需要配置的映射器、适配器和解析器还是让我们应接不暇 因此SpringBoot将配置的一切全部托管给启动器的自动装配思想值得我们去梳理和学习一下 复制 正文 我们开始SpringBoot之前我们需要先通过一个例子来抛砖引玉一下原理 首先我们来学习一下@ConfigurationProperties注解从配置文件中获取配置 复制 #yaml文件中定义people对象 people: name:songbaicheng age:12 复制 //定义People类 @ConfigurationProperties(prefix="people") publicclassPeople{ privateStringname; privateintage; publicPeople(){

  • Android中Parcelable的原理和使用方法

    Parcelable的简单介绍 介绍Parcelable不得不先提一下Serializable接口,Serializable是Java为我们提供的一个标准化的序列化接口,那什么是序列化呢? 进行Android开发的时候,无法将对象的引用传给Activities或者Fragments,我们需要将这些对象放到一个Intent或者Bundle里面,然后再传递。简单来说就是将对象转换为可以传输的二进制流(二进制序列)的过程,这样我们就可以通过序列化,转化为可以在网络传输或者保存到本地的流(序列),从而进行传输数据,那反序列化就是从二进制流(序列)转化为对象的过程. Parcelable是Android为我们提供的序列化的接口,Parcelable相对于Serializable的使用相对复杂一些,但Parcelable的效率相对Serializable也高很多,这一直是Google工程师引以为傲的,有时间的可以看一下Parcelable和Serializable的效率对比ParcelablevsSerializable号称快10倍的效率 Android源码中的Parcelable

  • JSON序列化的长度

    前晚在内部系统出现跟其他系统对接数据,出现莫名奇妙的错误,然后查各自的发布记录,近来都没有发布过,所以问题可能出现在数据上,然后用postman模拟一下请求,出现了下图的error 最后查明是因为数据量有点大,且没有指明json序列化的长度导致了,最后一顿设置,搞好了,以下是搞好设置,记录一下 {"Message":"使用JSONJavaScriptSerializer进行序列化或反序列化时出错。字符串的长度超过了为maxJsonLength属性设置的值。 ","StackTrace":"在System.Web.Script.Serialization.JavaScriptSerializer.Serialize(Objectobj,StringBuilderoutput, SerializationFormatserializationFormat)\r\n在System.Web.Script.Serialization.JavaScriptSerializer.Serialize(Objectobj, SerializationFormatserializationFormat)

  • 在云服务器上使用tomcat部署web应用

    tomcat安装与环境配置见:linux下tomcat8安装详解 配置完成后手动将tomcat默认端口号8080改为http端口80:修改tomcat端口号 但按照此方法操作后输入服务器ip,仍不能正常打开网页。显示网络连接错误。   原因:80端口未设置为对外开放。 解决方案:linux下centos7对外开放端口    成功。

  • python 正则

    "."Matchesanycharacterexceptanewline."^"Matchesthestartofthestring."$"Matchestheendofthestringorjustbeforethenewlineattheendofthestring."*"Matches0ormore(greedy)repetitionsoftheprecedingRE.Greedymeansthatitwillmatchasmanyrepetitionsaspossible."+"Matches1ormore(greedy)repetitionsoftheprecedingRE."?"Matches0or1(greedy)oftheprecedingRE.*?,+?,??Non-greedyversionsofthepreviousthreespecialcharacters.{m,n}MatchesfrommtonrepetitionsoftheprecedingRE.{m,n}?Non-greedyversionoftheabove."\\"Eitherescapesspec

  • django之ORM数据库操作

    django之ORM数据库操作 一、ORM介绍 映射关系:   表名--------------------》类名   字段--------------------》属性   表记录-----------------》类实例化对象 ORM的两大功能:   操作表:     -创建表     -修改表     -删除表   操作数据行:     -增删改查 ORM利用pymysql第三方工具链接数据库 Django没办法帮我们创建数据库,只能我们创建完之后告诉它,让django去链接 二、创建表之前的准备工作 一、自己创建数据库 二、在settings里面配置mysql数据库链接   sqlite3------改为mysql #修改django默认的数据库的sqlite3为mysql DATABASES={ 'default':{ 'ENGINE':'django.db.backends.mysql',#通过这个去链接mysql 'NAME':'djangotsgl', 'USER':'root', 'PASSWORD':'123456', 'HOST':'localhost

  • AJAX

    1、概述 2、XMLHttpRequest对象 3、AJAX异步实现步骤 4、使用Ajax计算BMI 5、AJAX联合json使用 6、FormData对象 概述 1、AJAX是用来做局部刷新的。局部刷新使用的核心对象是异步对象(XMLHttpRequest)。这个异步对象是存在浏览器内存中的,使用javascript语法创建和使用XMLHttpRequest对象。AJAX是一种在无需重新加载整个网页的情况下,能够更新部分页面内容的新方法。 2、ajax:AsynchronousJavaScriptandXML(异步的JavaScript和XML)。 Asynchronous:异步。 JavaScript:javascript脚本,在浏览器中执行。 xml:一种数据格式 3、ajax是一种做局部刷新的新方法,不是一种语言。ajax包含的技术主要有javascript、dom、css、xml等等。核心是javascript和xml: javascript:负责创建异步对象,发送请求,更新页面的dom对象。ajax请求需要服务器端的数据。 xml:网络中的传输的数据格式。 全局刷新和局部

  • Mac实用操作技巧(八)

    利用QuickLook快速预览 在Mac系统中,如果你想浏览一个文件,比如文档或者图片,可以不用双击打开相关的程序,只要利用Mac的QuickLook功能就能进行快速预览。QuickLook就是让你在不打开程序的情况下进行文件的查看,可以用在Finder窗口、Email、文本文档等等场合。启动QuickLook的方式是选中文件后,按下Space空格键,或者按下ForceTouch,之后就可以对文档进行快速预览了。当打开一个文件时,QuickLook的上方有几个按钮可以进行功能选择, 如果同时选中多个文件再进行预览,首先出现的是第一个文件,但是QuickLook的功能按钮和只选中一个文件的情况有所不同, 如果你想展示的更酷一些,可以在选中多个文件的时候,同时按下Option+Space,这时QuickLook将会像幻灯片一样全屏播放。想要退出QuickLook,点击左上角的取消按钮,或者再次按下Space。 在Finder中显示状态栏和路径栏 在Finder的底部有两个栏目,一个是StatusBar状态栏,一个是PathBar路径栏,这两个栏目会给你提供很重要的Mac信息,Statu

相关推荐

推荐阅读