對于生活離不開手機的我們來說,手機的電量就是一條重要的生命線,一般來說,當電量低于 20% 的時候,我們的心總是那么揪著。作為一個開發者來說,我們應該為用戶的手機省電,讓用戶有限的電量能夠更長時間的使用我們開發的 APP,對用戶,對我們開發者來說是兩全其美的方案。所以 APP 的電量消耗也應該是性能優化的點。
案例
還是以 raywenderlich 的 Catstagram APP 作為分析案例。該案例是一個帶有圖片的列表。
值的注意的是在我的開發環境下 Energy 需要運行在真機設備上,我的開發環境是 Xcode 8.3.2 , iPhone 6 (10.3.1)。
使用 Energy Impact
Energy Impact 是 Xcode 自帶的一個用于查看設備電量開銷概況的工具。
如上圖所示,點擊 Xcode 左邊的 Energy Impact 欄目就可以看到設備上正在運行的 APP 的電量消耗水平。
看圖左邊有 CPU ,Network , Location , GPU, Background 五個指標,這 5 個 指標也是能耗大戶,右邊的表格中的若是被灰色填充,那么就意味著在那個時刻,該指標是活躍的。比如圖上所示 CPU 和 Network 一直都是被灰色填充,那么就意味著 CPU 和 Network 一直處于活躍狀態。頂部有藍色和紅色的柱形圖,紅色是Overhead指標,表示除這個 APP 外,系統的其他電量消耗,藍色是Cost,表示這個 APP 的電量消耗。關于更多的 Energy Impact 信息可以參考 Apple 的官方文檔 https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/MonitorEnergyWithXcode.html 。這里就不再累贅。
APP 運行一段時間,滑動幾次列表之后, APP 的能耗就變的非常高,從圖中就可以看出,APP 在靜止狀態的電量高消耗情況肯定是不正常的,Energy Impact 只能看出是否有問題,而不能指出哪里可能有問題。那么這個時候就要祭出 Instruments 利器了。
Instruments 之 Energy
Command + I 運行 Instruments 選擇 Energy Log 模板。
看左邊的 Energy Log 的指標有 Energy,CPU,Network等等應有盡有。
點擊開始按鈕,錄制 APP 運行情況
從圖中可以看出整個 APP 的能量消耗情況,但是存在一個問題,這個問題就是我們已經知道了APP 的這些能量消耗情況,但是怎么知道要去修改哪里的代碼呢?這個時候我們需要 Time Profiler 工具。
如上圖所示,我們添加了 Time Profiler 工具用來記錄 APP 在某個時間段的代碼運行情況。
萬事具備之后,我們重新開始錄制 APP 的運行情況。
Energy Log 結合 Timer Profiler 的使用,避免干擾我們隱藏系統庫內容,顯示我們的代碼調用。
按照代碼執行時間的權重比,找到了 CatPhotoTableViewCell 的 panImage(with yRotation: CGFloat) 方法。通過代碼追溯,我們找到了 CatFeedViewController.swift 文件的 viewDidLoad() 方法,找到了 panImage(with yRotation: CGFloat) 方法被頻繁調用的地方
motionManager.startDeviceMotionUpdates(to: .main, withHandler:{ deviceMotion, error in
guard let deviceMotion = deviceMotion else { return }
self.lastY = deviceMotion.rotationRate.y
let xRotationRate = CGFloat(deviceMotion.rotationRate.x)
let yRotationRate = CGFloat(deviceMotion.rotationRate.y)
let zRotationRate = CGFloat(deviceMotion.rotationRate.z)
print("y \(yRotationRate) and x \(xRotationRate) and z\(zRotationRate)")
if abs(yRotationRate) > (abs(xRotationRate) + abs(zRotationRate)) {
for cell in self.tableView.visibleCells as! [CatPhotoTableViewCell] {
cell.panImage(with: yRotationRate)
}
}
})
這段代碼的關鍵在于 self.lastY = deviceMotion.rotationRate.y 這個語句,無論 deviceMotion.rotationRate.y 變化多大,都執行后面的代碼,正常應該是 deviceMotion.rotationRate.y 的變化范圍超過多少的時候才執行后面的代碼,所以優化如下
motionManager.startDeviceMotionUpdates(to: .main, withHandler:{ deviceMotion, error in
guard let deviceMotion = deviceMotion else { return }
guard abs(self.lastY - deviceMotion.rotationRate.y) > 0.1 else { return }
let xRotationRate = CGFloat(deviceMotion.rotationRate.x)
let yRotationRate = CGFloat(deviceMotion.rotationRate.y)
let zRotationRate = CGFloat(deviceMotion.rotationRate.z)
print("y \(yRotationRate) and x \(xRotationRate) and z\(zRotationRate)")
if abs(yRotationRate) > (abs(xRotationRate) + abs(zRotationRate)) {
for cell in self.tableView.visibleCells as! [CatPhotoTableViewCell] {
cell.panImage(with: yRotationRate)
}
}
})
修改 self.lastY = deviceMotion.rotationRate.y 的邏輯為 guard abs(self.lastY - deviceMotion.rotationRate.y) > 0.1 else { return }
。當 deviceMotion.rotationRate.y 變化范圍超過 0.1 的時候才執行后面的代碼邏輯。修改完代碼之后進行驗證修改效果。
使用 Energy Impact 進行驗證之后,發現能耗還是非常高,降不下來。那么接下來就繼續使用 Instruments 查找原因。
發現 CatFeedViewController 的 sendLogs() 也是占用了大量的 CPU 時間,接下來使用 Xcode 查看代碼。通過代碼追溯,找到了 CatFeedViewController 的init() 方法。
init() {
super.init(nibName: nil, bundle: nil)
navigationItem.title = "Catstagram"
tableView.autoresizingMask = UIViewAutoresizing.flexibleWidth;
tableView.delegate = self
tableView.dataSource = self
let timer = Timer(timeInterval: 1.0, target: self, selector: #selector(CatFeedViewController.sendLogs), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .commonModes)
}
在這個init() 方法里面發現了一個驚人的代碼,有一個定時器每隔 1 s 發起一次 sendlog 的網絡請求。不用懷疑了,肯定就是這個坑爹的代碼消耗了大量的電量。正常的發送 log 操作應該是在 APP 啟動完成的時候發送上次的 log 或者在 APP 進入 applicationWillResignActive 的回調方法發送 log。我們的修改方案是在 APP 進入 applicationWillResignActive 的回調方法發送 log。打開 AppDelegate.swift 文件,添加如下代碼同時刪除 CatFeedViewController 的init() 方法里面的 sendlog 定時器。
func applicationWillResignActive(_ application: UIApplication) {
rootVC.sendLogs()
}
接下來就是驗證修改效果了,使用 Energy Impact 這個工具來驗證,對于 驗證 APP 的能耗概況來說, Energy Impact 工具足以滿足需求。
現在 APP 的能耗是處于低水平,并且 Network 斌不是一直處于活躍狀態。
將 APP 退到后臺,再進入前臺,觸發 APP 的 sendlog 操作。這個時候 APP 的能耗進入高等級,但是隨后下降到低等級能耗。這個是 APP 的正常表現。
總結
APP 性能優化中,能耗優化決定了用戶在同樣的電量消耗情況下能使用你的 APP 多長時間。能耗優化的一般步驟如下
1、使用 Energy Impact 查看 APP 能耗概況
2、若是存在高能耗情況,使用 Instruments 的 Energy Log 模板進行細致驗證,并配合 Time Profiler 模板抓取代碼的運行細節。
3、根據代碼的運行細節,判斷潛在的問題點,然后修改代碼
4、驗證修改效果,若是無效,那么重復 2 - 4 步驟
參考
本文是 raywenderlich 的課程筆記,內容參考 Practical Instruments 課程
1、demo下載地址 https://files.betamax.raywenderlich.com/attachments/videos/789/9560e62e-96d3-47e5-b604-5d20c72bf9ee.zip
2、 Energy Log 課程地址
https://videos.raywenderlich.com/courses/74-practical-instruments/lessons/7
3、Energy Impact 官方文檔
https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/MonitorEnergyWithXcode.html