Android性能優化典範

2021-02-11 程序猿

作者介紹:胡凱(@胡凱me),就職於騰訊,從事Android開發的工作,個人博客:http://hukai.me/。


摘要:Google在Udacity上的《Android性能優化》在線課程詳細介紹了該如何優化性能,這些課程是Google之前在Youtube上發布的Android性能優化典範專題課程的細化與補充。本文是對渲染、運算、內存、電量四個篇章的學習筆記。

Google近期在Udacity上發布了Android性能優化的在線課程,分別從渲染,運算與內存,電量幾個方面介紹了如何去優化性能,這些課程是Google之前在Youtube上發布的Android性能優化典範專題課程的細化與補充。

下面是本文作者@胡凱me對渲染、運算、內存、電量篇章的學習筆記,部分內容和前面的性能優化典範有重合,歡迎大家一起學習交流!

渲染篇

1)Why Rendering Performance Matters

現在有不少App為了達到很華麗的視覺效果,會需要在界面上層疊很多的視圖組件,但是這會很容易引起性能問題。如何平衡Design與Performance就很需要智慧了。

2)Defining 『Jank』

大多數手機的屏幕刷新頻率是60hz,如果在1000/60=16.67ms內沒有辦法把這一幀的任務執行完畢,就會發生丟幀的現象。丟幀越多,用戶感受到的卡頓情況就越嚴重。

3)Rendering Pipeline: Common Problems

渲染操作通常依賴於兩個核心組件:CPU與GPU。CPU負責包括Measure,Layout,Record,Execute的計算操作,GPU負責Rasterization(柵格化)操作。CPU通常存在的問題的原因是存在非必需的視圖組件,它不僅僅會帶來重複的計算操作,而且還會佔用額外的GPU資源。

4)Android UI and the GPU

了解Android是如何利用GPU進行畫面渲染有助於我們更好的理解性能問題。一個很直接的問題是:activity的畫面是如何繪製到屏幕上的?那些複雜的XML布局文件又是如何能夠被識別並繪製出來的?

Resterization柵格化是繪製那些Button,Shape,Path,String,Bitmap等組件最基礎的操作。它把那些組件拆分到不同的像素上進行顯示。這是一個很費時的操作,GPU的引入就是為了加快柵格化的操作。

CPU負責把UI組件計算成Polygons,Texture紋理,然後交給GPU進行柵格化渲染。

然而每次從CPU轉移到GPU是一件很麻煩的事情,所幸的是OpenGL ES可以把那些需要渲染的紋理Hold在GPU Memory裡面,在下次需要渲染的時候直接進行操作。所以如果你更新了GPU所hold住的紋理內容,那麼之前保存的狀態就丟失了。

在Android裡面那些由主題所提供的資源,例如Bitmaps,Drawables都是一起打包到統一的Texture紋理當中,然後再傳遞到GPU裡面,這意味著每次你需要使用這些資源的時候,都是直接從紋理裡面進行獲取渲染的。當然隨著UI組件的越來越豐富,有了更多演變的形態。例如顯示圖片的時候,需要先經過CPU的計算加載到內存中,然後傳遞給GPU進行渲染。文字的顯示比較複雜,需要先經過CPU換算成紋理,然後交給GPU進行渲染,返回到CPU繪製單個字符的時候,再重新引用經過GPU渲染的內容。動畫則存在一個更加複雜的操作流程。

為了能夠使得App流暢,我們需要在每幀16ms以內處理完所有的CPU與GPU的計算,繪製,渲染等等操作。

5)GPU Problem: Overdraw

Overdraw(過度繪製)描述的是屏幕上的某個像素在同一幀的時間內被繪製了多次。在多層次重疊的UI結構裡面,如果不可見的UI也在做繪製的操作,會導致某些像素區域被繪製了多次。這樣就會浪費大量的CPU以及GPU資源。

當設計上追求更華麗的視覺效果的時候,我們就容易陷入採用複雜的多層次重疊視圖來實現這種視覺效果的怪圈。這很容易導致大量的性能問題,為了獲得最佳的性能,我們必須儘量減少Overdraw的情況發生。

幸運的是,我們可以通過手機設置裡面的開發者選項,打開Show GPU Overdraw的選項,觀察UI上的Overdraw情況。

藍色、淡綠、淡紅、深紅代表了4種不同程度的Overdraw情況,我們的目標就是儘量減少紅色Overdraw,看到更多的藍色區域。

6)Visualize and Fix Overdraw - Quiz & Solution

這裡舉了一個例子,通過XML文件可以看到有好幾處非必需的background。通過把XML中非必需的background移除之後,可以顯著減少布局的過度繪製。其中一個比較有意思的地方是:針對ListView中的Avatar ImageView的設置,在getView的代碼裡面,判斷是否獲取到對應的Bitmap,在獲取到Avatar的圖像之後,把ImageView的Background設置為Transparent,只有當圖像沒有獲取到的時候才設置對應的Background佔位圖片,這樣可以避免因為給Avatar設置背景圖而導致的過度渲染。

總結一下,優化步驟如下:

●移除Window默認的Background

●移除XML布局文件中非必需的Background

●按需顯示佔位背景圖片

7)ClipRect & QuickReject

前面有提到過,對不可見的UI組件進行繪製更新會導致Overdraw。例如Nav Drawer從前置可見的Activity滑出之後,如果還繼續繪製那些在Nav Drawer裡面不可見的UI組件,這就導致了Overdraw。為了解決這個問題,Android系統會通過避免繪製那些完全不可見的組件來儘量減少Overdraw。那些Nav Drawer裡面不可見的View就不會被執行浪費資源。

但是不幸的是,對於那些過於複雜的自定義的View(通常重寫了onDraw方法),Android系統無法檢測在onDraw裡面具體會執行什麼操作,系統無法監控並自動優化,也就無法避免Overdraw了。但是我們可以通過canvas.clipRect()來幫助系統識別那些可見的區域。這個方法可以指定一塊矩形區域,只有在這個區域內才會被繪製,其他的區域會被忽視。這個API可以很好的幫助那些有多組重疊組件的自定義View來控制顯示的區域。同時clipRect方法還可以幫助節約CPU與GPU資源,在clipRect區域之外的繪製指令都不會被執行,那些部分內容在矩形區域內的組件,仍然會得到繪製。

除了clipRect方法之外,我們還可以使用canvas.quickreject()來判斷是否沒和某個矩形相交,從而跳過那些非矩形區域內的繪製操作。

8)Apply clipRect and quickReject - Quiz & Solution

上面的示例圖中顯示了一個自定義的View,主要效果是呈現多張重疊的卡片。這個View的onDraw方法如下圖所示:

打開開發者選項中的顯示過度渲染,可以看到我們這個自定義的View部分區域存在著過度繪製。那麼是什麼原因導致過度繪製的呢?

9)Fixing Overdraw with Canvas API

下面的代碼顯示了如何通過clipRect來解決自定義View的過度繪製,提高自定義View的繪製性能:



下面是優化過後的效果:

10)Layouts, Invalidations and Perf

Android需要把XML布局文件轉換成GPU能夠識別並繪製的對象。這個操作是在DisplayList的幫助下完成的。DisplayList持有所有將要交給GPU繪製到屏幕上的數據信息。

在某個View第一次需要被渲染時,Display List會因此被創建,當這個View要顯示到屏幕上時,我們會執行GPU的繪製指令來進行渲染。

如果View的Property屬性發生了改變(例如移動位置),我們就僅僅需要Execute Display List就夠了。

然而如果你修改了View中的某些可見組件的內容,那麼之前的DisplayList就無法繼續使用了,我們需要重新創建一個DisplayList並重新執行渲染指令更新到屏幕上。

請注意:任何時候View中的繪製內容發生變化時,都會需要重新創建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。這個流程的表現性能取決於你的View的複雜程度,View的狀態變化以及渲染管道的執行性能。舉個例子,假設某個Button的大小需要增大到目前的兩倍,在增大Button大小之前,需要通過父View重新計算並擺放其他子View的位置。修改View的大小會觸發整個HierarcyView的重新計算大小的操作。如果是修改View的位置則會觸發HierarchView重新計算其他View的位置。如果布局很複雜,這就會很容易導致嚴重的性能問題。

11)Hierarchy Viewer: Walkthrough

Hierarchy Viewer可以很直接的呈現布局的層次關係,視圖組件的各種屬性。 我們可以通過紅,黃,綠三種不同的顏色來區分布局的Measure,Layout,Executive的相對性能表現如何。

12)Nested Hierarchies and Performance

提升布局性能的關鍵點是儘量保持布局層級的扁平化,避免出現重複的嵌套布局。例如下面的例子,有2行顯示相同內容的視圖,分別用兩種不同的寫法來實現,他們有著不同的層級。


下圖顯示了使用2種不同的寫法,在Hierarchy Viewer上呈現出來的性能測試差異:

13)Optimizing Your Layout

下圖舉例演示了如何優化ListItem的布局,通過RelativeLayout替代舊方案中的嵌套LinearLayout來優化布局。

運算篇

1)Intro to Compute and Memory Problems

Android中的Java代碼會需要經過編譯優化再執行的過程。代碼的不同寫法會影響到Java編譯器的優化效率。例如for循環的不同寫法就會對編譯器優化這段代碼產生不同的效率,當程序中包含大量這種可優化的代碼的時候,運算性能就會出現問題。想要知道如何優化代碼的運算性能就需要知道代碼在硬體層的執行差異。

2)Slow Function Performance

如果你寫了一段代碼,它的執行效率比想像中的要差很多。我們需要知道有哪些因素有可能影響到這段代碼的執行效率。例如:比較兩個float數值大小的執行時間是int數值的4倍左右。這是因為CPU的運算架構導致的,如下圖所示:

雖然現代的CPU架構得到了很大的提升,也許並不存在上面所示的那麼大的差異,但是這個例子說明了代碼寫法上的差異會對運算性能產生很大的影響。

通常來說有兩類運行效率差的情況:第1種是相對執行時間長的方法,我們可以很輕鬆的找到這些方法並做一定的優化。第2種是執行時間短,但是執行頻次很高的方法,因為執行次數多,累積效應下就會對性能產生很大的影響。

修復這些細節效率問題,需要使用Android SDK提供的工具,進行仔細的測量,然後再進行微調修復。

3)Traceview Walkthrough

通過Android Studio打開裡面的Android Device Monitor,切換到DDMS窗口,點擊左邊欄上面想要跟蹤的進程,再點擊上面的Start Method Tracing的按鈕,如下圖所示:

啟動跟蹤之後,再操控app,做一些你想要跟蹤的事件,例如滑動listview,點擊某些視圖進入另外一個頁面等等。操作完之後,回到Android Device Monitor,再次點擊Method Tracing的按鈕停止跟蹤。此時工具會為剛才的操作生成TraceView的詳細視圖。

關於TraceView中詳細數據如何查看,這裡不展開了,有很多文章介紹過。

4)Batching and Caching

為了提升運算性能,這裡介紹2個非常重要的技術,Batching與Caching。

Batching是在真正執行運算操作之前對數據進行批量預處理,例如你需要有這樣一個方法,它的作用是查找某個值是否存在與於一堆數據中。假設一個前提,我們會先對數據做排序,然後使用二分查找法來判斷值是否存在。我們先看第一種情況,下圖中存在著多次重複的排序操作。

在上面的那種寫法下,如果數據的量級並不大的話,應該還可以接受,可是如果數據集非常大,就會有嚴重的效率問題。那麼我們看下改進的寫法,把排序的操作打包綁定只執行一次:

上面就是Batching的一種示例:把重複的操作拎出來,打包只執行一次。

Caching的理念很容易理解,在很多方面都有體現,下面舉一個for循環的例子:

上面這2種基礎技巧非常實用,積極恰當的使用能夠顯著提升運算性能。

5)Blocking the UI Thread

提升代碼的運算效率是改善性能的一方面,讓代碼執行在哪個線程也同樣很重要。我們都知道Android的Main Thread也是UI Thread,它需要承擔用戶的觸摸事件的反饋,界面視圖的渲染等操作。這就意味著,我們不能在Main Thread裡面做任何非輕量級的操作,類似I/O操作會花費大量時間,這很有可能會導致界面渲染發生丟幀的現象,甚至有可能導致ANR。防止這些問題的解決辦法就是把那些可能有性能問題的代碼移到非UI線程進行操作。

6)Container Performance

另外一個我們需要注意的運算性能問題是基礎算法的合理選擇,例如冒泡排序與快速排序的性能差異:

避免我們重複造輪子,Java提供了很多現成的容器,例如Vector,ArrayList,LinkedList,HashMap等等,在Android裡面還有新增加的SparseArray等,我們需要了解這些基礎容器的性能差異以及適用場景。這樣才能夠選擇合適的容器,達到最佳的性能。

內存篇

1)Memory, GC, and Performance

眾所周知,與C/C++需要通過手動編碼來申請以及釋放內存有所不同,Java擁有GC的機制。Android系統裡面有一個Generational Heap Memory的模型,系統會根據內存中不同的內存數據類型分別執行不同的GC操作。例如,最近剛分配的對象會放在Young Generation區域,這個區域的對象通常都是會快速被創建並且很快被銷毀回收的,同時這個區域的GC操作速度也是比Old Generation區域的GC操作速度更快的。

除了速度差異之外,執行GC操作的時候,所有線程的任何操作都會需要暫停,等待GC操作完成之後,其他操作才能夠繼續運行。

通常來說,單個的GC並不會佔用太多時間,但是大量不停的GC操作則會顯著佔用幀間隔時間(16ms)。如果在幀間隔時間裡面做了過多的GC操作,那麼自然其他類似計算,渲染等操作的可用時間就變得少了。

2)Memory Monitor Walkthrough

Android Studio中的Memory Monitor可以很好地幫助我們查看程序的內存使用情況。



3)Memory Leaks

內存洩漏表示的是不再用到的對象因為被錯誤引用而無法進行回收。

發生內存洩漏會導致Memory Generation中的剩餘可用Heap Size越來越小,這樣會導致頻繁觸發GC,更進一步引起性能問題。

舉例內存洩漏,下面init()方法來自某個自定義View:

private void init() {

ListenerCollector collector = new ListenerCollector();

collector.setListener(this, mListener);

}


上面的例子容易存在內存洩漏,如果activity因為設備翻轉而重新創建,自定義的View會自動重新把新創建出來的mListener給綁定到ListenerCollector中,但是當activity被銷毀的時候,mListener卻無法被回收了。

4)Heap Viewer Walkthrough

下圖演示了Android Tools裡面的Heap Viewer的功能,我們可以看到當前進程中的Heap Size的情況,分別有哪些類型的數據,佔比是多少。

5)Understanding Memory Churn

Memory Churn內存抖動,內存抖動是因為在短時間內大量的對象被創建又馬上被釋放。瞬間產生大量的對象會嚴重佔用Young Generation的內存區域,當達到閥值,剩餘空間不夠的時候,會觸發GC從而導致剛產生的對象又很快被回收。即使每次分配的對象佔用了很少的內存,但是他們疊加在一起會增加Heap的壓力,從而觸發更多其他類型的GC。這個操作有可能會影響到幀率,並使得用戶感知到性能問題。

解決上面的問題有簡潔直觀方法,如果你在Memory Monitor裡面查看到短時間發生了多次內存的漲跌,這意味著很有可能發生了內存抖動。

同時我們還可以通過Allocation Tracker來查看在短時間內,同一個棧中不斷進出的相同對象。這是內存抖動的典型信號之一。

當你大致定位問題之後,接下去的問題修復也就顯得相對直接簡單了。例如,你需要避免在for循環裡面分配對象佔用內存,需要嘗試把對象的創建移到循環體之外,自定義View中的onDraw方法也需要引起注意,每次屏幕發生繪製以及動畫執行過程中,onDraw方法都會被調用到,避免在onDraw方法裡面執行複雜的操作,避免創建對象。對於那些無法避免需要創建對象的情況,我們可以考慮對象池模型,通過對象池來解決頻繁創建與銷毀的問題,但是這裡需要注意結束使用之後,需要手動釋放對象池中的對象。

6)Allocation Tracker

關於Allocation Tracker工具的使用,不展開了,參考下面的連結:

●http://developer.android.com/tools/debugging/ddms.html#alloc

●http://android-developers.blogspot.com/2009/02/track-memory-allocations.html

7)Improve Your Code To Reduce Churn

下面演示一個例子,如何通過修改代碼來避免內存抖動。優化之前的內存檢測圖:

定位代碼之後,修復了String拼接的問題:

優化之後的內存監測圖:

8)Recap

上面提到了三種測量內存的工具,下面再簡要概括一下他們各自的特點:

●Memory Monitor:跟蹤整個app的內存變化情況。

●Heap Viewer:查看當前內存快照,便於對比分析哪些對象有可能發生了洩漏。

●Allocation Tracker:追蹤內存對象的來源。

電量篇

1)Understanding Battery Drain

手機各個硬體模塊的耗電量是不一樣的,有些模塊非常耗電,而有些模塊則相對顯得耗電量小很多。

電量消耗的計算與統計是一件麻煩而且矛盾的事情,記錄電量消耗本身也是一個費電量的事情。唯一可行的方案是使用第三方監測電量的設備,這樣才能夠獲取到真實的電量消耗。

當設備處於待機狀態時消耗的電量是極少的,以N5為例,打開飛行模式,可以待機接近1個月。可是點亮屏幕,硬體各個模塊就需要開始工作,這會需要消耗很多電量。

使用WakeLock或者JobScheduler喚醒設備處理定時的任務之後,一定要及時讓設備回到初始狀態。每次喚醒蜂窩信號進行數據傳遞,都會消耗很多電量,它比WiFi等操作更加的耗電。

2)Battery Historian

Battery Historian是Android 5.0開始引入的新API。通過下面的指令,可以得到設備上的電量消耗信息:

$ adb shell dumpsys batterystats > xxx.txt //得到整個設備的電量消耗信息

$ adb shell dumpsys batterystats > com.package.name > xxx.txt //得到指定app相關的電量消耗信息

得到了原始的電量消耗數據之後,我們需要通過Google編寫的一個python腳本把數據信息轉換成可讀性更好的html文件:

$ python historian.py xxx.txt > xxx.html

打開這個轉換過後的html文件,可以看到類似TraceView生成的列表數據,這裡的數據信息量很大,這裡就不展開了。

3)Track Battery Status & Battery Manager

我們可以通過下面的代碼來獲取手機的當前充電狀態:

// It is very easy to subscribe to changes to the battery state, but you can get the current

// state by simply passing null in as your receiver. Nifty, isn't that?

IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);

Intent batteryStatus = this.registerReceiver(null, filter);

int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);

boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);

if (acCharge) {

Log.v(LOG_TAG,「The phone is charging!」);

}

在上面的例子演示了如何立即獲取到手機的充電狀態,得到充電狀態信息之後,我們可以有針對性的對部分代碼做優化。比如我們可以判斷只有當前手機為AC充電狀態時 才去執行一些非常耗電的操作。

/**

* This method checks for power by comparing the current battery state against all possible

* plugged in states. In this case, a device may be considered plugged in either by USB, AC, or

* wireless charge. (Wireless charge was introduced in API Level 17.)

*/

private boolean checkForPower() {

// It is very easy to subscribe to changes to the battery state, but you can get the current

// state by simply passing null in as your receiver. Nifty, isn't that?

IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);

Intent batteryStatus = this.registerReceiver(null, filter);

// There are currently three ways a device can be plugged in. We should check them all.

int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);

boolean usbCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_USB);

boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);

boolean wirelessCharge = false;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {

wirelessCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS);

}

return (usbCharge || acCharge || wirelessCharge);

}

4)Wakelock and Battery Drain

高效的保留更多的電量與不斷促使用戶使用你的App會消耗電量,這是矛盾的選擇題。不過我們可以使用一些更好的辦法來平衡兩者。

假設你的手機裡面裝了大量的社交類應用,即使手機處於待機狀態,也會經常被這些應用喚醒用來檢查同步新的數據信息。Android會不斷關閉各種硬體來延長手機的待機時間,首先屏幕會逐漸變暗直至關閉,然後CPU進入睡眠,這一切操作都是為了節約寶貴的電量資源。但是即使在這種睡眠狀態下,大多數應用還是會嘗試進行工作,他們將不斷的喚醒手機。一個最簡單的喚醒手機的方法是使用PowerManager.WakeLock的API來保持CPU工作並防止屏幕變暗關閉。這使得手機可以被喚醒,執行工作,然後回到睡眠狀態。知道如何獲取WakeLock是簡單的,可是及時釋放WakeLock也是非常重要的,不恰當的使用WakeLock會導致嚴重錯誤。例如網絡請求的數據返回時間不確定,導致本來只需要10s的事情一直等待了1個小時,這樣會使得電量白白浪費了。這也是為何使用帶超時參數的wakelock.acquice()方法是很關鍵的。

但是僅僅設置超時並不足夠解決問題,例如設置多長的超時比較合適?什麼時候進行重試等等?解決上面的問題,正確的方式可能是使用非精準定時器。通常情況下,我們會設定一個時間進行某個操作,但是動態修改這個時間也許會更好。例如,如果有另外一個程序需要比你設定的時間晚5分鐘喚醒,最好能夠等到那個時候,兩個任務捆綁一起同時進行,這就是非精確定時器的核心工作原理。我們可以定製計劃的任務,可是系統如果檢測到一個更好的時間,它可以推遲你的任務,以節省電量消耗。

這正是JobScheduler API所做的事情。它會根據當前的情況與任務,組合出理想的喚醒時間,例如等到正在充電或者連接到WiFi的時候,或者集中任務一起執行。我們可以通過這個API實現很多免費的調度算法。

5)Network and Battery Drain

下面內容來自官方Training文檔中高效下載章節關於手機(Radio)蜂窩信號對電量消耗的介紹。

通常情況下,使用3G行動網路傳輸數據,電量的消耗有三種狀態:

●Full power: 能量最高的狀態,行動網路連接被激活,允許設備以最大的傳輸速率進行操作。

●Low power: 一種中間狀態,對電量的消耗差不多是Full power狀態下的50%。

●Standby: 最低的狀態,沒有數據連接需要傳輸,電量消耗最少。

下圖是一個典型的3G Radio State Machine的圖示(來自AT&T,詳情請點擊這裡):

總之,為了減少電量的消耗,在蜂窩行動網路下,最好做到批量執行網絡請求,儘量避免頻繁的間隔網絡請求。

通過前面學習到的Battery Historian我們可以得到設備的電量消耗數據,如果數據中的移動蜂窩網絡(Mobile Radio)電量消耗呈現下面的情況,間隔很小,又頻繁斷斷續續的出現,說明電量消耗性能很不好:

經過優化之後,如果呈現下面的圖示,說明電量消耗的性能是良好的:

另外WiFi連接下,網絡傳輸的電量消耗要比行動網路少很多,應該儘量減少行動網路下的數據傳輸,多在WiFi環境下傳輸數據。

那麼如何才能夠把任務緩存起來,做到批量化執行呢?下面就輪到Job Scheduler出場了。

6)Using Job Scheduler

使用Job Scheduler,應用需要做的事情就是判斷哪些任務是不緊急的,可以交給Job Scheduler來處理,Job Scheduler集中處理收到的任務,選擇合適的時間,合適的網絡,再一起進行執行。

下面是使用Job Scheduler的一段簡要示例,需要先有一個JobService:

public class MyJobService extends JobService {

private static final String LOG_TAG = "MyJobService";

@Override

public void onCreate() {

super.onCreate();

Log.i(LOG_TAG, "MyJobService created");

}

@Override

public void onDestroy() {

super.onDestroy();

Log.i(LOG_TAG, "MyJobService destroyed");

}

@Override

public boolean onStartJob(JobParameters params) {

// This is where you would implement all of the logic for your job. Note that this runs

// on the main thread, so you will want to use a separate thread for asynchronous work

// (as we demonstrate below to establish a network connection).

// If you use a separate thread, return true to indicate that you need a "reschedule" to

// return to the job at some point in the future to finish processing the work. Otherwise,

// return false when finished.

Log.i(LOG_TAG, "Totally and completely working on job " + params.getJobId());

// First, check the network, and then attempt to connect.

if (isNetworkConnected()) {

new SimpleDownloadTask() .execute(params);

return true;

} else {

Log.i(LOG_TAG, "No connection on job " + params.getJobId() + "; sad face");

}

return false;

}

@Override

public boolean onStopJob(JobParameters params) {

// Called if the job must be stopped before jobFinished() has been called. This may

// happen if the requirements are no longer being met, such as the user no longer

// connecting to WiFi, or the device no longer being idle. Use this callback to resolve

// anything that may cause your application to misbehave from the job being halted.

// Return true if the job should be rescheduled based on the retry criteria specified

// when the job was created or return false to drop the job. Regardless of the value

// returned, your job must stop executing.

Log.i(LOG_TAG, "Whelp, something changed, so I'm calling it on job " + params.getJobId());

return false;

}

/**

* Determines if the device is currently online.

*/

private boolean isNetworkConnected() {

ConnectivityManager connectivityManager =

(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();

return (networkInfo != null && networkInfo.isConnected());

}

/**

* Uses AsyncTask to create a task away from the main UI thread. This task creates a

* HTTPUrlConnection, and then downloads the contents of the webpage as an InputStream.

* The InputStream is then converted to a String, which is logged by the

* onPostExecute() method.

*/

private class SimpleDownloadTask extends AsyncTask<JobParameters, Void, String> {

protected JobParameters mJobParam;

@Override

protected String doInBackground(JobParameters... params) {

// cache system provided job requirements

mJobParam = params[0];

try {

InputStream is = null;

// Only display the first 50 characters of the retrieved web page content.

int len = 50;

URL url = new URL("https://www.google.com");

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setReadTimeout(10000); //10sec

conn.setConnectTimeout(15000); //15sec

conn.setRequestMethod("GET");

//Starts the query

conn.connect();

int response = conn.getResponseCode();

Log.d(LOG_TAG, "The response is: " + response);

is = conn.getInputStream();

// Convert the input stream to a string

Reader reader = null;

reader = new InputStreamReader(is, "UTF-8");

char[] buffer = new char[len];

reader.read(buffer);

return new String(buffer);

} catch (IOException e) {

return "Unable to retrieve web page.";

}

}

@Override

protected void onPostExecute(String result) {

jobFinished(mJobParam, false);

Log.i(LOG_TAG, result);

}

}

}

然後模擬通過點擊Button觸發N個任務,交給JobService來處理:

public class FreeTheWakelockActivity extends ActionBarActivity {

public static final String LOG_TAG = "FreeTheWakelockActivity";

TextView mWakeLockMsg;

ComponentName mServiceComponent;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_wakelock);

mWakeLockMsg = (TextView) findViewById(R.id.wakelock_txt);

mServiceComponent = new ComponentName(this, MyJobService.class);

Intent startServiceIntent = new Intent(this, MyJobService.class);

startService(startServiceIntent);

Button theButtonThatWakelocks = (Button) findViewById(R.id.wakelock_poll);

theButtonThatWakelocks.setText(R.string.poll_server_button);

theButtonThatWakelocks.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

pollServer();

}

});

}

/**

* This method polls the server via the JobScheduler API. By scheduling the job with this API,

* your app can be confident it will execute, but without the need for a wake lock. Rather, the

* API will take your network jobs and execute them in batch to best take advantage of the

* initial network connection cost.

*

* The JobScheduler API works through a background service. In this sample, we have

* a simple service in MyJobService to get you started. The job is scheduled here in

* the activity, but the job itself is executed in MyJobService in the startJob() method. For

* example, to poll your server, you would create the network connection, send your GET

* request, and then process the response all in MyJobService. This allows the JobScheduler API

* to invoke your logic without needed to restart your activity.

*

* For brevity in the sample, we are scheduling the same job several times in quick succession,

* but again, try to consider similar tasks occurring over time in your application that can

* afford to wait and may benefit from batching.

*/

public void pollServer() {

JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);

for (int i=0; i<10; i++) {

JobInfo jobInfo = new JobInfo.Builder(i, mServiceComponent)

.setMinimumLatency(5000) // 5 seconds

.setOverrideDeadline(60000) // 60 seconds (for brevity in the sample)

.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) // WiFi or data connections

.build();

mWakeLockMsg.append("Scheduling job " + i + "!\n");

scheduler.schedule(jobInfo);

}

}

}

作者介紹:胡凱(@胡凱me),就職於騰訊,從事Android開發的工作,個人博客:http://hukai.me/。

—————————————————

●本文編號823,以後想閱讀這篇文章直接輸入823即可。

●本文分類「安卓開發」,搜索分類名可以獲得相關文章。

●輸入m可以獲取到全部文章目錄

●輸入r可以獲取到熱門文章推薦

●輸入f可以獲取到全部分類名稱

—————————————————

網際網路創業的時代,創業更具人文氣息。聚最經典的IT創業技巧,最頂尖的商界精英交流平臺。

相關焦點

  • 幾乎是史上最全最實用的Android性能全面分析與優化方案研究
    藉助性能優化工具分析解決問題性能優化指標性能問題分類1、渲染問題: 過度繪製、布局冗雜2、內存問題: 內存浪費(內存管理)、內存洩漏3、功耗問題: 耗電性能優化原則和方法1、性能優化原則堅持性能測試(開發和測試同學的測試方法略有不同):不要憑感覺去檢測性能問題、評估性能優化的效果,應該保持足夠多的測量,用數據說話(主要針對測試同學)。
  • Google 發布 Android 性能優化典範 - OSCHINA - 中文開源技術交流...
    2015年伊始,Google發布了關於Android性能優化典範的專題,一共16個短視頻,每個3-5分鐘,幫助開發者創建更快更優秀的
  • Android性能優化典範(一)
    11) Performance Cost of Memory Leaks雖然Java有自動回收的機制,可是這不意味著Java中不存在內存洩漏的問題,而內存洩漏會很容易導致嚴重的性能問題。內存洩漏指的是那些程序不再使用的對象無法被GC識別,這樣就導致這個對象一直留在內存當中,佔用了寶貴的內存空間。
  • Android性能優化:手把手帶你全面實現內存優化
    最近有想換工作的同學們,可參考《5月技術崗位內推|RN開發招聘啦》,再往下看,一篇關於性能優化的好文章,很值得去學習。前言在 Android開發中,性能優化策略十分重要本文主要講解性能優化中的內存優化,希望你們會喜歡目錄
  • Android性能優化典範(三)
    4) The price of ENUMs 在StackOverFlow等問答社區常常出現關於在Android系統裡面使用枚舉類型的性能討論,關於這一點,Android官方的Training課程裡面有下面這樣一句話:
  • Android性能優化--內存優化
    本文來自Dotry投稿,連結:https://www.jianshu.com/p/38b627adaecd上一篇文章關於Android性能優化--啟動優化探討了啟動優化相關的知識點,在本篇將介紹內存優化的相關優化。
  • Android性能優化:帶你全面實現內存優化
    今日限免課程:《Android性能優化詳解—內存篇》(滑動查看課程目錄)課程免費
  • Google《Android性能優化》學習筆記
    Google近期在Udacity上發布了Android性能優化的在線課程,分別從渲染,運算與內存,電量幾個方面介紹了如何去優化性能,這些課程是Google之前在Youtube上發布的Android性能優化典範專題課程的細化與補充。
  • Android開發必備的「80」個開源庫
    你所不知道的Android Studio調試技巧https://www.jianshu.com/p/011eb88f4e0d一份系統、全面的安卓進階學習指南https://github.com/iwannabetop/Awesome-Android-Learning-GuideTrinea - 性能優化系列總篇
  • android 復用 布局優化專題及常見問題 - CSDN
    在布局優化中,Androi的官方提到了這三種布局<include />、<merge />、<ViewStub />,並介紹了這三種布局各有的優勢,下面也是簡單說一下怎麼使用.
  • 最全的Android內存優化技巧
    本文主要介紹性能優化的一些手段,但是為了便於理解以及融會貫通,建議先了解Android內存管理機制,本文將從四個角度來介紹內存優化技巧減小對象的內存佔用儘量減少新分配出來的對象佔用內存的大小,使用更加輕量的對象1.
  • Android性能優化總結
    這是來自一位粉絲「MeloDev」的投稿,講真,我這裡投稿的不少,但是只有我自己覺得很不錯的才會通過,這篇文章我覺得對大家有用,而且性能優化也算是我面試必問的一個話題了,所以這裡推薦給大家。程序執行效率:糟糕的代碼會嚴重影響程序的運行效率,UI線程過多的任務會阻塞應用的正常運行,長時間持有某個對象會導致潛在的內存洩露,頻繁的IO操作、網絡操作而不用緩存會嚴重影響程序的運行效率。一、布局複雜度的優化關於布局的優化,主要分兩個大方向1.
  • android內存優化總結
    ※1、追查內存的方法使用lintlint會提醒你很多使用不得當的地方,主要會集中再這麼幾個地方:(1)handler等長周期匿名內部類的使用,具體原因下文表(2)數據結構的優化,hashmap向稀疏數組的優化(3)未使用的圖片資源當然lint
  • ​吹爆系列:深入探索 Android 包體積優化
    在 Android 性能優化的知識體系當中,包體積優化一直被排在優先級比較低的位置,從而導致很多開發同學對自身應用的大小並不重視。但是,這樣 別的平臺使用時性能上就會有所損耗,失去了對特定平臺的優化。此外,在做性能優化過程中,為了提升研發效率,降低研發成本,我漸漸發現 AOP 編譯插樁、Gradle 自動化構建 的知識越來越重要;並且,一旦涉及 Native 層甚至 Android 內核層的深度優化時,就越發感覺到功力不足。因此,為了 補充深度優化所需的養分,從下篇開始,筆者將暫停更新 深入探索 Android 性能優化系列文章。
  • Android一些你需要知道的布局優化技巧
    那麼我們優化一下:但是它的這樣仍然會創建View,會影響性能。這時就可以用到ViewStub了,ViewStub是一個輕量級的View,不佔布局位置,佔用資源非常小。例子:比如我們請求網絡加載列表,如果網絡異常或者加載失敗我們可以顯示一個提示View,上面可以點擊重新加載。當然一直沒有錯誤時,我們就不顯示。
  • Android應用耗電量分析與優化建議
    www.jianshu.com/p/ebac88cdf9d6文章源自網絡,如果涉及侵權等問題,請第一時間聯繫我們予以下架Battery Historian工具使用Battery Historian 一款由Google提供的Android系統電量分析工具,從手機中導出bugreport文件上傳至頁面,在網頁中生成詳細的圖表數據來展示手機上各模塊電量消耗過程,最後通過App數據的分析制定出相關的電量優化的方法
  • Android 性能優化之內存洩漏,使用MAT&LeakCanary解決問題
    本文較長,閱讀大約5分鐘App進行到最終的測試的時候,往往會出現一些性能上,以及內存上的問題,需要優化,這也是一個Android高級工程師所需要了解並且掌握的知識點
  • QQ音樂Android客戶端Web頁面通用性能優化實踐
    QQ音樂 Android 客戶端的 Web 頁面日均 PV 達到千萬量級,然而頁面的打開耗時與 Native 頁面相距甚遠,需要系統性優化。本文將介紹 QQ 音樂 Android 客戶端在進行 Web 頁面通用性能優化過程中的問題、思路、方案和效果,並嘗試對跨端場景的常見瓶頸和對策進行歸納。文章作者:關嶽,QQ音樂客戶端開發工程師。
  • 高頻面試點:Android性能優化之內存優化(上篇)
    眾所周知,內存優化可以說是性能優化中最重要的優化點之一,可以說,如果你沒有掌握系統的內存優化方案,就不能說你對Android的性能優化有過多的研究與探索。本篇,筆者將帶領大家一起來系統地學習Android中的內存優化。
  • 性能優化測試中的相關名詞
    當一個APP或遊戲各種功能越來越多時,性能優化的重要性就不言而喻了,況且現在APP或遊戲的功能逐漸趨同,提升用戶體驗已從產品設計本身轉到了APP或遊戲的流暢性上,這也讓越來越多的開發者更加關注性能優化與測試。