• iphone13.4 升级后无法真机测试。在xcode中加入这个文件就可以安装了。 安装路径是:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport
  • Xcode iOS11.0 配置包

    2020-03-24 23:30:04
    Xcode iOS11.0版本的配置包 给需要真机调试iOS11.0系统版本手机又不想升级Xcode的童鞋 为什么会有摘要必须大于100字节这种逆天的设定!!!
  • 今天升级了一下iPhone5s(土豪金版)的系统到iOS8.4,xcode只能支持到8.3,真机调试遇到问题???下面是解决办法! 找到xcode,点击右键,打开显示包内容,按路径Contents/Develpoer/Platforms/iPoneOS.platform/...

    今天升级了一下iPhone5s(土豪金版)的系统到iOS8.4,xcode只能支持到8.3,真机调试遇到问题???下面是解决办法!

    找到xcode,点击右键,打开显示包内容,按路径Contents/Develpoer/Platforms/iPoneOS.platform/Developer/SDKs,这时候你就看到安装包了,我的是iPhoneOS8.3.sdk。

    然后copy一下该sdk到同目录,然后将名字命名为iPhoneOS8.4.sdk

    再然后Contents/Develpoer/Platforms/iPoneOS.platform/DeviceSupport,这里你会看到这样的一系列的文件夹,找到那个数字最大的文件夹,我这里是8.3(12F69),

    copy一份到DeviceSupport文件夹下,更名为你8.4(12H143)(为什么要改为这样的名字,去你手机去找版本号,那个版本号就是了)

    还差一步,按照刚才的那种路径的找法,找到SDKsettings.plist文件夹,将其中所有的与版本号有关的东东都改到最新的iOS的SDk的版本,我的是把8.3改为8.4.(直接改改不了,拷贝出来先,改完之后再拷贝替换)

    Xcode重启

    然后再接入真机,这个时候就可以进行真机调试了。

    展开全文
  • 业务部门的人员来测试,iphone手机需要打新版本的安装包,就当我像往日那样正常操作的时候,突然报错了,我有点傻眼了。原谅我只能拍照,我们的电脑不能连外网也不能插U盘。大神留下的真是一个大坑啊,我赶紧搜索...

    业务部门的人员来测试,iphone手机需要打新版本的安装包,就当我像往日那样正常操作的时候,突然报错了,我有点傻眼了。

    原谅我只能拍照,我们的电脑不能连外网也不能插U盘。大神留下的真是一个大坑啊,我赶紧搜索一下什么原因,原来是ios更新到最新的系统之后,Xcode版本不够支持最新的ios系统了。这不是坑爹吗,一更新ios还得跟着更新Xcode,我想起上次更新的时候的场景,4个多g更新了半天,我还是用手机开热点给他更新的,我真的肉痛。

    网上搜索了下有没有其他的解决方法,找到了以下这篇快速解决的方法。

    https://blog.csdn.net/u013316626/article/details/54100011

    但是,卵用没有,我搞了半天,xcode里的文件还不能随便更改的,要改权限又改了半天,我之前没怎么用过mac,我快奔溃了,改完了还是不行。。。。。

    请了做ios的同事来帮忙,他给我操作了一番。原来,只要在网上搜索相应版本的ios真机调试文件,放到/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport 路径下即可。


    不用改什么其他的文件!!!

    可能上面的博主的方法比较旧的版本可以,但是我试了下是真的不行。

    展开全文
  • iOS启动时间优化

    2018-08-09 00:37:44
    点击上方“iOS开发”,选择“置顶公众号”关键时刻,第一时间送达!作者:ZoomFenghttp://www.zoomfeng.com/blog/launch-time....
        

    点击上方“iOS开发”,选择“置顶公众号”

    关键时刻,第一时间送达!

    640?640?wx_fmt=gif


    作者:ZoomFeng

    http://www.zoomfeng.com/blog/launch-time.html

    iOS开发整理发布,转载请联系作者获得授权


    背景


    一个项目做的时间长了,启动流程往往容易杂乱,库也用的越来越多,APP的启动时间也会慢慢变长。本次将针对iOS APP的启动时间优化一波。


    通常针对一个技术点做优化的时候,都要先了解清楚这个技术点有哪些流程,优化的方向往往是减少流程的数量,以及减少每个流程的消耗。


    本次优化从结果上来看,main阶段的优化效果最显著,尤其是启动时的一些IO操作处理,对启动时间的减少有很大作用。多线程启动的设计和验证最有意思,但是在实践上由于我们业务本身的原因,只开了额外一个子线程来并行启动,且仅在子线程做了少量的独立操作,主要还是我们的业务之间耦合性太强了,不太适合拆分到子线程。


    一般说来,pre-main阶段的定义为APP开始启动到系统调用main函数这一段时间;main阶段则代表从main函数入口到主UI框架的viewDidAppear函数调用的这一段时间。(本文后续main阶段的时间统计都用viewDidAppear作为基准而非的applicationWillFinishLaunching)


    本文前半部分讲原理(内容基本是从网上借鉴/摘录),后半部分讲实践,pre-main阶段的原理比较难理解,不过实践倒是根据结论直接做就好了。


    App启动过程


    ①解析Info.plist 

    • 加载相关信息,例如闪屏

    • 沙箱建立、权限检查


    ②Mach-O加载 

    • 如果是胖二进制文件,寻找合适当前CPU架构的部分

    • 加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)

    • 定位内部、外部指针引用,例如字符串、函数等

    • 执行声明为__attribute__((constructor))的C函数

    • 加载类扩展(Category)中的方法

    • C++静态对象加载、调用ObjC的 +load 函数


    ③程序执行 

    • 调用main()

    • 调用UIApplicationMain()

    • 调用applicationWillFinishLaunching


    换成另一个说法就是:


    App开始启动后,系统首先加载可执行文件(自身App的所有.o文件的集合),然后加载动态链接器dyld,dyld是一个专门用来加载动态链接库的库。 执行从dyld开始,dyld从可执行文件的依赖开始, 递归加载所有的依赖动态链接库。


    动态链接库包括:iOS 中用到的所有系统 framework,加载OC runtime方法的libobjc,系统级别的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)。


    可执行文件的内核流程


    如图,当启动一个应用程序时,系统最后会根据你的行为调用两个函数,fork和execve。fork功能创建一个进程;execve功能加载和运行程序。这里有多个不同的功能,比如execl,execv和exect,每个功能提供了不同传参和环境变量的方法到程序中。在OSX中,每个这些其他的exec路径最终调用了内核路径execve。


    640?wx_fmt=png


    1、执行exec系统调用,一般都是这样,用fork()函数新建立一个进程,然后让进程去执行exec调用。我们知道,在fork()建立新进程之后,父进程与子进程共享代码段(TEXT),但数据空间(DATA)是分开的,但父进程会把自己数据空间的内容copy到子进程中去,还有上下文也会copy到子进程中去。


    2、为了提高效率,采用一种写时copy的策略,即创建子进程的时候,并不copy父进程的地址空间,父子进程拥有共同的地址空间,只有当子进程需要写入数据时(如向缓冲区写入数据),这时候会复制地址空间,复制缓冲区到子进程中去。从而父子进程拥有独立的地址空间。而对于fork()之后执行exec后,这种策略能够很好的提高效率,如果一开始就copy,那么exec之后,子进程(确定不是父进程?)的数据会被放弃,被新的进程所代替。


    启动时间的分布,pre-main和main阶段原理浅析


    640?wx_fmt=png


    640?wx_fmt=png


    rebase修复的是指向当前镜像内部的资源指针; 而bind指向的是镜像外部的资源指针。


    rebase步骤先进行,需要把镜像读入内存,并以page为单位进行加密验证,保证不会被篡改,所以这一步的瓶颈在IO。bind在其后进行,由于要查询符号表,来指向跨镜像的资源,加上在rebase阶段,镜像已被读入和加密验证,所以这一步的瓶颈在于CPU计算。这两个步骤在下面会详细阐述。


    pre-main过程

    640?wx_fmt=png



    main过程

    640?wx_fmt=png


    一些概念


    什么是dyld?


    动态链接库的加载过程主要由dyld来完成,dyld是苹果的动态链接器。


    系统先读取App的可执行文件(Mach-O文件),从里面获得dyld的路径,然后加载dyld,dyld去初始化运行环境,开启缓存策略,加载程序相关依赖库(其中也包含我们的可执行文件),并对这些库进行链接,最后调用每个依赖库的初始化方法,在这一步,runtime被初始化。


    当所有依赖库的初始化后,轮到最后一位(程序可执行文件)进行初始化,在这时runtime会对项目中所有类进行类结构初始化,然后调用所有的load方法。最后dyld返回main函数地址,main函数被调用,我们便来到了熟悉的程序入口。


    当加载一个 Mach-O 文件 (一个可执行文件或者一个库) 时,动态链接器首先会检查共享缓存看看是否存在其中,如果存在,那么就直接从共享缓存中拿出来使用。每一个进程都把这个共享缓存映射到了自己的地址空间中。这个方法大大优化了 OS X 和 iOS 上程序的启动时间。


    640?wx_fmt=png


    问题:测试发现,由于手机从开机后,连续两次启动同一个APP的pre-main实际时间的差值比较大,这一步可以在真机上复现,那么这两次启动pre-main的时间差值,是跟系统的framework关系比较大,还是跟APP自身依赖的第三方framework关系比较大呢?


    Mach-O 镜像文件


    Mach-O 被划分成一些 segement,每个 segement 又被划分成一些 section。segment 的名字都是大写的,且空间大小为页的整数。页的大小跟硬件有关,在 arm64 架构一页是 16KB,其余为 4KB。


    section 虽然没有整数倍页大小的限制,但是 section 之间不会有重叠。几乎所有 Mach-O 都包含这三个段(segment): __TEXT,__DATA和__LINKEDIT。


    • __TEXT 包含 Mach header,被执行的代码和只读常量(如C 字符串)。只读可执行(r-x)。

    • __DATA 包含全局变量,静态变量等。可读写(rw-)。

    • __LINKEDIT 包含了加载程序的『元数据』,比如函数的名称和地址。只读(r–)。


    ASLR(Address Space Layout Randomization):地址空间布局随机化,镜像会在随机的地址上加载。


    传统方式下,进程每次启动采用的都是固定可预见的方式,这意味着一个给定的程序在给定的架构上的进程初始虚拟内存都是基本一致的,而且在进程正常运行的生命周期中,内存中的地址分布具有非常强的可预测性,这给了黑客很大的施展空间(代码注入,重写内存);


    如果采用ASLR,进程每次启动,地址空间都会被简单地随机化,但是只是偏移,不是搅乱。大体布局——程序文本、数据和库是一样的,但是具体的地址都不同了,可以阻挡黑客对地址的猜测 。


    代码签名:可能我们认为 Xcode 会把整个文件都做加密 hash 并用做数字签名。其实为了在运行时验证 Mach-O 文件的签名,并不是每次重复读入整个文件,而是把每页内容都生成一个单独的加密散列值,并存储在 __LINKEDIT 中。这使得文件每页的内容都能及时被校验确并保不被篡改。


    关于虚拟内存


    我们开发者开发过程中所接触到的内存均为虚拟内存,虚拟内存使App认为它拥有连续的可用的内存(一个连续完整的地址空间),这是系统给我们的馈赠,而实际上,它通常是分布在多个物理内存碎片,系统的虚拟内存空间映射vm_map负责虚拟内存和物理内存的映射关系。


    ARM处理器64bit的架构情况下,也就是0x000000000 - 0xFFFFFFFFF,每个16进制数是4位,即2的36次幂,就是64GB,即App最大的虚拟内存空间为64GB。


    共享动态库其实就是共享的物理内存中的那份动态库,App虚拟内存中的共享动态库并未真实分配物理内存,使用时虚拟内存会访问同一份物理内存达到共享动态库的目的。


    iPhone7 PLUS(之前的产品最大为2GB)的物理内存RAM也只有3GB,那么超过3GB的物理内存如何处理呢,系统会使用一部分硬盘空间ROM来充当内存使用,在需要时进行数据交换,当然磁盘的数据交换是远远慢于物理内存的,这也是我们内存过载时,App卡顿的原因之一。


    系统使用动态链接有几点好处:


    代码共用:很多程序都动态链接了这些 lib,但它们在内存和磁盘中中只有一份。


    易于维护:由于被依赖的 lib 是程序执行时才链接的,所以这些 lib 很容易做更新,比如libSystem.dylib 是 libSystem.B.dylib 的替身,哪天想升级直接换成libSystem.C.dylib 然后再替换替身就行了。


    减少可执行文件体积:相比静态链接,动态链接在编译时不需要打进去,所以可执行文件的体积要小很多。


    640?wx_fmt=png


    上图中,TEXT段两个进程共用,DATA段每个进程各一份。

    下面开始详细分析pre-main的各个阶段


    加载 Dylib


    从主执行文件的 header 获取到需要加载的所依赖动态库列表,而 header 早就被内核映射过。然后它需要找到每个 dylib,然后打开文件读取文件起始位置,确保它是 Mach-O 文件。接着会找到代码签名并将其注册到内核。然后在 dylib 文件的每个 segment 上调用 mmap()。


    应用所依赖的 dylib 文件可能会再依赖其他 dylib,所以 dyld 所需要加载的是动态库列表一个递归依赖的集合。一般应用会加载 100 到 400 个 dylib 文件,但大部分都是系统 dylib,它们会被预先计算和缓存起来,加载速度很快。


    加载系统的 dylib 很快,因为有优化(因为操作系统自己要用部分framework所以在操作系统开机后就已经缓存了?)。但加载内嵌(embedded)的 dylib 文件很占时间,所以尽可能把多个内嵌 dylib 合并成一个来加载,或者使用 static archive。使用 dlopen() 来在运行时懒加载是不建议的,这么做可能会带来一些问题,并且总的开销更大。


    在每个动态库的加载过程中, dyld需要:


    1. 分析所依赖的动态库

    2. 找到动态库的mach-o文件

    3. 打开文件

    4. 验证文件

    5. 在系统核心注册文件签名

    6. 对动态库的每一个segment调用mmap()


    针对这一步骤的优化有:


    1. 减少非系统库的依赖;

    2. 使用静态库而不是动态库;

    3. 合并非系统动态库为一个动态库;


    Rebase && Binding


    Fix-ups


    在加载所有的动态链接库之后,它们只是处在相互独立的状态,需要将它们绑定起来,这就是 Fix-ups。代码签名使得我们不能修改指令,那样就不能让一个 dylib 的调用另一个 dylib。这时需要加很多间接层。


    现代 code-gen 被叫做动态 PIC(Position Independent Code),意味着代码可以被加载到间接的地址上。当调用发生时,code-gen 实际上会在 __DATA 段中创建一个指向被调用者的指针,然后加载指针并跳转过去。


    所以 dyld 做的事情就是修正(fix-up)指针和数据。Fix-up 有两种类型,rebasing 和 binding。


    Rebase


    Rebasing:在镜像内部调整指针的指向,针对mach-o在加载到内存中不是固定的首地址(ASLR)这一现象做数据修正的过程;


    由于ASLR(address space layout randomization)的存在,可执行文件和动态链接库在虚拟内存中的加载地址每次启动都不固定,所以需要这2步来修复镜像中的资源指针,来指向正确的地址。 rebase修复的是指向当前镜像内部的资源指针; 而bind指向的是镜像外部的资源指针。


    在iOS4.3前会把dylib加载到指定地址,所有指针和数据对于代码来说都是固定的,dyld 就无需做rebase/binding了。


    iOS4.3后引入了 ASLR ,dylib会被加载到随机地址,这个随机的地址跟代码和数据指向的旧地址会有偏差,dyld 需要修正这个偏差,做法就是将 dylib 内部的指针地址都加上这个偏移量,偏移量的计算方法如下:


    Slide = actual_address - preferred_address


    然后就是重复不断地对 __DATA 段中需要 rebase 的指针加上这个偏移量。这就又涉及到 page fault 和 COW。这可能会产生 I/O 瓶颈,但因为 rebase 的顺序是按地址排列的,所以从内核的角度来看这是个有次序的任务,它会预先读入数据,减少 I/O 消耗。


    在 Rebasing 和 Binding 前会判断是否已经 Prebinding。如果已经进行过预绑定(Prebinding),那就不需要 Rebasing 和 Binding 这些 Fix-up 流程了,因为已经在预先绑定的地址加载好了。


    rebase步骤先进行,需要把镜像读入内存,并以page为单位进行加密验证,保证不会被篡改,所以这一步的瓶颈在IO。bind在其后进行,由于要查询符号表,来指向跨镜像的资源,加上在rebase阶段,镜像已被读入和加密验证,所以这一步的瓶颈在于CPU计算。


    Binding


    Binding:将指针指向镜像外部的内容,binding就是将这个二进制调用的外部符号进行绑定的过程。比如我们objc代码中需要使用到NSObject, 即符号_OBJC_CLASS_$_NSObject,但是这个符号又不在我们的二进制中,在系统库 Foundation.framework中,因此就需要binding这个操作将对应关系绑定到一起;


    lazyBinding就是在加载动态库的时候不会立即binding, 当时当第一次调用这个方法的时候再实施binding。 做到的方法也很简单: 通过dyld_stub_binder这个符号来做。lazyBinding的方法第一次会调用到dyld_stub_binder, 然后dyld_stub_binder负责找到真实的方法,并且将地址bind到桩上,下一次就不用再bind了。


    Binding 是处理那些指向 dylib 外部的指针,它们实际上被符号(symbol)名称绑定,也就是个字符串。__LINKEDIT段中也存储了需要 bind 的指针,以及指针需要指向的符号。dyld 需要找到 symbol 对应的实现,这需要很多计算,去符号表里查找。找到后会将内容存储到 __DATA 段中的那个指针中。Binding 看起来计算量比 Rebasing 更大,但其实需要的 I/O 操作很少,Binding的时间主要是耗费在计算上,因为IO操作之前 Rebasing 已经替 Binding 做过了,所以这两个步骤的耗时是混在一起的。


    可以从查看 __DATA 段中需要修正(fix-up)的指针,所以减少指针数量才会减少这部分工作的耗时。对于 ObjC 来说就是减少 Class,selector 和 category 这些元数据的数量。从编码原则和设计模式之类的理论都会鼓励大家多写精致短小的类和方法,并将每部分方法独立出一个类别,其实这会增加启动时间。


    对于 C++ 来说需要减少虚方法,因为虚方法会创建 vtable,这也会在 __DATA 段中创建结构。虽然 C++ 虚方法对启动耗时的增加要比 ObjC 元数据要少,但依然不可忽视。最后推荐使用 Swift 结构体,它需要 fix-up 的内容较少。


    Objective-C 中有很多数据结构都是靠 Rebasing 和 Binding 来修正(fix-up)的,比如 Class 中指向父类的指针和指向方法的指针。


    Rebase&&Binding该阶段的优化关键在于减少__DATA segment中的指针数量。我们可以优化的点有:


    1. 减少Objc类数量, 减少selector数量,把未使用的类和函数都可以删掉 

    2. 减少C++虚函数数量

    3. 转而使用swift stuct(其实本质上就是为了减少符号的数量,使用swift语言来开发?)


    未使用类的扫描,可以利用linkmap文件和otool工机具反编译APP的可进行二进制文件得出一个大概的结果,但是不算非常精确,扫描出来后需要手动一个个确认。扫描原理大致是classlist和classref两者的差值,所有的类和使用了的类的差值就是未使用的类啦。因为未使用的类主要优化的是pre-main的时间,根据测试我们的工程pre-main时间并不长,所以本次并没有针对这一块做优化。(TODO:写脚本来验证这一点)。


    ObjC SetUp


    主要做以下几件事来完成Objc Setup:


    1. 读取二进制文件的 DATA 段内容,找到与 objc 相关的信息

    2. 注册 Objc 类,ObjC Runtime 需要维护一张映射类名与类的全局表。当加载一个 dylib 时,其定义的所有的类都需要被注册到这个全局表中; 

    3. 读取 protocol 以及 category 的信息,把category的定义插入方法列表 (category registration), 

    4. 确保 selector 的唯一性 


    ObjC 是个动态语言,可以用类的名字来实例化一个类的对象。这意味着 ObjC Runtime 需要维护一张映射类名与类的全局表。当加载一个 dylib 时,其定义的所有的类都需要被注册到这个全局表中。


    C++ 中有个问题叫做易碎的基类(fragile base class)。ObjC 就没有这个问题,因为会在加载时通过 fix-up 动态类中改变实例变量的偏移量。


    在 ObjC 中可以通过定义类别(Category)的方式改变一个类的方法。有时你想要添加方法的类在另一个 dylib 中,而不在你的镜像中(也就是对系统或别人的类动刀),这时也需要做些 fix-up。


    ObjC 中的 selector 必须是唯一的。


    由于之前2步骤的优化,这一步实际上没有什么可做的。几乎都靠 Rebasing 和 Binding 步骤中减少所需 fix-up 内容。因为前面的工作也会使得这步耗时减少。


    Initializers


    以上三步属于静态调整,都是在修改__DATA segment中的内容,而这里则开始动态调整,开始在堆和栈中写入内容。 工作主要有:


    1. Objc的+load()函数

    2. C++的构造函数属性函数 形如attribute((constructor)) void DoSomeInitializationWork()

    3. 非基本类型的C++静态全局变量的创建(通常是类或结构体)(non-trivial initializer) 比如一个全局静态结构体的构建,如果在构造函数中有繁重的工作,那么会拖慢启动速度


    Objc的load函数和C++的静态构造函数采用由底向上的方式执行,来保证每个执行的方法,都可以找到所依赖的动态库


    1. dyld开始将程序二进制文件初始化

    2. 交由ImageLoader读取image,其中包含了我们的类、方法等各种符号

    3. 由于runtime向dyld绑定了回调,当image加载到内存后,dyld会通知runtime进行处理

    4. runtime接手后调用mapimages做解析和处理,接下来loadimages中调用 callloadmethods方法,遍历所有加载进来的Class,按继承层级依次调用Class的+load方法和其 Category的+load方法


    整个事件由dyld主导,完成运行环境的初始化后,配合ImageLoader 将二进制文件按格式加载到内存,动态链接依赖库,并由runtime负责加载成objc 定义的结构,所有初始化工作结束后,dyld调用真正的main函数


    C++ 会为静态创建的对象生成初始化器。而在 ObjC 中有个叫 +load 的方法,然而它被废弃了,现在建议使用 +initialize。对比详见StackOverflow的一个连接;


    这一步可以做的优化有:


    ①使用 +initialize 来替代 +load

    ②不要使用 atribute((constructor)) 将方法显式标记为初始化器,而是让初始化方法调用时才执行。比如使用 dispatch_once(),pthread_once() 或 std::once()。也就是在第一次使用时才初始化,推迟了一部分工作耗时。也尽量不要用到C++的静态对象。


    从效率上来说,在+load 和+initialize里执行同样的代码,效率是一样的,即使有差距,也不会差距太大。 但所有的+load 方法都在启动的时候调用,方法多了就会严重影响启动速度了。 就说我们项目中,有200个左右+load方法,一共耗时大概1s 左右,这块就会严重影响到用户感知了。 


    而+initialize方法是在对应 Class 第一次使用的时候调用,这是一个懒加载的方法,理想情况下,这200个+load方法都使用+initialize来代替,将耗时分摊到用户使用过程中,每个方法平均耗时只有5ms,用户完全可以无感知。 因为load是在启动的时候调用,而initialize是在类首次被使用的时候调用,不过当你把load中的逻辑移到initialize中时候,一定要注意initialize的重复调用问题,能用dispatch_once()来完成的,就尽量不要用到load方法。


    如果程序刚刚被运行过,那么程序的代码会被dyld缓存,因此即使杀掉进程再次重启加载时间也会相对快一点,如果长时间没有启动或者当前dyld的缓存已经被其他应用占据,那么这次启动所花费的时间就要长一点,这就分别是热启动和冷启动的概念。下文中的启动时间统计,均统计的是第二次启动后的数据。(具体dyld缓存的是动态库还是APP的可执行代码,缓存多长时间,需要再研究,有懂的大神可以告知一下?)


    见下图,出处是这里:


    640?wx_fmt=jpeg


    其实在我们APP的实践过程中也会遇到类似的事情,只不过我只统计了第二次启动后的时间,也就是定义中的热启动时间。


    注:


    通过在工程的scheme中添加环境变量DYLD_PRINT_STATISTICS,设置值为1,App启动加载时Xcode的控制台就会有pre-main各个阶段的详细耗时输出。但是DYLD_PRINT_STATISTICS 这个变量对真机的iOS9.3系统不行,iOS10就没问题。估计是iOS系统的bug.


    pre-main阶段耗时的影响因素:


    1. 动态库加载越多,启动越慢。

    2. ObjC类越多,函数越多,启动越慢。

    3. 可执行文件越大启动越慢。

    4. C的constructor函数越多,启动越慢。

    5. C++静态对象越多,启动越慢。

    6. ObjC的+load越多,启动越慢。


    整体上pre-main阶段的优化有:


    ①减少依赖不必要的库,不管是动态库还是静态库;如果可以的话,把动态库改造成静态库;如果必须依赖动态库,则把多个非系统的动态库合并成一个动态库;


    ②检查下 framework应当设为optional和required,如果该framework在当前App支持的所有iOS系统版本都存在,那么就设为required,否则就设为optional,因为optional会有些额外的检查; 


    ③合并或者删减一些OC类和函数;关于清理项目中没用到的类,使用工具AppCode代码检查功能,查到当前项目中没有用到的类(也可以用根据linkmap文件来分析,但是准确度不算很高);有一个叫做[FUI](https://github.com/dblock/fui)的开源项目能很好的分析出不再使用的类,准确率非常高,唯一的问题是它处理不了动态库和静态库里提供的类,也处理不了C++的类模板。


    ④删减一些无用的静态变量,


    ⑤删减没有被调用到或者已经废弃的方法,方法见http://stackoverflow.com/questions/35233564/how-to-find-unused-code-in-xcode-7和https://developer.Apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/CheckingCodeCoverage.html。


    ⑥将不必须在+load方法中做的事情延迟到+initialize中,尽量不要用C++虚函数(创建虚函数表有开销)


    ⑦类和方法名不要太长:iOS每个类和方法名都在__cstring段里都存了相应的字符串值,所以类和方法名的长短也是对可执行文件大小是有影响的;因还是object-c的动态特性,因为需要通过类/方法名反射找到这个类/方法进行调用,object-c对象模型会把类/方法名字符串都保存下来;


    ⑧用dispatch_once()代替所有的 attribute((constructor)) 函数、C++静态对象初始化、ObjC的+load函数;


    ⑨在设计师可接受的范围内压缩图片的大小,会有意外收获。压缩图片为什么能加快启动速度呢?因为启动的时候大大小小的图片加载个十来二十个是很正常的,图片小了,IO操作量就小了,启动当然就会快了,比较靠谱的压缩算法是TinyPNG。


    我们的实践


    统计了各个库所占的size(使用之前做安装包size优化的一个脚本),基本上一个公共库越大,类越多,启动时在pre-main阶段所需要的时间也越多。


    所以去掉了Realm,DiffMatchPatch源码库,以及AlicloudHttpDNS,BCConnectorBundl,FeedBack,SGMain和SecurityGuardSDK几个库;


    结果如下:


    静态库,少了7M左右:

    640?wx_fmt=png


    第三方framework(其实也是静态库,只是脚本会分开统计而已),少了1M左右:

    640?wx_fmt=png


    我们使用cocoapodbs并没有设置user_frameworks,所以pod管理的有源码的第三方库都是静态库的形式,而framework形式的静态库基本都是第三方公司提供的服务,上图可以看到,size占比最大的还是阿里和腾讯两家的SDK,比如阿里的推送和腾讯的直播和IM。


    上图在统计中,AliCloudHttpDNS的可执行文件在Mac的Finder下的大小大概是10M,AlicloudUtils是3.4M,UTMini是16M,而UTDID只有1.6M。依赖关系上,AliCloudHttpDNS依赖AlicloudUtils,而AlicloudUtils依赖UTMini和UTDID,UTMini依赖UTDID。


    上图中在统计上,应该是有所AlicloudUtils在左右两个图中size差值过大,应该是依赖关系中UTMini导致的统计偏差。两边几个库加起来的差值大概是200kb,这也应该是AlicloudHttpDNS这个库所占的size大小。


    main阶段


    总体原则无非就是减少启动的时候的步骤,以及每一步骤的时间消耗。


    main阶段的优化大致有如下几个点:


    1. 减少启动初始化的流程,能懒加载的就懒加载,能放后台初始化的就放后台,能够延时初始化的就延时,不要卡主线程的启动时间,已经下线的业务直接删掉; 

    2. 优化代码逻辑,去除一些非必要的逻辑和代码,减少每个流程所消耗的时间; 

    3. 启动阶段使用多线程来进行初始化,把CPU的性能尽量发挥出来; 

    4. 使用纯代码而不是xib或者storyboard来进行UI框架的搭建,尤其是主UI框架比如TabBarController这种,尽量避免使用xib和storyboard,因为xib和storyboard也还是要解析成代码来渲染页面,多了一些步骤; 


    这里重点讲一下多线程启动设计的原理


    首先,iPhone有双核,除了维持操作系统运转和后台进程(包括守护进程等),在打开APP时,猜想双核中的另一个核应该有余力来帮忙分担启动的任务【待验证】;


    其次,iPhone7开始使用A10,iPhone8开始使用A11处理器,根据维基百科的定义,A10 CPU包括两枚高性能核心及两枚低功耗核心,A11则包括两个高性能核心和四个高能效核心,而且相比A10,A11两个性能核心的速度提升了25%,四个能效核心的速度提升了70%。而且苹果还准备了第二代性能控制器,因此可以同时发挥六个核心的全部威力,性能提升最高可达70%,多线程能力可见一斑。


    多线程测试结果如下图:


    640?wx_fmt=png


    结论如下:


    1. 纯算法的CPU运算,指定计算的总数量,对于iPhone6和iPhone X来说,把计算量平均分配到多线程比全部放在主线程执行要快;

    2. .iPhone6三个线程跟两个线程的总体耗时总体一致,甚至要多一点点,所以对于iPhone6来说用两个线程来做启动设计足够了;

    3. iPhone X三个线程的耗时要比两个线程短一些,但是差值已经不算太大了;四个线程跟三个线程的总体一致,偶尔能比三个线程快一点点;


    综上,利用多个线程来加速启动时间的设计,是合理的。


    但是多线程启动的设计有几个需要注意的点:


    1. 黑屏问题;

    2. 用状态机来设计,每个状态机有2或3个线程在跑不同的任务,所有线程任务都完成后,进入到下一个状态,方便扩展;

    3. 线程保活问题,以及用完后要销毁;

    4. 资源竞争或线程同步造成卡死的问题。


    针对第一点,多线程跑初始化任务的时候,可能主线程会有空闲等待子线程的阶段,而主线程一旦空闲,iOS系统的启动画面就会消失,如果此时APP尚未初始化完全,则可能会黑屏。为了避免黑屏,我们需要一个假的启动画面,在刚开始跑初始化任务时,就生成这个启动画面,启动过程完全结束后再去掉。或者当一个状态机里的主线程跑完时检查下是否所有线程都执行完任务了,如果没有则去生成这个假的初始化页面,避免黑屏。


    第二点用状态机来设计启动,每个状态跑两个或者多个线程,单个状态里每个线程的耗时是不一样的,跑完一个状态再继续下一个状态,可以多次测试去动态调整分派到各个线程里的任务。


    第三点线程保活则跟runloop有关,每个线程启动后,跑完一个状态,不能立马就回收,不然下一个状态的子线程就永远不会执行了;另外就是,APP初始化完成后,线程要注意回收。


    第四点跟具体的业务有关,只要不是一个线程去做初始化,就有可能遇到线程间死锁的问题,比如下面采坑记录里就有提到一个例子。


    我们在实践中大概做了以下的几点:


    1.把启动时RN包的删除和拷贝操作,仅在APP安装后第一次启动时才做,之后的启动不再做这操作,而是等到网络请求RN数据回来,根据是否需要更新RN包的判断,再去做这些IO操作从而避免启动的耗时。iPhone5C能节省1.4s;


    2.OSS token的获取不是一个需要在启动的时候必须要做的操作,放到子线程去处理,大部分时候是节省10-15ms,偶尔能去到50ms;


    3.去掉启动状态机里的原有定位服务,原来SSZLocationManager的定位服务因为内部依赖高德的SDK,需要初始化SDK,iPhone5C大概耗时100ms。同时SSZLocationManager这个类代码保留,但是APP的工程去除对其的依赖;


    4.打点统计模块里的定位服务权限请求改成异步,大概有50ms;


    5.阿里百川的Feedback,在网校并没有使用,直接去掉其初始化流程,大概5ms左右;


    6.友盟的分享服务,没有必要在启动的时候去初始化,初始化任务丢到后台线程解决,大概600-800ms;


    7.状态机跑完后的启动内存统计,放到后台去做,大概50ms;


    8.UserAgentManager里对于webview是否为UIWebview的判断,以前是新创建一个对象使用对象方法来判断,修改为直接使用类方法,避免创建对象的消耗,节省约200ms;


    9.阿里云的HTTPDNS已经没有再使用了,所以这里也可以直接去掉。大概20-40ms;


    10.SSZAppConfig里把网络请求放到后台线程,这样子就可以配合启动状态机把该任务放到子线程进行初始化了,否则子线程消耗的时间太长了;


    11.采用两个线程来进行启动流程的初始化,用状态机来控制状态的变化。但是要针对业务区分开,并不是把一部分业务拆分到子线程,就可以让整体的启动速度更快;因为如果子线程有一些操作是要在主线程做的,有可能会出现等待主线程空闲再继续的情况;或者当两个线程的耗时操作都是IO时,拆开到两个线程,并不一定比单个线程去做IO操作要快。


    12.主UI框架tabBarController的viewDidLoad函数里,去掉一些不必要的函数调用。


    13.NSUserDefaults的synchronize函数尽量不要在启动流程中去调用,统一在APP进入后台,willTerminate和完全进入前台后把数据落地;


    因为我们的项目用到了React Native技术(简称RN),所以会有RN包的拷贝和更新这一操作,之前的逻辑是每次启动都从bundle里拷贝一次到Document的指定目录,本次优化修正为除了安装后第一次启动拷贝,其他时候不在做这个拷贝操作,不过RN包热更新的覆盖操作,还是每次都要做检查的,如果有需要则执行更新操作


    其中遇到几个坑:


    ①并不是什么任务都适合放子线程,有些任务在主线程大概10ms,放到子线程需要几百ms,因为某些任务内部可能会用到UIKit的api,又或者某些操作是需要提交到主线程去执行的,关键是要搞清楚这个任务里边究竟做了啥,有些SDK并不见得适合放到子线程去初始化,需要具体情况具体去测试和分析。


    ②AccountManager初始化导致的主线程卡死,子线程的任务依赖AccountManager,主线程也依赖,当子线程比主线程先调用时,造成了主线程卡死,其原因是子线程会提交一个同步阻塞的操作到主线程,而此时主线程被dipatch_one的锁锁住了,所以造成子线程不能返回,主线程也无法继续执行。调试的时候还会用到符号断点和LLDB去打印函数入参的值。


    实际优化效果对比


    由于只是去掉了几个静态库,而且本来pre-main阶段的耗时就不长,基本在200ms-500ms左右,所以pre-main阶段优化前后效果并不明显,有时候还没有前后测试的误差大。。。


    main的阶段的优化效果还是很明显的:


    • iPhone5C iOS10.3.3系统优化前main阶段的时间消耗为4秒左右,优化后基本在1.8秒内;

    • iPhone7 iOS10.3.3系统优化前main阶段的时间消耗为1.1秒左右,优化后基本在600ms内;

    • iPhoneX iOS11.3.1系统优化前main阶段的时间消耗基本在1.5秒以上,优化后在1秒内;


    可以看到,同样arm64架构的机器,main阶段是iPhone7比iPhoneX更快,说明就操作系统来说,iOS11.3要比iOS10.3慢不少;


    详细测试数据见下图


    640?wx_fmt=jpeg

    640?wx_fmt=jpeg


    上图中iPhone5C为啥前后测试pre-main时间差值那么大?而且是优化后的值比优化前还要大?我也不知道,大概机器自己才知道吧。。。


    注意:


    1.关于冷启动和热启动,业界对冷启动的定义没有问题,普遍认为是手机开机后第一次启动某个APP,但是对热启动有不同的看法,有些人认为是按下home键把APP挂到后台,之后点击APP的icon再拉回来到前台算是热启动,也有些人认为是手机开机后在短时间内第二次启动APP(杀掉进程重启)算是热启动(此时dyld会对部分APP的数据和库进行缓存,所以比第一次启动要快)。


    笔者认为APP从后台拉起到前台的时间没啥研究的意义,而即使是短时间内第二次启动APP,启动时间也是很重要的,所以在统计启动时间时,笔者会倾向于后一种说法,不过具体怎么定义还是看个人吧,知道其中的区别就好。


    2.关于如何区分framework是静态库还是动态库见[这里](https://www.jianshu.com/p/1cfdf363143b )。原理就是在终端使用指令file,输出如果是ar archive就是静态库,如果是动态库则会输出dynamically linked相关信息。


    特别鸣谢


    在做启动优化的过程中,得到了很多朋友们的帮助和支持。借鉴了淮哥状态机的设计思路,同时也感谢singro大神的指点,感谢刘金哥叫我玩LLDB,感谢长元兄对于动态库和静态库的指教,感谢森哥的鞭策和精神鼓舞,以及展少爷在整个过程中的技术支持,引导和不耐其烦的解释,再次谢谢大家,爱你们哟

    展开全文
  • OS X与iOS内核编程

    2019-07-07 02:37:00
    《OS X与iOS内核编程》 基本信息 原书名:OS X and iOS kernel programming 作者: (澳)哈尔沃森(Halvorsen,O.H.) (澳)克拉克(Clarke,D.) 译者: 贾伟 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ...
    《OS X与iOS内核编程》
    基本信息
    原书名:OS X and iOS kernel programming
    作者: (澳)哈尔沃森(Halvorsen,O.H.) (澳)克拉克(Clarke,D.)
    译者: 贾伟
    丛书名: 图灵程序设计丛书
    出版社:人民邮电出版社
    ISBN:9787115318244
    上架时间:2013-5-16
    出版日期:2013 年6月
    开本:16开
    页码:1
    版次:1-1
    所属分类:计算机
    更多关于 》》》《OS X与iOS内核编程
    内容简介
    计算机书籍
      《os x与ios内核编程》面向mac os x 和ios 操作系统,介绍了操作系统和内核架构等基础知识,以及内存管理、线程同步、i/o kit 框架等基本概念。通过最贴近实战的方法帮助读者编写高效的内核级代码。本书两位作者具备丰富的计算机硬软件以及设备驱动程序开发经验,在他们的指引下,读者定能为usb 和thunderbolt 等设备开发设备驱动程序。
       如果你对ios 和mac os x 操作系统感兴趣,关注内核开发实践,或者你是驱动程序开发人员,那么本书适合你阅读。
       与传统的用户应用程序开发不同,内核环境更加复杂多变,任何问题都会严重影响系统的稳定性、安全性和性能,所以一定要确保内核代码中没有任何错误。本书介绍了非常实用的内核开发方法,是xnu内核开发者必备的“九阴真经”。
       《os x与ios内核编程》两位作者都活跃在计算机软硬件以及驱动程序开发一线,具有丰富的实战经验。ole henry halvorsen是最早一批为usb 3.0和thunderbolt创建视频硬件设备的开发人员。douglas clarke具有15年以上的麦金塔计算机专业软硬件开发经验。本书汇集了两位作者多年的实战经验与智慧感悟,展现了一幅xnu内核编程与设备驱动程序开发的斑斓画卷。相信在作者循循善诱、通俗易懂的讲解下,读者定能为书中的精彩内容深深吸引,渐入化境,在不知不觉中炼就内核开发的绝世武功。
       如果你对ios和mac os x操作系统感兴趣,关注内核开发,或者你本身就是驱动程序开发人员,那么如此精彩的一本好书绝对不容错过。
    目录
    《os x与ios内核编程》
    第1章 操作系统原理  1
    1.1  操作系统的作用  3
    1.2  进程管理  3
    1.3  进程地址空间  4
    1.4  操作系统服务  5
    1.5  虚拟内存  6
    1.6  调度  8
    1.7  硬件和驱动程序  9
    1.8  小结  11
    第2章 mac os x和ios  12
    2.1  xnu内核  17
    2.1.1  内核扩展(kext)  18
    2.1.2  mach  18
    2.1.3  bsd层  27
    2.1.4  i/o kit  29
    2.2  小结  31
    第3章 xcode和内核开发环境  32
    3.1  语言的选择:c++  32
    3.2  xcode  33
    .3.3  “hello world”内核扩展  34
    3.4  加载和卸载内核扩展  37
    3.5  使用控制台查看输出  39
    3.6  小结  40
    第4章 i/o kit框架  41
    4.1  i/o kit 模型  41
    4.2  对象关系  43
    4.3  info.plist文件  43
    4.3.1  驱动程序类  46
    4.3.2  ioregistryexplorer  50
    4.4  内核库:libkern  52
    4.4.1  osobject  52
    4.4.2  容器类  54
    4.5  小结  56
    第5章 应用程序与驱动程序的交互  57
    5.1  i/o kit框架  58
    5.2  查找驱动程序  58
    5.3  观察设备移除  63
    5.4  修改设备驱动程序属性  65
    5.5  基于状态的交互  68
    5.6  驱动程序的通知  79
    5.7  小结  83
    第6章 内存管理  84
    6.1  内存类型  84
    6.1.1  cpu物理地址  85
    6.1.2  总线物理地址  85
    6.1.3  用户和内核虚拟地址  85
    6.1.4  内存顺序:大端序和小端序  86
    6.1.5  32位和64位内存寻址  87
    6.2  内存分配  88
    6.2.1  底层分配机制  89
    6.2.2  mach内存域分配器  89
    6.2.3  kalloc家族  89
    6.2.4  bsd中的内存分配  90
    6.2.5  i/o kit内存分配  91
    6.2.6  使用c++的new操作符分配内存  92
    6.3  内存描述符  92
    6.3.1  iobuffermemorydescriptor  94
    6.3.2  其他的内存描述符  95
    6.4  映射内存  95
    6.4.1  用户空间任务到内核空间的内存映射  95
    6.4.2  iomemorymap类  97
    6.4.3  内核到用户空间任务的内存映射  98
    6.4.4  将内存映射到指定的用户空间任务  99
    6.4.5  物理地址映射  100
    6.5  小结  101
    第7章 同步和线程  102
    7.1  同步原语  102
    7.2  原子操作  104
    7.3  锁  107
    7.3.1  自旋锁  107
    7.3.2  互斥  109
    7.3.3  条件变量  110
    7.3.4  读/写互斥  112
    7.4  同步异步事件:工作环  113
    7.4.1  iocommandgate  115
    7.4.2  定时器  116
    7.4.3  释放工作环  117
    7.5  内核线程  118
    7.6  小结  119
    第8章 usb  120
    8.1  usb体系结构  120
    8.1.1  usb传输速度  122
    8.1.2  主机控制器  123
    8.1.3  usb协议  124
    8.1.4  端点  126
    8.1.5  usb描述符  126
    8.1.6  usb设备类  127
    8.2  i/o kit usb支持  128
    8.2.1  usb设备和驱动程序处理  129
    8.2.2  加载usb驱动程序  130
    8.2.3  usb prober  132
    8.2.4  驱动程序示例:usb大容量存储器设备驱动程序  133
    8.2.5  驱动程序启动  138
    8.2.6  处理设备移除  138
    8.2.7  枚举接口  139
    8.2.8  枚举端点  140
    8.2.9  执行设备请求  141
    8.2.10  执行批量端点和中断端点的i/o  144
    8.3  小结  147
    第9章 pci express和thunderbolt  148
    9.1  i/o kit中的pci  150
    9.1.1  匹配和加载驱动程序  151
    9.1.2  驱动程序示例:一个简单的pci驱动程序  154
    9.1.3  访问配置空间寄存器  156
    9.1.4  pci i/o内存区  158
    9.1.5  处理设备移除  161
    9.2  中断  162
    9.2.1  i/o kit中断机制  163
    9.2.2  注册接收中断  164
    9.2.3  启用msi  166
    9.2.4  处理主中断  166
    9.2.5  处理二级中断  168
    9.3  直接内存访问  168
    9.3.1  将物理地址转换为总线地址  170
    9.3.2  为dma准备内存  171
    9.3.3  建立分散/聚集列表  172
    9.3.4  iodmacommand类  173
    9.4  小结  175
    第10章 电源管理  177
    10.1  响应电源状态改变  179
    10.2  请求电源状态改变  184
    10.3  处理设备空闲  185
    10.4  观察设备电源状态改变  186
    10.5  汇总  187
    10.6  小结  191
    第11章 串行端口驱动程序  192
    11.1  mac os x串行端口体系结构概览  192
    11.2  串行端口驱动程序  194
    11.3  实现ioserialdriversync类  197
    11.4  串行端口状态  200
    11.5  串行端口事件  204
    11.6  串行数据传输  207
    11.7  从用户空间访问串行端口  211
    11.8  小结  214
    第12章 音频驱动程序  215
    12.1  数字音频和音频设备简介  215
    12.2  core audio  217
    12.3  i/o kit音频支持  218
    12.4  实现一个音频驱动程序  219
    12.4.1  驱动程序和硬件初始化  221
    12.4.2  注册音频控制  223
    12.4.3  实现音频引擎  225
    12.4.4   i/o引擎初始化  226
    12.4.5  其他的音频引擎功能  235
    12.5  小结  236
    第13章 网络  238
    13.1  网络内核扩展  242
    13.1.1  内核控制kpi  242
    13.1.2  套接字过滤器  242
    13.1.3  因特网协议过滤器  250
    13.1.4  接口过滤器  255
    13.2  调试和测试网络扩展  258
    13.3  i/o kit中的网络  259
    13.3.1  构建简单以太网控制器驱动程序  261
    13.3.2  myethernetdriver设计  262
    13.3.3  驱动程序初始化和启动  264
    13.3.4  介质和状态选择  266
    13.3.5  配置设备硬件地址  268
    13.3.6  启用和禁用设备  268
    13.3.7  传输网络分组  270
    13.3.8  接收分组  271
    13.3.9  对myethernetdriver进行测试  274
    13.4  小结  276
    第14章 存储系统  277
    14.1  传输层驱动程序  278
    14.2  ioblockstoragedevice接口  279
    14.3  构建ram磁盘设备  282
    14.4  分区规则  292
    14.4.1  实现一个示例分区规则  293
    14.4.2  媒介内容线索属性  300
    14.5  媒介过滤器驱动程序  300
    14.5.1  加密过滤器规则示例  302
    14.5.2  创建一个自定义guid分区表  306
    14.6  小结  308
    第15章 用户空间usb驱动程序  310
    15.1  背景  310
    15.2  iousblib框架  311
    15.3  处理异步操作  315
    15.4  iousbdeviceinterface类  316
    15.5  iousbinterfaceinterface类  320
    15.5.1  属性方法  321
    15.5.2  端点数据传输方法  322
    15.5.3  低延迟同步传输  328
    15.6  小结  330
    第16章 调试  331
    16.1  常见的问题类型  331
    16.2  内核恐慌  332
    16.3  调试机制  333
    16.3.1  启动时修复崩溃  335
    16.3.2  采用iolog()追踪  335
    16.3.3  输出栈跟踪  336
    16.3.4  通过firewire远程跟踪  337
    16.3.5  远程内核核心转储  339
    16.3.6  kdb  340
    16.3.7  通过以太网或firewire使用gdb远程调试  340
    16.3.8  实时调试运行的内核  344
    16.3.9  使用虚拟机调试  344
    16.3.10  在内核中使用gdb调试  344
    16.3.11  使用activity monitor诊断挂起进程  354
    16.3.12  查找内存和资源泄漏  355
    16.4  小结  356
    第17章 高级内核编程  357
    17.1  内核中的sse和浮点  357
    17.2  多功能驱动程序  358
    17.3  编写i/o kit族  358
    17.4  内核控制kpi  359
    17.4.1  内核控制注册  361
    17.4.2  客户端连接  362
    17.4.3  获取和设置选项  363
    17.4.4  从用户空间访问内核控制  364
    17.5  内核中的进程处理  365
    17.6  加载资源  366
    17.7  kext资源之外的内容  367
    17.8  内核驱动程序通知  368
    17.9  小结  371
    第18章 部署  372
    18.1  安装和加载内核扩展  372
    18.2  加载首选项和设置  374
    18.3  内核扩展的版本管理  375
    18.4  测试和品质保证  375
    18.5  打包kext和软件  376
    18.5.1  构建hello world内核扩展安装包  378
    18.5.2  将内容添加至安装包  378
    18.5.3  配置安装包  379
    18.5.4  构建安装包  382
    18.5.5  卸载安装包  383
    18.6  小结  383
    索引  385
    图书信息来源:中国互动出版网

    转载于:https://www.cnblogs.com/china-pub/archive/2013/05/17/3084024.html

    展开全文
  • Android与iOS测试注意点

    2019-06-11 15:31:54
    App测试中Android和IOS测试区别 分辨率测试:   Android端种类多常见的手机分辨率:   (4:3   VGA 640480 (Video Graphics Array)   QVGA 320240 (Quarter VGA)   HVGA 480320 (Half-size VGA)   SVGA...

    App测试中Android和IOS测试区别

    1. 分辨率测试:

      Android端种类多常见的手机分辨率:

      (4:3   VGA 640480 (Video Graphics Array)   QVGA 320240 (Quarter VGA)   HVGA 480320 (Half-size VGA)   SVGA 800600 (Super VGA)

      5:3   WVGA 800*480 (Wide VGA)

      16:9   FWVGA 854480 (Full Wide VGA)   HD 19201080 High Definition   QHD 960540   720p 1280720 标清   1080p 1920*1080 高清)再加上不同的手机品牌;

      ios就比较少了;

    1. 手机操作系统:

      ios较少且不能降级,只能单向升级;新的ios系统中的资源库不能完全兼容低版本中的ios系统中的应用,低版本ios系统中的应用调用了新的资源库,会直接导致闪退(Crash);

      Android较多;

    1. 操作习惯:

      Android,Back(返回)键是否被重写,测试点击Back(返回)键后的反馈是否正确;应用数据从内存移动到SD卡后能否正常运行等;

    1. push(信息推送)测试:

      Android:点击home键,程序后台运行时,此时接收到push,点击后唤醒应用,此时是否可以正确跳转;

      ios,点击home键关闭程序和屏幕锁屏的情况(红点的显示);

    1. 安装卸载测试:

      Android的下载和安装的平台、工具和渠道比较多;

      ios主要有app store,iTunes和testflight下载;

    1. 升级测试:

      可以被升级的必要条件:新旧版本具有相同的签名;新旧版本具有相同的包名;有一个标示符区分新旧版本(如版本号),对于Android若有内置的应用需检查升级之后内置文  件是否匹配(如内置的输入法)

    测试注意点:

    1. 并发(中断)测试:另一个应用的启动、视频音频的播放,来电、用户正在输入等,语音、录音等的播放时强制其他正在播放的要暂停;
    2. 数据来源的测试:输入、选择、复制、语音输入,安装不同输入法输入等;
    3. push(推送)测试:在开关机、待机状态下执行推送,消息先死及其推送跳转的正确性;应用在开发、未打开状态、应用启动且在后台运行的情况下是push显示和跳转否正确;推送消息阅读前后数字的变化是否正确;多条推送的合集的显示和跳转是否正确;
    4. 分享跳转:分享后的文案是否正确;分享后跳转是否正确,显示的消息来源是否正确;
    5. 触屏测试:同时触摸不同的位置或者同时进行不同操作,查看客户端的处理情况,是否会crash等 我:  归纳和总结了Android APP在测试过程中经常出现Bug的关键节点,希望对大家有所帮助!

      启动:

      1. 启动入口:桌面正常启动,最近运行启动,所有程序列表中启动,锁屏快捷启动

      2. 其他入口:从其他程序开启应用,从外部以文件形式打开应用(如果有)

      3. 退回:从其他程序退回时回到被测应用,被测应用打开其他应用再从桌面图标启动

      以上需要交叉组合测试。

      4. 异常启动:崩溃后启动,写文件时被强制杀进程后启动,网络请求未收到回包强制杀进程后再启动,网络超时时启动(启动需要有超时机制)

      功能介绍,引导图,流量提示等:

      1 全新安装程序第一次启动,会有些初始化,或者弹框提示,功能介绍,当外部打开,比如第三方打开(一般不算做第一次启动)后,再启动程序,检查该有的动作是否都有。

      

    权限:

      1 当某些权限被安全软件或者系统禁止时,是否可以正常使用,需要注意的权限:麦克风/摄像头/定位/短信息等权限 —_luguo

      2 在做兼容性测试时,需要特别注意权限管理较严的Rom,比如魅族在摄像头等权限为默认关闭

      3 第三方安全软件弹框请求禁止权限时,选择后,是否会影响到被测应用的正常流程

    文件错误

      1 使用错误的文件类型(但是有正确的后缀名),替代被测应用使用的文件

      2 adb push 从pc上传到设备的文件如果有中文名,会生成一个错误的文件,被测应用需要适应这种错误情况

    屏幕旋转:

      1 确认哪些界面是需要允许横屏或者禁止横屏的

      2 将屏幕锁定为竖屏或者横屏,在几个界面跳转,界面是否正常

      3 当适应横屏时,是否对横屏进行了适配

    流量:

      1 首次启动应用的流量是否符合预期

      2 在主界面有很多图片时,是否已经达到图片文件大小与显示效果的平衡

      3 当需要使用较大的网络流量时(加载大图片,视频播放缓冲,下载更新包),显示出当前网速或者进度,对用户来说更友善

    缓存(/sdcard/data/com.your.package/cache/):

      1. 卸载后将删除缓存,其中是否存在不应该删除的文件(下载文件,用户资料)

      2. 缓存易被360手机卫士,猎豹清理大师等清理,需检查该文件夹是否放了适合的文件

    正常中断:

      1. 在所有界面执行锁屏操作,解锁后观察是否正常运行

      2. 在所有界面执行长时间锁屏操作,解锁后观察是否正常运行

      3. 在所有界面,和所有过程,按home键切后台,再切回时观察是否正常

      4. 在所有的loading过程中,按back键

      5. 在所有的loading过程中,按home键

      6. 界面切换动画时尝试多次按back键

      7. 正常的点击动作,尝试快速按两次

      8. 后摄像头和前摄像头无法同时启动,后摄像头启动还包括打开闪光灯(与手电筒类应用的兼容性)

      9. 检查键盘展开和收起的时机,与home、back、锁屏组合测试

      10. 从第三方启动后,home键回到桌面,再从桌面打开被测应用,显示是否正确(应确定是显示第三方启动界面,还是显示程序正常启动界面,前者如微信,后者如QQ)

    异常中断:

      1. 断电后重启

      2. 当界面被意外崩溃,是否可以重启界面,并且恢复到崩溃之前的状态

      3. 来电结束后,返回被测应用界面

      4. 在某些特殊情况下,来电后,移动网络会被切断

    用户体验:

      1. 以最挑剔最无理的用户角度来使用应用的主打功能

      2. 是否每个动作都有反馈

      3. 每个按钮都有按下的状态

      4. 当界面有返回按钮时,back将执行同样的操作

      5. 当图片需要网络拉取,或者无图片时,是否有默认图片替代

      6. 开启开发者选项,勾选显示布局边界,检查每一个按钮的可点击范围是否合理

    多语言:

      1. 其他语言的语言习惯(如繁体)

      2. 英文注意复数

      3. 多语言下,需要特别检查以图片形式展示的文案(如果功能引导,启动引导图)

      4. 一致性。(一方面是代指的一致性,一方面如果有系列应用共用功能的文案也需要保持一致)

      5. 如果在简体下载了一个文件or创建了一个快捷方式or进行了一笔购买,马上切换语言去做一次同样的动作

      6. 检查英文语言下的切断、断行是否正确

      7. 当文字长度不一致时,UI界面是否能正常适配

    升级:

      1. 是否有完整的升级策略(强制升级,灰度发布)

      2. 下载升级包过程中是否可以取消

      3. 升级包下载是否可以续传

      4. 升级过程是否可以中断(取消按钮,back键)

      5. Android系统第一次安装非官方市场应用,需要手动取消限制,此过程是否会影响到apk包安装(正对预装应用的测试)

      6. 在没有更新或者网络时,需要给予用户正确的信息表达

      7. 如果升级有忽略本次版本升级,那么当有新的升级版本时,是否还有提示升级

      8. 收到升级提示后,下载了apk并未安装,同名替换该apk,下次再收到升级提示后,是否正常升级

    应用外部升级:

      1 应用如果是跳网页下载安装包升级,需要在文件名加版本号或者标识,防止浏览器下载时有缓存,而无法安装最新版本—_luguo

      2 交由系统下载升级安装包,尝试进行两次版本升级

    列表:

      1. 列表中的文字长度超过限制

      2. 点击列表项返回后的列表项的状态变化(尤其在有多个进度条存在时,需要注意状态)

      3. 上下滑动时,是否可接受卡顿(帧率)

      双卡双待:

      1. 当手机为双卡双待时,影响到的功能有:需要获取手机号码的功能,如支付;自动读取短信息

    网络:

      1. 是否以较好的体验从网络问题导致的数据问题中恢复界面展示数据

      2. 是否每个需要网络请求的操作,都有做超时处理,并且测试出他的超时时间

      3. 检查每一个需要网路请求的动作在网络延迟,服务器发生错误时,界面的响应

    monkey测试:

      1. 必跑monkey,不要仅跑一次,不要到测试后期才跑

      2. 更换不同的界面和不同的参数跑monkey

      3. 记得记录所有日志,如果能有日志筛选的工具更好

    性能测试:

      1. cpu占用,内存,流量,耗时等数据,必须有标准和参考值(可以是市场上同类应用的类似操作的表现,或者同应用的不同版本测试)

      2 特定操作(一般为主打功能)的压力测试,一般长时间大批量执行某个动作或者组合,尤其为该动作是通过JNI实现,或者使用了其他第三方sdk。一般写脚本或者录制脚本。

      病毒检测:

      1. apk文件在PC杀软上检测

      2. 手机杀毒软件检测

    Android端手机测试体系

      1.冒烟测试

      跟web端的测试流程一样,你拿到一个你们开发做出来的apk首先得去冒烟,也就是保证他的稳定性,指定时间内不会崩溃。这款原生sdk自带的monkey可以当做我们的测试工具。就跟我之前博客所说的,monkey测试模拟了用户的按键输入,触摸屏输入等等,看设备多久时间出异常。因为它得用到adb命令,那么同学们可能还得去自学一下adb命令。如果抓到crash(比如内存溢出,空指针异常什么的),及时向你家开发及时反馈,你也可以把它看成一个性能测试工具   - Monkey程序由Android系统自带,使用Java语言写成,在Android文件系统中的存放路径是:/sdk/sdk/tools/lib/monkey.jar;

    • Monkey.jar程序是由一个名为“monkey”的Shell脚本来启动执行,shell脚本在Android文件系统中的存放路径是:/sdk/sdk/tools/bin/monkey;

    • Monkey命令

      • adb shell monkey –help

      • 指定一个包: adb shell monkey -p com.example.sellclientapp 100

      • 指定多个包:adb shell monkey -p com.htc.Weather –p com.htc.pdfreader -p com.htc.photo.widgets 100

      • 不指定包:adb shell monkey 100(说明:Monkey随机启动APP并发送100个随机事件)

      • 要查看设备中所有的包,在CMD窗口中执行以下命令:

        >adb shell

        #cd data/data

        #ls(没有root所以不能用这个属性)

      • 参数: -v

      用于指定反馈信息级别(信息级别就是日志的详细程度),总共分3个级别,分别对应的参数如下表所示:

      日志级别 Level 0

      示例 adb shell monkey -p com.htc.Weather –v 100

      说明 缺省值,仅提供启动提示、测试完成和最终结果等少量信息 相应源代码如图所示了,这十分有利于调试了。

      • Level 1

      示例 adb shell monkey -p com.htc.Weather –v -v 100

      • Level 2

        示例 adb shell monkey -p com.htc.Weather –v -v –v 100

      • 示例:

         Monkey测试1:adb shell monkey -p com.htc.Weather –s 10 100

        Monkey 测试2:adb shell monkey -p com.htc.Weather –s 10 100

      2.安装,卸载测试

      这项对web应用可能测得比较少,这一块很可能归拢到我在后面讲的适配性测试里面。在不同机型测试你们应用的安装,以及卸载,看看会不会出现问题;

      这是个第三方手机助手蔓延的时代,那么发布到手机助手上,检查能不能装卸也是很重要的

      3.在线升级

      一个手机apk得顺应时代的需求,定期时不时的进行更新,在我第一份工作里,很重要的一个内容就是测试在线升级功能。

      开发给你一个新版本,你得去测试安装卸载,以及功能流程

      4.功能测试

      你测试了你们apk的功能测试用例。涵盖三个方面:

      4.1业务逻辑测试,测试你家公司apk的业务逻辑流的

      4.2功能测试:测试你家apk的功能点的。

      4.3与web端的交互测试。很多apk,比如淘宝啊,京东的app,会跟web客户端进行功能的交互,我想大家都用app来购物,那么你买的东西能正常在web端查看到,这就是你的测试点了。大家可以理解这个意思吧。

      5.稳定性及异常性测试

      稳定性及异常性测试主要两点;

      5.1.交互性测试:客户端作为手机特性测试,包含被打扰的情况13种,来电,来短信,低电量测试等,还要注意手机端硬件上,如:待机,插拔数据线、耳机等操作不会影响客户端。

      5.2.异常性测试:主要包含了断网、断电、服务器异常等情况下,客户端能否正常处理,保证数据正确性。

      其实这一块又可以跟monkey牵扯到

      6.性能测试

      测试点跟web应该差不多,比如尖峰性能啊,压力测试啊,基准性能等等

      1.尖峰性能,在一个时间点,你app的用户量出现井喷现象,有名的双11抢猫猫活动嘿嘿

      2.压力测试,逐渐加压

      3.基准,压服务器端接口及客户端在不同网络环境下响应速度。主要为258原则

      在一块,我曾帮别人百度到loadrunner测试手机apk,很幸运,他成功解决了他的问题。我的想法或许,android端或许,注意是或许,将app的什么socket,tcp/IP映射到web端的测试工具上,比如jmeter,来进行测试,这是我的一个猜想。另外的大神或许会给我们好的测试方法意见

      7.UI测试

      界面易用,简介,符合用户者的口味。

      8.适配性测试

      也就是兼容性测试。

      1.手机分辨率:主流的800480、854480、960540、1280720、1920*1080

      2.不同手机版本,2.3,4.2,4.3等等,用户面尽量测的覆盖率高一点

      3.手机系统,很多手机厂商喜欢在android原生系统的基础进行修改,你得测试啦

      4.手机尺寸:3.4,5.0,4.7等等,这里我来插一下话,我在自学android开发的时候有时候会想,如何将自己开发的app调到适合这么多android机型。百分比是个不错的选择

      9.电量及流量测试

      用户除了你app的基础功能,适配,UI美观。还关心耗不耗流量跟电量。一般非唤醒状态,后台状态会比较低耗能。这个测试范围,得看你的公司定下的基准。广播貌似可以监控用电量,大家了解了可以留言告诉我学习学习哈哈

      10.内存泄露

      这个东西你可以用monkey抓到,当然也可以DDMS抓

    11.回归测试工具

      这个版块的测试工具为大家介绍monkeyrunner,robotium,appnium。

      11.1monkeyrunner

      该monkeyrunner工具提供了编写控制Android设备或仿真器从Android的代码之外程序的API。随着monkeyrunner,您可以编写安装一个Android应用程序或测试包,运行它,发送击键它,需要它的用户界面截图,并将截图工作站上的Python程序。

      脚本用jython,what is jython?It is A mix of python and Java。因此可以用java api,当然你也可以扩展。

      11.2appnium

      Appium是一个开源、跨平台的测试框架,可以用来测试原生及混合的移动端应用。Appium支持IOS、Android及FirefoxOS平台。Appium使用WebDriver的json wire协议,来驱动Apple系统的UIAutomation库、Android系统的UIAutomator框架。Appium对IOS系统的支持得益于Dan Cuellar’s对于IOS自动化的研究。Appium也集成了Selendroid,来支持老android版本。

      跨架构,native hybrid webview

      跨设备:android ios

      跨语言:java python ruby

      跨app,多个app直接交互

      native本地应用,如android的信息应用

      hybrid native跟webview的结合

      webview浏览器应用

      11.3robotium

      Robotium是一款国外的Android自动化测试框架,主要针对Android平台的应用进行黑盒自动化测试,它提供了模拟各种手势操作(点击、长按、滑动等)、查找和断言机制的API,能够对各种控件进行操作。Robotium结合Android官方提供的测试框架达到对应用程序进行自动化的测试。另外,Robotium 4.0版本已经支持对WebView的操作。Robotium对Activity,Dialog,Toast,Menu都是支持的。

      robotium底层是android的instrument,通过Solo对象来创建一系列操作。但是只能android端,不像appnium跨设备。

      这里扩展一下大家的知识面,如何定位元素,我总结了三个,还有其他方法,请大家留言。

      针对坐标的:

      1.monkey的recorder,之前的博客有详细讲

      2.hierarchyviewer:sdk自带的工具。很多朋友会问商业机为什么不行,因为谷歌做了安全措施。无版本限制,如果你想测4.2以上版本也可以。注意,开发板或者模拟器可以用。

      效率为获得一个界面的所有节点和图片时间为10s左右

      3.uiautomator,4.2以上android版本的定位利器,可以真机哟。他也是可以做功能测试的

    12.网络测试

    2G/3G/4G/wifi,移动/电信/联通,保证APP在各种环境下的访问是否正常

    转载于:https://juejin.im/post/5cad6487e51d456e5a0728b2

    展开全文
  • 前前后后大概半个月,利用零碎时间看完了这本书。这本书前年就有所耳闻,网上的评价也褒贬不一,唐巧写的相对来说,语言通俗易懂,感觉像是博客一样,没有什么限制,读起来很畅快。这本书适合初学者,虽然这本书大...
  • 愿每一个北漂的孩子都能找到自己为之奋斗的方向,不负那些在远方默默支持我们的父母和亲人。 历时将近1的新书《Android安全技术揭秘与防范》终于出版了,第一次写书万分激动,这里向大家推广一下本书。...
  • HTML5 Plus应用概述 HTML5 Plus移动App,简称5+App,是一种基于HTML、JS、CSS编写的运行于手机端的App,这种App可以通过扩展的JS API任意调用手机的原生能力,实现与原生App同样强大的功能和性能。 ... ...
  • 1. Unity 5权威讲解 #目录 第1章 Unity 5简介 1 1.1 Unity 3D游戏引擎的诞生 2 1.2 Unity 5的优势 2 1.2.1 支持多平台 2 1.2.2 集成开发环境 3 1.2.3 所有功能免费 4 1.2.4 中间件内建 5 ...
  • QQ:365543212 Digital Canal Multiple Load Footing v4.5 1CD(定义出梯形的、带状的或矩形的脚柱的多种负重) Digital Canal Quick Wall v5.7 1CD(保留墙设计软件,同时拥有卓越的精确性) ...
  • 抓包工具篇|Charles

    2019-09-29 11:45:52
    移动开发时,我们为了调试与服务器端的网络通讯协议,常常需要截取网络封包来分析。 Charles 是收费软件,可以免费试用 30 天。试用期过后,未付费的用户仍然可以继续使用,但是每次使用时间不能超过 30 分钟,并且...
  • 以下是HBuilderX manifest.json配置说明大全,涉及的较全面,如果不方便查看; 可以直接Ctrl+ F 在页面查找相应字段配置。 ...manifest.json文件是5+移动App的配置文件,用于指定应用的显示名称、图标、入口页面等...
  • 一、创建离线安装包: 1、根据自己下载的VS2017的版本,在打开的命令提示符窗口输入下面对应的命令,等待程序启动即会开始下载,以下命令是下载完整版的离线包。 企业版:  vs_enterprise.exe --layout c:\vs...
  • 第1章 初识Android 1 1.1 Android历史 1 l Android之父是谁? 答案:Andy Rubin(安迪...答案:Windows Mobile / Phone的开发语言是C#和C++、iOS的开发语言是Objective-C、Symbian的开发语言是C++、BlackBe
1 2
收藏数 33
精华内容 13