一、 项目包体积优化
影响安装包体积大小的三个因素:
- Xcode 配置
- 资源文件
- 代码层面
资源文件瘦身
- 移除未使用资源(图片,音频,gif等资源)
- 删除
x1
图片 - 压缩图片等资源文件
- 删除重复文件
- 部分大资源文件通过运行下载
- 图片资源放入
.xcassets
代码瘦身
- 删除未使用代码
- 删除版本遗留代码
- 精简重复代码
Xcode
编译选项配置
Valid Architectures
设置编译生成的ipa包所支持的架构
Strip Link Product
和Deployment Postprocessing
Strip Linked Product
默认为 Yes
,Deployment Postprocessing
默认为 No
,Strip Linked Product
在 Deployment Postprocessing
设置为 YES
的时候才生效。当Strip Linked Product
设为YES的时候,ipa
会去除掉symbol
符号,运行 App
断点不会中断,在程序中打印[NSThread callStackSymbols]
也无法看到类名和方法名。而在程序崩溃时,终端的函数调用栈中也无法看到类名和方法名。但是不会影响正常的崩溃日志生成和解析,依然可以通过符号表来解析崩溃日志,适合线上使用,建议在 release
下都设置为 Yes
。
Generate Debug Symbols
默认为 Yes
,当设置为 Yes
时,编译生成的 .o
文件会更大,包含了断点信息和符号化的调试信息,方便开发阶段调试,建议在 release
下设置为 No
,线上需要获取崩溃信息时搭配编译生成的 dSYM
文件解析符号。
Enable C++ Exceptions
和Enable Objective-C Exceptions
默认都为 Yes
,用于捕获 C++
和 OC
的异常,如果项目中使用了 try catch
, 可考虑去掉并在 release
下设置为 No
,配合在 Other C Flags
添加 -fno-exceptions
和 -fno-rtt
,会有比较明显的体积减小。
其它
Framework
静态库瘦身
- 只保留需要的指令集
- 精简
Framework
代码,移除未使用代码
第三方库引入
- 可根据需要,保留部分第三方库代码,制作供项目使用的二方库
二、 组件化怎么做的
解耦分层
- 基础层:不常改动的基础类
- 通用层:工具类,数据管理,第三方库等
- 网络层:网络库,pingback投递等
- 业务层:首页,设置,详情等
- 路由:中间件
各个模块采用cocoapods
管理, 库采用的是制作成Framework
方式
路由实现方案
采用注册制,维护一个类的字符串的plist
文件,动态解析类名生成控制器,类似于蘑菇街的URL
注册方案
三、 https原理及charles抓包https原理
https
三要素:
- 加密:通过对称算法加密
- 认证:通过数字签名验证,因为私钥只有合法的发送方持有,其他人伪造的数字签名无法通过验证
- 报文完整性:通过数字签名实现,数字签名中包含了消息摘要,其他人篡改的消息无法通过验证
https
三阶段:
- CA证书校验:客户端验证证书合法性,从而验证公钥合法性
- 秘钥协商:客户端生成随机数,通过非对称加密,用公钥和私钥进行加密解密传输随机数
- 数据传输:使用第二步得到的随机数,通过对称加密进行数据传输
https
原理:
证书验证
- 客户端发起https请求
- 服务端返回https证书
- 客户端验证证书是否合法
数据传输
- 证书合法后,客户端在本地生成随机数
- 客户端用从服务器收到的证书中的公钥进行加密随机数,并把加密后的随机数传递到服务端
- 服务端通过私钥解密随机数
- 服务端通过客户端传递的随机数,用对称加密算法加密数据,传递给客户端
加密选择
https
使用的是对称加密和非对称加密混合,非对称加密是传输随机数用的,对称加密是对数据进行加密传输,因为非对称加密性能低
Charles
抓包原理概括为:
Charles抓包HTTPS的原理是通过拦截设备或模拟器上的网络流量,并使用自签名的SSL证书来解密和查看HTTPS请求和响应的内容。这样,开发者可以方便地分析和调试应用程序与服务器之间的加密通信。
具体有以下几个步骤:
代理设置
:为了拦截HTTPS流量,Charles需要在设备或模拟器上设置代理。通常,你需要在设备或模拟器的网络设置中配置代理服务器地址和端口,将其指向运行Charles的机器。SSL证书生成
:当设备或模拟器连接到Charles代理时,Charles会生成一个自签名的SSL证书。该证书用于代表Charles来与应用程序建立安全连接。安装根证书
:为了让设备或模拟器信任Charles生成的证书,你需要在设备或模拟器上安装Charles的根证书。这样,设备或模拟器就会将Charles的自签名证书视为受信任的证书。HTTPS代理
:当应用程序发起HTTPS请求时,Charles会拦截该请求并使用自己的SSL证书与应用程序建立安全连接。此时,Charles充当了应用程序与服务器之间的中间人。解密和查看流量
:使用自己的SSL证书,Charles可以解密经过它的HTTPS流量。这允许Charles查看请求和响应的内容,包括URL、请求头、请求体、响应头和响应体等信息。
中间人攻击原理
- 本地请求被劫持(如DNS劫持等),所有请求均发送到中间人的服务器
- 中间人服务器返回中间人自己的证书
- 客户端创建随机数,通过中间人证书的公钥对随机数加密后传送给中间人,然后凭随机数构造对称加密对传输内容进行加密传输
- 中间人因为拥有客户端的随机数,可以通过对称加密算法进行内容解密
- 中间人以客户端的请求内容再向正规网站发起请求
- 因为中间人与服务器的通信过程是合法的,正规网站通过建立的安全通道返回加密后的数据
- 中间人凭借与正规网站建立的对称加密算法对内容进行解密
- 中间人通过与客户端建立的对称加密算法对正规内容返回的数据进行加密传输
- 客户端通过与中间人建立的对称加密算法对返回结果数据进行解密
由于缺少对证书的验证,所以客户端虽然发起的是 HTTPS 请求,但客户端完全不知道自己的网络已被拦截,传输内容被中间人全部窃取。
参考资料: HTTPS原理和防范中间人攻击
四、 自动释放池原理(销毁的原理)
1. 基本
-
场景:
有些函数或者方法需要返回一个对象,而系统可能在返回对象之前,就已经销毁了对象。
如果要保证能正常返回对象,就需要让对象延迟销毁,自动释放池就可以满足这种情况。
-
概念:
自动释放池是一个存放对象的容器,它会保证延迟销毁池中的所有对象。
-
autorelease
:该方法不会改变对象的引用计数,只是将该对象添加到自动释放池中,它会返回调用该方法的对象本身。
当该自动释放池释放时,它里面的所有对象都会执行
release
方法 -
autoreleasepool
是由多个autoreleasepoolpage
以双向链表的形式连接起来 -
autoreleasepool
的基本原理:在每个自动释放池创建的时候,会在当前的
autoreleasepoolpage
中设置一个标记位,当有对象调用autorelease
时,会把对象添加到autoreleasepoolpage
中,若当前页添加满了,会初始化一个新页,然后用双向链表连接起来,并把初始化的新页作为hotpage
,自动释放池释放时,会从最下面依次往上pop
,调用每一个对象的release
方法,直到遇到标志位.
五、 App冷启动做了什么
启动时,App的进程不在系统里,需要开启新的进程
冷启动需要分为三个阶段:
main()
函数执行前(pre-main
阶段)main()
函数执行后(从main()
函数执行后到设置完rootViewController
)- 首屏渲染完成后(从
rootViewController
设置完成到didFinishLaunchWithOptions
方法作用域结束)
main()
函数执行前
- 加载可执行文件(
App
中所有的.o
文件) - 加载动态链接库,进行
rebase
指针调整和bind
符号绑定 ObjC
的runtime
初始化:包括ObjC
相关class
、category
注册和selector
唯一性检查- 初始化:包括执行
+load()
方法,attribute(constructor)
修饰的函数的调用,创建c++
静态全局变量等
总结:
1. 首先app
启动后,main()
函数调用前,系统内核(kernel
)会创建一个进程
其次,加载可执行文件
然后,加载dyld
,主要分为4
步
load dylibs
:这一阶段会分析应用依赖的dylib
,找到mach-o
文件,打开和读取文件并验证其有效性,接着找到代码签名注册到内核,最后对dylib
的每一个segment
进行mmap()
调用- 进行
rebase
指针调整和bind
符号绑定 ObjC
的setup()
:包括ObjC
相关class
、category
注册和selector
唯一性检查initializers
: 包括执行+load()
方法,attribute(constructor)
修饰的函数的调用,创建c++
静态全局变量等
2. main()
函数执行后
- 首屏初始化所需配置文件的读写操作
- 首屏列表大数据的读取
- 首屏渲染的计算
3. 首屏渲染完成后
- 初始化首屏渲染不需要的功能
- 优化主线程,先处理会卡主主线程的方法
六、flutter在setstate之后做了什么
@protected
void setState(VoidCallback fn) {
...
...
final Object? result = fn() as dynamic;
...
...
_element!.markNeedsBuild();
}
源码发现,在调用setState
之后,最终会到当前_element
元素调用markNeedsBuild
方法
element
是一个抽象类
abstract class Element extends DiagnosticableTree implements BuildContext
void markNeedsBuild() {
assert(_lifecycleState != _ElementLifecycle.defunct);
assert(owner != null);
assert(_lifecycleState == _ElementLifecycle.active);
...
if (dirty) {
return;
}
_dirty = true;
owner!.scheduleBuildFor(this);
}
markNeedsBuild
主要做了这几件事:
- 标记当前
element
为dirty
BuildOwner
拿到当前的element
,调用scheduleBuildFor
方法
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when [WidgetsBinding.drawFrame] calls [buildScope].
void scheduleBuildFor(Element element) {
assert(element.owner == this);
if (element._inDirtyList) {
_dirtyElementsNeedsResorting = true;
return;
}
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled!();
}
_dirtyElements.add(element);
element._inDirtyList = true;
assert(() {
if (debugPrintScheduleBuildForStacks) {
debugPrint('...dirty list is now: $_dirtyElements');
}
return true;
}());
}
将被标记dirty
的element
添加到_dirtyElements
链表
然后调用scheduleFrame
来注册Vsync
回调。 当下一次vsync
信号的到来时会执行handleBeginFrame()
和handleDrawFrame()
来更新UI
简单总结如下:
在Flutter中,setState()
方法是用于更新与当前Widget相关的状态并触发重新构建的方法。当调用setState()
时,Flutter会执行以下步骤:
- 标记状态变化:Flutter会标记当前Widget的状态已经发生变化,这意味着需要进行重新构建。
- 调用build方法:Flutter会调用当前Widget的
build()
方法,该方法返回一个新的Widget树。 - 对比前后Widget树:Flutter会将新的Widget树与之前的Widget树进行对比,找出差异。
- 更新差异部分:Flutter会将差异部分更新到底层的渲染引擎中,以更新屏幕上的UI。
基本上,setState()
方法的目的是通知Flutter框架当前Widget的状态已经改变,并触发重新构建,以便更新UI。这种机制使得Flutter能够高效地根据状态变化来更新UI,而不需要重建整个Widget树。
七、 链表反转
在遍历列表时,将当前节点的 next 指针改为指向前一个元素。由于节点没有引用其上一个节点,因此必须事先存储其前一个元素。在更改引用之前,还需要另一个指针来存储下一个节点,在最后返回新的头引用! 代码如下:
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode nextTemp = curr.next;
curr.next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
八、 快排时间复杂度
O(nlogn)~ O(n^2)
九、 flutter热重载,热启动区别
热重载
Flutter 的热重载功能可帮助你在无需重新启动应用程序的情况下快速、轻松地测试、构建用户界面、添加功能以及修复错误。通过将更新的源代码文件注入到正在运行的 Dart 虚拟机(VM) 来实现热重载。在虚拟机使用新的字段和函数更新类之后, Flutter 框架会自动重新构建 widget 树,以便你可以快速查看更改的效果。
JIT 和 AOT
JIT
(Just In Time):指的是即时编译或运行时编译,在 Debug
模式中使用,可以动态下发和执行代码,启动速度快,但执行性能受运行时编译影响;
AOT
(Ahead Of Time):指的是提前编译或运行前编译,在 Release
模式中使用,可以为特定的平台生成稳定的二进制代码,执行性能好、运行速度快,但每次执行均需提前编译,开发调试效率低。
这两种编译模式,AOT
是静态编译,最终产物编译成可直接执行的机器码,而 JIT
则是动态编译,dart
代码编译成的是中间代码,在程序运行的时候通过 Dart VM
解释运行。
热重载步骤
-
工程改动:热重载
Server
会逐一扫描工程中的文件,检查是否有新增、删除或者改动,直到找到在上次编译之后,发生变化的Dart
代码。 -
增量编译:热重载模块会将发生变化的
Dart
代码,通过编译转化为增量的Dart Kernel
文件。 -
推送更新:热重载
Server
将增量的Dart Kernel
文件通过RPC
协议,发送给正在手机上运行的Dart VM
。 -
代码合并:
Dart VM
会将收到的增量Dart Kernel
文件,与原有的Dart Kernel
文件进行合并,然后重新加载新的Dart Kernel
文件。 -
Widget
增量渲染:在确认Dart VM
资源加载成功后,Flutter
会将其UI
线程重置,通知flutter.framework
重建Widget
。
不支持热重载的场景
main
方法里的更改initState
方法里的更改- 代码出现编译错误
- 全局变量和静态属性的更改
Widget
状态无法兼容- 枚举和泛类型更改
这些情况只能通过热启动或者冷启动来处理
热重载只执行 build
方法,重新构建widget
树,不会执行 initState
和main
方法
参考资料: 热重载原理解析
十、 flutter与原生交互的方式
交互方式 | 用途 | 交互方向 | 返回值 |
---|---|---|---|
BasicMessageChannel | 传递字符串和半结构化信息 | 双向交互 | 有 |
MethodChannel | 传递方法 | 双向交互 | 有 |
EventChannel | 数据流通信 | 原生发送到Flutter | 无 |
三者本质上都是传递的数据
具体的使用方法可以参照我的如下文章: