2015/08/18

Store and retrieve a class object in SharedPreferences

之前在 Save ArrayList to SharedPreferences 提到過,如何將 ArrayList 物件存到 SharedPreferences 裡,最近看到一個更棒的方式,那就是使用 Gson

假設我現在有個 MyObject 物件想要存起來,可以這樣寫:

要取出來時:

回到之前 ArrayList 的問題,儲存可以改用上面範例來處理,至於取出的方法需要改寫成這樣:

比起原本的程式碼,變得乾淨俐落許多!

Reference:
http://stackoverflow.com/questions/5418160

2015/04/28

Test Your Deep Links

今天看文件才想到 adb 有提供指令可以直接發 Intent 給 App,而且還能帶 URI data,這樣要用來測試 protocol 就方便多了!

Reference:
https://developer.android.com/training/app-indexing/deep-linking.html#testing-filters

2015/04/27

android.database.CursorWindowAllocationException

當 Cursor 沒有確實關閉並且累積到一定數目時,App 就會出現異常報錯,無法查詢資料庫,甚至 crash。這邊記錄一些容易出錯的例子:

。提前返回
    有時會沒注意到在調用 close() 之前就 return 了,尤其在比較複雜的程式碼中就可能會疏忽。

。異常處理
    在還沒執行 close() 之前就因為處理 exception 而跳過。

要避免這種情形,應該要把 close() 放到 finally 裡確保它會執行。

另外我們也可以在 Cursor 的建構及關閉時加上 log 來測試及偵錯。

這樣就能清楚檢查是否有 Cursor 未確實關閉,但要先等 App 完全結束程序時再做,因為有些 Cursor 可能仍在背景運行中。

Reference:
http://oteku.blogspot.tw/2013/11/how-to-detect-android-cursor-leak-en.html

2015/04/26

java.util.ConcurrentModificationException

前陣子在做 for-each 執行某項過濾功能時遇到這個異常報錯,原因在於 for-each 的架構是不能使用於過濾需求的,一旦變動了集合的內容便會發生錯誤。在這邊可以使用 Iterator 調動集合的方式來解決,在多個執行緒中也能做到同步。

另外如果集合不大,也可以使用 counted loop 來處理。

Reference:
http://stackoverflow.com/a/223929/1770125

2015/03/01

POJO

POJO,全名為 Plain Old Java Object。它是一個簡單的 Java Object 概念定義,可以包含邏輯處理,以下三種情況不能稱作 POJO:

1. 繼承 Java frameworks 的 class
2. 實作 Java frameworks 的 interface
3. 包含 Java frameworks 的 annotation
Reference:
http://martinfowler.com/bliki/POJO.html
http://en.wikipedia.org/wiki/Plain_Old_Java_Object

Programmatically Declare Styleable In Custom View

在我們設計 custom View 的時候,一般都會想要定義一些專屬的 attributes 放在 attrs.xml 裡面,方便之後可以調整它的參數來因應 layout 規格。例如在 Chris Banes 的 FloatLabelLayout 中,他先在 attrs.xml 定義了每個方向的 padding,以及要用作 label 的 hint 和 TextAppearance,

然後在 FloatLabelLayout.java 取得外部利用這些 attributes 所設定的數值,進而調整裡面需要變動的元件。

但是有些時候,我們可能想要直接在程式碼中定義這些行為,那該如何實作呢?

在這個例子中,我們先宣告了 styleable 這個 int array,放入需要用到的 attributes,接著調用 Context#obtainStyledAttributes() 傳回一個 TypedArray 的物件,裡面便會包含 reference 到這些 attributes 所設定的數值,再來就可以選擇適當的 method 來取用了。

這邊有幾個需要注意的地方,由於 context.obtainStyledAttributes() 內部設計預設是認定 styleable 中的 attributes 是排序過的,所以他回傳回來的數值是依其在 native 的順序寫入到 TypedArray,若是我們的 styleable 沒 sort 過,就會得到非預期的結果;另外就是從 TypedArray 取值時傳入的是 attribute 在 styleable 的 index,而非 attribute 本身。

Reference:
http://droidux.com/programmatically-declare-styleable-custom-view/

2015/02/16

Avoid float and double if exact answers are required

float 和 double 主要是被設計用作科學及工程運算,若是需要精確結果的商業計算,它們並不適合,原因在於它們不能準確地表達 0.1 或任何 10⁻¹ 的倍數。

假設你口袋有 $1.03,而你花了 42¢,那會剩下多少?
不幸地,它的輸出結果是 0.6100000000000001,這不是唯一的特殊案例;假設你口袋有 $1,你買了 9 個 10¢ 的物品,那會找回多少零錢?
根據這個結果,你會拿回 $0.09999999999999998。

我們可能會覺得只要利用輸出前幾位數做四捨五入來 work around 就可以了,但這招並不是每次都能奏效。再來舉一個例子,假設現在你口袋有 $1,架上擺著糖果,價格依序為 10¢、20¢、30¢,依此類推,如果從第一顆糖果開始買起,你可以買多少顆糖果,並且能找回多少零錢?
當你跑完這段程式碼,你會發現你可以買 3 顆糖果,然後找回 $0.3999999999999999,這結果當然是錯的。要解決這個情況,我們必須改用 BigDecimal 來處理商業計算。
這次我們得到了正確的答案,你可以買 4 顆糖果,找回 $0.00。

不過,使用 BigDecimal 也有缺點,它寫起來不是很方便,而且格式也麻煩,不容易 review。另外一個方法就是視情況使用 int 或 long 來代換,像是在這個例子中,因為數值不大,我們可以把單位都改用分來計算。
Reference:
http://stackoverflow.com/questions/5257166
Effective Java》Item 48

2015/01/21

Context, What Context?

我們在開發 Android app 時,最常用到的元件非 Context 莫屬了,但它也最有可能會被誤用。假設現在定義了一個 singleton,需要傳入 Context 來做後續如載入 resources 之類的工作,並且會將這個 reference 存下來。

這時會造成一個問題,我們不知道這個 Context 的來源是什麼,而如果來源是 Activity 或 Service,那我們扣著這個 reference 也是不安全的。原因在於為了維持這個 static reference,那與它相關聯的所有物件就不會被回收,如果這個 Context 來源是 Activity,這就意味著連同在這個 Activity 裡面的 View 以及各種物件都會被扣著,增加 memory leak 的風險。為了避免這個情況,我們可以調整 reference 到 Application Context 身上。

這樣我們便不用顧慮 Context 來源是什麼,因為 Application Context 本身就是 singleton,不需要再花資源建另外一個 static reference。

那我們為什麼不乾脆都只 reference 到 Application Context 身上就好了呢?那是因為有些功能不適合由 Application Context 執行,最主要的就是 UI 行為,例如 Layout Inflation,而執行 UI 功能最適合的人選就是 Activity,一般來說這些功能原本也就被設計在 Activity 內運作。

Reference:
http://possiblemobile.com/2013/06/context/

2015/01/18

Android Performance Best Practices


Android Performance Best Practices from Amgad Muhammad

這幾天在看 Android Performance Patterns 相關文章時看到這份投影片,裡面提到的有些東西我以前沒用過,就來寫摘要記錄一下。

如何有效運用資源:
  1. 謹慎使用 service
    • 利用 IntentService 可以確保任務執行完後會結束,藉此限制它的生命週期。
    • 若 app 結束或不需要 service 資源時,務必釋放掉避免佔用。
  2. 當 UI 退場或是 memory 吃緊時,都可以利用 onTrimMemory() (API level 14 以上) 提供的 callback 得知是否需要釋放資源。
  3. 透過 getMemoryClass() 得知這台 device 可以用的 heap memory,超過便會造成 OOM;另外在 manifest<application> 中設定 largeHeap 為 true 的話,可以得到更多的 heap memory,此時若想得知可以用的 heap memory,需要改用 getLargeMemoryClass()。由於資源有限,若非必要,還是盡量避免使用 largeHeap。
  4. 小心引用 bitmap,並且確實回收避免資源浪費。
  5. 適當情況下可以利用 SparseArray 取代 Hashmap 改善資源運用。
  6. 注意 memory 基本用量
    • 不要使用 enums,因為它會需要比 static 更多的資源。
    • 每個 class 使用約 500 bytes。
    • 每個 class 的 instance 約有 12~16 bytes。
    • 每放一個項目到 Hashmap,便需要額外配置一個項目物件,佔用 32bytes。
  7. 小心使用抽象類別或方法,因為它同樣會需要 memory,如果它不能帶來明顯優勢,那應該避免使用。
  8. 小心使用第三方 library。
  9. 利用 ProGuard 精簡並混淆程式碼,減少資源需求。
Performance tips:
  1. 避免宣告無謂的物件,或是短暫使用的物件。
  2. 如果只需要調用物件內的方法,而不需要物件本身,可以設定方法為 static。
  3. 對於固定變數,使用 static final。
  4. 避免使用 getters 和 setters,因為同樣耗費調用成本,除非是在 public interface 裡使用;若是在 class,應該直接調用其變數。
  5. 使用 enhanced for-loop 語法會比傳統 counted loop 效率好非常多。
  6. 避免使用浮點數。

2015/01/11

Google Analytics SDK v4

最近將 Google Analytics 升級到 v4,碰到了一些改動及需要注意的地方。首先是 GA 已經內建在 Google Play Services SDK 中,我們只需要使用 Gradle 來 import 即可,原本在官網下載的 libGoogleAnalyticsServices.jar 就可以移除了。

在 GA v4 中,Google 依功能目的建立不同的 Tracker,並且建議統一放在 Application 來做管理。另外在 Application onCreate 時要記得做 Tracker 的初始化,不然會遺漏掉一些資訊,這是官方文件沒有提到的。

由於目前我們只需要收集自己 app 的數據,所以選擇 APP_TRACKER 來使用就夠了。接下來在 Application 提供一個 method 來讓其他元件取用。

GA v4 還新增了一個特色,就是可以讓 app 收集使用者的年齡、性別及興趣,只要像上述的程式碼一樣,利用 Tracker 呼叫 enableAdvertisingIdCollection 並啟用即可。

再來是原本我們都會在 res/values 底下放 analytics.xml 這個設定檔,現在則是改在 res/xml 底下定義 Tracker 各自的設定檔。如上所述,在這邊我們新增了一個 app_tracker.xml,

與之前的版本明顯不同的地方在於原本要在 report 中定義 screen name 的 tag,從 <string> 變更為 <screenName>。另外由於 Fragment 的生命週期特性,目前仍然不能在此定義它的 screen name,必須在 onStart 時主動傳送 screen view,

而傳送 event 則是改用 EventBuilder 來建立。

這次還多了幾個好用的進階功能,例如我們若想避免在 test 或 debug 時的行為也被 GA 記錄到 report 中,可以使用 dryRun 這個 flag 來設定。

若是想調整 GA 的 log 層級,也可以利用 Logger 來設定,預設是輸出 warning 和 error。

Reference:
https://developers.google.com/analytics/devguides/collection/android/v4/
https://support.google.com/analytics/answer/2799357
http://stackoverflow.com/questions/22821139