天天看點

Android最佳實踐之流暢(Seamlessness)設計

即使你的應用程式是快速且響應靈敏的,但一些設計仍然會給使用者造成問題——與其它應用程式或對話框未事先計劃的互動,意外的資料丢失,意料之外的阻塞等等。避免這些問題,有助于了解應用程式運作的上下文和系統的互動過程,而這些又正影響着你的應用程式。簡而言之,你應該竭盡全力去開發一個與系統和其它應用程式流暢互動的應用程式。

一個常見的流暢問題是,一個應用程式的背景處理——例如,一個Service或者BroadcastReceiver——彈出一個對話框來響應一些事件。這可能看起來沒啥大礙,尤其是你在模拟器上單獨地建構和測試你的應用程式的時候。然而,當你的應用程式運作在真機上時,有可能你的應用程式在沒有獲得使用者焦點時背景處理顯示了一個對話框。是以,可能會出現在活躍的應用程式後方顯示了你的應用程式的對話框,或者從目前應用程式奪取焦點顯示了一個對話框,而不管目前使用者正在做什麼(例如,正在打電話)。那種行為,對應用程式或使用者來說,就不應該出現。

為了避免這些問題,你的應用程式應該使用合适的系統資源來通知使用者——Notification類。使用Notification,你的應用程式可以在狀态欄顯示一個icon來通知使用者已經發生的事情,而不是奪取焦點和打斷使用者。

另一個流暢問題的例子是未能正确實作Activity的onPause()和其它生命周期方法而造成意外丢失了狀态或使用者資料。又或者,如果你的應用程式想暴露資料給其它應用程式使用,你應該通過ContentProvider來暴露,而不是(舉例)通過一個可讀的原始檔案或資料庫來實作。

這些例子的共同點是它們都應該與系統和其它應用程式協作好。Android系統設計時,就把應用程式看作是一堆松散耦合的元件,而不是一堆黑盒代碼。作為開發者來說,允許我們把整個系統看作是更大的元件集合。這有益于我們可以與其它應用程式進行清晰無縫的內建,是以,作為回報,我們應該更好的設計我們的代碼。

這篇文章将讨論常見的流暢問題以及如何避免它們。它将囊括這些主題:

1)     别丢棄資料

2)     不要暴露原始資料

3)     不要打斷使用者

4)     有太多事情要做?線上程裡做

5)     不要讓一個Activity超負荷

6)     擴充系統主題

7)     設計你的UI可以應付多螢幕分辨率

8)     假設網絡很慢

9)     不要假定觸摸屏或鍵盤

10)   節省裝置電池

1)别丢棄資料

一定要記住Android是一個移動平台。可以顯而易見地說,其它Activity(例如,“Incoming Phone Call”應用程式)可能會在任何時候彈出來遮蓋你的Activity,記住這個事實很重要。因為這個過程将觸發 onSaveInstanceState()和onPause()方法,并可能導緻你的應用程式被殺死。

如果使用者在你的應用程式中正在編輯資料時,其它Activity出現了,這時,你的應用程式被殺死時可能丢失那些資料。當然了,除非你事先儲存了正在進行的工作。“Android方式”是這樣做的:能接收和編輯使用者輸入的Android應用程式應該重寫onSaveInstanceState()方法,并以恰當的方式儲存它們的狀态。當使用者重新通路應用程式時,她能得到她的資料。

進行這種處理方式最經典的例子是mail應用程式。如果使用者正在輸入email,這時其它Activity啟動了,mail應用程式應該把正在編輯的email以草稿的方式儲存起來。

2)不要暴露原始資料

如果你不想穿着内衣在大街上溜達的話,你的資料也不應該這樣。盡管可能存在暴露應用程式的某種形式給其它應用程式,但這通常不是最好的主意。暴露原始資料,要求其它應用程式能夠了解你的資料的格式;如果你變更了格式,那麼,你将破壞那些沒有進行同步更新的應用程式。

“Android方式”是建立一個ContentProvider,以一種清晰的、深思熟慮的和可維護的API方式暴露你的資料給其它應用程式。使用ContentProvider,就好像是插入Java接口來分離群組裝兩片高耦合的代碼。這意味着你可以修改資料的内部格式,而不用修改由 ContentProvider暴露的接口,這樣,也不會影響其它應用程式。

3)不要打斷使用者

如果使用者正在運作一個應用程式(例如,Phone程式),斷定對使用者操作的目的才是安全的。這也就是為什麼必須避免建立Activity,而是直接在目前的Activity中響應使用者的輸入。

那就是說,不要在BroadcastReceiver或在背景運作的Service中調用callActivity()。這麼做會中斷目前運作的應用程式,并導緻使用者惱怒。也許更糟糕的是,你的Activity可能成為“按鍵強盜”,竊取了使用者要提供給前一個Activity的輸入。視乎你的應用程式所做的事情,這可能是個壞消息。

不選擇在背景直接建立Activity UI,取而代之的是,應該使用NotificationManager來設定Notification。它們會出現在狀态欄,并且使用者可以在他空閑的時候點選它們,來檢視你的應用程式向他顯示了什麼。

(注意,如果你的Activity已經在前台了,以上将不适用:這時,對于使用者的輸入,使用者期望的是看到下一個Activity來響應。)

4)有太多事情要做?線上程裡做

如果你的應用程式需要執行一些昂貴或耗時的計算的話,你應該盡可能地将它挪到線程裡。這将阻止向使用者顯示可怕的“Application Not Responding”對話框,如果不這樣做,最終的結果會導緻你的應用程式完全終止。

一般情況下,Activity中的所有代碼,包括它的View,都運作在相同的線程裡。在這個線程裡,還需要處理UI事件。例如,當使用者按下一個按鍵,一個key-down事件就會添加到Activity的主線程隊列裡。事件處理系統需要很快讓這個事件出列并得到處理;如果沒有,系統數秒後會認為應用程式已經挂起并為使用者提供殺死應用程式的機會。

如果有耗時的代碼,内聯在Activity上運作也就是運作在事件處理線程裡,這在很大程度上阻塞了事件處理。這會延遲輸入處理,并導緻ANR對話框。為了避免這個,把你的計算移到線程裡。在響應靈敏性設計的文章裡已經讨論了如何做。

5)不要讓一個Activity超負荷

任何值得使用的應用程式都可能有幾個不同的螢幕。當設計UI螢幕時,請一定要使用多個Activity對象執行個體。

依賴于你的開發背景,你可能了解Activity類似于Java Applet,它是你應用程式的入口點。然而,那并不精确:Applet子類是一個Java Applet的單一入口點,而一個Activity應該看作是你的應用程式多個潛在入口點之一。你的“main”Activity和其它之間的唯一不同點是“main”Activity正巧是在AndroidManifest.xml檔案中唯一對“android.intent.action.MAIN” 動作感興趣的Activity。

是以,當設計你的應用程式的時候,把你的應用程式看作是Activity對象的集合。從長遠來看,這會使得你的代碼更加友善維護。

6)擴充系統主題

當談到UI觀感時,巧妙地交融非常重要。使用者在使用與自己期望相反的UI的應用程式時,會産生不愉快的感覺。當設計你的UI時,你應該盡量避免太多自己的主題。相反的,使用同一個主題。你可以重寫或擴充你需要的主題部分,但至少在與其它應用程式相同的UI基礎上開始。詳細請參照“應用風格和主題”部分。

7)設計你的UI可以應對多螢幕分辨率

不同的Android裝置可能支援不同的螢幕分辨率。甚至一些可以自己變更分辨率,例如,切換到風景模式。確定你的布局和圖檔能足夠靈活地在不同的裝置螢幕上正常顯示。

幸運的是,這很容易做到。簡而言之,你需要做的是為主要分辨率提供不同版本的作品,然後為不同的尺寸設計你的布局。(例如,避免使用寫死位置而使用相對布局。)如果那樣做的話,系統會處理剩下的部分,而且你的應用程式在任何裝置上都看起來很棒。

8)假設網絡很慢

Android裝置會有多種網絡連接配接選項。所有的都提供資料通路,但之間肯定有更快的。其中,速度最慢的是GPRS,GSM網絡的非3G資料服務。即使具備3G能力的裝置在非3G的網絡上也會花費很多的時間,是以,網絡很慢仍然是一個長期存在的事實。

這就是為什麼你應該按照最小化的網絡通路和帶寬來編寫你的代碼。你不能假設網絡是快速的,是以,你應該總是計劃它是慢的。如果你的使用者碰巧在一個快速的網絡上,那很好——他們的使用者體驗會提升。你要避免相反的情形:在不同的地點和不同時間,應用程式有時可用,有時慢得令人抓狂,這樣的程式可能不會受歡迎。

還有一個潛在的地方是,如果你正在使用模拟器,那麼你很容易受它迷糊,因為模拟器使用電腦的網絡連接配接。這比手機網絡快很多,是以,你需要修改模拟器設定來模拟較低的網絡速度。你可以在Eclipse中做到這點,在啟動選項的模拟器設定頁裡設定或者在啟動模拟器時通過指令行選項設定。

9)不要假定觸摸屏或鍵盤

Android可以支援多種外觀形狀。也就是說,一些Android裝置擁有全“QWERTY”鍵盤,而其它可能會有40鍵、12鍵或其它鍵盤設定。同樣的,一些裝置可能有觸摸屏,但一些也會沒有。

當建立你的應用程式的時候,記住這一點。不要假定特定的鍵盤布局——除非你真的想限定你的應用程式隻運作在某些裝置上。

10)節省裝置電池

如果移動裝置經常插在牆上,那麼,它也就不是很“移動”。移動裝置是電池供電的,如果我們能讓每次充電的電池使用得更持久一些,那麼每個人都會更加開心——尤其是使用者。其中兩大耗電硬體是處理器和無線;這也就是我們為什麼要寫盡可能少做工作、盡可能少去使用網絡的應用程式的重要原因。

如何讓你的應用程式最小化的占用處理器,歸根結底還是要寫高效代碼。為了減少無線的電量消耗,確定對錯誤條件進行正确的處理,并隻擷取你要的東西。例如,如果某一個網絡操作失敗了,不要不斷地進行重試。如果失敗了一次,有可能是使用者不受歡迎,是以,如果你再以正确的方式操作,有可能還會失敗;所有你做的都是在浪費電池。

使用者是相當聰明的:如果你的程式高耗電,他們是一定會發現的。到那個時點,你唯一可以确定的是,你的程式将很快被解除安裝掉。

可能會存在這樣的情況,你寫的代碼通過了世界上所有的性能測試,但當使用者嘗試使用你的應用程式時,仍然讓使用者感到不爽。應用程式響應不夠靈敏的地方包括——反映遲鈍,挂起或當機很長時間,或者需要花費很長的時間來處理輸入。

在 Android上,如果你的應用程式有一段時間響應不夠靈敏,系統會向使用者顯示一個對話框,這個對話框稱作應用程式無響應(ANR:Application Not Responding)對話框。使用者可以選擇讓程式繼續運作,但是,他們在使用你的應用程式時,并不希望每次都要處理這個對話框。是以,在程式裡對響應性能的設計很重要,這樣,系統不會顯示ANR給使用者。

一般說來,如果應用程式不能響應使用者輸入的話,系統會顯示一個ANR。例如,一個應用程式阻塞在一些I/O操作上(通常是網絡通路),這時,應用程式的主線程就不能再處理使用者的輸入事件。經過一定的時間後,系統認為應用程式已經挂起,并顯示ANR來讓使用者選擇殺死應用程式。

相似地,如果你的應用程式花費太多的時間來建構詳細的記憶體結構,或者也許是在遊戲裡花費太多時間來計算下一步移動,這時,系統會認為你的應用程式已經挂起。是以,確定這些計算是高效的往往很重要,但即使是最高效的代碼仍然需要花費時間來運作。

在這兩種情況下,解決的方法通常是建立一個子線程,然後線上程裡做你的大部分工作。這能讓主線程(驅動UI事件循環)保持運作,并阻止系統認為你的代碼已經當機。因為這些線程通常是在類級别上完成的,是以,你可以認為響應性能問題是一個類的問題。(與基本性能相比而言,基本性能問題認為是方法級别的問題)

這篇文章将讨論Android系統如何判斷一個應用程式處于無響應狀态,并為保證應用程式的響應性提供向導。

這篇文章囊括這些主題:

  • 什麼引發了ANR?
  • 如何避免ANR?
  • 增強響應靈敏性

1) 什麼引發了 ANR ?

在Android裡,應用程式的響應性是由Activity Manager和Window Manager系統服務監視的。當它監測到以下情況中的一個時,Android就會針對特定的應用程式顯示ANR:

  • 在5秒内沒有響應輸入的事件(例如,按鍵按下,螢幕觸摸)
  • BroadcastReceiver在10秒内沒有執行完畢
Android最佳實踐之流暢(Seamlessness)設計

一個ANR對話框顯示給使用者

2) 如何避免 ANR ?

考慮上面的ANR定義,讓我們來研究一下為什麼它會在Android應用程式裡發生和如何最佳建構應用程式來避免ANR。

Android應用程式通常是運作在一個單獨的線程(例如,main)裡。這意味着你的應用程式所做的事情如果在主線程裡占用了太長的時間的話,就會引發ANR對話框,因為你的應用程式并沒有給自己機會來處理輸入事件或者Intent廣播。

是以,運作在主線程裡的任何方法都盡可能少做事情。特别是,Activity應該在它的關鍵生命周期方法(如onCreate()和onResume())裡盡可能少的去做建立操作。潛在的耗時操作,例如網絡或資料庫操作,或者高耗時的計算如改變位圖尺寸,應該在子線程裡(或者以資料庫操作為例,通過異步請求的方式)來完成。然而,不是說你的主線程阻塞在那裡等待子線程的完成——也不是調用Thread.wait()或是Thread.sleep()。替代的方法是,主線程應該為子線程提供一個Handler,以便完成時能夠送出給主線程。以這種方式設計你的應用程式,将能保證你的主線程保持對輸入的響應性并能避免由于5秒輸入事件的逾時引發的ANR對話框。這種做法應該在其它顯示UI的線程裡效仿,因為它們都受相同的逾時影響。

IntentReceiver 執行時間的特殊限制意味着它應該做:在背景裡做小的、瑣碎的工作如儲存設定或者注冊一個Notification。和在主線程裡調用的其它方法一樣,應用程式應該避免在BroadcastReceiver裡做耗時的操作或計算。但不再是在子線程裡做這些任務(因為BroadcastReceiver的生命周期短),替代的是,如果響應Intent廣播需要執行一個耗時的動作的話,應用程式應該啟動一個Service。順便提及一句,你也應該避免在 Intent Receiver裡啟動一個Activity,因為它會建立一個新的畫面,并從目前使用者正在運作的程式上搶奪焦點。如果你的應用程式在響應Intent廣播時需要向使用者展示什麼,你應該使用Notification Manager來實作。

3)增強響應靈敏性

 一般來說,在應用程式裡,100到200ms是使用者能感覺阻滞的時間門檻值。是以,這裡有一些額外的技巧來避免ANR,并有助于讓你的應用程式看起來有響應性.如果你的應用程式為響應使用者輸入正在背景工作的話,可以顯示工作的進度(ProgressBar和ProgressDialog對這種情況來說很有用)。特别是遊戲,在子線程裡做移動的計算。如果你的應用程式有一個耗時的初始化過程的話,考慮可以顯示一個Splash Screen或者快速顯示主畫面并異步來填充這些資訊。在這兩種情況下,你都應該顯示正在進行的進度,以免使用者認為應用程式被當機了。

繼續閱讀