Skip to content

Latest commit

 

History

History
409 lines (289 loc) · 27.5 KB

系统相关.md

File metadata and controls

409 lines (289 loc) · 27.5 KB

系统相关

在这里记录了跟 Apple 相关操作系统中遇到的问题和记录。

iOS中的内存区域划分,如下图所示:

iOS中的推送相关,如下图所示:

程序的可执行文件类型:

  • ELF:Linux下的可执行文件格式;
  • PE32/PE32+:Windows下的可执行文件格式;
  • Mach-o:Mac和iOS下的可执行文件格式,比如可执行文件、库文件、dsym文件、动态库、动态链接器

代码编译要经过的几个器件(C语言):

  • 预处理器:将.c 文件转化成 .i文件,使用的gcc命令是:gcc –E,对应于预处理命令cpp;
  • 编译器:将.c/.h文件转换成.s文件,使用的gcc命令是:gcc –S,对应于编译命令 cc –S;
  • 汇编器:将.s 文件转化成 .o文件,使用的gcc 命令是:gcc –c,对应于汇编命令是 as;
  • 链接器:将.o文件转化成可执行程序,使用的gcc 命令是: gcc,对应于链接命令是 ld;
  • 加载器:将可执行程序加载到内存并进行执行,loader和ld-linux.so。

NSTimer

// 该方法默认是添加到 NSDefaultRunLoopMode
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

// 该方法需要手动添加到的NSRunLoop中
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

// 添加方法
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
  • NSDefaultRunLoopMode :默认mode,当UIScrollView拖动时会影响。点击事件、普通回调事件、不滑动,是个默认状态、空闲状态,程序启动后,自动被切到这个 mode
  • UITrackingRunLoopMode: 界面跟踪mode,用于scrollView跟踪触摸滑动,保证界面滑动时不受其它Mode影响
  • UIInitializationRunLoopMode: 启动App时进入的第一个mode,启动完成后就不再使用。程序启动之后,私有,当出现第一个页面时就被切了。
  • GSEventReceiveRunLoopMode:Graphic相关事件、接收系统事件内部的mode,通常用不到
  • NSRunLoopCommonModes: 占位用的mode,不是真的mode,包含第一个和第二个。

scheduledTimerWithTimeInterval 使用该方法创建出来的timer默认加入到当前线程的RunLoop中,且模式为 NSDefaultRunLoopMode,如果当线程是主线程(UI线程),某些UI事件比如UIScrollView的拖动操作,会将Run Loop 切换成 NSEventTrackingRunLoopMode模式,在这个过程中,创建出来的timer的注册的事件是不会被执行的。需要把创建出来的 timer 使用 NSRunLoopCommonModes 模式添加到 Run Loop中,这个模式等效于 NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的结合。

NSCoding 协议和 runtime 的结合使用

iOS开发之NSCoding协议(使用runtime):https://www.jianshu.com/p/b33bdbccfa57。正常对用NSCoding写数据持久化,如果该对象的属性较少直接开干没啥问题,但是如果是一个多属性的对象,那就得写死人了。直接用runtime的特性去遍历出当前对象的所有属性。

终结掉 self.var 和 _var 的纠结

property = _var + setter + getterself 类似 java 中的 thisC++ 中的 selfOCself 同样也具备相同的职能,只不过因为 property 机制的存在,事情并且不简单。

实例变量(成员变量)具有私有性,一般情况下只在类内部使用,为了方便给外界读写这个实例变量,就有了属性(@property),编译器会自动的(@synthesize var = _var)为我们生成对应的一个以下划线加属性名命名的实例变量、 settergetter 方法,并且 self.var 会让 var 计数器 + 1,而 _var 不会。需要注意在 vargetter 方法中,我们必须要使用 _var 进行访问,如果还是 self.var 则会进入死循环,但是在 setter 中如果还是使用 self.var 则也会触发 setter 。懒加载中也需要用 _var 来访问实例变量。一句话就是,“获取 var 只能 _var ,赋值 var 可以 _varself.var”。如果我们同时对 var 写了 settergetter 方法,@property 机制将不会生效,我们要自己声明 _var

使用 readonly 关键字修饰后,编译器只会为我们生成 getter。如果一个属性被 @dynamic 修饰,则编译器不会为其自动生成对应的 settergetter,如果没有我们没有自行编写 gettersetter ,将不会在编译器得到提醒,在程序运行时将会发生 crash 。我们还可以使用 @dynamic 替换到某个类中本来就存在的 property

Swift 中没有 @property 类似的机制,属性是否对外部可见通过 private 关键词进行决定,Swift 中的属性分为计算属性和存储属性。计算属性不直接存储值,而是通过 gettersetter 方法间接访问其它属性,类似 OC 中的属性。存储属性充当存储值的角色,可直接被外部访问,类似 OC 中的实例变量。

UIView 和 CALayer 的区别

  • UIView 和 CALayer 一样都是 UI 操作的对象 :两者都是 NSObject 的子类,发生在 UIView 上的操作本质上也发生在 CALayer 上。
  • UIView 是 CALayer 用于交互的对象 :UIView 是 UIResponder 的子类( UIResponder 是 NSObject 的子类),UIView 提供了很多 CALayer 交互上所没有的接口,其主要负责处理用户触发的各种操作。
  • CALayer 在图像和动画上渲染性能更好 :正是因为 UIView 有冗余的交互接口,而且相比 CALayer 还有层级之分,而 CALayer 在无须处理交互时进行渲染,可以节省大量时间。

layeroutIfNeeded ,layoutSubviews 和 setNeedsLayout 的区别

  • layeroutIfNeeded : 该方法一旦被调用,主线程会立即强制重新布局。它从当前视图开始,一直到完成所有子视图的布局;
  • layoutSubviews :该方法用于自定义视图尺寸。系统调用,开发者只能重写该方法,让系统在调整尺寸时能够按照开发者希望的效果进行布局。该方法主要用在屏幕旋转、滑动或触摸界面、修改子视图时被触发。
  • setNeedsLayout :与 layoutSubviews 方法作用类似,不同的是它不会立刻强制视图重新布局,而是在下一个布局周期才会触发更新。该方法主要用于多个视图布局先后更新的场景下。比如,在两个位置不断变化的点之间连一条线,该条线的布局就可以调用 setNeedsLayout 方法。

适配 iPad

UIAlertController crash

详见:https://stackoverflow.com/questions/31577140/uialertcontroller-is-crashed-ipad

打开第三方 app 及被第三方 app 打开

如果能够保证这个 scheme 一定是存在的,可以直接执行:

[[UIApplication sharedApplication] openURL:request.URL
                                   options:@{}
                         completionHandler:nil];

如果不能保证,以免其它特殊问题(我没遇到过),可以先 canOpenURL 判断一下:

BOOL result = [[UIApplication sharedApplication] canOpenURL:request.URL];
if(result) {
    [[UIApplication sharedApplication] openURL:request.URL
                                        options:@{}
                              completionHandler:nil];
} else {
    // 是否需要提示?
}

执行 canOpenURL 方法,记得现在 target -> Info -> Custom iOS Target Properties 中添加字段 LSApplicationQueriesSchemes,类型为 Array,在 item 中添加对应需要打开的 app scheme 即可。

在 URL Types 中写的 scheme 指的是被其它 app 回调我方 app 时的标识符。

removeFromSuperView 删除 View

我们经常会使用 [UIView removeFromSuperView] 方法把一个 UIView 及其子类(下文称 ViewA)从当前视图中进行移除,当我们执行这个操作时,潜意识里是想把这个视图彻底从当前页面上进行移除,但是这个方法只能保证把 ViewA 从当前视图上进行移除,并不能保证该 ViewA 真的被移除”,内存中还是存在 ViewA

还需要执行 ViewA = nil

如何修改 UIAlertAction 的文字颜色

applyAction.setValue(UIColor.lightGray, forKey: "_titleTextColor")

关于使用友盟推送收不到 push 消息的原因之一

可以使用友盟后台进行“推送测试”,如果是自建了集成友盟推送 SDK 的后台,进行“推送测试”的话,需要把 app 进行构建,通过安装并使用构建出来的 ipa 包才能够收到自建集成了友盟推送 SDK 的后台推送。

注意:测试设备例如 iPhone、iPad 等设备请登陆 Apple ID,否则 app 中集成友盟推送 SDK 将无法对该测试设备进行 device Token 的生成,也即无法推送至友盟中心。

什么是元编程

元编程。一件事情通过正常编程去做到是「正常编程」,元编程是通过编程的方法做到其他需要编程的事情,可以理解为「元编程」的本质上在做一个新的 DSL。@property 就是一个「元编程」思路。

统计启动耗时。统计启动到初始化结束的耗时,需要的是对关键业务的统计。

自旋锁

当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该现场将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会推出循环。

存在的问题:如果某个线程持有锁的时间过长,就会导致其他等待获取锁的线程进入循环等待,消耗 CPU。

优点:自旋锁不会使线程状态发生切换,其会一直处于用户态,线程一直都是 active 的,不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。非自旋锁在获取不到锁时会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换,因为线程被阻塞后便进入「内核调度状态」,会导致系统在用户态和内核态之间来回切换。

可查看:https://zhuanlan.zhihu.com/p/40729293

CocoaPods 都做了什么?

首先明确一点,pods 是一个依赖管理工具,能够解析出用在 Podfile 文件中的配置信息,解析配置信息的过程不是特别复杂, 但解析的逻辑代码量一点都不小,考虑了很多的 case,尤其是依赖解析部分,自建了一个依赖图算法完成各个依赖库之间的重复依赖关系。

pods 是基于 ruby 的一个依赖管理工具,虽然 Podfile 写起来给人感觉一点都不像是写代码,而是在写一个描述文档,这是因为 ruby 的方法调用可以不写方法调用时的左右括号,而且也支持 block 的调用(与 OC block 不一致)。解析出依赖库后,会调用一个下载器,下载器会根据当前的执行命令是 pod install 还是 pod update 来决定下载器的入参。详情可见

如何造一个 Router

看了头条的 Router 实现大概流程,在开始的做法是通过 .strings 文件填写好 URL 做好映射,再通过 OC 的反射机制把字符串反射为类,现在虽然已经修改为了不需要引入配置文件,之前在应用初始化的时候就能初始化好了 Router,但实际上的做法还是基于反射。

个人认为 router 的好处在于能够应对多个 type 的跳转。

打包过程

  • 源代码编译
  • 静态库链接
  • 资源编译、优化、导入
  • 配置文件生成
  • 签名打包

编译

编译器前端部分主要负责把「编程语言」翻译成「平台无关语言」,编译器后端主要作用是把「平台无关语言」翻译成「平台相关语言」 编译的基本架构

LLVM 架构

LLVM 架构

Clang

  • 基于 LLVM 的编译器前端
  • 基于 LLVM 的 C 语言编译工具集,兼容 GCC

预处理

  • 引入头文件
  • 预处理指令
  • 去除注释
  • 宏定义展开。clang -E -fmodules test.m,命令执行后效果: 宏定义展开

Lexer 词法解析

clang -fsyntax-only -fmodules -Xclang -dump-tokens text.m,命令执行效果:

Lexer 词法解析

AST(抽象语法树)

clang -fsyntax-only -fmodules -Xclang -ast-dump

抽象语法树

iOS 不与磁盘交换

iOS 上没有「内存交换机制」,应用的性能不会逐步下滑,只会直接撞上南墙。,所以脏页面在 Apple 的移动设备上成本更高。无论使用多少。脏页面永远不会写入磁盘,所以 IOS 必须更积极地交换干净页面(可执行代码、映射文件)或终止进程。

内存警告的问题

发生「内存警告」时,其会在主线程上进行传递,如果 app 在主线程进入了忙碌状态时接收到了「内存警告」,那么 app 会被标记为无响应状态,然后就会被移除。

将内存分配操作移到后台也不行,当主线程在前台尝试处理「内存警告」时,后台线程还在大块大块的分配内存,此时 app 同样也会被移除。

解决思路

在进行分配大量内存的后台线程即将进行工作时,先向主线程利用 performSelector:onMainThread(将 waitUnitDone 标志位设置为 YES) 发送一条 ping 消息。如果主线程在处理「内存警告」,那么这条语句会阻塞后台线程中的内存分配操作,并允许在恢复操作之前将内存释放掉。

栈比堆快

栈是计算机自动管理,有单独的存储区域。堆需要手动管理。细节看这个

CocoaPods 的本地开发模式

如果是多组件并行开发的情况下,先在 Podfile 中修改对应的组件 :path 为本地路径,开发完毕后,本地 pod 组件库更新,再修改 Podfile 为原先的 Pod 地址。

用户态与内核态的切换为什么会耗费资源

内核相当于是控制计算机硬件资源的「软件」,用户态里包括了 shell 等一系列应用程序,当应用程序需要调用系统硬件资源时,就跑到了内核态中。

因为这相当于涉及到了两个「软件」的切换使用,所以从用户态切换到内核态时,需要保持用户态下的当前软件环境中的各种信息,再去调用内核态中的各种资源,也就是系统调用。详情可见这篇文章

UNIX 中的文件本质上就是「字节流」

只不过添加了诸如名称、所有者、访问权限及访问日期之类的元数据。

UIDocument

设置图标上的角标数字

  • [UIApplication sharedApplication].applicationIconBadgeNumber

Core Graphic 有时候也被称为 Quartz

纯函数。相同的输入,永远相同的输出

  • 纯函数是函数式编程的概念,必须遵守以下一些约束。
    • 不得改写参数
    • 不能调用系统 I/O 的API
    • 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

项目中如何做到防 crash

  • 如何防止 UIView 的刷新不在主线程之类的问题
    • 那就写个分类!帮它移动到主线程,但我觉得这种问题应该被暴露出来而不是被放过
  • 如何做「对象找不到」方法时的防 crash
    • runtime 里在找不到方法时有三次几乎可以触发保护
      1. resolveInstanceMethod。一般做 crash 防护时不会去碰这个方法,因为这个方法很有可能是本类在做一些动态插入的事情。
      2. forwardingTargetForSelector。一般在这个方法中做处理,通过方法交换,把本应该是 A 类处理,但 A 类没有相关处理的方法转移到另外一个 B 类中,通过在 B 类中替换 A 类的方法,可以做记录 crash 发生的参数等。
      3. forwardingInvocation。一般也不在这个方法中做处理,因为需要自行创建 NSInvocation 对象来做对象替换,并调用对应方法。

WKWebView 起 POST 请求时,不带 body?

由于 WKWebView 在独立进程里执行网络请求。一旦注册http(s) scheme后,网络请求将从 Network Process发送到 App Process,这样 NSURLProtocol 才能拦截网络请求。在 webkit2 的设计里使用 MessageQueue 进行进程之间的通信,Network Process 会将请求 encode 成一个 Message,然后通过 IPC 发送给 App Process。

出于性能的原因,encode 的时候 HTTPBodyHTTPBodyStream 这两个字段被丢弃掉了。 因此,如果通过 registerSchemeForCustomProtocol 注册了http(s) scheme, 那么由 WKWebView 发起的所有http(s)请求都会通过 IPC 传给主进程 NSURLProtocol 处理,导致 post 请求 body 被清空。

如何通过脚本自动生成一个 pod 库

#!/bin/sh
read -p "Please enter the Module name: " module

script_dir=$(dirname $0)
dev_path="${script_dir}/../../Development Pods/"
git_path="template.git"
module_name="${module}"

cd "${dev_path}"
pod lib create --template-url=${git_path} ${module_name}

iOS 给每个 app s分配的内存的上限为本机内存的一半再多一点

cocoapods 组件库多依赖问题的解决

比如 A 库依赖 AFNetworking 3.0,B 依赖 AFNetworking 2.0,此时 B 要引入 A 组件库,按照以往的昨晚是会出现重名库错误。

解决办法可以把 A 库达成二进制库,只暴露出 A 库应该对外暴露的方法,或者勇另外一个笨方法,手动把 A 库依赖的 AFNetworking 里方法都加上 A 库的前缀,但这种做法只能在不麻烦的情况下使用啦。

CATransaction 动画事物管理

  • UIView 修改 CALayer 动画时,实际上做的隐式事物,在下一个 RunLoop 中进行提交,显示事物会被立即提交
  • [CATransction commit]。不管当前 RunLoop 进行剩下多少耗时操作动画都会被理解提交
  • 把暴力操作移交到子线程去做

CocoaPods 一些基础知识

pod setup

pod setup 很智障,会把 github 上的所有的托管的 repo 都拉到本地,所以造成了第一次巨慢...

Podfile.lock

第一次执行 pod install 时生成,除非 Podfile 有修改或执行 pod update 否则不会改变

Headers

该目录下存放对应库的头文件软链接

Manifest.lock

Podfile.lock 的拷贝文件

Target Support Files

存放工程 build 时所依赖的一些文件

一些小点

  • 每一个 Pod 都会有 .xccoinfig, .pch, dummy.m(类的空实现)

  • debug.xcconfigrelease.xcconfig 对应主工程里的 Configurationsxcconfig

  • resource.sh 用来编译 storyboard 等资源文件或者 copy xcassets 等资源文件

  • Frameworks.sh 用于实现 framework 类型第三方库的链接

依赖关系

  • 首次使用 pod updatepod install 没有任何区别

  • pod install 会参考 Podfile.lockPodfile,在生成 Podfile.lock 之后,除非 Podfile 有所改动,否则运行 pod install 结果依然不变

  • Pods/ 文件夹不需要添加到 .gitignore 里。Podfile.lock 应该加入项目版本控制中,可以保证所有人在执行 pod install 后所依赖的三方库版本完全一致。

通过一些简单的 ruby 语法来调整 Podfile

![通过一些简单的 ruby 语法来调整 Podfile](https://i.loli.net/2019/07/28/5d3d0b4825cfb24533.png)

如何友好的调试本地 pod 库

如何友好的调试本地 pod 库

如何提高因为 Pod 库太多导致编译太慢?

提前将三方库打成静态库,存到公司内部仓库,每次 pod install 直接通过内网拉去静态库,build 时省去 Pod 的编译。

如何把主工程的宏同步到 Pod 项目上

  1. 直接把需要用到的东西都同步到一个新的 Pod 库里

  2. 利用 cocoapods 的特性。在 pod install 时,利用 pre_installpost_install 两个钩子函数,如下图所示:

    如何把主工程的宏同步到 Pod 项目上

传统工程和组件化工程的区别

传统工程和组件化工程的区别

拷贝问题

Foundation 框架中的所有集合类在默认情况下都执行浅拷贝,也就是说,只拷贝容器本身,而不复制其中的数据。这样做的原因在于容器内的对象未必都能拷贝,而且调用者也未必想在拷贝容器的时一并拷贝其中的每个对象。

如何执行深拷贝?

  • NSSet 方法有对应的初始化方法直接执行深拷贝 initWithSet:copyItems
  • OC 中没有专门定义深拷贝的协议,具体执行由每个类自行决定。
  • 不要假定遵循了 NSCopying 协议的对象都会执行深拷贝,在绝大多数情况下执行的都是浅拷贝。除非文档中说明了其深拷贝时基于

HTTPS 就是不想被抓包怎么办?

  • 再加一层密

动态库

将一系列代码封装为动态库,并在其中放入描述其接口的头文件,这就叫框架。iOS 上的第三方框架几乎都是静态库,因为 Apple 不允许 iOS 应用程序中包含动态库,但 Apple 自身的系统框架都是动态库。

对 pod 库使用 resource_bundles 来管理资源

Cocoapods 的 bundle_resourcesresources 可以并存,分别制定资源的加载方式,如果不通过 bundle 的方式去做,用 imageWithName 的方法去做即可。因为用之前的 resources 方法会导致 pod 库中的资源合并到主工程里,多个 pod 库之间都这么做的话,那同名资源会出现异常。

使用 bundle_resources 方式引入资源时,如果原先是 resources 方式引入的资源,各个 pod 库里的资源会被合并到主工程中,所以其它 pod 库可以通过「资源合并」的方式间接引用到其它 pod 里的资源,改用 bundle_resources 时,其它 pod 库原先引用的地方都要改为对应的 bundle 里的资源,如果在全局替换搜索时,很有可能会因为 pod 库饮用时是二进制包导致搜索不到,还有些没有暴露 .m 文件。

解决思路:

  • 各个 pod 库所属的各自资源都加上自己的前缀。
  • 使用 resource_bundles 的方式。
     + (NSBundle *)mainBundle {
     		static NSBundle *bundle = nil;
     		static dispatch_once_t onceToken;
     		dispatch_once(&onceToken, ^{
         		NSString *bundlePath = [[NSBundle bundleForClass:TTVideoBusinessBundle.class].resourcePath stringByAppendingPathComponent:@"TTVideoBusinessResource.bundle"];
         		bundle = [NSBundle bundleWithPath:bundlePath];
         		if (!bundle) {
             		NSAssert(!bundle, @"未正确加载 	TTVideoBusinessResource.bundle");
         		}
     	});
     
     		return bundle;
     }
     + (UIImage *)ttv_bundleImageName:(NSString *)imageName {
     		return [UIImage imageNamed:imageName inBundle:[TTVideoBusinessBundle mainBundle] compatibleWithTraitCollection:nil];
     }

[UIView setNeedsLayout]

本质上只是加个个标记,在下一个 runloop 布局。

子线程没有 runloop

在子线程中手动起一个 runloop,直接 run,停不下来,之后的内容执行不到。

Auto Layout 的实现

  • 基于 cassowary 算法
  • cassowary 还有 JS、Java 和 C++ 等库的实现
  • Auto Layout 不只有布局算法,还包含了布局在运行时的生命周期等,这一套布局引擎系统叫 Layout Engine
    • 每个视图在获取到自己的布局之前, Layout Engine 会将试图、约束、优先级、固定大小通过计算转换成最终的大小和位置。
    • Layout Engine 里,每当约束发送变化,就会触发 Deffered Layout Pass,完成后进入监听约束变化的状态。
    • 监听到约束变化时,即进入下一轮 RunLoop 循环中。
    • 添加、删除视图时会触发约束变化,设置约束或优先级也会触发约束变化
    • Layout Engine 在约束变化时,会重新计算布局。获取到布局后调用 superView.setNeedLayout() 然后进入 Defferred Layout Pass
    • Defferred Layout Pass 的主要作用是做容错处理。如果视图在更新约束时没有确定或缺失布局声明的话,会先在这里做容错处理。
    • 接下来 Layout Engine 会从上到下调用 layoutSubview(),通过 cassowary 算法计算各个子视图的位置,计算完成后,再从子视图的 frame 从 Layout Engine 里 copy 出去。
    • 与 frame 布局多了一个通过 cassowary 布局计算的过程。

链接器的作用

就是完成变量、函数符号和其地址绑定这样的任务。

合并 Mach-O 文件的作用

可以理解为是把分离的源代码文件合并在一起,解决调用其它文件中的方法问题。

提供一个 pod 库给不同的 app 使用时

可以使用不同的 podspec,引用不同的宏定义,通过宏去执行不同的逻辑。

矢量图片在编译时会生成位图。

载动态库的方式有两种:

  • 在程序开始运行时通过 dyld 动态加载。需要在编译时进行链接,链接时会做标记,绑定的地址在加载后再决定。
  • 显式运行时链接,在运行时通过动态链接器提供的 API dlopendlsym 来动态加载。这种方式,在编译时是不需要参与链接的。但不允许上线,但可通过其加速调试。

XNU 内核决定应当使用的线程数

并只生成所需的线程执行处理。当处理结束,应当执行的处理数减少,XNU 内核会结束不再需要的线程。

多 icon 可以通过多 target 完成。

怎么通过多 target 做隔离。

  • New 一个 target
  • copy 一个 target
    • copy 只会多一些配置文件,可以在这些配置文件中读取一些特定的字段进行操作。

app 长期保活

保活指的是 app 在后台不会因为资源不足而被系统结束进程。

  • 地理位置更新,打点range:涉及到后台获取地理位置。
  • 录音。Plan A。
  • 持续下载文件。带宽问题。
  • 静默loop播放音乐。

静默播放音乐可能最优。 优点:无权限,无带宽,性能损耗低,无影响。 缺点:某些情况下play也会失效。比如和平精英的语音转文字场景。(目前还没有有效方案)。

在不同 app 间传递大文件

如果是同一个 groupID 的 app 集合,可以通过共享文件夹的方式随意存取文件,但一个公司/集团不可能只有一个开发者账号,不同的开发者账号不同的 appID,如何做到互相传递大文件呢?

直接用剪贴板,针对被传递出去的文件做加密,剪贴板传资源不费事,但对传输出去的文件做加密比较费事。