发布于 

Android面试题整理3(音视频)

音视频开发高频面试题

1.为什么巨大的原始视频可以编码成很小的视频呢?这其中的技术是什么呢?

1)空间冗余:图像相邻像素之间有较强的相关性
2)时间冗余:视频序列的相邻图像之间内容相似
3)编码冗余:不同像素值出现的概率不同
4)视觉冗余:人的视觉系统对某些细节不敏感
5)知识冗余:规律性的结构可由先验知识和背景知识得到

2.怎么做到直播秒开优化?

  • DNS 解析慢 为了有效降低 DNS 解析对首开的影响,我们可以提前完成播放域名->IP 地址的解析, 并缓存起 来,播放的时候,直接传入带 IP 地址的播放地址,从而省去了 DNS 解析的耗时。 如果要支持用 IP 地址播放, 是需要修改底层 ffffmpeg 源码的。

  • 播放策略 很多侧重点播的播放器,为了减少卡顿,会有一些缓冲策略,当缓冲足够多的数据之后 ,再送入解码播放。而为了加快首开效果,需要对播放的缓冲策略做一些调整,如果第一帧还没有渲染出来的情况下, 不要做任何缓冲,直接送入解码器解码播放,这样就可以保证没有任何因为「主动」缓冲带来的首开延时。

  • 播放参数设置 所有基于 ffffmpeg 的播放器,都会遇到avformat_fifind_stream_info这个函数耗时比较久, 从而增大了首开时间,该函数主要作用是通过读取一定字节的码流数据, 来分析码流的基本信息,如编码信息、时长、码率、帧率等等,它由两个参数来控制其读取的数据量大小和时长, 一个是 probesize,一个是analyzeduration。减少 probesize 和 analyzeduration 可以有效地减少avformat_fifind_stream_info的函数耗时, 从而加快首开,但是需要注意的是,设置地太小可能会导致读取的数据量不足,从而无法解析出码流信息,导致播放失败, 或者出现只有音频
    没有视频,只有视频没有音频的问题。

  • 服务端优化

  • 服务器关键帧缓冲

  • CDN最近策略

3.直方图在图像处理里面最重要的作用是什么?

  1. 灰度直方图的定义:灰度级的函数,描述图像中该灰度级的像素个数或该灰度级像素出现的频率。反映了图像 灰度分布
    的情况。
  2. 灰度直方图只能反映图像的灰度分布情况,不能反映图像像素的位置,即所有的空间信息全部丢失。

直方图的应用:
a.数字化参数:判断一幅图像是否合理的利用了全部被允许的灰度级范围。一般一幅图应该利用全部或几乎全部可能
的灰度级,否则等于增加了量化间隔,丢失的信息将不能恢复。
b.边界阈值选取(确定图像二值化的阈值):假定某图像的灰度直方图具有二峰性,则表明这个图像的较亮区域和较
暗区域可以很好地分离,以这一点为阈值点,可以得到很好地2值处理效果(区分物体与背景)。
c.当物体部分的灰度值比其他部分的灰度值大时,可利用直方图统计图像中物体的面积。
d.计算图像的信息量H。

4.数字图像滤波有哪些方法?

均值滤波(邻域平均法)、中值滤波(消除独立的噪声点)、高斯滤波(线性平滑滤波,消除高斯噪声,对整幅图像进行加权平均,每一个像素点的值都由其本身和邻域内的其他像素值经过加权平均后得到)、KNN滤波、高通滤波、低通滤波等。

5.图像可以提取的特征有哪些?

颜色、纹理(粗糙度、方向度、对比度)、形状(曲率、离心率、主轴方向)、色彩等。

6.衡量图像重建好坏的标准有哪些?怎样计算?

  • SNR(信噪比)
  • PSNR=10*log10((2n-1)2/MSE) (MSE是原图像与处理图像之间均方误差,所以计算PSNR需要2幅图像的数据!)
  • SSIM (结构相似性分别从亮度对比度、对比度、结构3方面度量图像的相似性)

7.衡量图像重建好坏的标准有哪些?怎样计算?

  • SNR(信噪比)
  • PSNR=10*log10((2n-1)2/MSE) (MSE是原图像与处理图像之间均方误差,所以计算PSNR需要2幅图像的数据!)
  • SSIM (结构相似性分别从亮度对比度、对比度、结构3方面度量图像的相似性)

7.AAC和PCM的区别?

AAC在数据开始时候加了一些参数:采样率、声道、采样大小

8.H264存储的两个形态?

  • a. Annex B :
    StartCode :NALU单元,开头一般是0001或者001
    防竞争字节:为了区分 0 0 0 1,它采用0 0 0 0x3 1作为区分
    多用于网络流媒体中:rtmp、rtp格式

  • b. AVCC :
    表示NALU长度的前缀,不定长用4、2、1来存储这个NALU的长度
    防竞争字节多用于文件存储中mp4的格式

9.FFMPEG:图片如何合成视频

编码流程:

  1. av_register_all

  2. 为AVFormatContext 分配内存

  3. 打开文件

  4. 创建输出码流AVSream

  5. 找到编码器

  6. 打开编码器

  7. 写文件头,没有的就不写入

  8. 循环编码视频像素数据->视频压缩数据

    • 循环编码音频采样数据->音频压缩数据 ———>AVFrame转化为AVPacket
  9. 将编码后的视频码流写入文件 ——>AVPacket转化为AVFormat函数

  10. 关闭编码器

  11. 写文件尾

  12. 关闭资源文件
    解码流程:

  13. av_register_all

  14. 创建AVFormatContext的对象上下文

  15. 打开文件4. avformat_fifind_stream_info

  16. 找到解码器

  17. 打开解码器

  18. 创建AVCodecContext上下文

  19. av_read_frame :将avPacket数据转换为avFrame数据
    glUniform1i() ——>这个可以设置对应纹理的第几层 glTexSubImage2D() 和glTexImage2D区别————>替换纹理的内容

10.常见的音视频格式有哪些?

  1. MPEG(运动图像专家组)是Motion Picture Experts Group 的缩写。这类格式包括了MPEG-1,MPEG-2和MPEG-4在内的多种视频格式。
  2. AVI,音频视频交错(Audio Video Interleaved)的英文缩写。AVI这个由微软公司发布的视频格式,在视频领域可以说是最悠久的格式之一。
  3. MOV,使用过Mac机的朋友应该多少接触过QuickTime。QuickTime原本是Apple公司用于Mac计算机上的一种图像视频处理软件。
  4. ASF(Advanced Streaming format高级流格式)。ASF 是MICROSOFT 为了和的Real player 竞争而发展出来的一种可以直接在网上观看视频节目的文件压缩格式。
  5. WMV,一种独立于编码方式的在Internet上实时传播多媒体的技术标准,Microsoft公司希望用其取代QuickTime之类的技术标准以及WAV、AVI之类的文件扩展名。
  6. NAVI,如果发现原来的播放软件突然打不开此类格式的AVI文件,那你就要考虑是不是碰到了n AVI。n AVI是New AVI的缩写,是一个名为Shadow Realm 的地下组织发展起来的一种新视频格式。
  7. 3GP是一种3G流媒体的视频编码格式,主要是为了配合3G网络的高传输速度而开发的,也是目前手机中最为常见的一种视频格式。
  8. REAL VIDEO(RA、RAM)格式由一开始就是定位在视频流应用方面的,也可以说是视频流技术的始创者。
  9. MKV,一种后缀为MKV的视频文件频频出现在网络上,它可在一个文件中集成多条不同类型的音轨和字幕轨,而且其视频编码的自由度也非常大,可以是常见的DivX、XviD、3IVX,甚至可以是RealVideo、QuickTime、WMV 这类流式视频。
  10. FLV是FLASH VIDEO的简称,FLV流媒体格式是一种新的视频格式。由于它形成的文件极小、加载速度极快,使得网络观看视频文件成为可能,它的出现有效地解决了视频文件导入Flash后,使导出的SWF文件体积庞大,不能在网络上很好的使用等缺点。
  11. F4V,作为一种更小更清晰,更利于在网络传播的格式,F4V已经逐渐取代了传统FLV,也已经被大多数主流播放器兼容播放,而不需要通过转换等复杂的方式。

11.视频分量YUV的意义及数字化格式?

4:2:0;4:1:1;4:2:2;4:4:4;多种

12.在MPEG标准中图像类型有哪些?

I帧图像, P帧图像, B帧图像

13.列举一些音频编解码常用的实现方案?

  • 第一种就是采用专用的音频芯片对 语音信号进行采集和处理,音频编解码算法集成在硬件内部,如 MP3 编解码芯片、语音合成 分析芯片等。使用这种方案的优点就是处理速度块,设计周期短;缺点是局限性比较大,不灵活,难以进行系统升级。

  • 第二种方案就是利用 A/D 采集卡加上计算机组成硬件平台,音频编解码算法由计算机上的软件来实现。使用这种方案的优点是价格便 宜,开发灵活并且利于系统的升级;缺点是处理速度较慢,开发难度较大。

  • 第三种方案是使用高精度、高速度 的 A/D 采集芯片来完成语音信号的采集,使用可编程的数据处理能力强的芯片来实现语音信号处理的算法,然后 用 ARM 进行控制。采用这种方案的优点是系统升级能力强,可以兼容多种音频压缩格式甚至未来的音频压缩格 式,系统成本较低;缺点是开发难度较大,设计者需要移植音频的解码算法到相应的ARM 芯 片中去。

14.请叙述MPEG视频基本码流结构?

  1. Sequence Header
  2. Sequence Extention
  3. Group of picture Header
  4. Picture Header
  5. Picture coding extension

15.sps和pps的区别?

SPS是序列参数集 0x67 PPS是图像参数集 0x68 在SPS序列参数集中可以解析出图像的宽,高和帧率等信息。而在h264文件中,最开始的两帧数据就是SPS和PPS,这个h264文件只存在一个SPS帧和一个PPS帧。

16.请叙述AMR基本码流结构?

AMR文件由文件头和数据帧组成,文件头标识占6个字节,后面紧跟着就是音频帧;
格式如下所示:
文件头(占 6 字节)| :— | 语音帧1 | 语音帧2 | … |

  • 文件头: 单声道和多声道情况下文件的头部是不一致的,单声道情况下的文件头只包括一个Magic number,而多声道情况下文件头既包含Magic number,在其之后还包含一个32位的Chanel description fifield。多声道情况下的32位通道描述字符,前28位都是保留字符,必须设置成0,最后4位说明使用的声道个数。

  • 语音数据: 文件头之后就是时间上连续的语音帧块了,每个帧块包含若干个8位组对齐的语音帧,相对于若干个声道,从第一个声道开始依次排列。每一个语音帧都是从一个8位的帧头开始:其中P为填充位必须设为0,每个帧都是8位组对齐的。

17.说一说ffffmpeg的数据结构?

ffffmpeg的数据结构可以分为以下几类:


(1)解协议(http,rtsp,rtmp,mms) AVIOContext,URLProtocol,URLContext主要存储视音频使用的协议的类型以及状态。 URLProtocol存储输入音视频使用的封装格式。每种协议都对应一个URLProtocol结构。(注意:FFMPEG中文件也被当做一种协议“fifile”)

(2)解封装(flflv,avi,rmvb,mp4) AVFormatContext主要存储视音频封装格式中包含的信息 ffffmpeg支持各种各样的音视频输入和输出文件格式(例如FLV, MKV, MP4, AVI),而 AVInputFormat和AVOutputFormat 结构体则保存了这些格式的信息和一些常规设置。

(3)解码(h264,mpeg2,aac,mp3) AVStream是存储每一个视频/音频流信息的结构体。 AVCodecContext: 编解码器上下文结构体,存储该视频/音频流使用解码方式的相关数据。 AVCodec: 每种视频(音频)编解码器(例如H.264解码器)对应一 个该结构体。 三者的关系如下图:

(4)存数据 对于视频,每个结构一般是存一帧;音频可能有好几帧

  • 解码前数据:AVPacket
  • 解码后数据:AVFrame

18.说一说AVFormatContext 和 AVInputFormat之间的关系?

  • AVInputFormat被封装在AVFormatContext里
  • AVFormatContext 作为API被外界调用AVInputFormat 主要是FFmpeg内部调用
  • AVFormatContext里保存了视频文件封装格式相关信息,它是负责储存数据的结构体。而AVInputFormat代表了各个封装格式,属于方法,这是一种面向对象的封装。

通过 int avformat_open_input(AVFormatContext ps, const char* fifilename,AVInputFormat *fmt,AVDictionary options)函数装载解封装器. AVFormatContext 和 AVInputFormat之间的关系

19.说一说AVFormatContext, AVStream和AVCodecContext之间的关系?

AVStream和AVpacket中都有index字段用于区分不同的码流(视频、音频、字幕等),AVFormatContext中包含输入的AVStream数组用于记录各个码流,nb_streams记录输入码流的数量。AVCodecContext记录着AVStream需要用那种解码器来进行解码。

20.说一说视频拼接处理步骤?(细节处理,比如分辨率大小不一,时间处理等等)

解封装、解码、决定分辨率大小、编码、时间处理、封装。

21.NV21如何转换成I420?

首先需要明白为什么需要将NV21转换成I420,这是因为x264只支持编码I420的数据。实际上就是YUV420p与 YUV420sp之间的转换。 YUV420p与YUV420sp的相关知识请参考:《音视频基础知识-YUV
图像》

22.DTS与PTS共同点?

  • PTS就是Presentation Time Stamp也就说这个帧什么时候会放在显示器上;
  • DTS就是Decode Time Stamp,就是说这个帧什么时候被放在编码器去解。 在没有B帧的情况下,DTS和PTS的输出顺序是一样的。

23.影响视频清晰度的指标有哪些?

帧率 码率 分辨率 量化参数(压缩比)

24.编解码处理时遇到什么困难?

  • ffffmpeg 在编解码的过程:
    使用 ffffmpeg 的 libavcoder,libavformat 的库及可能性编解码
  • 编码过程:
    采集出来的视频、音频数据使用 ffffmpeg 进行压缩进行按连续的视频,音频进行分组打包,为了区分各种包,也要包加上包头,包头写上显示时间戳 PTS,解码时间戳 DTS,通过网络传输到播放端
  • 解码过程:
    通过 TCP 协议接收到媒体流,FFmpeg 解封装,解码最终获取到原始视频 YUV,音频 PCM 格式,利用播放器进行播放

25.如何秒开视频?什么是秒开视频?

  • 什么是秒开视频? 秒开是指用户点击播放到看到画面的时间非常短,在 1 秒之内。
  • 为什么需要秒开? 目前主流的直播协议是 RTMP,HTTP-FLV 和 HLS,都是基于 TCP 的长连接。在播放的 过程中,若播放端所处的网络环境在一个较佳的状态,此时播放会很流畅。若网络环境不是很稳定,经常 会发生抖动,如果播放端没有特殊处理,可能会经常发生卡顿,严重的甚至会出现黑屏。而移动直播由于 其便捷性,用户可以随时随地发起和观看直播,我们无法保证用户的网络一直处于非常好的状态,所以, 在网络不稳定的情况下保证播放的流畅度是非常重要的。
  1. 解决思路
  • 3.1 获取关键帧后显示 改写播放器逻辑让播放器拿到第一个关键帧后就给予显示。 GOP 的第一个帧通常都是关键帧,由于加载的数据较少,可以达到 “首帧秒开”。如果直播服务器支持 GOP 缓存,意味着播放器在和服务器建立连接后可立即拿到数据,从而省却跨地域和跨运营商的回源传输时间。 GOP 体现了关键帧的周期,也就是两个关键帧之间的距离,即一个帧组的最大帧数。假设一个视频的恒定帧率是24fps(即 1 秒 24 帧图像),关键帧周期为 2s,那么一个 GOP 就是 48 张图像。一般而言,每一秒视频至少需要使用一个关键帧。 增加关键帧个数可改善画质(GOP通常为 FPS 的倍数),但是同时增加了带宽和网络负载。这意味着,客户端播放器下载一个 GOP,毕竟该 GOP 存
    在一定的数据体积,如果播放端网络不佳,有可能不是能够快速在秒级以内下载完该 GOP,进而影响观感体验。 如果不能更改播放器行为逻辑为首帧秒开,直播服务器也可以做一些取巧处理,比如从缓存 GOP 改成缓存双关键帧(减少图像数量),这样可以极大程度地减少播放器加载 GOP 要传输的内容体积。
  • 3.2 app 业务逻辑层面优化 比如提前做好 DNS 解析(省却几十毫秒),和提前做好测速选线(择取最优线路)。经过这样的预处理之后,在点击播放按钮时,将极大提高下载性能。一方面,可以围绕传输层面做性能优化;另一方面,可以围绕客户播放行为做业务逻辑优化。两者可以有效的互为补 充,作为秒开的优化空间。
  1. 秒开视频方案
    4.1 优化服务器策略 播放器接入服务器请求数据的时间点的视频不一定是关键帧,那么需要等到下一个关键帧的到来,如果关键帧的周期是 2s 的话,那么等待的时间可能会在 0~2s 的范围内,这段等待的时间会影响首屏的加载时间。如果服务器有缓存,则播放端在接入的时候,服务器可以向前找最近的关键帧发给播放端,这样就可以省去等待的时间,可以大大的减少首屏的加载时间。
    4.2 优化播放端策略 播放端请求到的第一帧数据肯定是关键帧,关键帧能够通过帧内参考进行解码。这样播放端就可以在接收到第一个关键帧的时候就立即开始解码显示,而不需要等到缓存一定数量的视频帧才开始解码,这样也能减少首屏画面显示的时间。
    5 播放端首屏时长的优化 播放器的首屏过程中的几个步骤:
    首屏时间,指的是从进入直播间开始到第一次看到直播画面的时间。首屏时间过长极易导致用户失去对直播的耐心,降低用户的留存。但游戏直播对画面质量和连贯性的要求高,对应推流端编码后的数据量和其他类型直播相比大的多,如何降低首屏时间是一个不小的难题。
    在播放端的首屏过程中,主要有以下三个操作需要进行:加载直播间 UI(包括播放器本身)、下载直播
    数据流(未解码)和解码数据播放。其中数据解码播放又细分为以下几个步骤:
  • 检测传输协议类型(RTMP、RTSP、HTTP 等)并与服务器建立连接接收数据;
  • 视频流解复用得到音视频编码数据(H.264/H.265、AAC 等);总结: 首先,加载 UI 可以以单例的方式进行,能够一定程度地提升首屏展示速度;其次,可以预设解码类型,减少数据类型检测时间;再次,设定合理的下载缓冲区大小,尽可能减少下载的数据量,当检测到 I 帧数据,立即开始解码单帧画面进行播放,提高首屏展示时间。

26.如何降低延迟?如何保证流畅性?如何解决卡顿?解决网络抖动?

  1. 产生原因 保证直播的流畅性是指在直播过程中保证播放不发生卡顿,卡顿是指在播放过程中声音和画面出现停滞,非常影响用户体验。造成卡顿的原因有几种情况:
    推流端网络抖动导致数据无法发送到服务器,造成播放端卡顿; 播放端网络抖动导致数据累计在服务器上拉不下来,造成频繁卡顿。 由于从服务器到播放器的网络情况复杂,尤其是在 3G 和带宽较差的 WIFI 环境下,抖动和延迟经常发生,导致播放不流畅,播放不流畅带来的负面影响就是延时增大。如何在网络抖动的情况下保证播放的流畅性和实时性是保障直播性能的难点。

  2. 流畅度优化 目前主流的直播协议是 RTMP、HTTP-FLV 和 HLS,都是基于 TCP 的长连接。在播放的过程中,若播放端所处的网络环境在一个较佳的状态,此时播放会很流畅。若网络环境不是很稳定,经常会发生抖动,如果播放端没有做特殊处理,可能会经常发生卡顿,严重的甚至会出现黑屏。而移动直播由于其便捷性,用户可以随时随地发起和观看直播,我们无法保证用户的网络一直处于一个非常好的状态,所以,在网络不稳定的情况下保证播放的流畅度是非常重要的。

    为了解决这个问题,首先播放器需要将拉流线程和解码线程分开,并建立一个缓冲队列用于缓冲音视频数据。拉流线程将从服务器上获取到的音视频流放入队列,解码线程从队列中获取音视频数据进行解码播放,队列的长度可以调整。当网络发生抖动时,播放器无法从服务器上获取到数据或获取数据的速度较慢,此时队列中缓存的数据可以起到一个过渡的作用,让用户感觉不到网络发生了抖动。当然这是对于网络发生抖动的情况所采取的策略,如果播放端的网络迟迟不能恢复或服务器的边缘结点出现宕机,则需要应用层进行重连或调度。

27.预测编码的基本原理是什么?

音视频数据解码,音频数据同步至外设,视频数据渲染都屏幕,至此,视频开始播放,首屏时间结束。预测编码是数据压缩理论的一个重要分支。根据离散信号之间存在一定相关性特点,利用前面的一个或多个信号对下一个信号进行预测,然后对实际值和预值的差(预测误差)进行编码。如果预测比较准确,那么误差信号就会很小,就可以用较少的码位进行编码,以达到数据压缩的目的。

原理:利用以往的样本值对新样本值进行预测,将新样本值的实际值与其预测值相减,得到误差值,对该误差值进行编码,传送此编码即可。理论上数据源可以准确地用一个数学模型表示,使其输出数据总是与模型的输出一致,因此可以准确地预测数据,但是实际上预测器不可能找到如此完美的数学模型;预测本身不会造成失真。误差值的編码可以采用无失真压縮法或失真压縮法。

28.为什么要有YUV这种数据出来?(YUV相比RGB来说的优点)

RGB是指光学三原色红、绿和蓝,通过这3种的数值(0-255)改变可以组成其他颜色,全0时为黑色,全255时为白色。RGB是一种依赖于设备的颜色空间:不同设备对特定RGB值的检测和重现都不一样,因为颜色物质(荧光剂或者染料)和它们对红、绿和蓝的单独响应水平随着制造商的不同而不同,甚至是同样的设备不同的时间也不同。

YUV,是一种颜色编码方法。常使用在各个视频处理组件中。三个字母分别表示亮度信号Y和两个色差信号R-Y(即U)、B-Y(即V),作用是描述影像色彩及饱和度,用于指定像素的颜色。Y’UV的发明是由于
彩色电视与黑白电视的过渡时期。

黑白视频只有Y视频,也就是灰阶值。与我们熟知的RGB类似,YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽。

YUV和RGB是可以相互转换的,基本上所有图像算法都是基于YUV的,所有显示面板都是接收RGB数据。

29.H264/H265有什么区别?

同样的画质和同样的码率,H.265比H2.64 占用的存储空间要少理论50%。如果存储空间一样大,那么意味着,在一样的码率下H.265会比H.264 画质要高一些理论值是30%~40%。

比起H.264,H.265提供了更多不同的工具来降低码率,以编码单位来说,最小的8x8到最大的64x64。信息量不多的区域(颜色变化不明显)划分的宏块较大,编码后的码字较少,而细节多的地方划分的宏块就相应的小和多一些,编码后的码字较多,这样就相当于对图像进行了有重点的编码,从而降低了整体的码率,编码效率就相应提高了。

H.265标准主要是围绕着现有的视频编码标准H.264,在保留了原有的某些技术外,增加了能够改善码流、编码质量、延时及算法复杂度之间的关系等相关的技术。H.265研究的主要内容包括,提高压缩效率、提高鲁棒性和错误恢复能力、减少实时的时延、减少信道获取时间和随机接入时延、降低复杂度。

30. 视频或者音频传输,你会选择TCP协议还是UDP协议?为什么?

选择UDP协议,UDP实时性好。TCP要保证丢失的package会被再次重发,确保对方能够收到。 而在视频播放中,如果有一秒钟的信号确实,导致画面出现了一点瑕疵,那么最合适的办法是把这点瑕疵用随便哪些信号补充上,这样虽然画面有一点点瑕疵但是不影响观看。如果用的TCP的话,这点缺失的信号会被一遍又一遍的发送过来直到接收端确认收到。这不是音视频播放所期待的。而UDP就很适合这种情况。UDP不会一遍遍发送丢失的package。

31.平时说的软解和硬解,具体是什么?

硬解就是硬件解码,指利用GPU来部分代替CPU进行解码,软解就是软件解码,指利用软件让CPU来进行解码。两者的具体区别如下所示:

硬解码:是将原来全部交由CPU来处理的视频数据的一部分交由GPU来做,而GPU的并行运算能力要远远高于CPU,这样可以大大的降低对CPU的负载,CPU的占用率较低了之后就可以同时运行一些其他的程序了,当然,对于较好的处理器来说,比如i5 2320,或者AMD 任何一款四核心处理器来说,硬解和软件的区别只是个人偏好问题了吧。

软解码:即通过软件让CPU来对视频进行解码处理;而硬解码:指不借助于CPU,而通过专用的子卡设备来独立完成视频解码任务。曾经的VCD/DVD解压卡、视频压缩卡等都隶属于硬解码这个范畴。而现如今,要完成高清解码已经不再需要额外的子卡,因为硬解码的模块已经被整合到显卡GPU的内部,所以目前的主流显卡(集显)都能够支持硬解码技术。

32.何为直播?何为点播?

直播:是一个三方交互(主播、服务器、观众),这个交互式实时的!尽管会根据选择的协议不同而有一些延迟,但我们仍认为它直播是实时的!—>主播在本地发送音视频给服务器(推流),观众从服务器实时解码(拉流)收看收听主播发送给服务器的音视频(直播内容)。直播是不能快进的点播:首先一定要明确的一点,点播不存在推流这一过程,你本身你的流已经早就推给服务器了,或者这么说也不对,应该是你的音视频早就上传到了服务器,观众只需要在线收看即可,由于你的音视频上传到了服务器,观众则可以通过快进,快退,调整进度条等方式进行收看!

33.简述推流、拉流的工作流程?

推流:在直播中,一方向服务器发送请求,向服务器推送自己正在实时直播的数据,而这些内容在推送到服务器的这一过程中是以 “流” 的形式传递的,这就是“推流”,把音视频数据以流的方式推送(或上传)到服务器的过程就是“推流”! 推流方的音视频往往会很大,在推流的过程中首先按照 aac音频-编码 和 h264视【公众平台不能出现视频这两个字,真是坑】频-编码的标准把推过来的音视频压缩 ,然后合并成 MP4或者 FLV格式,然后根据直播的封装协议,最后传给服务器完成推流过程。

拉流:与推流正好相反,拉流是用户从服务器获取推流方给服务器的音视频的过程,这就是“拉流”!拉流首先aac音频-解码 和 h.264视【公众平台不能出现视频这两个字,真是坑】 频-解码的内部把推过来的音视频解压缩,然后合成MP4或者 FLV 格式,再解封装,最后到我们的客户端与观众进行交互。

Flutter高频面试题

1. Dart 语言的特性?

Productive(生产力高,Dart的语法清晰明了,工具简单但功能强大)
Fast(执行速度快,Dart提供提前优化编译,以在移动设备和Web上获得可预测的高性能和快速启动。)
Portable(易于移植,Dart可编译成ARM和X86代码,这样Dart移动应用程序可以在iOS、Android和其他地方运行)

Approachable(容易上手,充分吸收了高级语言特性,如果你已经知道C++,C语言,或者Java,你可以在短短几天内用Dart来开发)Reactive(响应式编程)

2.Dart的一些重要概念?

在Dart中,一切都是对象,所有的对象都是继承自ObjectDart是强类型语言,但可以用var或dynamic来声明一个变量,Dart会自动推断其数据类型,dynamic类似c#没有赋初值的变量都会有默认值null
Dart支持顶层方法,如main方法,可以在方法内部创建方法
Dart支持顶层变量,也支持类变量或对象变量
Dart没有public protected private等关键字,如果某个变量以下划线(_)开头,代表这个变量在库中是私有的

3.Dart 当中的 「…」表示什么意思?

Dart 当中的 「…」意思是 「级联操作符」,为了方便配置而使用。
…」和「.」不同的是 调用「…」后返回的相当于是 this,而「.」返回的则是该方法返回的值 。
. Dart 的作用域
Dart 没有 「public」「private」等关键字,默认就是公开的,私有变量使用 下划线 _开头。
. Dart 是不是单线程模型?是如何运行的?
Dart 是单线程模型,

引用《Flutter中文网》里的话:
Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队
列” microtask queue,另一个叫做“事件队列” event queue。

入口函数 main() 执行完后,消息循环机制便启动了。首先会按照先进先出的顺序逐个执
行微任务队列中的任务,当所有微任务队列执行完后便开始执行事件队列中的任务,事件
任务执行完毕后再去执行微任务,如此循环往复,生生不息。

4. Dart 多任务如何并行的?

刚才也说了,既然 Dart 不存在多线程,那如何进行多任务并行?
Dart 当中提供了一个 类似于新线程,但是不共享内存的独立运行的 worker - isolate。
那他们是如何交互的?
这里引用 flutter入门之dart中的并发编程、异步和事件驱动详解 中的一部分答案:
在dart中,一个Isolate对象其实就是一个isolate执行环境的引用,一般来说我们都是通过
当前的isolate去控制其他的isolate完成彼此之间的交互,而当我们想要创建一个新的
Isolate可以使用Isolate.spawn方法获取返回的一个新的isolate对象,两个isolate之间使
用SendPort相互发送消息,而isolate中也存在了一个与之对应的ReceivePort接受消息用
来处理,但是我们需要注意的是,ReceivePort和SendPort在每个isolate都有一对,只有
同一个isolate中的ReceivePort才能接受到当前类的SendPort发送的消息并且处理。

5.dart是值传递还是引用传递?

dart是值传递。我们每次调用函数,传递过去的都是对象的内存地址,而不是这个对象的复制。
先来看段代码

main(){
    Test a = new Test(5);
    print("a的初始值为:${a.value}");
    setValue(a);
    print("修改后a的值为: ${a.value}");
}
class Test{
    int value = 1;
    Test(int newValue){
    this.value = newValue;
    }
}
    setValue(Test s){
    print("修改value为100");
    s.value = 100;
}

输出结果为:
a的初始值为:5
修改value为100
修改后a的值为:100
从这里可以看出是值传递,如果只是复制了一个对象,然后把这个新建的对象地址传递到函数里面的话,
setValue()函数中的修改是不会影响到main函数中的a的,因为二者所引用的内存地址是不一样。
有些人可能会以以下代码反驳我:

main(){
int s = 6;
setValue(s);
print(s); //输出6,而不是7
}
class Test{
    int value = 1;
    Test(int newValue){
    this.value = newValue;
    }
}
setValue(int s){
    s += 1;
}

你看,这输出的不是6吗,在dart中一切皆为对象,如果是引用传递,那为什么是6啊。
答案是这样的,在setValue()方法中,参数s实际上和我们初始化int s = 6的s不是一个对象,只是他们现在指的是同一块内存区域,然后在setValue()中调用s += 1的时候,这块内存区域的对象执行+1操作,然后在堆(类比java)中产生了一个新的对象,s再指向这个对象。所以s参数只是把main函数中的s的内存地址复制过去了,就比如java中的:

public class Test {
        public static void main(String[] args) {
            Test a = new Test();
            Test b = a;
            b = new Test();
    }
}

我们只要记住一点,参数是把内存地址传过去了,如果对这个内存地址上的对象修改,那么其他位置的引用该内存地址的变量值也会修改。千万要记住dart中一切都是对象。

6. Dart 属于是强类型语言 ,但可以用 var 来声明变量,Dart 会自推导出数据类型,var 实际上是编译期的“语法糖”。dynamic 表示动态类型, 被编译后,实际是一个 object 类型,在编译期间不进行任何的类型检查,而是在运行期进行类型检查。

7. Dart 中 if 等语句只支持 bool 类型,switch 支持 String 类型。

8.Dart 中数组和 List 是一样的。

9.Dart 中,Runes 代表符号文字 , 是 UTF-32 编码的字符串, 用于如 Runes input = newRunes(‘\u{1f596} \u{1f44d}’);

10.Dart 支持闭包。

11.Dart 中 number 类型分为 int 和 double ,没有 float 类型。

12.Dart 中 级联操作符 可以方便配置逻辑,如下代码:

event
.id = 1
.type = ""
.actor = "";

13. 说一下 Future?

Future,字面意思「未来」,是用来处理异步的工具。
刚才也说过:
Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队
列” microtask queue,另一个叫做“事件队列” event queue。
Future 默认情况下其实就是往「事件队列」里插入一个事件,当有空余时间的时候就去执行,当
执行完毕后会回调 Future.then(v) 方法。
而我们也可以通过使用 Future.microtask 方法来向 「微任务队列」中插入一个任务,这样就会提
高他执行的效率。
因为在 Dart 每一个 isolate 当中,执行优先级为 : Main > MicroTask > EventQueue

14. 说一下 Stream?

Stream 和 Feature 一样,都是用来处理异步的工具。

但是 Stream 和 Feature 不同的地方是 Stream 可以接收多个异步结果,而Feature 只有一
个。
Stream 的创建可以使用 Stream.fromFuture,也可以使用 StreamController 来创建和控制。
还有一个注意点是:普通的 Stream 只可以有一个订阅者,如果想要多订阅的话,要使用
asBroadcastStream()。

15. 说一下 mixin?

关于什么是 mixin,引用 张风捷特烈 文章中的:
首先mixin是一个定义类的关键字。直译出来是混入,混合的意思 Dart为了支持多重继承,
引入了mixin关键字,它最大的特殊处在于: mixin定义的类不能有构造方法,这样可以避
免继承多个类而产生的父类构造方法冲突

16.Widget和element和RenderObject之间的关系

首先我详细说下当时的情景,面试官问我Widget和Element之间是不是一对多的关系,如果是增加
一个Widget之后,这个关系又是什么。 这部分还是没有很好地答案,现在只是一个猜想,如果添
加了一个widget,Element树遍历后面所有的Element看类型是否发生改变,有的话再重建
RenderObject。Element和Widget之间应该还是一对一的关系,因为每个Widget的context都是独一无二的。等想好了再写上去吧。

17. widget树的root节点

还是没能理解面试官的意思。。有能够理解的同学请评论告知我一下。 现在理解了,面试官的意
思应该指是runApp()方法中的那个的Widget。我当时也想说的,不过忘记这个方法名是啥了。。。

18. mixin extends implement之间的关系

继承(关键字 extends)
Flutter中的继承是单继承,构造函数不能继承,子类重写超类的方法,要用@override
子类调用超类的方法,要用super Flutter中的子类可以访问父类中的所有变量和方法,因为Flutter中没有公有、私有的区别 混入 mixins (关键字 with)
mixins Dart赋予的多继承特性,类似C#的多继承,而Java是不支持类的多继承,混合的对象是类,可以混合多个接口实现(关键字 implements)Flutter是没有interface的,但是Flutter中的每个类都是一个隐式的接口,这个接口包含类里的所有成员变量,以及定义的方法。
当class被当做interface用时,class中的成员变量也需要在子类里重新实现。在成员变量前加@override 这三种关系可以同时存在,但是有前后顺序:
extends -> mixins -> implements

19.Future和microtask执行顺序

  • MricroTask
    Dart 中事件机制的实现 :Main isolate 中有一个Looper,但存在两个Queue:Event Queue 和 Microtask
    Queue 。
  • Dart 中事件的执行顺序:Main > MicroTask > EventQueue。

在 Main 中写代码将最先执行;
执行完 Main 中的代码,然后会检查并执行 Microtask Queue 中的任务,
通常使用 scheduleMicrotask 将事件添加到 MicroTask Queue 中;
最后执行 EventQueue 队列中的代码,通常使用 Future 向 EventQueue加入时间,也可以使用
async 和 await 向 EventQueue 加入事件。

Future 先进先出
Future提供链式调用

new Future (() => print('拆分任务_1'))
then((i) => print('拆分任务_2'))
then((i) => print('拆分任务_3'))
whenComplete(()=>print('任务完成'));

多Future 和 多micTask 的执行顺序

void testScheduleMicrotatsk() {
scheduleMicrotask(() => print('Mission_1'));
//注释1
new Future.delayed(new Duration(seconds: 1), () => print('Mission_2'));

//注释2
new Future(() => print('Mission_3')).then((_) {
print('Mission_4');
scheduleMicrotask(() => print('Mission_5'));
}
).then((_) => print('Mission_6'));

//注释3
new Future(() => print('Mission_7'))
then((_) => new Future(() => print('Mission_8')))
then((_) => print('Mission_9'));

//注释4
new Future(() => print('Mission_10'));
scheduleMicrotask(() => print('Mission_11'));
print('Mission_12');
}
输出结果
I/flutter (19025): Mission_12
I/flutter (19025): Mission_1
I/flutter (19025): Mission_11
I/flutter (19025): Mission_3


I/flutter (19025): Mission_4
I/flutter (19025): Mission_6
I/flutter (19025): Mission_5
I/flutter (19025): Mission_7
I/flutter (19025): Mission_10
I/flutter (19025): Mission_8
I/flutter (19025): Mission_9
Syncing files to device MIX 3...
I/flutter (19025): Mission_2

20.await for的使用方式

代码如下:

String data;
getData() async {
data = await http.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
data
//延迟执行后赋值给
}

await关键字必须在async函数内部使用调用async函数必须使用await关键字返回默认是一个future对象

//定义了返回结果值为String类型
Future<String> getDatas(String category) async {
var request = await _httpClient.getUrl(Uri.parse(url));
var response = await request.close();
return await response.transform(utf8.decoder).join();
}
run() async{
int data = await getDatas('keji');
//因为类型不匹配,IDE会报错
}
Furture的作用是为了链式调用
//案例3
funA(){
set an important variable...
//设置变量
//使用变量
}
funB(){
...use the important variable...
}
main(){
new Future.then(funA()).then(funB());
// 明确表现出了后者依赖前者设置的变量值
new Future.then(funA()).then((_) {new Future(funB())});
//还可以这样用
//链式调用,捕获异常
new Future.then(funA(),onError: (e) { handleError(e); }).then(funB(),onError: (e) { handleError(e); })
}

21. 赋值操作符

比较有意思的赋值操作符有:
AA ?? “999” ///表示如果 AA 为空,返回999
AA ??= “999” ///表示如果 AA 为空,给 AA 设置成 999
AA ~/999 ///AA 对于 999 整除

22.可选方法参数

Dart 方法可以设置 参数默认值 和 指定名称 。
比如: getDetail(Sting userName, reposName, {branch = “master”}){} 方法,这里 branch 不设置的话,默认是 “master” 。参数类型 可以指定或者不指定。调用效果:getRepositoryDetailDao(“aaa",“bbbb”, branch: “dev”); 。

23.作用域

Dart 没有关键词 public 、private 等修饰符,_ 下横向直接代表 private ,但是有@protected 注解 。

24.构造方法

Dart 中的多构造方法,可以通过命名方法实现。
默认构造方法只能有一个,而通过 Model.empty() 方法可以创建一个空参数的类,其实方法名称
随你喜欢,而变量初始化值时,只需要通过 this.name 在构造方法中指定即可:

class ModelA {
String name;
String tag;
    //默认构造方法,赋值给name和tag
    ModelA(this.name, this.tag);
    //返回一个空的ModelA
    ModelA.empty();
    //返回一个设置了name的ModelA
    ModelA.forName(this.name);
}

25.getter setter 重写

Dart 中所有的基础类型、类等都继承 Object ,默认值是 NULL, 自带 getter 和 setter ,而如果是 final 或者 const 的话,那么它只有一个 getter 方法,Object 都支持 getter、setter 重写:

@override
Size get preferredSize {
return Size.fromHeight(kTabHeight + indicatorWeight);}

26.Assert(断言)

assert 只在检查模式有效,在开发过程中,assert(unicorn == null); 只有条件为真才正常,否则直接抛出异常,一般用在开发过程中,某些地方不应该出现什么状态的判断。

27.重写运算符,如下所示重载 operator 后对类进行 +/- 操作。

class Vector {
    final int x, y;
    Vector(this.x, this.y);
    Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
    Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
}

void main() {
    final v = Vector(2, 3);
    final w = Vector(2, 2);
    assert(v + w == Vector(4, 5));
    assert(v - w == Vector(0, 1));
}

支持重载的操作符:

类、接口、继承

Dart 中没有接口,类都可以作为接口,把某个类当做接口实现时,只需要使用 implements ,然
后复写父类方法即可。
Dart 中支持 mixins ,按照出现顺序应该为extends 、 mixins 、implements 。

Zone

Dart 中可通过 Zone 表示指定代码执行的环境,类似一个沙盒概念,在 Flutter 中 C++ 运行 Dart也是在 _runMainZoned 内执行 runZoned 方法启动,而我们也可以通过 Zone ,在运行环境内捕获全局异常等信息:

runZoned(() {
        runApp(FlutterReduxApp());
    }, onError: (Object obj, StackTrace stack) {
        print(obj);
        print(stack);
    }
);

同时你可以给 runZoned 注册方法,在需要时执行回调,如下代码所示,这样的在一个 Zone 内
任何地方,只要能获取 onData 这个 ZoneUnaryCallback,就都可以调用到 handleData

///最终需要处理的地方
handleData(result) {
print("VVVVVVVVVVVVVVVVVVVVVVVVVVV");
print(result);
}
///返回得到一个 ZoneUnaryCallback
var onData = Zone.current.registerUnaryCallback<dynamic, int>(handleData);
///执行 ZoneUnaryCallback 返回数据
Zone.current.runUnary(onData, 2);
异步逻辑可以通过 scheduleMicrotask 可以插入异步执行方法:
Zone.current.scheduleMicrotask((){
//todo something
    }
);

更多可参看 :《Flutter完整开发实战详解(十一、全面深入理解Stream)》Future

Future 简单了说就是对 Zone 的封装使用。
比如 Future.microtask 中主要是执行了 Zone 的 scheduleMicrotask ,而result._complete 最后调用的是 _zone.runUnary 等等。

factory Future.microtask(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
scheduleMicrotask(() {
try {
        result._complete(computation());
    }
}
catch (e, s) {
    _completeWithErrorCallback(result, e, s);
    }
);
    return result;
}

Dart 中可通过 async/await 或者 Future 定义异步操作,而事实上 async/await 也只是语法糖,最终还是通过编译器转为 Future。
有兴趣看这里 :
generators
code_generator.dart
Flutter完整开发实战详解(十一、全面深入理解Stream)

Stream

Stream 也是有对Zone 的另外一种封装使用。
Dart 中另外一种异步操作, async* / yield 或者 Stream 可定义 Stream 异步, async* / yield 也只是语法糖,最终还是通过编译器转为 Stream。Stream 还支持同步操作。

1)、Stream 中主要有 Stream 、 StreamController 、StreamSink 和 StreamSubscription 四个关键对象,大致可以总结为:

  • StreamController :如类名描述,用于整个 Stream 过程的控制,提供各类接口用于创建各
    种事件流。
  • StreamSink :一般作为事件的入口,提供如 add , addStream 等。
  • Stream :事件源本身,一般可用于监听事件或者对事件进行转换,如 listen 、where 。
  • StreamSubscription :事件订阅后的对象,表面上用于管理订阅过等各类操作,如 cacenl 、
    pause ,同时在内部也是事件的中转关键。

2)、一般通过 StreamController 创建 Stream;通过 StreamSink 添加事件;通过 Stream 监听事件;

通过 StreamSubscription 管理订阅。

3 )、Stream 中支持各种变化,比如map 、expand 、where 、take 等操作,同时支持转换为 Future 。

更多可参看 :《Flutter完整开发实战详解(十一、全面深入理解Stream)》

Flutter 部分

1. Flutter 是什么?

Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。

2. Flutter 特性有哪些?

快速开发(毫秒级热重载)
绚丽UI(内建漂亮的质感设计Material Design和Cupertino Widget和丰富平滑的动画效果和平
台感知)
响应式(Reactive,用强大而灵活的API解决2D、动画、手势、效果等难题)
原生访问功能
堪比原生性能

3. 基础知识

Flutter 和 React Native 不同主要在于 Flutter UI是直接通过 skia 渲染的 ,而 React
Native 是将 js 中的控件转化为原生控件,通过原生去渲染的 ,相关更多可查看:《移动端跨
平台开发的深度解析》。
Flutter 中存在 Widget 、Element 、RenderObject 、Layer 四棵树,其中 Widget 与 Element是一对多的关系 ,Element 中持有 Widget 和 RenderObject , 而 Element 与 RenderObject 是一一对应的关系(除去 Element 不存在 RenderObject 的情况,如 ComponentElement 是不具备RenderObject) ,
当 RenderObject 的 isRepaintBoundary 为 true 时,那么个区域形成一个 Layer,所以不是
每个 RenderObject 都具有 Layer 的,因为这受 isRepaintBoundary 的影响。

更多相关可查阅 《Flutter完整开发实战详解(九、 深入绘制原理)》
Flutter 中 Widget 不可变,每次保持在一帧,如果发生改变是通过 State 实现跨帧状态保
存,而真实完成布局和绘制数组的是 RenderObject ,Element 充当两者的桥梁, State 就
是保存在 Element 中。
Flutter 中的 BuildContext 只是接口,而 Element 实现了它。
Flutter 中 setState 其实是调用了 markNeedsBuild ,该方法内部标记此Element 为 Dirty ,然后在下一帧 WidgetsBinding.drawFrame 才会被绘制,这可以看出 setState 并不是立即生效的。
Flutter 中 RenderObject 在 attch/layout 之后会通过 markNeedsPaint(); 使得页面重绘,流程大概如下:

通过isRepaintBoundary 往上确定了更新区域,通过 requestVisualUpdate 方法触发更新往
下绘制。
正常情况 RenderObject 的布局相关方法调用顺序是 : layout ->performResize->performLayout->markNeedsPaint , 但是用户一般不会直接调用 layout,而是通过 markNeedsLayout ,具体流程如下:


Flutter 中一般 json 数据从 String 转为 Object 的过程中都需要先经过 Map 类型。
Flutter 中 InheritedWidget 一般用于状态共享,如Theme 、Localizations 、MediaQuery 等,都是通过它实现共享状态,这样我们可以通过 context 去获取共享的状态,比如
ThemeData theme = Theme.of(context);
在 Element 的 inheritFromWidgetOfExactType 方法实现里,有一个
Map<Type, InheritedElement> _inheritedWidgets 的对象。

_inheritedWidgets 一般情况下是空的,只有当父控件是 InheritedWidget 或者本身是 InheritedWidgets 时才会有被初始化,而当父控件是 InheritedWidget时,这个 Map 会被一级一级往下传递与合并 。
所以当我们通过 context 调用 inheritFromWidgetOfExactType 时,就可以往上查找到父控件的 Widget 。

Flutter 中默认主要通过 runtimeType 和 key 判断更新:

static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
    && oldWidget.key == newWidget.key;
    }
}

4. Flutter 中的生命周期

initState() 表示当前 State 将和一个 BuildContext 产生关联,但是此时BuildContext 没有完全装载完成,如果你需要在该方法中获取 BuildContext ,可以 new Future.delayed(const
Duration(seconds: 0, (){//context}); 一下。

didChangeDependencies() 在 initState() 之后调用,当 State 对象的依赖关系发生变化时,
该方法被调用,初始化时也会调用。

deactivate() 当 State 被暂时从视图树中移除时,会调用这个方法,同时页面切换时,也会
调用。

dispose() Widget 销毁了,在调用这个方法之前,总会先调用 deactivate()。

didUpdateWidge 当 widget 状态发生变化时,会调用。

通过 StreamBuilder 和 FutureBuilder 我们可以快速使用 Stream 和 Future 快速构建我们
的异步控件: 《Flutter完整开发实战详解(十一、全面深入理解Stream)》

Flutter 中 runApp 启动入口其实是一个 WidgetsFlutterBinding ,它主要是通过BindingBase 的子类 GestureBinding 、ServicesBinding 、SchedulerBinding 、PaintingBinding 、SemanticsBinding 、 RendererBinding 、WidgetsBinding 等,通过 mixins 的组合而成的。

Flutter 中的 Dart 的线程是以事件循环和消息队列的形式存在,包含两个任务队列,一个是 microtask 内部队列,一个是 event 外部队列,而 microtask 的优先级又高于event 。

因为 microtask 的优先级又高于 event, 同时会阻塞event 队列,所以如果microtask 太多就可能会对触摸、绘制等外部事件造成阻塞卡顿哦。
Flutter 中存在四大线程,分别为 UI Runner、GPU Runner、IO Runner, Platform Runner (原生主线程) ,同时在 Flutter 中可以通过 isolate 或者 compute 执行真正的跨线程异步操作。

5. PlatformView

Flutter 中通过 PlatformView 可以嵌套原生 View 到 Flutter UI 中,这里面其实是使用了
Presentation + VirtualDisplay + Surface 等实现的,大致原理就是:

使用了类似副屏显示的技术,VirtualDisplay 类代表一个虚拟显示器,调用 DisplayManager 的
createVirtualDisplay() 方法,将虚拟显示器的内容渲染在一个 Surface 控件上,然后将 Surface 的id 通知给 Dart,让 engine 绘制时,在内存中找到对应的 Surface 画面内存数据,然后绘制出来。em… 实时控件截图渲染显示技术。
Flutter 的 Debug 下是 JIT 模式,release下是AOT模式。
Flutter 中可以通过 mixins AutomaticKeepAliveClientMixin ,然后重写 wantKeepAlive 保持住页面,记得在被保持住的页面 build 中调用 super.build 。(因为 mixins 特性)。
Flutter 手势事件主要是通过竞技判断的:

主要有 hitTest 把所有需要处理的控件对应的 RenderObject , 从 child 到 parent 全部组合成列表,从最里面一直添加到最外层。

然后从队列头的 child 开始 for 循环执行 handleEvent 方法,执行 handleEvent 的过程不会被拦截打断。

一般情况下 Down 事件不会决出胜利者,大部分时候是在 MOVE 或者 UP 的时候才会决出胜利者。
竞技场关闭时只有一个的就直接胜出响应,没有胜利者就拿排在队列第一个强制胜利响应。
同时还有 didExceedDeadline 处理按住时的 Down 事件额外处理,同时手势处理一般在GestureRecognizer 的子类进行。

更多详细请查看:《Flutter完整开发实战详解(十三、全面深入触摸和滑动原理)》
Flutter 中 ListView 滑动其实都是通过改变 ViewPort 中的 child 布局来实现显示的。
常用状态管理的:目前有 scope_model 、flutter_redux 、fish_redux 、bloc + Stream 等几种模式,具体可见 : 《Flutter完整开发实战详解(十二、全面深入理解状态管理设计)》

6. Platform Channel

Flutter 中可以通过 Platform Channel 让 Dart 代码和原生代码通信的:

  • BasicMessageChannel :用于传递字符串和半结构化的信息。
  • MethodChannel :用于传递方法调用(method invocation)。
  • EventChanne l: 用于数据流(event streams)的通信。
    同时 Platform Channel 并非是线程安全的 ,更多详细可查阅闲鱼技术的 《深入理解Flutter
    Platform Channel》

7. Android 启动页

Android 中 Flutter 默认启动时会在 FlutterActivityDelegate.java 中读取AndroidManifset.xml内 meta-data 标签,其中 io.flutter.app.android.SplashScreenUntilFirstFrame 标志位如果为 ture ,就会启动 Splash 画面效果(类似IOS的启动页面)。

启动时原生代码会读取 android.R.attr.windowBackground 得到指定的 Drawable , 用于显示启动闪屏效果,之后并且通过 flutterView.addFirstFrameListener,在onFirstFrame 中移除闪屏。

8. Flutter 和 Dart的关系是什么?

Flutter是一个使用Dart语言开发的跨平台移动UI框架,通过自建绘制引擎,能高性能、高保真地进行移动开发。
Dart囊括了多数编程语言的优点,它更符合Flutter构建界面的方式。

9. Widget 和 element 和 RenderObject 之间的关系?

  • Widget是用户界面的一部分,并且是不可变的。

  • Element是在树中特定位置Widget的实例。

  • RenderObject是渲染树中的一个对象,它的层次结构是渲染库的核心。

    Widget会被inflate(填充)到Element,并由Element管理底层渲染树。Widget并不会直接管理状态及渲染,而是通过State这个对象来管理状态。Flutter创建Element的可见树,相对于Widget来说,是可变的,通常界面开发中,我们不用直接操作Element,而是由框架层实现内部逻辑。就如一个UI视图树中,可能包含有多个TextWidget(Widget被使用多次),但是放在内部视图树的视角,这些TextWidget都是填充到一个个独立的Element中。Element会持有renderObject和widget的实例。记住,Widget 只是一个配置,RenderObject 负责管理布局、绘制等操作。
    在第一次创建 Widget 的时候,会对应创建一个 Element, 然后将该元素插入树中。如果之后 Widget 发生了变化,则将其与旧的 Widget 进行比较,并且相应地更新 Element。重要的是,Element 不会被重建,只是更新而已。

10. mixin extends implement 之间的关系?

继承(关键字 extends)、混入 mixins (关键字 with)、接口实现(关键字 implements)。这三者可以同时存在,前后顺序是extends -> mixins -> implements。

Flutter中的继承是单继承,子类重写超类的方法要用@Override,子类调用超类的方法要用super。
在Flutter中,Mixins是一种在多个类层次结构中复用类代码的方法。mixins的对象是类,mixins绝不是继承,也不是接口,而是一种全新的特性,可以mixins多个类,mixins的使用需要满足一定条件。

11. 使用mixins的条件是什么?

因为mixins使用的条件,随着Dart版本一直在变,这里讲的是Dart2.1中使用mixins的条件:
mixins类只能继承自object
mixins类不能有构造函数
一个类可以mixins多个mixins类
可以mixins多个类,不破坏Flutter的单继承

12. mixin 怎么指定异常类型?

mixin允许你在一个类中混入另一个类的定义,以增加对象的功能。要指定异常类型,可以使用try-except-else语句。例如:

try:
   # code
except ExceptionType1 as e1:
   # handle ExceptionType1
except ExceptionType2 as e2:
   # handle ExceptionType2
else:
   # code if no exceptions were raised
finally:
   # code that will always be executed

13. Flutter main future mirotask 的执行顺序?

普通代码都是同步执行的,结束后会开始检查microtask中是否有任务,若有则执行,执行完继续检查microtask,直到microtask列队为空。最后会去执行event队列(future)。

14.Future和Isolate有什么区别?

future是异步编程,调用本身立即返回,并在稍后的某个时候执行完成时再获得返回结果。在普通代码中可以使用await 等待一个异步调用结束。
isolate是并发编程,Dartm有并发时的共享状态,所有Dart代码都在isolate中运行,包括最初的main()。每个isolate都有它自己的堆内存,意味着其中所有内存数据,包括全局数据,都仅对该isolate可见,它们之间的通信只能通过传递消息的机制完成,消息则通过端口(port)收发。isolate只是一个概念,具体取决于如何实现,比如在Dart VM中一个isolate可能会是一个线程,在Web中可能会是一个Web Worker。

15.Stream 与 Future是什么关系?

Stream 和 Future 是 Dart 异步处理的核心 API。Future 表示稍后获得的一个数据,所有异步的操作的返回值都用 Future 来表示。但是 Future 只能表示一次异步获得的数据。而 Stream 表示多次异步获得的数据。
比如界面上的按钮可能会被用户点击多次,所以按钮上的点击事件(onClick)就是一个 Stream 。简单地说,Future将返回一个值,而Stream将返回多次值。Dart 中统一使用 Stream 处理异步事件流。Stream 和一般的集合类似,都是一组数据,只不过一个是异步推送,一个是同步拉取。

16.Stream 两种订阅模式?

Stream有两种订阅模式:单订阅(single) 和 多订阅(broadcast)。单订阅就是只能有一个订阅者,而广播是可以有多个订阅者。这就有点类似于消息服务(Message Service)的处理模式。单订阅类似于点对点,在订阅者出现之前会持有数据,在订阅者出现之后就才转交给它。而广播类似于发布订阅模式,可以同时有多个订阅者,当有数据时就会传递给所有的订阅者,而不管当前是否已有订阅者存在。
Stream 默认处于单订阅模式,所以同一个 stream 上的 listen 和其它大多数方法只能调用一次,调用第二次就会报错。但 Stream 可以通过 transform() 方法(返回另一个 Stream)进行连续调用。通过Stream.asBroadcastStream() 可以将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性可以判断当前 Stream 所处的模式。

17. await for 如何使用?

await for是不断获取stream流中的数据,然后执行循环体中的操作。它一般用在直到stream什么时候完成,并且必须等待传递完成之后才能使用,不然就会一直阻塞。

Stream<String> stream = new Stream<String>.fromIterable(['不开心', '面试', '没', '过']);
main() async{
    print('上午被开水烫了脚');
    await for(String s in stream){
    print(s);
}
    print('晚上还没吃饭');
}

18. Flutter中的Widget、State、Context 的核心概念?是为了解决什么问题?

Widget: 在Flutter中,几乎所有东西都是Widget。将一个Widget想象为一个可视化的组件(或与应用可视化方面交互的组件),当你需要构建与布局直接或间接相关的任何内容时,你正在使用Widget。

Widget树: Widget以树结构进行组织。包含其他Widget的widget被称为父Widget(或widget容器)。包含在父widget中的widget被称为子Widget。

Context: 仅仅是已创建的所有Widget树结构中的某个Widget的位置引用。简而言之,将context作为widget树的一部分,其中context所对应的widget被添加到此树中。一个context只从属于一个widget,它和widget一样是链接在一起的,并且会形成一个context树。

State: 定义了StatefulWidget实例的行为,它包含了用于”交互/干预“Widget信息的行为和布局。应用于State的任何更改都会强制重建Widget。

这些状态的引入,主要是为了解决多个部件之间的交互和部件自身状态的维护。

19. Widget的两种类型是什么?

StatelessWidget: 一旦创建就不关心任何变化,在下次构建之前都不会改变。它们除了依赖于自身的配置信息(在父节点构建时提供)外不再依赖于任何其他信息。比如典型的Text、Row、Column、Container等,都是StatelessWidget。它的生命周期相当简单:初始化、通过build()渲染。

StatefulWidget: 在生命周期内,该类Widget所持有的数据可能会发生变化,这样的数据被称为State,这些拥有动态内部数据的Widget被称为StatefulWidget。比如复选框、Button等。State会与Context相关联,并且此关联是永久性的,State对象将永远不会改变其Context,即使可以在树结构周围移动,也仍将与该context相关联。当state与context关联时,state被视为已挂载。StatefulWidget由两部分组成,在初始化时必须要在createState()时初始化一个与之相关的State对象。

20. State 对象的初始化流程?

  • initState() : 一旦State对象被创建,initState方法是第一个(构造函数之后)被调用的方法。可通过重写来执行额外的初始化,如初始化动画、控制器等。重写该方法时,应该首先调用super.initState()。在initState中,无法真正使用context,因为框架还没有完全将其与state关联。initState在该State对象的生命周期内将不会再次调用。
  • didChangeDependencies(): 这是第二个被调用的方法。在这一阶段,context已经可用。如果你的Widget链接到了一个InheritedWidget并且/或者你需要初始化一些listeners(基于context),通常会重写该方法。
  • build(BuildContext context): 此方法在didChangeDependencies()、didUpdateWidget()之后被调用。每次State对象更新(或当InheritedWidget有新的通知时)都会调用该方法!我们一般都在build中来编写真正的功能代码。为了强制重建,可以在需要的时候调用setState((){…})方法。
  • dispose(): 此方法在Widget被废弃时调用。可重写该方法来执行一些清理操作(如解除listeners),并在此之后立即调用super.dispose()。

21. Widget 唯一标识Key有那几种?

在flutter中,每个widget都是被唯一标识的。这个唯一标识在build或rendering阶段由框架定义。该标识对应于可选的Key参数,如果省略,Flutter将会自动生成一个。

在flutter中,主要有4种类型的Key:GlobalKey(确保生成的Key在整个应用中唯一,是很昂贵的,允许element在树周围移动或变更父节点而不会丢失状态)、LocalKey、UniqueKey、ObjectKey。

22. 什么是Navigator? MaterialApp做了什么?

Navigator是在Flutter中负责管理维护页面堆栈的导航器。MaterialApp在需要的时候,会自动为我们创建Navigator。Navigator.of(context),会使用context来向上遍历Element树,找到MaterialApp提供的_NavigatorState再调用其push/pop方法完成导航操作。

23.flutter与React Native有什么不同?

React Native利用JavaScript桥将其小部件转换为OEM小部件。而且由于它不断地进行这种转换(比较和更新周期),因此会产生瓶颈并导致性能下降。
虽然仍然使用反应式视图的优势,但Flutter并没有使用这种桥将其自己的小部件转换为OEM小部件。除了快速和流畅的UI性能和可预测性之外,作为此项的另一个优势,您在Android KitKat设备上看到的内容与您在Android Pie上获得的内容相同。这种兼容性是显而易见的,因为Flutter不使用OEM小部件,并且不受不同Android版本之间的UI / UX更改的影响。

24.为什么说flutter是原生的

Flutter应用程序与平台的交互Flutter使用名为Skia的图形引擎在应用程序端执行所有UI呈现。这意味着它不依赖于平台提供的OEM小部件。

它只需要平台的画布来绘制自己的渲染。这确保了可预测性和开发人员对小部件和布局的完全控制。


Flutter内部小部件树

除此之外,Flutter将其结构保持为小部件树。顺便说一下,Flutter中的几乎所有东西都是一个小部件,它使您能够在小部件内部的小部件结构中构建您的应用程序。此内部树结构允许Skia仅呈现需要更新的小部件,并从缓存中检索未更改的甚至移动的小部件。

25. 讲一下flutter的几个特点/优缺点?

Dart是用于开发Flutter应用程序的面向对象,垃圾收集的编程语言。它也是由谷歌创建的,但它是开源的,因此它在Google内外都有社区。
除了Google的起源之外,Dart还被选为Flutter的语言,原因如下:它是极少数可以同时编译AOT(提前)和JIT(即时)的语言之一。
在应用程序开发过程中使用JIT编译,因为它可以通过动态编译代码来实现热重新加载(我将在下一个问题中详细讨论)和快速开发周期。
完成开发并准备发布后,将使用AOT编译。然后将代码AOT编译为本机代码,从而实现应用程序的快速启动和高性能执行。
就个人而言,我对Dart的经验是,如果您是熟悉Java或类似语言的开发人员,只需要几天的时间就可以习惯它。
因此,如果您是Android开发人员,那么这种语言的学习曲线应该非常低。
凭借其干净但灵活的语法,Dart可以被识别为仅包含任何高级编程语言中最需要的功能的语言。

26.什么是ScopedModel / BLoC模式?

ScopedModel和BLoC(业务逻辑组件)是常见的Flutter应用程序架构模式,可帮助将业务逻辑与UI代码分离,并使用更少的状态窗口小部件

27.什么是stateWidget和statelessWidget?

内边距margin 和外边距边距 padding

body: Center(
child: Container(
child: new Text("hello zzl ",
style: TextStyle(
fontSize: 40.0,
),textAlign: TextAlign.center,),alignment: Alignment.topCenter,
width: 500.0,
height: 400.0,
color: Colors.blue,padding: const EdgeInsets.all(50.0),//外边距
margin: const EdgeInsets.all(100.0),//内边距
    )
)
//填充控件 Padding

Padding的布局分为两种情况:
当child为空的时候,会产生一个宽为left+right,高为top+bottom的区域;
当child不为空的时候,Padding会将布局约束传递给child,根据设置的padding属性,缩小child的布局尺寸。
然后Padding将自己调整到child设置了padding属性的尺寸,在child周围创建空白区域。

import 'package:flutter/material.dart';
class PaddingDemo extends StatelessWidget{
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title:new Text("padding填充控件"),
),
body: new Padding(
padding: const EdgeInsets.all(8.0),
child: new Image.asset("images/hua3.png"),),);
    }
}
void main(){
    runApp(new MaterialApp(
    title: "padding填充控件",
    home: new PaddingDemo(),
    )
);

28.如何在Flutter中定义边距和填充?

内边距margin 和外边距边距 padding

body: Center(
child: Container(
child: new Text("hello zzl ",
style: TextStyle(
fontSize: 40.0,),textAlign: TextAlign.center,),alignment: Alignment.topCenter,width: 500.0,
height: 400.0,
color: Colors.blue,
padding: const EdgeInsets.all(50.0),//外边距
// margin: const EdgeInsets.all(100.0),//内边距
    )
)

填充控件 Padding
Padding的布局分为两种情况:
当child为空的时候,会产生一个宽为left+right,高为top+bottom的区域;
当child不为空的时候,Padding会将布局约束传递给child,根据设置的padding属性,缩小child的布局尺寸。
然后Padding将自己调整到child设置了padding属性的尺寸,在child周围创建空白区域。

import 'package:flutter/material.dart';
class PaddingDemo extends StatelessWidget{
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title:new Text("padding填充控件"),),body: new Padding(padding: const EdgeInsets.all(8.0),child: new Image.asset("images/hua3.png"),),);
    }
}
void main(){
    runApp(new MaterialApp(
    title: "padding填充控件",
    home: new PaddingDemo(),
    )
);

29. 谈一下flutter state的生命周期

State 的生命周期
从上面的例子中可以看到, 会要求提供一个含有视图树的 。
既然 能够控制一个视图的状态,那它肯定会有一系列的生命周期。

上图就是 State 的生命周期图。

  1. StatefulWidget.createState()
    Framework 调用会通过调用 StatefulWidget.createState() 来创建一个 State。

  2. initState()
    新创建的 State 会和一个 产生关联,此时认为 State 已经被安装好了,initState() 函数将会被调用。通常,我们可以重写这个函数,进行初始化操作。

  3. didChangeDependencies()
    在 initState() 调用结束后,这个函数会被调用。
    事实上,当 State 对象的依赖关系发生变化时,这个函数总会被 Framework 调用。

  4. build()
    经过以上步骤,系统认为一个 State 已经准备好了,就会调用 build() 来构建视图。
    我们需要在这个函数中,返回一个 Widget。

  5. deactivate()
    当 State 被暂时从视图树中移除时,会调用这个函数。
    页面切换时,也会调用它,因为此时 State 在视图树中的位置发生了变化,需要先暂时移除后添加。
    注意,重写的时候必须要调用 super.deactivate()。

  6. dispose()
    当 State 被永久的从视图树中移除,Framework 会调用该函数。
    在销毁前触发,我们可以在这里进行最终的资源释放。
    在调用这个函数之前,总会先调用 deactivate()。
    注意,重写的时候必须要调用 super.dispose()。

  7. didUpdateWidget(covariant T oldWidget)

当 widget 的配置发生变化时,会调用这个函数。比如, 的时候就会调用这个函数。
这个函数调用后,会调用 build()。

  1. setState()

当我需要更新 State 的视图时,需要手动调用这个函数,它会触发 build() 。

31. Flutter和RN的对比。

对比项 Flutter React Native
开发语言 Dart JavaScript
开发工具 Android Studio/Visual Studio Code Android Studio/Visual Studio Code
性能优势 动画:UI渲染性能高,60帧/秒 内存:RN底层采用弱类型编程,内存占用小
开发效率 代码可复用性高,开发过程快 需要联网调试,部分功能模块缺乏,开发过程缓慢
兼容性 兼容Android和iOS 兼容Android和iOS

32. 说一下Hot Reload,Hot Restart,热更新三者的区别和原理。

Hot Reload:热重载是一种技术,它允许在不停止应用程序的情况下动态更新应用程序的源代码,从而减少了重新启动应用程序的次数。它通过在应用程序运行时扫描源文件,以查看是否有任何更改,如果有,它将使用一系列编译器和反射技术来动态加载新的代码,而无需重新启动应用程序。

Hot Restart:热重启是指在不停止应用程序的情况下重新部署应用程序,以便应用程序可以继续运行。它主要是在更改应用程序的配置文件时使用,以便在不中断服务的情况下更新配置文件。

热更新:热更新是一种技术,用于在不停止应用程序的情况下更新应用程序的行为。它允许开发人员在不重新启动应用程序的情况下更新应用程序的功能,而不会中断服务。它通过重新加载更改的内容,而不必重新加载应用程序的全部代码来实现。

总结:热重载和热重启都是在应用程序运行时动态更新应用程序的技术,但是它们的目标不同。热重载针对的是应用程序的源代码,而热重启则针对应用程序的配置文件。而热更新则是允许在不停止应用程序的情况下更新应用程序的行为。

33. Flutter是如何做到一套Dart代码可以编译运行在Android和iOS平台的?所以说具体的原理。

Flutter使用Dart语言作为开发语言,因此可以在Android和iOS平台上编译运行一套代码。Flutter主要是通过跨平台技术实现的,它主要有三个层次:

  1. Flutter引擎:用于管理应用程序的生命周期和资源的系统框架。

  2. Flutter框架:建立在Flutter引擎之上的应用程序开发框架,它提供了构建应用程序的基础设施。

  3. 跨平台技术:主要是Flutter的渲染引擎,它可以将Dart代码编译成本地代码,并将其绘制到屏幕上,从而让应用程序在Android和iOS上运行。

Flutter使用了一种自定义的渲染引擎来将Dart代码编译成本地代码,它采用Skia图形库,使用自定义的高性能的渲染引擎来渲染图形,这样就能在Android和iOS上运行一套代码。此外,Flutter还提供了一个叫做Dart虚拟机的跨平台虚拟机,它可以在Android和iOS上运行Dart代码,从而实现一套代码在不同平台上运行的功能。

34. Flutter不具备反射,如果要使用反射,你应该如何使用?说一下大概的思路。

Flutter不具备反射,但是可以使用dart:mirrors包来模拟反射。大概的思路如下:

  1. 引入dart:mirrors包;
  2. 创建一个MirrorSystem对象;
  3. 使用MirrorSystem对象来获取相关类、接口、函数、属性、变量等信息;
  4. 通过获取的这些信息来进行反射操作。

35. Flutter在不使用WebView和JS方案的情况下。如何做到热更新?说一下大概思路。

  • 使用Flutter实现iOS和Android的代码分离,将更新的部分提取出来,放到云端服务器上;
  • 使用新的Flutter实例加载云端服务器上更新的部分;
  • 将新的Flutter实例与原有的Flutter实例进行比较,将不同的部分替换掉;
  • 将替换的部分保存到原有的Flutter实例中,实现热更新。

36. 如何让Flutter 编译出来的APP的包大小尽可能的变小?

  1. 优化图片文件。确保所有图片尽可能都采用压缩格式,如JPG、PNG等,并且尽量减小图片的分辨率,也可以采用webP格式;

  2. 优化代码。可以采用Dart编译器工具,对Dart代码进行缩减,减少代码中无用的空白字符,以及没有用到的代码和注释;

  3. 减小可执行文件的体积。可以使用AOT(Ahead Of Time)预编译技术,使可执行文件尽可能小化;

  4. 使用混淆技术。可以使用一些混淆工具,减小可执行文件的体积;

  5. 使用懒加载技术。可以将一些不需要立即加载的资源,使用懒加载技术,延迟加载;

  6. 减少Flutter包依赖的第三方库。可以尽量减少flutter依赖的第三方库,以及只使用必要的第三方库;

  7. 将Flutter应用分割成模块。可以将Flutter应用分割成多个模块,并且按需加载,以减少APP的总体大小;

37. 我们这个项目时一个综合系统的老项目,里面有Android,iOS,还有Web代码,是一个混合开发的项目,现在需要迁移到Flutter,加入你加入团队做这个项目的迁移工作,你觉得这个项目如何工程化、容器化以及架构演变应该从哪些维度思考?

  1. 首先,要考虑的是技术架构的更新,比如是否需要引入Flutter框架来替换Android,iOS和Web端的代码,使用Flutter框架来实现原有项目的功能。

  2. 其次,要考虑的是容器化的改造,可以考虑使用Docker容器来替换原来的虚拟机环境,减少部署成本和维护成本。

  3. 再次,要考虑的是工程化的改造,要考虑利用自动化构建工具,比如Gitlab或者Jenkins来实现代码的自动化测试、自动发布以及自动部署等。

  4. 最后,要考虑的是架构演变,要考虑是否需要把原有项目的技术架构进行演变,比如是否要进行微服务架构演变,以及是否需要进行大数据的引入等。

38. APP启动速度以及页面加载速度一直是我们比较关心的一个问题,特别是混合开发项目,谈谈你对Flutter渲染优化有哪些见解?

1、Flutter采用了新的渲染模型,采用了对象图和组件式结构,把大量的UI细节提取出来,更好的被编译器处理,从而极大地提高了渲染性能;

2、Flutter采用Skia作为其底层渲染引擎,并且完全基于GPU进行2D渲染,极大地提高了渲染性能;

3、Flutter的布局机制更加适合移动端应用,采用了双树结构,让布局表达更加简洁,从而更好的提高了渲染性能;

4、Flutter采用了基于Dart的Hot Reload技术,一些修改可以立即反应到APP界面上,把开发过程中的等待时间降低到了最低,从而提高了开发效率;

5、Flutter采用类似于浏览器中的DOM树的概念,把复杂的UI结构拆分成一个个的组件,从而更好的提高渲染性能。

39. 谈谈Flutter的内存回收管理机制,以及你平时是怎么处理内存的?内存泄漏和内存溢出你是怎么解决的?

我平时处理内存的方法是:

  1. 尽量使用系统自带的工具,比如使用LeakCanary来发现和跟踪内存泄漏。
  2. 避免使用静态变量,尤其是在创建大量对象时,应该尽量使用局部变量。
  3. 应该尽可能地释放不再使用的对象,以避免内存泄漏。
  4. 尽可能地优化程序结构,以减少内存耗费。

解决内存泄漏和内存溢出的方法是:

  1. 避免使用大量的静态变量,尤其是在创建大量对象时。
  2. 尽量释放不再使用的对象,以避免内存泄漏。
  3. 尽可能地优化程序结构,以减少内存耗费。
  4. 使用系统自带的工具,比如LeakCanary来发现和跟踪内存泄漏。

40. 再问一个简单一点的,你是如何把控混合项目开发时的生命周期(比如类似安卓的onCreate、onResume这种)和路由管理的?

我会采用MVVM架构,并使用RxJava2来控制生命周期,并使用一些库来处理路由管理,例如Dagger2,Moxy和RxJava2,等等。通过使用这些库可以更好地把控项目的生命周期和路由管理,这样可以更有效地实现和维护代码。

41. Flutter for web 和Flutter1.9推出的FlutterWeb有何本质上的区别?

Flutter for web 是一个开源的Web开发框架,它允许开发者使用Dart语言在Web环境中开发Flutter应用程序。Flutter for web 并不是一款完全独立的Web开发框架,而是基于Flutter框架,将Flutter框架用于Web开发的一个实现。

Flutter1.9中推出的FlutterWeb是一个完全独立的Web开发框架,它不仅支持Dart语言,还支持JavaScript和TypeScript等语言,可以让开发者使用不同的语言来开发Web应用程序。此外,FlutterWeb还支持Web Components和WebAssembly,以及支持移动端和桌面端的Flutter应用程序,可以更好地满足开发者的需求。

42. 谈谈你认为的Flutter Web应该如何改进?哪些内容可以改造之后可以用于平时的Web开发。谈谈你的改造方案。

Flutter Web是一种可以使用Flutter框架进行Web开发的工具,它拥有像原生应用一样的性能,同时具有跨平台性,可以支持多个浏览器。但是目前Flutter Web仍然存在一些限制,需要更多的改造来支持日常Web开发。

我认为Flutter Web应该增加对更多高级Web功能的支持,比如增强支持CSS3和HTML5,支持Web应用程序的跨域通信,以及增加对Web安全性的支持,例如禁止XSS攻击。同时,Flutter Web也应该支持更多的前端开发技术,如响应式Web设计、Ajax、Web服务和Web存储等。

此外,Flutter Web还应该加强对移动设备的支持,支持触摸、摇杆和手势操作等,以及支持跨平台的可视化界面,使得开发者可以在不同的设备上实现一致的体验。

最后,Flutter Web应该简化开发流程,提供更多的封装接口,让开发者可以更快地构建Web应用。

43. 谈谈如何打造低延迟的视频直播?为什么这样用?

1、建立基础设施:优化网络带宽,提升服务器的带宽和性能,同时采用网络加速技术;
2、增加视频缓存:在视频传输环节增加缓存,减少网络传输时间;
3、优化编码方案:优化视频编码方案,采用H.264、H.265、VP9等高效率编码格式,减少视频传输时的码率;
4、采用P2P传输:采用P2P传输技术,提高传输效率,减少网络延迟;
5、采用CDN加速:采用CDN加速技术,加快视频传输速度,提高用户的访问体验;

以上所述是打造低延迟的视频直播的方法,它们之间有一个共同的目的,就是通过优化网络带宽,提高服务器性能,减少视频传输时的码率,增加视频缓存,采用P2P传输和CDN加速的方式,最终实现低延迟的视频直播。

44. StatefulWidget 的生命周期

initState():Widget 初始化当前 State,在当前方法中是不能获取到 Context 的,如想获取,可
以试试 Future.delayed()
didChangeDependencies():在 initState() 后调用,State对象依赖关系发生变化的时候也会调用。
deactivate():当 State 被暂时从视图树中移除时会调用这个方法,页面切换时也会调用该方法,和
Android里的 onPause 差不多。
dispose():Widget 销毁时调用。
didUpdateWidget:Widget 状态发生变化的时候调用。
借用 CoorChice 文章 里的一张图:

45. Flutter 如何与 Android iOS 通信?

Flutter 通过 PlatformChannel 与原生进行交互,其中 PlatformChannel 分为三种:

  1. BasicMessageChannel:用于传递字符串和半结构化的信息。
  2. MethodChannel:用于传递方法调用。Flutter主动调用Native的方法,并获取相应的返回值。
  3. EventChannel:用于数据流(event streams)的通信。
    具体可以查看 闲鱼技术:深入理解 Flutter Platform Channel。

46. 什么是 Widgets、RenderObjects 和Elements?

  • Widget 仅用于存储渲染所需要的信息。
  • RenderObject 负责管理布局、绘制等操作。
  • Element 才是这颗巨大的控件树上的实体。
    具体可以查看 [译] Flutter,什么是 Widgets、RenderObjects 和 Elements?

47. 说一下什么是状态管理,为什么需要它?

首先状态其实是一个概念上的东西,区分全局状态和局部状态。
局部状态比如说一个控件中输入的信息,全局状态比如是登陆后从后台请求回来的 userId。
当全局状态越来越多,多个页面共享一个状态时,我们就需要管理它。
常用的状态管理有:

  • ScopedModel
  • BLoC
  • Redux / FishRedux
  • Provider

48. 说一下 BLoC 模式?

具体可以查看:
Vadaski - Flutter | 状态管理探索篇——BLoC(三)