2015年2月16日 星期一

[Android] 讓不同螢幕尺寸與解析度顯示相同比例的UI

Android支援太多手機,造成開發者必須因應不同的手機螢幕設計UI.
同樣是4.3吋的螢幕,有800*480的解析度,也有1280*720的解析度。
又或者一樣是1280*720解析度,但螢幕尺寸有5.5吋,也有7吋的。
在這麼錯縱複雜的螢幕下,如何設計出清晰且正常比例的UI呢?
首先要釐清一些概念與名詞。

像素 (Pixel)
也稱畫素,螢幕呈現影像的最小單位,也可說是點陣圖的最小單位。
數位影像尺寸通常是以像素計算,例如: 48*48 pixel 就是一個正方形的影像大小。

因為像素是數位影像的最小單位,可以想像成一個像素就是一個點,
48*48 pixel就是長和寬都由48個點所組成的。如下圖:




螢幕尺寸
也就是我們大家熟知的幾吋幾吋,手機是4吋的,
電腦螢幕是22吋的,電視螢幕是32吋的。

像素與螢幕尺寸的關係
請問,48*48 pixel的影像到底有多大? 他是一個正方形,長與寬相同,
不過你能告訴我長寬是幾公分嗎? 或是幾英寸呢? 不知道。
前面提到像素是最小單位,最小單位總也有個長度吧?
你會發現找了一堆資料,沒有人跟你說一像素是多大,只會說是最小單位。
其實48*48 pixel的圖片,它可以是長寬10公分,也可以是1公分。
取決於像素和螢幕尺寸的關係。

現在有兩支手機如下:
手機A,4.3吋,解析度800*400
手機B,4.3吋,解析度1280*720

手機A,請問800*480是什麼單位?
其實就是像素 (Pixel), 我個人覺得我們濫用了解析度這個詞。
800*480就是Pixel單位,它說明4.3吋的手機螢幕中,長有800個像素填充,
寬有480個像素填充。手機B解析度則較高,
因為它的長有1280個像素填充,寬有720個像素填充。
你可以把像素想成點,這兩支手機螢幕尺寸都是4.3吋,
可是兩支手機填充的像素不同,同樣尺寸下,像素填充越多,
解析度就越高。

這兩支手機如果顯示一張圖48*48 pixel會怎麼樣呢?
手機A會比較大張,手機B會比較小張,請看下圖:

圖中的鉛筆都是48*48 pixel,同樣是4.3吋手機A和手機B,
手機A,800個點(Pixel)填滿4.3吋的長,
手機B,1280個點(Pixel)填滿4.3吋的長,
它們的長一樣,使得手機A的點(Pixel)比手機B的點還要大點。
也就是你比較容易用肉眼看到手機A的一格格的畫格 (Pixel),
所以同樣Pixel的圖,手機A會比較大張,手機B比較小張。
手機A解析度比較低,手機B解析度比較高。

PPI
800*400就是填充像素,我會說解析度800*400濫用,
是因為螢幕解析度跟螢幕尺寸與填充像素有關,而不是只和填充像素有關。
1280*720的解析度一定比800*400高嗎? 如果同樣的螢幕尺寸是對的,
但不同的螢幕尺寸就不一定了喔。PPI才是正確拿來描述解析度的,請看下圖:


此圖來自iPhone官方網站,從左至右為iPhone 6 Plus, iPhone 6, iPhone 5s, iPhone 5c.
可以發現iPhone 6和iPhone 5s的螢幕尺寸與像素皆不同,但解析度都是326 ppi.
ppi的公式為 √(像素長度2+像素寬度2)/ 螢幕尺寸 ,將iPhone 5s和iPhone 6套用公式如下:
  • iPhone 5s,(1136*1136 + 640*640) / 4 = 325.96
  • iPhone 6 ,(1334*1334 + 750*750) / 4.7 = 325.61
iPhone 6雖然填充像素增加,但螢幕尺寸也增加,所以與iPhone 5的解析度相同。
也因為兩支手機的解析度相同,所以使用同樣像素尺寸的icon即可。
例如iPhone 6使用128*128的圖片,iPhone 5s也是要使用128*128的圖片,
如此圖片就會在這兩支手機中有相同的顯示比例。
所以,PPI的值越大,需要的圖片要越大張
另外,DPI通常用於列印出版用,在螢幕的世界裡,DPI = PPI.

讓Android自行選擇適合的圖片大小
螢幕世界中,DPI = PPI ,由於官方文件都使用DPI,
所以在此段落我們一概使用DPI當作解析度,但意思跟PPI是一樣的。
寫過Android程式就知道透過不同的drawable資料夾可以支援多尺寸螢幕。
現在我們知道PPI才是決定圖片大小,而非螢幕尺寸,也非像素填充。
資料夾的命名應該如下:
  • drawable-ldpi
    • 約 120 dpi
  • drawable-mdpi
    • 約 160 dpi
  • drawable-hdpi
    • 約 240 dpi
  • drawable-xhdpi
    • 約 320 dpi
  • drawable-xxhdpi
    • 約 480 dpi
別再使用small, normal, large等螢幕尺寸來規劃圖片大小,
可參考Google的Supporting Multiple Screens.

資料夾間,圖片大小相對比例應該為何呢? Google也有提供說明如圖:
這些倍率是怎麼算出來呢? Android有自己的圖片單位Density-independent pixel(dp),
在layout中UI的單位建議使用dp,因為它會幫你算出正確的pixel大小,公式如下:
  • pixel = dp * (dpi / 160)
由此可見,當dpi = 160 (也就是mdpi)時,pixel = dp. 更大的dpi只要透過此公式,
便可算出需要比mdpi使用多大的比例的圖。

DPI與PPI的差別
PPI = Pixel Per Inch,每英吋使用多少像素,用於螢幕。
DPI = Dot Per Inch,每英吋使用多少點,用於印刷。

在螢幕的世界裡,最小單位就是像素,
也就是點(所以前面才提到可以想像成點)。
嚴格來說,在螢幕上解析度應該都要說PPI,
只是現在都混用,所以只能說在螢幕中,
PPI = DPI。

在印刷中,是以一英吋裡可以噴出幾滴油墨計算,
一滴油墨就是一個點,所以使用DPI作為單位。
當影像由螢幕要列印出來時,PPI和DPI是不同的。
因為印刷機的不同,一像素未必等於一滴油墨(點)。

結論
  • 螢幕解析度由PPI決定,而不是螢幕尺寸也不是像素填充
  • PPI越高,所需要的圖案也越大,才能顯示相同比例的UI
  • drawable應該要以ldpi, mdpi, hdpi來分類,而不是small, normal, large來分類
  • 螢幕使用PPI,印刷使用DPI,只有在螢幕世界中PPI = DPI.

2015年2月11日 星期三

[Android] In-App Billing/Purchases 實作與測試

關於Android In-App Billing的簡介請看此篇
本篇將著墨在開發者實作部分,
由於Google Play Billing Library中已經有一個很完整的範例程式。
所以本篇會直接拿來使用,並暫時不講解程式碼。
按照底下步驟做便能完成APK與Google Play Developer Console連動。
  1. 下載Google Play Billing Library
    • 打開Android SDK manager並下載安裝Google Play Billing Library
    • 路徑為: /{SDK路徑}/sdk/extras/google/play_billing
  2. 將範例程式Import到Eclipse當中
    • 在Eclipse中點擊File -> Import -> Android -> Existing Android Code Into Workspace
    • 範例程式路徑為: /{SDK路徑}/sdk/extras/google/play_billing/samples/TrivialDrive
  3. 將Package Name更名
    • 原先的Package為com.example.android.trivialdrivesample, 由於com.example.android會無法上傳至Google Play Developer Console. 所以需要先行更名。
  4. 前往Google Play Developer Console建立一個新的應用程式
    • 點擊"新增應用程式"
    • 填入標題
    • 倘若您無法進入此頁面,代表您沒有支付25美金的上架費
  5. 將此應用程式的Public Key複製起來
  6. 將剛才的Key填入程式碼當中
    • 在MainActivity.java中可以看到下列程式碼,將之取代成剛才的Key
    • String base64EncodedPublicKey = "CONSTRUCT_YOUR_KEY_AND_PLACE_IT_HERE";
  7. 此應用程式有三個內購項目,我們必須先記下他的名稱,才方便我們等下在Google Play Developer Console建立內購項目
    • 從程式碼可以得知"premium"是屬於非消耗品,"gas"屬於消耗品,"infinite_gas"屬於訂閱項目
    • // SKUs for our products: the premium upgrade (non-consumable) and gas (consumable)
      static final String SKU_PREMIUM = "premium";
      static final String SKU_GAS = "gas";
      
      // SKU for our subscription (infinite gas)
      static final String SKU_INFINITE_GAS = "infinite_gas";
  8. 將此Project輸出成signed的APK, 不能使用debug key.
    • 對專案按右鍵Android Tools -> Export Signed Application Package...
    • 到此處整個Project應該不會有任何錯誤,若有錯誤需先處理再做此步驟
  9. 上傳剛才的APK至Google Play Developer Console
    • 將APK上傳至ALPHA階段,才能作測試用
  10. 至應用程式內產品加入剛才的三項產品
    • 在API v3中,納入管理的產品和為納入管理的產品是一樣的
    • "premium"和"gas"為納入管理的產品,"infinite_gas"則為訂閱
    • 在訂閱項目中,有一個免費試用期,建議填寫7天,好方便測試
    • 將需要的資訊填好後,記得儲存並啟用
  11. 將其他的Google帳號加入測試帳號中,之後才能透過此帳號進行購買測試。
    • 您無法使用發佈此App的Google帳號進行內購測試
    • 被加入的帳號必須登陸信用卡資訊才能進行測試
  12. 接下來您可以決定是否要發佈,發佈前仍需要將其他資訊填好,直到發佈應用程式的按鈕亮起
    • 如果沒有發佈,第11步驟的測試帳號仍可測試內購已簽章的APK,為測試購買
    • 如果發佈,第11步驟的測試帳號的購買行為是測試購買,其他帳號則是真實購買
  13. 發佈後,將您的Android手機登入剛才加入測試的Google帳號,安裝Signed好的APK檔,便能測試購買
    • 發佈後,需要等待幾個小時才能順利購買,這期間您可能在購買過程中發生錯誤,包括"需要認證,請登入Google帳號"等錯誤訊息
    • 底下是此範例程式的執行結果
請注意,納入管理的產品屬於測試訂單,不會扣錢。
可是訂閱則是真實訂單,是會扣錢的,
所以前面才會提醒要填寫7天的免費試用期,
在測試的時候記得7天內要記得取消訂閱,
才不會被扣到款。
超過7天沒有取消就會被扣款了,
只能透過商家電子錢包進行退款。
而且Google Play屬於國外交易,
大部分的信用卡都會收取國外交易手續費的喔。

另外要提醒的是,
所有在Google Play Developer Console的動作,
都不會馬上生效,
需要等待幾個小時甚至一兩天才會生效。
建議每一個動作都要想清楚再做!

如果想要給別的朋友或帳號測試,
可以將signed的APK傳給別人並將帳號加入測試。
或者建立一個Google網上論壇,
並在Google Play Developer Console將此論壇加入至ALPHA測試人員也可。
如何使用Google網上論壇可參照此篇文章

針對測試購買的結論
  • 不發佈APP,只有測試帳號才能測試購買。發佈APP,所有帳號都能購買,只是測試帳號屬於測試購買,其他帳號屬於真實購買。
  • 對於測試帳號,納入管理的產品屬於測試購買,訂閱是真實購買,所以將訂閱設定試用期,可免去退款動作。
  • 因為發佈ALPHA版本並不會在Google Play上公開,所以建立Google網上論壇只是方便特定測試人員能在Google Play上下載,並非必要動作。
  • 所有在Google Play Developer Console的動作都不會馬上生效,請至少等待數個小時甚至一天。

[Android] In-App Billing/Purchases 簡介

在Android官方名稱,內購稱為In-App Billing,
也就是大眾熟悉的In-App Purchases.
目前最新的API version是3, 故接下來的介紹皆以API v3為主要。

Google Play提供三種商品類型

  1. 納入管理的產品
    • Google Play會記錄購買項目
    • 已購買的項目需要進行"消耗",才能再次購買
  2. 非納入管理的產品
    • Google Play不會記錄購買項目,故可以重複購買
    • 在API v3中,非納入管理仍會納入管理。也就是說,API v3會將非納入管理的產品也視為納入管理的產品。需要進行"消耗",才能再次購買
  3. 訂閱
    • 分為年,月,季節三種方式訂閱
    • 訂閱後即馬上收費,若使用者沒有停止訂閱,會在訂閱到期時展期並再次收費

上述提到的"消耗",是當使用者購買項目後,
透過函式consumeAsync( )將購買紀錄清除,
讓使用者可以再次購買。
通常用於遊戲中的鑽石、水晶等消耗性產品。

獲利分帳
  • Google 取得30%,開發者取得70%
身份辨識
  • 綁定Google Play帳號,使用此帳號的裝置皆能取得已購買項目
關於取消訂閱
  • 截至目前,Google未提供API實作取消訂閱服務,詳見此
  • 使用者須前往Google Wallet取消訂閱,或者至Google Play上的我的訂單取消訂閱
  • 取消訂閱後,下一次將不會展期與收費,但本次已付費的部分仍會維持到訂閱期間結束
關於退費
  • In-App Billing不適用購買後2小時的退費服務,詳見此
  • 由開發人員決定是否要退費
  • 使用者可以前往Google管道填寫問卷要求退費,或者聯絡開發人員
  • 開發人員可根據訂單編號至Wallet Merchant Center退款