精华内容
下载资源
问答
  • 有自定义控件的网站项目发布时,“不允许循环文件引用”的错误”的解决方法
    千次阅读
    2013-01-11 13:54:39

    写了一个自定义控件,在项目中使用了多层嵌套母版,在使用各级母版的各个页面中添加了上面的自定义控件,编译测试无错,但发布总因为““不允许循环文件引用”的错误”而过不去,

     

    于是采取在web.config文件中注册自定义控件的方式

    <pages validateRequest="false">
       <controls>

            <add tagPrefix="Acme" src="~/usercontrol12/top.ascx" tagName="Message"/>
          </controls>
      </pages>

    然后将各个页面中的<%@ Register TagPrefix="Acme" TagName="Message" Src="~/top.ascx" %>注销的方式(其实就是在这里多个母版嵌套造成了一个页面中有多个这样的声明造成的上面的错误),发布通过。

     

    但随后再次发布,又出现““不允许循环文件引用”的错误”的错误,参考了很长时间网上资料,多次尝试后,发现,每次发布前需要将自定义控件换个位置,比方说放在一个新建的文件夹下(当然web.config也要同步更新位置),才能顺利编译发布。

     

    更多相关内容
  • 原文转自:腾讯技术工程 当 Flutter 遇见 Web,会有怎样的秘密? 在线教育团队(简称:OED)已经将 Flutter 这样技术在业务中落地了,做为 IMWeb 前端团队的我们也要进行一些尝试。本文从前端角度进行 Flutter ...

    原文转自:腾讯技术工程

    当 Flutter 遇见 Web,会有怎样的秘密?

    在线教育团队(简称:OED)已经将 Flutter 这样技术在业务中落地了,做为 IMWeb 前端团队的我们也要进行一些尝试。本文从前端角度进行 Flutter 开发的概况描述。主要是为了让您了解和感受一下:Flutter to Web 的实例、Flutter 为什么会出现、Flutter 设计实现原理、Flutter 技术特点和优势。

    前言

    OED的客户端团队在 2019 年上半年 ,就已经把 Flutter 落地到企鹅辅导的业务中了。今年我们又一起去上海参加了 2019 年谷歌开发者大会,遇见了更多的 Flutter 开发者,这次体验比第一次去的时候感觉熟悉了很多。希望未来有机会把他们邀请来深圳,进行一些 Flutter 的技术分享。此次开发者大会又恰逢 Flutter to Web 也已经正式合入 Master,那么,前端同学是否可以趁着这股东风一起参与到 Flutter 的协同开发中呢,我想这问题会困扰着很多人?如果您有好的想法,可以在留言区参与评论。

    本文不是一篇 Flutter 详细的学习教程,更像是一个概览,用尽可能平实的语言和对比的思路去描述它。本着依旧从前端同学的角度出发,去理解一项新的技术,但又不限于前端技术本身。希望您能通过这篇文章相对全面的理解 Flutter 这项技术本身。特别感谢领导的鼓励和支持,让我有机会去学习和理解 Flutter 框架,因为相对我而言,OED 的客户端团队的同学经验会远超于我,他们已经完整经历了业务从 0 到 1 的过程,这是一种非常有意思的体验。

    1、Flutter to Web 案例

    下面转换的工程案例 是我们的 企鹅辅导APP 里面的业务代码(详细的操作流程)。Flutter 官方文档具有很好的说明,如果您单纯转一个完全不依赖 APP 的项目,您安装完环境并且切换到 Master 版本就可以直接进行了,甚至不需要任何代码修改。转换业务产品中的代码,还需要处理一些奇奇怪怪的问题,相信这对您来说,应该都不是问题。可以在这里安装和环境配置 进行环境安装。Flutter 官网提供了一个 案例可以尝试一下。官方给了一个开箱即用的 开发文档

    下面是一个处理无限列表的场景,无论是在 Mac,还是在移动端时,依旧会有卡顿的现象,FPS 表现并不理想。

    首先官方目前还不建议,在产品化中使用 。但既然已经合入 Master,相信这一天也不会远了。

    转换这里需要解决一些问题,整理了一下官方建议和实践的体验:

    业务转换使用的时候,需要把系统依赖解决掉,部分样式问题跟 Flutter 排版组件有关,而系统相关的如本地存储、网络请求需要我们自定义转化方案。例如:客户端使用的是 WNS 协议,而前端需要使用的是 HTTPS。目前看 Flutter to Web 作为业务容灾的策略还是可以的,总是优于 APP Crash 或者 影响范围非常大的 BUG 导致用户无法使用的情况出现。这里 Skia 有个明显的短板,就是 3D 动画,如果您业务对 3D 动画依赖比较强,一段时间内,就不要选择 Flutter 作为业务的技术选型了。

    上面简单的罗列了一些前端在 Flutter 的工作范围,前端定位更多是打辅助的!当然如果您是全能型开发,也可以全部都做。技术本质上没有边界,局限的只有自己。从前端角度看 Flutter 的开发成本相比于 H5 确实还是多了一些成本,但学起来也不会太难,只是时间的问题。对于团队有人力,并且希望尝试新技术的,完全放在业务中尝试使用。

    至于未来是否能确定是 Flutter 这个框架成为行业标杆,还不得而知。但如果想彻底统一全端技术栈,Flutter 今天的设计思路是一个非常有突破性的存在!应该说,类 Flutter 的自绘引擎方案在未来会有机会大放异彩。

    站在前端的角度上,我们尝试着在组件化和工程化的方向找到自己在 Flutter 生态中的定位。并且相比于 IOS 和 Android,Flutter 的 Dart 代码是完全开源的,参与感是会让开发者提升不少的,就类似 linux 和 windows 的感觉。

    至于团队是否要参与进去,很多时候是看综合的成本和收益,做与不做,做到什么程度,适合什么时候进行业务跟进,其实,都是要以团队的价值最大化为目标,没有绝对的对与错,结合团队的实际情况量身定制就好。错误的时机进入,也会付出不小的成本,您自己考量。

    如下图,横向对比行业开源方案:

    简单对比来看,结合团队的技术实践和能力。站在第三方的角度上。RN 和 Flutter 相对是 2 个比较好的跨平台方案。而且其它方案或多或少都有一些局限。

    那真的放到业务上,又当怎么选择呢?

    回答这个问题确实有些艰难,仁者见仁智者见智。无论是相对成熟的 React Native,还是新贵 Flutter,放眼到整个大前端技术史里面看,都不如 Hybird 影响的深远。但如今它们也都达到了商用的标准。我同时使用过 Hybird 和 RN 作为业务接入方,算是会有一点点经验之谈。

    从目前行业的产品,以及社区生态来说,React Native 整体还是胜出 Flutter 一筹。毕竟早出来几年,市场占有率和行业积累还是在的。但是长远来看,技术发展也有它的必然规律,Flutter 的技术理念已经领先了 React Native,作为大公司、或者大前端团队的技术储备和技术选型,科技公司要想在未来在行业有一席之地,使用 Flutter 这样的技术,必然也会是一个趋势。至于开发者对于技术本身,应该也会对 Flutter 保有浓厚的兴趣吧,毕竟技术永远都会向前发展,无论是谁,不进步一样会被淘汰。因此,作为技术开发人员首先保持的就是一颗好奇的心,进而持续成长。

    当然,未来可能成功的框架不一定就是 Flutter,但是它设计理念和设计思路是一脉相承的,类 Flutter 框架一样也会出现。 就像 React 出来了,Vue 也会跟着出来了。有的团队使用 Vue,有的团队使用 React。但是,您会发现它们越来越像了。使用 Vue 的也没必要说使用 React 的同学水平不好,不存在的!理念很重要,有一句话说的很有意思,叫 ~ 思路决定出路!

    2、Flutter 技术架构

    1)拥有了 RN,为什么又会出现 Flutter

    在谈及 Flutter 之前,我们还是要先简单回顾一下,客户端的上一次技术革新 —— ReactNative(此后简称 RN)。相信非常多的团队都有去落地实践 RN 的机会,很多 APP 的首屏渲染方案都是用 RN 技术栈进行的。我们自己的产品 企鹅辅导 和 腾讯课堂 内的应用也是一样。

    这里简单回顾一下,在有客户端开发的场景下,为什么又出现了 RN ?

    RN 的价值简单来讲就是—— 可接受的页面性能 + 高效开发 + 热更新。

    • 更新:传统的 APP 上架之后,出现了业务 BUG,用户只能去更新 APP,进行 BUG 修复。客户端实现热更新修复 BUG,有多难,可以问问 IOS 的开发同学。大概率猜测,手 Q 和微信,应该还是有方案可以热更新的。但是对很多小厂商这确实是非常艰难的事情。因此,得益于强大的动态化能力 RN 的价值也就完美的体现出来了。

    • 高效:一个 APP 发布上线,Android 和 IOS 同时需要开发两个应用,而 RN 只需要一套代码,就可以运行在双平台上,节省很大的人力成本。并且很多业务线有很强的业务运营诉求,可能会存在很短时间内的多次改版和发布的情况出现,客户端开发的人力瓶颈和发布周期的限制,已经很难满足这样的业务场景了。尤其在一些有损发布的情况下,赶着时间点,带着 BUG 上线的场景,在后续进行增量的修复,再这样的情况下,传统客户端的表现,简直就是灾难性的。

    • 性能:RN 具有优于 H5 的性能体验。毕竟是通过客户端进行的页面渲染,速度上比 WebView 渲染还是要快不少的。这个在 Weex、Hippy、Plato 上都有所体现,虽然低于 Native 的性能,但是在可接受范围。PS:这里的表达,不是描述客户端开发不好。只是单纯从业务角度上看待问题,而把合适的技术放在合适的位置是非常重要的,这也是架构师核心价值之一

    回顾了以上三点,我们发现 RN 的出现,有它的必然性。那么回到主题,RN 已经这么优秀了,为什么还要有 Flutter 的存在,有一次向 Ab 哥请教技术成长的时候,Ab 哥提到了很有意思的一个观点,就是您对一项技术了解的深入程度,取决于是否能认清这项技术的局限。 就像人一样,他(她)有多少优点,就会存在多少缺点。没发现,不等于不存在,因为一定存在。因此,顺着这个思路,我们简单的看一下 RN 的问题。

    首先,看维护成本,虽然 RN 是一套代码多端运行。但还是需要 IOS 和 Android 开发帮助我们去一个一个的绘制组件,尤其遇到特殊诉求的时候,还要 case by case 的处理,并且随着 IOS 和 Android 系统本身的迭代和升级,以及框架自身发展的历史包袱,我们可能还需要处理很多与原生系统之间的平台差异,修复各种奇奇怪怪的 BUG,这对业务来说是很大的负担。

    其次,对性能诉求,无论是产品还是开发同学,对于用户体验的追求,永远都不会停止。RN 存在诸多性能的短板,因此,才会有 Weex 这样的产品出现,去定制化的解决业务场景下的问题。JS 和 Native 的通信,页面的事件监听,复杂动画的渲染和交换成本,都是很大的性能挑战。

    最后,在存在更强的业务诉求的时候,人们就不得不去寻找更好的方式去实现。非常存感激的看待谷歌这家公司,都是定位于商业公司,但实际上对世界的影响力上面,公司与公司之间差距还是非常大的。这个课题范围太大,以后有机会可以深度讨论一下。

    您看到了上面的描述,为了解决上面这些问题 —— 自绘引擎时代出现了,以 Flutter 为代表的技术方案会应运而生,相信一定不会只有 Flutter 一项跨平台技术出现的,历史总是惊人的相似。其实想到自绘引擎,我最先想到的是那些游戏引擎。那现在又为什么给出 自绘引擎 这样的一个概念呢?H5 是依赖于浏览器渲染,RN 依赖于客户端渲染,而 Flutter 基于 Skia 自己绘制的图形界面。因此,Flutter 才能真正实现跨端!相信在不久的未来,在传统客户端上也能看到 Flutter 的身影,这样才能真正达到多端统一。

    最后,我们再简单总结一下有哪些问题:
    1、Web 性能差,跟原生 App 存在肉眼可见的差距;
    2、React Native 跟 Web 相比,支持的能力非常有限,特定长场景问题,需要三端团队一个一个处理;
    3、Web 浏览器的安卓碎片化严重(感谢 X5,腾讯的同学过得相对轻松一些)。

    为了解决上面的问题,Flutter 出现了:
    一套代码可以运行在两端;
    自绘 UI,脱离平台,也可以简单的把它理解为一个浏览器的子集。

    铺垫了这么多,就是为了帮助您回忆起技术发展的脉络和技术趋势,可以更好的理解下面即将要表达的文稿,下面我们正式开始介绍 Flutter。

    2)Flutter 实现原理Flutter

    能介绍的技术点其实非常多,这里找了一些具有代表性的技术项,结合自己的理解跟大家分享一下。包括设计思路、渲染方式、UI 的生命周期。因为这几个点,跟 React 技术栈风格非常相似,以这种思考结构去对比介绍,可以帮助大家更好的理解这项技术本身。

    Flutter 整体架构设计

    Google 了一下关键词,搜素得到了这张图片,从下向上进行一些描述:

    Embedder:是操作系统适配层,实现了渲染 Surface 设置,线程设置,以及平台插件等平台相关特性的适配。从这里我们可以看到,Flutter 平台相关特性并不多,这就使得从框架层面保持跨端一致性的成本相对较低。

    Flutter Engine:这是一个纯 C++实现的 SDK,其中囊括了 Skia 引擎、Dart 运行时、文字排版引擎等。不过说白了,它就是 Dart 的一个运行时,它可以以 JIT、JITSnapshot 或者 AOT 的模式运行 Dart 代码。在代码调用 dart:ui 库时,提供 dart:ui 库中 Native Binding 实现。 不过别忘了,这个运行时还控制着 VSync 信号的传递、GPU 数据的填充等,并且还负责把客户端的事件传递到运行时中的代码。具体的绘制方式,我们放在后面描述。

    Flutter Framework:这是一个纯 Dart 实现的 SDK,类似于 React 在 JavaScript 中的作用。它实现了一套基础库, 用于处理动画、绘图和手势。并且基于绘图封装了一套 UI 组件库,然后根据 Material 和 Cupertino 两种视觉风格区分开来。这个纯 Dart 实现的 SDK 被封装为了一个叫作 dart:ui 的 Dart 库。我们在使用 Flutter 写 App 的时候,直接导入这个库即可使用组件等功能。

    PS:虽然很早知道 Flutter,但实际写 Flutter 时间也比较短暂。引擎源码层面,目前也没有深入的涉猎。了解的方式可以通过自己阅读源码,或者找谷歌、阿里、美团、以及我司的开发者帮忙。从技术角度来了解这些,在需要的阶段,不会成为大家的瓶颈。毕竟商业世界充满了壁垒,而应用层面的技术本身是开放的。

    3)Flutter 应用层语言 Dart

    简单描述一下 JIT 与 AOT:

    • JIT在运行时即时编译,在开发周期中使用,可以动态下发和执行代码,开发测试效率高,但运行速度和执行性能则会因为运行时即时编译受到影响。
    • AOT即提前编译,可以生成被直接执行的二进制代码,运行速度快、执行性能表现好,但每次执行前都需要提前编译,开发测试效率低。

    Dart 是什么

    它的目标在于成为下一代结构化 Web 开发语言。Dart 发布于 2011 年 10 月 Google 的"GOTO 国际软件开发大会"。是一种基于类编程语言(class-based programminglanguage),在所有浏览器都能够有高性能的运行效率。Chrome 浏览器内置了 DartVM,可以直接高效的运行 dart 代码(2015 年被移出)。支持 Dart 代码转成 Javascript,直接在 Javascript引擎上运行。dart2js

    Dart 的特点

    • 开发时 JIT,提升开发效率;发布时 AOT,提升性能。
    • 不会面对 JS 与 Native 之间交互的问题了。
    • Dart 的内存策略,采用多生代算法(与 Node 有一些类似)。
    • 线程模型依旧是单线程 Event Loop 模型,通过 isolate 进行隔离,可以降低开发难度(与 Node 也非常类似)。
    • Dart 的生态,这个跟 Node.js 差距十分明显,npm 还是行业中最活跃的。
    • 而静态语法与排版方式,纯前端入门还是有一定成本。

    备注:

    1)TS 可以一定程度上帮助 JS 添加一些静态检测,但本质上依旧是无法达成这样的效果;

    1. 关于入门成本这个问题,如果您想深入,我相信这都不会成为问题。关键看是否能为业务和团队带来价值。

    Flutter 选择 Dart 的原因

    • 健全的类型系统,同时支持静态类型检查和运行时类型检查。
    • 代码体积优化(TreeShaking),编译时只保留运行时需要调用的代码(不允许反射这样的隐式引用),所以庞大的 Widgets 库不会造成发布体积过大。
    • 丰富的底层库,Dart 自身提供了非常多的库。多生代无锁垃圾回收器,专门为 UI 框架中常见的大量 Widgets 对象创建和销毁优化。
    • 跨平台,iOS 和 Android 共用一套代码。
    • JIT & AOT 运行模式,支持开发时的快速迭代和正式发布后最大程度发挥硬件性能。
    • Native Binding。在 Android 上,v8 的 Native Binding 可以很好地实现,但是 iOS 上的 *JavaScriptCore 不可以,所以如果使用 JavaScript,Flutter 基础框架的代码模式就很难统一了。而 Dart 的 Native Binding 可以很好地通过 Dart Lib 实现。

    4)Flutter 实现思路

    看到了上面的介绍,这里总结一下 Flutter 的实现思路。它开辟了新的设计理念,实现了真正的跨平台的方案,自研 UI 框架,它的渲染引擎是 Skia 图形库来实现的,而开发语言选择了同时支持 JIT 和 AOT 的 Dart。不仅保证了开发效率,同时也提升了执行效率。由于 Flutter 自绘 UI 的实现方式,因此也尽可能的减少了不同平台之间的差异。也保持和原生应用一样的高性能。因此,Flutter 也是跨平台开发方案中最灵活和彻底的那个,它重写了底层渲染逻辑和上层开发语言的一整套完整解决方案。

    彻底跨端:
    Flutter 构建了一整套包括底层渲染、顶层设计的全套开发套件。
    这样不仅可以保证视图渲染在 Android 和 IOS 上面的高度一致,也可以保证渲染和交互性能(媲美原生应用)。
    与现有方案核心区别:
    类 RN 方案,JS 开发,Native 渲染。数据通信 bridge;
    Hybird 浏览器渲染 + 原生组件绘制;
    Flutter 设计自闭环,完成渲染和数据通信。

    3、Flutter 的 UI 渲染方案

    渲染方案是 Flutter 目前独特的设计形态,就是由于渲染自闭环,才能真正跨平台。谈到 UI 渲染方案,作为前端开发,我们是绕不过现在如火如荼的三大框架的。为什么要谈类 React 方案呢?因为 Flutter 的设计方案,与 React 设计具有一样的思路。在渲染这里我们会谈及控件、渲染原理、以及生命周期。

    Flutter 是如何进行页面渲染的呢?传统 Web 是通过浏览器,而 Flutter 是自绘。所谓自绘就是用户界面上 Flutter 自己绘制到界面,无需依赖 IOS 和 Android 原生能力,是通过一个叫做 Skia 引擎进行页面绘图。

    1)介绍一下 SkiaSkia

    是一个 2D 的绘图引擎库,其前身是一个向量绘图软件,Chrome 和 Android 均采用 Skia 作为绘图引擎。Skia 提供了非常友好的 API,并且在图形转换、文字渲染、位图渲染方面都提供了友好、高效的表现。Skia 是跨平台的,所以可以被嵌入到 Flutter 的 iOS SDK 中,而不用去研究 iOS 闭源的 CoreGraphics / Core Animation。

    Skia 是用 C++ 开发的、性能彪悍的 2D 图像绘制引擎,其前身是一个向量绘图软件。Skia 在图形转换、文字渲染、位图渲染方面都表现卓越,并提供了开发者友好的 API。Android 自带了 Skia,所以 Flutter Android SDK 要比 iOS SDK 小很多。正是得益于 Skia 的存在:

    • Flutter 底层的渲染能力得到了统一,不在需要使用做双端适配;
    • 通过 OpenGL、GPU,不需要依赖原生的组件渲染框架。
    • Flutter 可以最大限度的抹平平台差异,提升渲染效率和性能。

    2)Flutter 的渲染流程

    用户可以看到一张图像展示,至少需要三类介质:CPU、GPU 和 显示器。CPU 负责图像的数据计算,GPU 负责图像数据的渲染,而显示器是最终图片展示的载体。CPU 拿到需要上屏的数据做处理和加工,处理完成之后交给 GPU,GPU 在渲染之后将数据放入帧缓冲区,随后随着控制同步信号 (VSync)以周期性的频率,从缓冲区内读出数据,在显示器上进行图像呈现。而且操作系统就是一个无限循环的机制,不停的重复上面的操作,进行显示器的更新.

    Flutter 的渲染整体流程也是这样的, Dart 进行视图数据的合成,然后交给 Skia 引擎进行处理,处理之后再交给 GPU 进行数据合成,然后准备上屏。当一帧图像绘制完毕后准备绘制下一帧时,显示器会发出一个垂直同步信号(VSync),所以 60Hz 的屏幕就会一秒内发出 60 次这样的信号。

    3)Flutter 绘制流程

     

    如上图所示,Flutter 渲染流程分为 7 个步骤:

    首先是获取到用户的操作,然后你的应用会因此显示一些动画 ;

    接着 Flutter 开始构建 Widget 对象。Widget 对象构建完成后进入渲染阶段,这个阶段主要包括三步:

    • 布局元素:决定页面元素在屏幕上的位置和大小;
    • 绘制阶段:将页面元素绘制成它们应有的样式;
    • 合成阶段:按照绘制规则将之前两个步骤的产物组合在一起

    最后的光栅化由 Engine 层来完成。

    4)布局

    布局时 Flutter 深度优先遍历渲染对象树。数据流的传递方式是从上到下传递约束,从下到上传递大小。也就是说,父节点会将自己的约束传递给子节点,子节点根据接收到的约束来计算自己的大小,然后将自己的尺寸返回给父节点。整个过程中,位置信息由父节点来控制,子节点并不关心自己所在的位置,而父节点也不关心子节点具体长什么样子。

    为了防止因子节点发生变化而导致的整个控件树重绘,Flutter 加入了一个机制——RelayoutBoundary,在一些特定的情形下 Relayout Boundary 会被自动创建,不需要开发者手动添加。

    边界:Flutter 使用边界标记需要重新布局和重新绘制的节点部分,这样就可以避免其他节点被污染或者触发重建。就是控件大小不会影响其他控件时,就没必要重新布局整个控件树。有了这个机制后,无论子树发生什么样的变化,处理范围都只在子树上。

    缓存:要提升性能表现,缓存也是少不了的。在 Flutter 中,几乎所有的 Element 都会具有一个 key,这个 key 是唯一的。当子树重建后,只会刷新 key 不同的部分,而节点数据的复用就是依靠 key 来从缓存中取得。

    在确定每个空间的位置和大小之后,就进入绘制阶段。绘制节点的时候也是深度遍历绘制节点树,然后把不同的 RenderObject 绘制到不同的图层上。

    5)绘制

    在布局完成之后,渲染对象树中的每个节点都有了明确的尺寸和位置。Flutter 会把所有的 Element 绘制到不同的图层上。与布局过程类似,绘制的过程也是深度优先遍历,先绘制父节点,然后绘制子节点。以下图为例:节点 1、节点 2、节点 3、4、5,最好绘制节点 6。

    如上图可以看到一种场景,就是比如视图可能会合并,导致 节点 2 的子节点 5 与它的兄弟节点 6 处于同一个图层,这样会导致当 节点 2 需要重绘的时候,与其无关的节点 6 也会被重绘,带来性能问题。

    为了解决上面的问题,Flutter 提出了布局边界的机制 ——重绘边界(Repaint-Boundary)。在重绘边界内,Flutter 会强制切换新的图层,这样可以避免边界内外的互相影响,避免无关内容虽然处于同一个层级导致的不必要的重绘。

    重绘边界的一个典型场景就是 ScrollView。ScorllView 滚动的时候会刷新视图,从而触发内容重绘,而当滚动内容重绘时,一般情况下其它内容是不需要被重绘的。这个时候重绘边界就非常有价值了。

    这里简单理解,就是更精细化的对控件的更新,进行了小范围的控制。在时间复杂度和空间复杂度中进行权衡。未来我们优化业务,大概率也会优化这里,找到自身业务的平衡点。

    6)合成和渲染

    最上面已经展示了 Flutter 的 7 层渲染流水线(Renderingpipline)的图里。这里主要描述一下对合成和渲染的理解。渲染流水线是由垂直同步信号(Vsync)驱动的。这个概念很类似我们平时说的 FPS 的概念,每秒 60 帧,过低的频率会显得页面很卡。当每一次 Vsync 信号到来以后,Flutter 框架会按照图里的顺序执行一系列动作:动画(Animate)、构建(Build)、布局(Layout)和绘制(Paint)

    最终生成一个场景(Scene)之后送往底层,由 GPU 绘制到屏幕上。

    Flutter App 只有在状态发生变化的时候需要触发渲染流水线。当你的 App 无任何状态改变的时候,Flutter 是不需要重新渲染页面的。所以,Vsync 信号需要 Flutter App 去调度。比如,我们在 Widget 内使用了 setState 方法改变了控件的状态。

    整个渲染流水线是运行在 UI 线程里的,以 Vsync 信号为驱动,在框架渲染完成之后会输出 Layer Tree。Layer Tree 被送入 Engine,Engine 会把 Layer Tree 调度到 GPU 线程,在 GPU 线程内合成(compsite)Layer Tree,然后由 Skia 2D 渲染引擎渲染后送入 GPU 显示。这里提到 Layer Tree 是因为我们即将要分析的渲染流水线绘制阶段最终输出就是这样的 LayerTree。所以绘制阶段并不是简单的调用 Paint 函数这么简单了,而是很多地方都涉及到 Layer Tree 的管理。

    Flutter 只关心向 GPU 提供视图数据,GPU 的 VSync 信号同步到 UI 线程,UI 线程使用 Dart 来构建抽象的视图结构,这份数据结构在 GPU 线程进行图层合成,视图数据提供给 Skia 引擎渲染为 GPU 数据,这些数据通过 OpenGL 或者 Vulkan 提供给 GPU。

    这里描述一下合成的概念,所谓合成就是因为我们绘制的页面结构复杂,如果直接交付给绘图引擎去进行图层渲染,可能会出现大量的渲染内容重绘,因此,需要先进性一次图层合成,就是说先把所有的图层根据大小、层级等规则计算出最终的显示效果,将相同的图层合并,简化渲染树,提升渲染效率。

    Flutter 会将合成之后的数据,交给 Skia 进行页面二维图层的渲染

    5、学习路线

    粗略了整理了一下我最近这 2 周体验开发过程中认知的学习范围,这上面除了跟 APP 相关的部分,大部分场景已经通过代码体验和实践过了。这里由于篇幅限制,就不在一一的介绍了。最重要的是关注 Flutter 的官方文档。

    展开全文
  • 解决vue v-for 遍历循环时key值报错的问题一 、问题如下:[Vue warn] Avoid using non-primitive value as key, use string/number value instead.non-primitive表示的是对象这里的[Vue warn]是指不要用对象或是数组...

    解决vue v-for 遍历循环时key值报错的问题

    一 、问题如下:

    [Vue warn] Avoid using non-primitive value as key, use string/number value instead.

    non-primitive表示的是对象

    这里的[Vue warn]是指不要用对象或是数组作为key,用string或number作为key。

    :key相当于是索引的作用,提高循环性能,如果循环量较小,不写也可以的。

    以上这篇解决vue v-for 遍历循环时key值报错的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

    时间: 2018-09-05

    本篇文章主要介绍了 Vue中使用create-keyframe-animation与动画钩子完成复杂动画,分享给大家 如何实现这个动画?  效果分析 点`start`的时候,我们把整个动画拆分为两种效果(过渡和动画). 1. 中间cd消失,下方播放条显示,这是属于`过渡` 2. `过渡`开始的同时,cd同时移动.放大.缩小到左下方播放条 ,这属于`动画` 上面的效果是[过渡]加[动画]同时使用完成的 对于第一种[过渡],我们用vue中transition标签,加设置v-enter.v-leave

    今天,在写关于Vue2.0的代码中发现 $key这个值并不能渲染成功,问题如下: 结果这个对象的key值并不能够显示: 后来查阅了文档才知道,这是因为在Vue2.0中,v-for迭代语法已经发生了变化: 丢弃了: 新数组语法 value in arr (value, index) in arr 新对象语法 value in obj (value, key) in obj (value, key, index) in obj 解决后: 以上这篇浅谈Vue2.0中v-for迭代语法的变化(key.i

    解决办法: 加上.native覆盖原有封装的keyup事件即可. 以上这篇vue element-ui 绑定@keyup事件无效的解

    Vue2+采用diff算法来进行新旧vnode的对比从而更新DOM节点.而通常在我们使用v-for这个指令的时候,Vue会要求你给循环列表的每一项添加唯一的key,那么这个key在渲染列表时究竟起到了什么作用呢? 在解释这一点之前,你最好已经了解Vue的diff算法的具体原理是什么. Vue2更新真实DOM的操作主要是两种:创建新DOM节点并移除旧DOM节点和更新已存在的DOM节点,这两种方式里创建新DOM节点的开销肯定是远大于更新或移动已有的DOM节点,所以在diff中逻辑都是为了减少新的创建

    当Vue用 v-for 正在更新已渲染过的元素列表是,它默认用"就地复用"策略.如果数据项的顺序被改变,Vue将不是移动DOM元素来匹配数据项的改变,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素. 为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性.key属性的类型只能为 string或者number类型. 在下面这个例子中,如果不给 p 元素绑定key,我先选中第一个, 然后输入ID和Nam

    如果没有这个属性的时候vue应用 in-place patch(就地复用)策略.列表里的顺序发生改变的时候比如shuffle(列表打乱)的时候,vue为了提升性能,不会移动dom元素,只是更新相应元素的内容节点. 就地复用的弊端 这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出. 如上引用自官网,这个模式就是上面的"就地复用"策略.那么是不是依赖子组件状态的列表渲染采用上面的模式就出问题了呢.如下测试代码:

    一.vue2.0移除了$index和$key 虽然说现在很多文章说他们的代码是vue2.0版本的,但是有一些仔细一看,发现并不全是2.0版本,有些语法还是1.0的版本,比如这个$index,$key,这两个压根就不是2.0的写法,2.0早就把这两个给删除了,我们先来看看之前的列表渲染是怎么写的

    • {{

      vue运行为v-on在监听键盘事件时,添加了特殊的键盘修饰符: vue还非常贴心地给出了常用按键的别名,这样就不必去记keyCode ~ ~ 上面代码,还可以在这样写: 全部的键盘别名: .enter .tab .delet

      在html或jsp页面中我们总能碰到监听DOM事件来触发javaScript代码,下面我们就简单聊聊Vue.js中的监听事件是怎么处理的. 在vue.js中监听事件是通过v-on指令来实现的,先看一下简单的监听事件代码.

      在我们的项目经常需要监听一些键盘事件来触发程序的执行,而Vue中允许在监听的时候添加关键修饰符: 对于一些常用键,还提供了按键别名: 全部的按键别名: .enter  .tab  .delete (捕获"删除"和"退格"键)  .esc  .s

      前面的话 父组件使用props传递数据给子组件,子组件怎么跟父组件通信呢?这时,Vue的自定义事件就派上用场了.本文将详细介绍Vue自定义事件 事件绑定 每个 Vue 实例都实现了事件接口 (Events interface),即 使用 $on(eventName) 监听事件 使用 $emit(eventName) 触发事件 [注意]Vue 的事件系统分离自浏览器的EventTarget API.尽管它们的运行类似,但是 $on 和 $emit 不是addEventListener 和 disp

      写在最前面 拥有全球数据库国内好像就只有百度地图有,高德.搜狗.腾讯的都不行,但是由于百度地图的数据更新不及时,所以在做相关项目要用到国外数据的时候,最好还是推荐使用bingMap. bing Map 使用教程(基础) 参考文档:bing Map 官方教程 bing Map 初始化 引入bing map资源

    &

    L3Byb3h5L2h0dHAvaW1nLmpiemouY29tL2ZpbGVfaW1hZ2VzL2FydGljbGUvMjAxOTAzLzIwMTkzMTkxNTIzMDUzNDMucG5nJiMwNjM7MjAxOTIxOTE1MjMyNg==.jpg

    在Vue中,使用了Object.defineProterty()这个函数来实现双向绑定,这也就是为什么Vue不兼容IE8 1 响应式原理 让我们先从相应式原理开始.我们可以通过Object.defineProterty()来自定义Object的getter和setter 从而达到我们的目的. 代码如下 function observe(value, cb) { Object.keys(value).forEach((key) => defineReactive(value, key, value

    L3Byb3h5L2h0dHAvaW1nLmpiemouY29tL2ZpbGVfaW1hZ2VzL2FydGljbGUvMjAxODExLzIwMTgxMTIxNDMyMzg3MzUucG5nJiMwNjM7MjAxODEwMjE0MzI1MQ==.jpg

    vue-resource特点 vue-resource插件具有以下特点: 1. 体积小 vue-resource非常小巧,在压缩以后只有大约12KB,服务端启用gzip压缩后只有4.5KB大小,这远比jQuery的体积要小得多. 2. 支持主流的浏览器 和Vue.js一样,vue-resource除了不支持IE 9以下的浏览器,其他主流的浏览器都支持. 3. 支持Promise API和URI Templates Promise是ES6的特性,Promise的中文含义为"先知",Pro

    1.GET 请求 //向具有指定ID的用户发出请求 axios.get('/user?ID=12345') .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); // 也可以通过 params 对象传递参数 axios.get('/user', { params: { ID: 12345 } }) .then(function (respons

    概述 主要用于全国地区数据的操作,包括省,市,区三级联动,地区数据的添加和删除: 在操作地区数据时,以前也用过树形的地区选择组件,但因其在再操作大量的地区数据时,渲染缓慢,所以我们就换了另一种数据展示形式和交互形式,从而就有了这个组件. 注意:该组件是 vue.js 组件 demo 抢鲜体验请点击这里 demo API Props 参数 类型 说明 area Array 传入组件的地区的数据 Events 事件名 参数 说明 selected area 组件中选中的地区 详细说明 Props a

    展开全文
  • 点击上方蓝色“方志朋”,选择“设为星标”回复“666”获取独家整理的学习资料!我们都知道 JVM 垃圾回收中,GC判断堆中的对象实例或数据是不是垃圾的方法有引用计数法和可达性算法两种。无论...

    点击上方蓝色“方志朋”,选择“设为星标”

    回复“666”获取独家整理的学习资料!

    我们都知道 JVM 垃圾回收中,GC判断堆中的对象实例或数据是不是垃圾的方法有引用计数法可达性算法两种。

    无论是通过引用计数算法判断对象的引用数量,还是通过根搜索算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。

    引用

    先说说引用,Java中的引用,类似 C 语言中的指针。初学 Java时,我们就知道 Java 数据类型分两大类,基本类型和引用类型。

    基本类型:编程语言中内置的最小粒度的数据类型。它包括四大类八种类型:

    • 4种整数类型:byte、short、int、long

    • 2种浮点数类型:float、double

    • 1种字符类型:char

    • 1种布尔类型:boolean

    引用类型:引用类型指向一个对象,不是原始值,指向对象的变量是引用变量。在 Java 里,除了基本类型,其他类型都属于引用类型,它主要包括:类、接口、数组、枚举、注解

    有了数据类型,JVM对程序数据的管理就规范化了,不同的数据类型,它的存储形式和位置是不一样的

    怎么跑偏了,回归正题,通过引用,可以对堆中的对象进行操作。引用《Java编程思想》中的一段话,

    ”每种编程语言都有自己的数据处理方式。有些时候,程序员必须注意将要处理的数据是什么类型。你是直接操纵元素,还是用某种基于特殊语法的间接表示(例如C/C++里的指针)来操作对象。所有这些在 Java 里都得到了简化,一切都被视为对象。因此,我们可采用一种统一的语法。尽管将一切都“看作”对象,但操纵的标识符实际是指向一个对象的“引用”(reference)。”

    比如:

    Person person = new Person("张三");
    

    这里的 person 就是指向Person 实例“张三”的引用,我们一般都是通过 person 来操作“张三”实例。

    在 JDK 1.2 之前,Java 中的引用的定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该 refrence 数据是代表某块内存、某个对象的引用。这种定义很纯粹,但是太过狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态,对于如何描述一些“食之无味,弃之可惜”的对象就显得无能为力。

    比如我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。

    在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为

    • 强引用(Strong Reference)

    • 软引用(Soft Reference)

    • 弱引用(Weak Reference)

    • 虚引用(Phantom Reference)

    这四种引用强度依次逐渐减弱。

    Java 中引入四种引用的目的是让程序自己决定对象的生命周期,JVM 是通过垃圾回收器对这四种引用做不同的处理,来实现对象生命周期的改变。

    JDK 8中的 UML关系图

    FinalReference 类是包内可见,其他三种引用类型均为 public,可以在应用程序中直接使用。

    强引用

    在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。类似 “Object obj = new Object()” 这类的引用。

    当一个对象被强引用变量引用时,它处于可达状态,是不可能被垃圾回收器回收的,即使该对象永远不会被用到也不会被回收。

    当内存不足,JVM 开始垃圾回收,对于强引用的对象,就算是出现了 OOM 也不会对该对象进行回收,打死都不收。因此强引用有时也是造成 Java 内存泄露的原因之一。

    对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示地将相应(强)引用赋值为 null,一般认为就是可以被垃圾收集器回收。(具体回收时机还要要看垃圾收集策略)。

    coding~

    public class StrongRefenenceDemo {
    
        public static void main(String[] args) {
            Object o1 = new Object();
            Object o2 = o1;
            o1 = null;
            System.gc();
            System.out.println(o1);  //null
            System.out.println(o2);  //java.lang.Object@2503dbd3
        }
    }
    

    demo 中尽管 o1已经被回收,但是 o2 强引用 o1,一直存在,所以不会被GC回收

    软引用

    软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference 类来实现,可以让对象豁免一些垃圾收集。

    软引用用来描述一些还有用,但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。

    对于只有软引用的对象来说:当系统内存充足时它不会被回收,当系统内存不足时它才会被回收。

    coding~

    //VM options: -Xms5m -Xmx5m
    public class SoftRefenenceDemo {
    
        public static void main(String[] args) {
            softRefMemoryEnough();
            System.out.println("------内存不够用的情况------");
            softRefMemoryNotEnough();
        }
    
        private static void softRefMemoryEnough() {
            Object o1 = new Object();
            SoftReference<Object> s1 = new SoftReference<Object>(o1);
            System.out.println(o1);
            System.out.println(s1.get());
    
            o1 = null;
            System.gc();
    
            System.out.println(o1);
            System.out.println(s1.get());
        }
    
         /**
         * JVM配置`-Xms5m -Xmx5m` ,然后故意new一个一个大对象,使内存不足产生 OOM,看软引用回收情况
         */
        private static void softRefMemoryNotEnough() {
            Object o1 = new Object();
            SoftReference<Object> s1 = new SoftReference<Object>(o1);
            System.out.println(o1);
            System.out.println(s1.get());
    
            o1 = null;
    
            byte[] bytes = new byte[10 * 1024 * 1024];
    
            System.out.println(o1);
            System.out.println(s1.get());
        }
    }
    

    Output

    java.lang.Object@2503dbd3
    java.lang.Object@2503dbd3
    null
    java.lang.Object@2503dbd3
    ------内存不够用的情况------
    java.lang.Object@4b67cf4d
    java.lang.Object@4b67cf4d
    java.lang.OutOfMemoryError: Java heap space
    	at reference.SoftRefenenceDemo.softRefMemoryNotEnough(SoftRefenenceDemo.java:42)
    	at reference.SoftRefenenceDemo.main(SoftRefenenceDemo.java:15)
    null
    null
    

    软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收。

    我们看下 Mybatis 缓存类 SoftCache 用到的软引用

    public Object getObject(Object key) {
        Object result = null;
        SoftReference<Object> softReference = (SoftReference)this.delegate.getObject(key);
        if (softReference != null) {
            result = softReference.get();
            if (result == null) {
                this.delegate.removeObject(key);
            } else {
                synchronized(this.hardLinksToAvoidGarbageCollection) {
                    this.hardLinksToAvoidGarbageCollection.addFirst(result);
                    if (this.hardLinksToAvoidGarbageCollection.size() > this.numberOfHardLinks) {
                        this.hardLinksToAvoidGarbageCollection.removeLast();
                    }
                }
            }
        }
        return result;
    }
    

    弱引用

    弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

    弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短。

    对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,都会回收该对象占用的内存。

    coding~

    public class WeakReferenceDemo {
    
        public static void main(String[] args) {
            Object o1 = new Object();
            WeakReference<Object> w1 = new WeakReference<Object>(o1);
    
            System.out.println(o1);
            System.out.println(w1.get());
    
            o1 = null;
            System.gc();
    
            System.out.println(o1);
            System.out.println(w1.get());
        }
    }
    

    Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. Weak references are most often used to implement canonicalizing mappings.

    官方文档这么写的,弱引用常被用来实现规范化映射,JDK 中的 WeakHashMap 就是一个这样的例子

    面试官:既然你都知道弱引用,那能说说 WeakHashMap 吗

    public class WeakHashMapDemo {
    
        public static void main(String[] args) throws InterruptedException {
            myHashMap();
            myWeakHashMap();
        }
    
        public static void myHashMap() {
            HashMap<String, String> map = new HashMap<String, String>();
            String key = new String("k1");
            String value = "v1";
            map.put(key, value);
            System.out.println(map);
    
            key = null;
            System.gc();
    
            System.out.println(map);
        }
    
        public static void myWeakHashMap() throws InterruptedException {
            WeakHashMap<String, String> map = new WeakHashMap<String, String>();
            //String key = "weak";
            // 刚开始写成了上边的代码
            //思考一下,写成上边那样会怎么样? 那可不是引用了
            String key = new String("weak");
            String value = "map";
            map.put(key, value);
            System.out.println(map);
            //去掉强引用
            key = null;
            System.gc();
            Thread.sleep(1000);
            System.out.println(map);
        }
    }
    

    我们看下 ThreadLocal  中用到的弱引用

    static class ThreadLocalMap {
    
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //......
    }
    

    虚引用

    虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。

    虚引用,顾名思义,就是形同虚设,与其他几种引用都不太一样,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。

    虚引用需要java.lang.ref.PhantomReference 来实现。

    如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(RefenenceQueue)联合使用。

    虚引用的主要作用是跟踪对象垃圾回收的状态。仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。

    PhantomReference 的 get 方法总是返回 null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入 finalization 阶段,可以被 GC 回收,用来实现比 finalization 机制更灵活的回收操作。

    换句话说,设置虚引用的唯一目的,就是在这个对象被回收器回收的时候收到一个系统通知或者后续添加进一步的处理

    Java 允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

    public class PhantomReferenceDemo {
    
        public static void main(String[] args) throws InterruptedException {
            Object o1 = new Object();
            ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
            PhantomReference<Object> phantomReference = new PhantomReference<Object>(o1,referenceQueue);
    
            System.out.println(o1);
            System.out.println(referenceQueue.poll());
            System.out.println(phantomReference.get());
    
            o1 = null;
            System.gc();
            Thread.sleep(3000);
    
            System.out.println(o1);
            System.out.println(referenceQueue.poll()); //引用队列中
            System.out.println(phantomReference.get());
        }
    
    }
    
    java.lang.Object@4554617c
    null
    null
    null
    java.lang.ref.PhantomReference@74a14482
    null
    

    引用队列

    ReferenceQueue 是用来配合引用工作的,没有ReferenceQueue 一样可以运行。

    SoftReference、WeakReference、PhantomReference 都有一个可以传递 ReferenceQueue 的构造器。

    创建引用的时候,可以指定关联的队列,当 GC 释放对象内存的时候,会将引用加入到引用队列。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动,这相当于是一种通知机制。

    当关联的引用队列中有数据的时候,意味着指向的堆内存中的对象被回收。通过这种方式,JVM 允许我们在对象被销毁后,做一些我们自己想做的事情。

    最后,稍微了解下源码中的实现

    Reference源码(JDK8)

    强软弱虚四种引用,我们有了个大概的认识,我们也知道除了强引用没有对应的类型表示,是普遍存在的。剩下的三种引用都是 java.lang.ref.Reference 的直接子类。

    那就会有疑问了,我们可以通过继承 Reference,自定义引用类型吗?

    Abstract base class for reference objects. This class defines the operations common to all reference objects. Because reference objects are implemented in close cooperation with the garbage collector, this class may not be subclassed directly.

    JDK 官方文档是这么说的,Reference是所有引用对象的基类。这个类定义了所有引用对象的通用操作。因为引用对象是与垃圾收集器紧密协作而实现的,所以这个类可能不能直接子类化。

    Reference 的4种状态

    • Active:新创建的引用实例处于Active状态,但当GC检测到该实例引用的实际对象的可达性发生某些改变(实际对象处于 GC roots 不可达)后,它的状态将变化为Pending或者Inactive。如果 Reference 注册了ReferenceQueue,则会切换为Pending,并且Reference会加入pending-Reference链表中,如果没有注册ReferenceQueue,会切换为Inactive

    • Pending:当引用实例被放置在pending-Reference 链表中时,它处于Pending状态。此时,该实例在等待一个叫Reference-handler的线程将此实例进行enqueue操作。如果某个引用实例没有注册在一个引用队列中,该实例将永远不会进入Pending状态

    • Enqueued:在ReferenceQueue队列中的Reference的状态,如果Reference从队列中移除,会进入Inactive状态

    • Inactive:一旦某个引用实例处于Inactive状态,它的状态将不再会发生改变,同时说明该引用实例所指向的实际对象一定会被GC所回收

    Reference的构造函数和成员变量

    public abstract class Reference<T> {
       //引用指向的对象
       private T referent;    
       // reference被回收后,当前Reference实例会被添加到这个队列中
       volatile ReferenceQueue<? super T> queue;
       //下一个Reference实例的引用,Reference实例通过此构造单向的链表
       volatile Reference next;
       //由transient修饰,基于状态表示不同链表中的下一个待处理的对象,主要是pending-reference列表的下一个元素,通过JVM直接调用赋值
       private transient Reference<T> discovered;
       // 等待加入队列的引用列表,这里明明是个Reference类型的对象,官方文档确说是个list?
       //因为GC检测到某个引用实例指向的实际对象不可达后,会将该pending指向该引用实例,
       //discovered字段则是用来表示下一个需要被处理的实例,因此我们只要不断地在处理完当前pending之后,将discovered指向的实例赋予给pending即可。所以这个pending就相当于是一个链表。
       private static Reference<Object> pending = null;
        
        /* -- Constructors -- */
        Reference(T referent) {
            this(referent, null);
        }
    
        Reference(T referent, ReferenceQueue<? super T> queue) {
            this.referent = referent;
            this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
        }
    }
    

    Reference 提供了两个构造器,一个带引用队列 ReferenceQueue,一个不带。

    带 ReferenceQueue 的意义在于我们可以从外部通过对 ReferenceQueue 的操作来了解到引用实例所指向的实际对象是否被回收了,同时我们也可以通过 ReferenceQueue  对引用实例进行一些额外的操作;但如果我们的引用实例在创建时没有指定一个引用队列,那我们要想知道实际对象是否被回收,就只能够不停地轮询引用实例的get() 方法是否为空了。

    值得注意的是虚引用 PhantomReference,由于它的 get() 方法永远返回 null,因此它的构造函数必须指定一个引用队列。

    这两种查询实际对象是否被回收的方法都有应用,如 WeakHashMap 中就选择去查询 queue 的数据,来判定是否有对象将被回收;而 ThreadLocalMap,则采用判断 get() 是否为 null 来作处理。

    实例方法(和ReferenceHandler线程不相关的方法)

    private static Lock lock = new Lock();
    // 获取持有的referent实例
    public T get() {
        return this.referent;
    }
    // 把持有的referent实例置为null
    public void clear() {
        this.referent = null;
    }
    // 判断是否处于enqeued状态
    public boolean isEnqueued() {
        return (this.queue == ReferenceQueue.ENQUEUED);
    }
    // 入队参数,同时会把referent置为null
    public boolean enqueue() {
        return this.queue.enqueue(this);
    }
    

    ReferenceHandler线程

    通过上文的讨论,我们知道一个Reference实例化后状态为Active,其引用的对象被回收后,垃圾回收器将其加入到pending-Reference链表,等待加入ReferenceQueue。

    ReferenceHandler线程是由Reference静态代码块中建立并且运行的线程,它的运行方法中依赖了比较多的本地(native)方法,ReferenceHandler线程的主要功能就pending list中的引用实例添加到引用队列中,并将pending指向下一个引用实例。

    // 控制垃圾回收器操作与Pending状态的Reference入队操作不冲突执行的全局锁
    // 垃圾回收器开始一轮垃圾回收前要获取此锁
    // 所以所有占用这个锁的代码必须尽快完成,不能生成新对象,也不能调用用户代码
    static private class Lock { }
    private static Lock lock = new Lock();
    
    private static class ReferenceHandler extends Thread {
    
        private static void ensureClassInitialized(Class<?> clazz) {
            try {
                Class.forName(clazz.getName(), true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }
    
        static {
            ensureClassInitialized(InterruptedException.class);
            ensureClassInitialized(Cleaner.class);
        }
    
        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }
    
        public void run() {
            while (true) {
                tryHandlePending(true);
            }
        }
    }
    
    static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                // 判断pending-Reference链表是否有数据
                if (pending != null) {
                    // 如果有Pending Reference,从列表中取出
                    r = pending;
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // unlink 'r' from 'pending' chain
                    pending = r.discovered;
                    r.discovered = null;
                } else {
        // 如果没有Pending Reference,调用wait等待
                    if (waitForNotify) {
                        lock.wait();
                    }
                    // retry if waited
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            return true;
        } catch (InterruptedException x) {
            return true;
        }
    
        // Fast path for cleaners
        if (c != null) {
            c.clean();
            return true;
        }
    
        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }
    
    //ReferenceHandler线程是在Reference的static块中启动的
    static {
        // ThreadGroup继承当前执行线程(一般是主线程)的线程组
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        // 创建线程实例,命名为Reference Handler,配置最高优先级和后台运行(守护线程),然后启动
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        // ReferenceHandler线程有最高优先级
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();
    
        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }
    

    由于ReferenceHandler线程是Reference的静态代码创建的,所以只要Reference这个父类被初始化,该线程就会创建和运行,由于它是守护线程,除非 JVM 进程终结,否则它会一直在后台运行(注意它的run()方法里面使用了死循环)。

    ReferenceQueue源码

    public class ReferenceQueue<T> {
    
        public ReferenceQueue() { }
     // 内部类Null类继承自ReferenceQueue,覆盖了enqueue方法返回false
        private static class Null<S> extends ReferenceQueue<S> {
            boolean enqueue(Reference<? extends S> r) {
                return false;
            }
        }
      // 用于标识没有注册Queue
        static ReferenceQueue<Object> NULL = new Null<>();
        // 用于标识已经处于对应的Queue中
        static ReferenceQueue<Object> ENQUEUED = new Null<>();
    
        // 静态内部类,作为锁对象
        static private class Lock { };
        /* 互斥锁,用于同步ReferenceHandler的enqueue和用户线程操作的remove和poll出队操作 */
        private Lock lock = new Lock();
        // 引用链表的头节点
        private volatile Reference<? extends T> head = null;
        // 引用队列长度,入队则增加1,出队则减少1
        private long queueLength = 0;
    
        // 入队操作,只会被Reference实例调用
        boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
            synchronized (lock) {
       // 如果引用实例持有的队列为ReferenceQueue.NULL或者ReferenceQueue.ENQUEUED则入队失败返回false
                ReferenceQueue<?> queue = r.queue;
                if ((queue == NULL) || (queue == ENQUEUED)) {
                    return false;
                }
                assert queue == this;
                // 当前引用实例已经入队,那么它本身持有的引用队列实例置为ReferenceQueue.ENQUEUED
                r.queue = ENQUEUED;
                // 如果链表没有元素,则此引用实例直接作为头节点,否则把前一个引用实例作为下一个节点
                r.next = (head == null) ? r : head;
                // 当前实例更新为头节点,也就是每一个新入队的引用实例都是作为头节点,已有的引用实例会作为后继节点
                head = r;
                // 队列长度增加1
                queueLength++;
                // 特殊处理FinalReference,VM进行计数
                if (r instanceof FinalReference) {
                    sun.misc.VM.addFinalRefCount(1);
                }
                // 唤醒所有等待的线程
                lock.notifyAll();
                return true;
            }
        }
    
        // 引用队列的poll操作,此方法必须在加锁情况下调用
        private Reference<? extends T> reallyPoll() {       /* Must hold lock */
            Reference<? extends T> r = head;
            if (r != null) {
                @SuppressWarnings("unchecked")
                Reference<? extends T> rn = r.next;
                // 更新next节点为头节点,如果next节点为自身,说明已经走过一次出队,则返回null
                head = (rn == r) ? null : rn;
                r.queue = NULL;
                // 当前头节点变更为环状队列,考虑到FinalReference尚为inactive和避免重复出队的问题
                r.next = r;
                // 队列长度减少1
                queueLength--;
                if (r instanceof FinalReference) {
                    sun.misc.VM.addFinalRefCount(-1);
                }
                return r;
            }
            return null;
        }
    
        // 队列的公有poll操作,主要是加锁后调用reallyPoll
        public Reference<? extends T> poll() {
            if (head == null)
                return null;
            synchronized (lock) {
                return reallyPoll();
            }
        }
    // 移除引用队列中的下一个引用元素,实际上也是依赖于reallyPoll的Object提供的阻塞机制
        public Reference<? extends T> remove(long timeout)
            throws IllegalArgumentException, InterruptedException
        {
            if (timeout < 0) {
                throw new IllegalArgumentException("Negative timeout value");
            }
            synchronized (lock) {
                Reference<? extends T> r = reallyPoll();
                if (r != null) return r;
                long start = (timeout == 0) ? 0 : System.nanoTime();
                for (;;) {
                    lock.wait(timeout);
                    r = reallyPoll();
                    if (r != null) return r;
                    if (timeout != 0) {
                        long end = System.nanoTime();
                        timeout -= (end - start) / 1000_000;
                        if (timeout <= 0) return null;
                        start = end;
                    }
                }
            }
        }
    
        public Reference<? extends T> remove() throws InterruptedException {
            return remove(0);
        }
    
        void forEach(Consumer<? super Reference<? extends T>> action) {
            for (Reference<? extends T> r = head; r != null;) {
                action.accept(r);
                @SuppressWarnings("unchecked")
                Reference<? extends T> rn = r.next;
                if (rn == r) {
                    if (r.queue == ENQUEUED) {
                        // still enqueued -> we reached end of chain
                        r = null;
                    } else {
                        // already dequeued: r.queue == NULL; ->
                        // restart from head when overtaken by queue poller(s)
                        r = head;
                    }
                } else {
                    // next in chain
                    r = rn;
                }
            }
        }
    }
    

    ReferenceQueue只存储了Reference链表的头节点,真正的Reference链表的所有节点是存储在Reference实例本身,通过属性 next 拼接的,ReferenceQueue提供了对Reference链表的入队、poll、remove等操作

    参考与感谢

    https://juejin.im/post/5bce68226fb9a05ce46a0476

    http://www.kdgregory.com/index.php?page=java.refobj

    https://blog.csdn.net/Jesministrator/article/details/78786162

    http://throwable.club/2019/02/16/java-reference/

    《深入理解java虚拟机》

    热门内容:
    

    最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
    明天见(。・ω・。)ノ♡
    
    展开全文
  • 文章目录什么是ANR,如何避免主线程中的Looper.loop()一直无限循环为什么不会造成ANR?ListView原理与优化ContentProvider实现原理如何使用ContentProvider进行批量操作?为什么要使用通过`Con...
  • 从这里可以看出,解决循环依赖可以通过setter注入的方式,何为构造注入、何为setter注入此处详细解释了。毕竟DI是Spring中必须掌握的重点,参考官方文档即可。 如果说如何解决循环依赖,基本上对Spring有一点深入...
  • 腾讯、百度、阿里、京东、快手、斗鱼、华为、海康威视等面试过程。昨天分享了快手,今天来看看腾讯。 在面试之前给大家看看我的简历以及个人简介部分,因为面试过程与自己的简历有很大关系。面试官往往会结合简历...
  • 我们面试的时候 ,经常会被问这种到问题:Spring中bean的循环依赖怎么解决? Spring中bean的加载过程? spring相关的问题一直是大厂面试常问到的一个问题,也是一直困扰这我们,知道从哪里下手,今天举例分析大厂的...
  • 2017腾讯校招性格测试

    2021-07-23 14:41:29
    小伙伴们你们收到腾讯的面试通知书么,腾讯有一项考核性格测试的,下面是学习啦小编为你整理相关的内容以及一套软件测试题,希望大家喜欢!腾讯校招性格测试题如下;1.我常常会在没有任何特殊而且明显理由下,会觉得有...
  • 作者简介:  陈开江,希为科技CTO,曾任新浪微博资深算法工程师,...本文为《程序员》原创文章,未经允许不得转载,更多精彩文章请订阅《程序员》 推荐系统工程师技能树   掌握核心原理的技能 数学:微积分,...
  • 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 腾讯开发规范整理(精简) 阿里开发规范整理(精简) 总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:...
  • # 断开 obj 与其他对象的引用关系 gc(obj) # 回收 obj 内存 优点:思路简单,对象无用即回收,延迟低,适合内存少的场景 缺点:此算法中对象是孤立的,无法在全局视角检查对象的真实有效性,循环引用的双方...
  • 之前秋招面试腾讯云,一个多月的时间整理了全网的面经 希望可以帮助到面试CSIG的同学
  • 3. 函数文档 4. 函数参数 5. 函数的返回值 6. 变量作用域 Lambda-表达式 1. 匿名函数的定义 2. 匿名函数的应用 类与对象 1. 属性和方法组成对象 2. self是什么? 3. Python的魔法方法 4. 公有和私有 5. 继承 6. ...
  • 可能是全网最好的MySQL重要知识点/面试题总结

    万次阅读 多人点赞 2019-06-29 20:20:34
    在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。 不可重复度和幻读区别: 不可重复读的重点是修改,幻读的重点在于新增或者删除。 例1(同样的...
  • 【进大厂必学】3W字180张图学习Linux基础总结

    千次阅读 多人点赞 2021-05-22 10:39:50
    多说这段时间干啥去了吧,期间和很多的同学聊了天,有的童鞋已经开始工作,聊了聊工作上的事儿。有的是今年即将毕业的童鞋,有着自己的小目标,有的想尝试互联网,所以现在基本上都快进行二轮的复习了,有的同学...
  • 我一般会这样,保持这样一种状态:用户可以需要任何人就从文档中找到他需要的所有答案。如果我发现用户的问题无法从文档中找到,或者难以找到在文档中的什么地方,就会更新文档,或者重新组织文档。如果用户的问题...
  • 4.循环遍历确定每个字符的十进制值。 5.通过*=和-=进行计算拼接。 6.判断是否为负值返回结果。 14.静态代理和动态代理的区别,什么场景使用? 代理是一种常用的设计模式,目的是:为其他对象提供一个代理以控制对...
  • 腾讯前端面试经验(一)

    千次阅读 2018-03-12 08:36:30
    作者:汪汪链接:https://zhuanlan.zhihu.com/p/22606894来源:知乎著作权归作者所有,...腾讯面试对基础比较看重,然后需要你有两三个比较好的项目,一面重视面试者对前端基础的把握,还要手写代码,不过难,二面...
  • JUC并发编程学习文档

    2020-12-15 13:49:41
    JUC并发编程 1,什么是JUC(java.util.concurrent) java.util工具包 业务:普通的线程代码:Thread Runnable 没有返回值,效率相对callable较低 ...例如开启了一个进程(腾讯视频),播放声音是一个
  • 缺点:无法解决循环引用的问题,当A引用B,B也引用A的时候,此时AB对象的引用都为0,此时也就无法垃圾回收,所以一般主流虚拟机都采用这个方法; 可达性分析法从一个被称为GC Roots的对象向下搜索,如果一个对象...
  • Service【服务】:后台运行服务,提供界面呈现。 BroadcastReceiver【广播接收器】:用来接收广播。 Content Provider【内容提供商】:支持在多个应用中存储和读取数据,相当于数据库。 2、四个组件的生命周期? ...
  • 前端面试题 腾讯微视

    2020-09-03 23:02:15
    ajax 需要导入jQuery【体积较大 只是为了ajax去引入是值得的】 针对方向 axios 符合前端MVVM的浪潮 ajax 本身是针对MVC的编程 实现方法与返回值 axios 用promise技术实现对ajax技术的封装 返回值是 ...
  • 1、分号 要加分号 ...需要空格 对象的属性名后 前缀一元运算符后 后缀一元运算符前 函数调用括号前 无论是函数声明还是函数表达式,’('前不要空格 数组的’[‘后和’]'前 对象的...
  • 社区版支持第三方系统链接接入 B. 通过修改配置文件添加第三方系统链接 C. 通过PaaS平台DB管理端:domain/admin,添加“常用链接” D. 直接登录数据库,修改DB数据,添加“常用链接” 正确答案:C,D 以下哪些是生成...
  • Java面试通过?这篇文章你看了吗?

    万次阅读 多人点赞 2020-08-06 11:12:48
    注:本人才疏学浅,知识还在积累中,能保证每个回答都满足各种等级的高手们,(由于一些技术的升级,部分答案能保证实时同步准确,还请大家在阅读的时候多多留意)若发现有问题的话,请评论指出。
  • 腾讯原生小程序框架 OMIX 2.0 发布

    千次阅读 2019-10-30 11:18:53
    //监听,允许绑定多个 store.onChange(handler) //移除监听 store.offChange(handler) 复杂 store 拆分到多文件 当小程序变得非常复杂的时候,单文件单一的 store 会变得非常臃肿,所以需要拆分为多个 store 到新的...
  • 我的一个朋友在入职腾讯之前,大大小小的面试经历了十几次,最后终于在 4 轮技术面+1 轮 HR面之后成功收到 Offer,40k*16 薪! 第一轮主要考察 Java 基础,比如: hashmap的实现 Java中的垃圾回收 ...
  • 广告关闭腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元!整个数据类型也可以看做是一个”类“ 。每一种数据类型都是一个对象,也具有其自己的属性和方法。 了解...
  • 之前的阿里面试题都有做总结,具体面试题内容整理成了文档,本文是针对MySQL系列的,所以下面只展示了自己第一次面试阿里时被吊打问到的一些MySQL难题。 1、请解释关系型数据库概念及主要特点? 2、请说出关系型...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,575
精华内容 1,030
关键字:

腾讯文档不允许存在循环引用

友情链接: dianyuan.rar