Google《Android性能優化》學習筆記

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

下面是本文做者@胡凱me對渲染、運算、內存、電量篇章的學習筆記,部份內容和前面的性能優化典範有重合,歡迎你們一塊兒學習交流!
java

渲染篇

1) Why Rendering Performance Matterspython

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

2) Defining ‘Jank’git

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


3) Rendering Pipeline: Common Problemsweb

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


4) Android UI and the GPUshell

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


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方法裏面執行復雜的操做,避免建立對象。對於那些沒法避免須要建立對象的狀況,咱們能夠kao慮對象池模型,經過對象池來解決頻繁建立與銷燬的問題,可是這裏須要注意結束使用以後,須要手動釋放對象池中的對象。

6) Allocation Tracker

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

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/

相關文章
相關標籤/搜索