iOS 是如何獲取夜間模式啟動(dòng)圖緩存路徑?
你是否了解過(guò)iOS 是如何獲取夜間模式啟動(dòng)圖緩存路徑??
百度APP技術(shù)團(tuán)隊(duì)曾經(jīng)發(fā)布過(guò)一篇深夜暗坑 - iOS啟動(dòng)圖異常修復(fù)方案。
該文章分享了一些關(guān)于啟動(dòng)圖的研究,但是通過(guò)文章的內(nèi)容,我們?nèi)匀粺o(wú)法解決這個(gè)問(wèn)題:iOS 是如何獲取夜間模式啟動(dòng)圖緩存路徑?
經(jīng)過(guò)一系列的研究,作者獲了模擬器場(chǎng)景下的方案,現(xiàn)分享作者的研究記錄。
方案一:嘗試通過(guò)啟動(dòng)圖文件名判斷緩存圖是否屬于夜間模式
我們首先對(duì) 深夜暗坑 - iOS啟動(dòng)圖異常修復(fù)方案 提供的信息進(jìn)行提煉。
原文提供了以下2個(gè)信息:
- 緩存啟動(dòng)圖的文件名具有規(guī)則,但其規(guī)則我們不得而知
- 4 張啟動(dòng)圖的文件名
- ├── 1FFD332B-EBA0-40C9-8EEE-BEC9AEF7C41A@3x.ktx
- ├── 96920D11-6312-4D69-BBDB-AFBB52DBDDB3@3x.ktx
- ├── 98F7B5B1-5B3B-478B-93A8-ED3DE6492AD1@3x.ktx
- └── D9D48845-8565-42CE-A834-479CC9CC8BAD@3x.ktx
通過(guò) 4 個(gè)文件名,我們可以發(fā)現(xiàn)4張圖片的命名都符合以下規(guī)則:
- xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx
再結(jié)合蘋(píng)果官方文檔 NSUUID 的內(nèi)容:
- `NSUUID`conform to RFC 4122 version 4 and are created with random bytes.
我們可以得到以下結(jié)論:
- 4 個(gè)文件名的都是通過(guò) NSUUID 動(dòng)態(tài)生成
- 文件名只包含版本 4,不再包含其它有效的信息
方案二:通過(guò)系統(tǒng)文件進(jìn)行分析
方案一失敗后,我們猜測(cè) iOS 是通過(guò)其它方式保存夜間模式啟動(dòng)圖緩存路徑信息。
經(jīng)過(guò)一系列的測(cè)試,作者發(fā)現(xiàn)了 applicationState.db 文件。
applicationState.db
系統(tǒng)會(huì)通過(guò) applicationState.db 保存程序狀態(tài)等各類(lèi)信息,當(dāng)然,也會(huì)包括夜間模式啟動(dòng)圖緩存路徑信息。
本文分析的文件位于 ~/Library/Developer/CoreSimulator/Devices/1F9B22C5-E446-4881-AFE4-3373E3513C59/data/Library/FrontBoard/applicationState.db
其中,1F9B22C5-E446-4881-AFE4-3373E3513C59代表 iOS 模擬器的設(shè)備ID。
模擬器的完整ID列表可以通過(guò)命令 plutil -p ~/Library/Developer/CoreSimulator/Devices/device_set.plist 查看
測(cè)試環(huán)境
為了方便對(duì)系統(tǒng)文件進(jìn)行分析,本文以 iOS 14 模擬器為目標(biāo)進(jìn)行分析。
版本信息如下所示:
- (lldb) platform status
- Platform: ios-simulator
- Triple: x86_64h-apple-macosx
- OS Version: 10.15.6 (19G2021)
- Kernel: Darwin Kernel Version 19.6.0: Thu Jun 18 20:49:00 PDT 2020; root:xnu-6153.141.1~1/RELEASE_X86_64
- Hostname: 127.0.0.1
- WorkingDir: /
- SDK Path: "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk"
- No devices are available.
另外,后續(xù)的代碼會(huì)假定 Bundle identifier 是 test.SplashTest
解析 applicationState.db
先嘗試通過(guò) file命令獲取 applicationState.db文件類(lèi)型:
- file ~/Library/Developer/CoreSimulator/Devices/1F9B22C5-E446-4881-AFE4-3373E3513C59/data/Library/FrontBoard/applicationState.db
輸出:
- SQLite 3.x database, last written using SQLite version 3032003
測(cè)試成功,通過(guò) file 命令的輸出可以看到文件類(lèi)型是 SQLite 3.x。
db 結(jié)構(gòu)
下面,再通過(guò) SQLite 相關(guān)的工具對(duì)該文件進(jìn)行dump,我們可以得到以下信息:
- sqlite> .schema
- CREATE TABLE schema(version INT NOT NULL);
- CREATE TABLE key_tab (id INTEGER PRIMARY KEY, key TEXT NOT NULL, UNIQUE(key));
- CREATE TABLE application_identifier_tab (id INTEGER PRIMARY KEY, application_identifier TEXT NOT NULL, UNIQUE(application_identifier));
- CREATE TABLE kvs ( id INTEGER PRIMARY KEY, application_identifier INT REFERENCES application_identifier_tab(id), key INT REFERENCES key_tab(id), value BLOB, UNIQUE(application_identifier, key));
- CREATE INDEX kvs_keys ON kvs(key);
- CREATE INDEX kvs_application_identifiers ON kvs(application_identifier);
- CREATE VIEW kvs_debug AS SELECT application_identifier_tab.application_identifier, key_tab.key, value FROM application_identifier_tab, key_tab, kvs WHERE kvs.application_identifier=application_identifier_tab.id AND kvs.key=key_tab.id
- /* kvs_debug(application_identifier,"key",value) */;
application_identifier_tab 保存了設(shè)備安裝的應(yīng)用列表( id 是主鍵,application_identifier 是 APP 的Bundle identifier)
key_tab 負(fù)責(zé)記錄常量字符串。
經(jīng)過(guò)測(cè)試,夜間模式啟動(dòng)圖緩存路徑的路徑屬于 XBApplicationSnapshotManifest。
- sqlite> .schema key_tab
- CREATE TABLE key_tab (id INTEGER PRIMARY KEY, key TEXT NOT NULL, UNIQUE(key));
- sqlite> .width 2 50
- sqlite> select * from key_tab;
- id key
- -- --------------------------------------------------
- 1 SBLaunchImageIngestionInfo
- 2 XBApplicationSnapshotManifest
- 3 _SBScenes
- 4 SBApplicationShortcutItems
- 5 compatibilityInfo
- 6 SBApplicationRecentlyUpdated
- 7 SBApplicationRecentlyUpdatedTimerStartDate
kvs_debug 負(fù)責(zé)串聯(lián)上面的3個(gè)表:
- kvs.application_identifier=application_identifier_tab.id
- kvs.key=key_tab.id
通過(guò) test.SplashTest,可以獲取到4個(gè)結(jié)果,其中第2負(fù)責(zé)保存快照相關(guān)信息
- sqlite> .width 15 32 8
- sqlite> SELECT * FROM kvs_debug WHERE application_identifier = 'test.SplashTest';
- application_ide key value
- --------------- -------------------------------- --------
- test.SplashTest _SBScenes bplist00
- test.SplashTest XBApplicationSnapshotManifest bplist00
- test.SplashTest SBApplicationRecentlyUpdated 0
- test.SplashTest compatibilityInfo bplist00
導(dǎo)出XBApplicationSnapshotManifest
經(jīng)過(guò)一番研究,我們發(fā)現(xiàn) XBApplicationSnapshotManifest 對(duì)應(yīng)的value 就是 SplashBoard 庫(kù) XBApplicationSnapshotManifestImpl 類(lèi)的持久化結(jié)果。
所以,我們可以通過(guò)通過(guò)以下代碼,對(duì) value 的內(nèi)容進(jìn)行 dump。
- +(void)load {
- void *lib = dlopen("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/SplashBoard.framework/SplashBoard", RTLD_NOW);
- printf("%p", lib);
- [self dump:@"/Users/test/Library/Developer/CoreSimulator/Devices/1F9B22C5-E446-4881-AFE4-3373E3513C59/data/Library/FrontBoard/XBApplicationSnapshotManifest.plist"];
- }
- +(void)dump:(NSString *)path {
- NSData *data0 = [NSData dataWithContentsOfFile:path];
- NSPropertyListFormat f = -1;
- NSError *error = nil;
- NSData *data1 = [NSPropertyListSerialization propertyListWithData:data0 options:NSPropertyListReadStreamError format:&f error:&error];
- if (f==kCFPropertyListXMLFormat_v1_0) {
- NSLog(@"kCFPropertyListXMLFormat_v1_0");
- }
- id obj = [NSKeyedUnarchiver unarchiveTopLevelObjectWithData:data1 error:&error];
- NSLog(@"%@",obj);
- }
dump 結(jié)果:
- <XBApplicationSnapshotManifestImpl: 0x600000050d80; clientCount: 0> {
- containerIdentity = 0x0;
- snapshots = {
- <XBApplicationSnapshotGroup: 0x600002e65ea0; identifier: test.SplashTest - {DEFAULT GROUP}> {
- <XBApplicationSnapshot: 0x7fcb53f04d90; identifier: CE275D00-5732-4AFD-88FD-00BAE541EC12; launchInterfaceIdentifier: __from_UILaunchStoryboardName__; contentType: GeneratedDefault; referenceSize: {375, 812}; interfaceOrientation: LandscapeLeft; userInterfaceStyle: Dark> {
- creationDate = September 27, 2020 at 6:08:50 PM GMT+8;
- keepsImageAccessUntilExpiration = NO;
- hasGenerationContext = NO;
- context = {
- contentType = GeneratedDefault;
- fullScreen = YES;
- referenceSize = {375, 812};
- interfaceOrientation = LandscapeLeft;
- userInterfaceStyle = Dark;
- additionalContext = {
- statusBarSettings = <XBStatusBarSettings: 0x600002c580f0; hidden: YES; style: 0x0; backgroundActivityEnabled: NO>;
- }
- }
- imageContext = {
- scale = 3.0;
- opaque = YES;
- fileRelativeLocation = default;
- fileFormat = png;
- }
- };
- <XBApplicationSnapshot: 0x7fcb57004830; identifier: B9DAB53E-29D9-47D2-873E-5772DE9220D1; launchInterfaceIdentifier: __from_UILaunchStoryboardName__; contentType: GeneratedDefault; referenceSize: {375, 812}; interfaceOrientation: Portrait; userInterfaceStyle: Light> {
- creationDate = September 27, 2020 at 6:08:50 PM GMT+8;
- lastUsedDate = September 27, 2020 at 6:08:50 PM GMT+8;
- keepsImageAccessUntilExpiration = NO;
- hasGenerationContext = NO;
- context = {
- contentType = GeneratedDefault;
- fullScreen = YES;
- referenceSize = {375, 812};
- interfaceOrientation = Portrait;
- userInterfaceStyle = Light;
- additionalContext = {
- statusBarSettings = <XBStatusBarSettings: 0x600002c5c1a0; hidden: NO; style: 0x0; backgroundActivityEnabled: NO>;
- }
- }
- imageContext = {
- scale = 3.0;
- opaque = YES;
- fileRelativeLocation = default;
- fileFormat = png;
- }
- };
- <XBApplicationSnapshot: 0x7fcb57004b60; identifier: 6B84614D-0867-4048-BE04-8E22E6742DDF; launchInterfaceIdentifier: __from_UILaunchStoryboardName__; contentType: GeneratedDefault; referenceSize: {375, 812}; interfaceOrientation: Portrait; userInterfaceStyle: Dark> {
- creationDate = September 27, 2020 at 6:08:50 PM GMT+8;
- keepsImageAccessUntilExpiration = NO;
- hasGenerationContext = NO;
- context = {
- contentType = GeneratedDefault;
- fullScreen = YES;
- referenceSize = {375, 812};
- interfaceOrientation = Portrait;
- userInterfaceStyle = Dark;
- additionalContext = {
- statusBarSettings = <XBStatusBarSettings: 0x600002c5c2d0; hidden: NO; style: 0x0; backgroundActivityEnabled: NO>;
- }
- }
- imageContext = {
- scale = 3.0;
- opaque = YES;
- fileRelativeLocation = default;
- fileFormat = png;
- }
- };
- <XBApplicationSnapshot: 0x7fcb57005140; identifier: D3E8D00C-EE33-466B-98A6-7E60865D8001; launchInterfaceIdentifier: __from_UILaunchStoryboardName__; contentType: GeneratedDefault; referenceSize: {375, 812}; interfaceOrientation: LandscapeLeft; userInterfaceStyle: Light> {
- creationDate = September 27, 2020 at 6:08:50 PM GMT+8;
- keepsImageAccessUntilExpiration = NO;
- hasGenerationContext = NO;
- context = {
- contentType = GeneratedDefault;
- fullScreen = YES;
- referenceSize = {375, 812};
- interfaceOrientation = LandscapeLeft;
- userInterfaceStyle = Light;
- additionalContext = {
- statusBarSettings = <XBStatusBarSettings: 0x600002c5c400; hidden: YES; style: 0x0; backgroundActivityEnabled: NO>;
- }
- }
- imageContext = {
- scale = 3.0;
- opaque = YES;
- fileRelativeLocation = default;
- fileFormat = png;
- }
- };
- };
- }
- }
SplashBoard 部分類(lèi)圖
通過(guò)類(lèi)信息,整理如下所示(只包含關(guān)鍵屬性):
獲取夜間模式啟動(dòng)圖緩存路徑
通常上面的內(nèi)容,我們可以對(duì) iOS 獲取夜間模式啟動(dòng)圖緩存路徑的流程進(jìn)行合理的猜測(cè)
啟動(dòng)時(shí),會(huì)先通過(guò) XBApplicationSnapshotGroup的identifier 獲取test.SplashTest - {DEFAULT GROUP} 的啟動(dòng)圖列表
再通過(guò) XBApplicationSnapshot 的 userInterfaceStyle = Dark; 和 interfaceOrientation = Portrait;等信息,判斷啟動(dòng)時(shí)應(yīng)該使用
- <XBApplicationSnapshot: 0x7fcb57004b60; identifier: 6B84614D-
- 0867-4048-BE04-8E22E6742DDF; launchInterfaceIdentifier:
- __from_UILaunchStoryboardName__; contentType: GeneratedDefault;
- referenceSize: {375, 812}; interfaceOrientation: Portrait;
- userInterfaceStyle: Dark>
最后再通過(guò) XBApplicationSnapshot的 _relativePath 拼接啟動(dòng)圖的真實(shí)路徑
- ~/Library/Developer/CoreSimulator/Devices/1F9B22C5-E446-4881-
- AFE4-3373E3513C59/data/Containers/Data/Application
- /FA902232-17D2-495F-B23E-410349A9921C/Library/SplashBoard/Snapshots
- /test.SplashTest - {DEFAULT GROUP}/6B84614D-0867-4048-
- BE04-8E22E6742DDF@3x.ktx
總結(jié)
本文通過(guò)對(duì) applicationState.db 進(jìn)行一系列的分析,最終實(shí)現(xiàn)了在模擬器下獲取獲取夜間模式啟動(dòng)圖緩存路徑的訴求。