Friday, February 27, 2015

TrivialDrive: Sample for Google Play In-app Purchase

萬萬沒想寫了iOS in app purchase之後四年, 我會寫Google Play in app purchase, 真是造化弄人(又亂用成語)啊~~

總之就是利用官方文件提到的TrivialDrive範例跑完整個流程囉!
不囉唆直接開始

[在Google Play設定要賣的東西]
1. 申請Google Play開發者帳號
2. 設定Google Wallet帳號
3. 在Google Play開發者網站新增app
4. 產生public key
5. 上傳用release key簽章的apk
6. 發佈app到alpha或beta測試
7. 新增in app商品, 可以是一次性或能多次購買的產品, 亦或是訂閱服務
8. 產生Google group並加入之後要用來購買東西的測試帳號
9. 設定Google group為alpha或beta測試群組


細節有點繁瑣, 有幾點要特別注意.
第一是雖然你的測試app只是要進到alpha或beta測試, 一般人沒有機會接觸到, 但還是要注意著作權的問題啊!
發佈app的時需要提供正確格式的截圖跟圖示, 我偷懶直接搜尋對的解析度的圖片就拿來用.
結果設定完成快樂回家, 隔天來公司就發現app被停權了, 只好砍掉重練Q.Q
另外就是一定要發佈app之後才能測試自己上架的商品,
若還只是draft僅能測Google提供的商品, 能分別針對不同的購買情境.
Note: Previously you could test an app by uploading an unpublished "draft" version. This functionality is no longer supported. However, you can test your app with static responses even before you upload it to the Google Play store. For more information, see Draft Apps are No Longer Supported.



[從app購買商品]
1. 下載TrivialDrive範例程式, 如果是用Andriod Studio,只要有抓Google Play Service SDK就可以直接匯入
2. 更新package的名稱 (用IDE提供的refactor->rename很快)
3. 更新public key (String base64EncodedPublicKey = ...)
4. 把商品換成在play developer裡面新增的product id
5. 建置並用把app用release key簽章
6. 把apk放上google play(對應到前一段的5&6)
7. 把apk裝到手機上(不能用emulator測哦)

進到app會看到這個畫面, 大概就是模擬開車, 開車會消耗油, 油不夠就要買等等

這個畫面是點選Upgrade My Card(對應到我的一次性購買商品)之後, 會出現商品說明及價格等資訊

之後可能會需要輸入密碼才能繼續購買, 然後就成功囉~

另外一個是點Get Infinite Gas(對應到我的訂閱型態的商品)會出現的畫面,
因為我設定了七天的試用期, 所以一開始不會馬上收費


從Google Wallet的記錄可以看出來, 一直到七天以後才正式付款哦



[查詢購買狀態]
在我們提供的產品或是服務需要由server提供的情況下,
server需要有辦法能夠確認使用者的購買紀錄,
這時Google Play API就派上用場啦!

1. 申請Google developer帳號
2. 開啟新的api專案
3. APIs&auth選單: 先到API設定開啓Google Play Android Developer API
4. APIs&auth選單: 新增Client ID, 並選擇web application為應用類型, 填妥必要資訊
(也可選擇用Service Account但我卡關XD)
5. 回到Google Play的API access設定頁面, 將剛剛的api專案連結到你的帳號
6. api帳號利用OAuth取得google play帳號的權限
(拿到authorization code之後, 就可以讓server用它換到refresh token然後就無敵惹XD)
7. 查查查

OAuth的url大概是長這樣, 重點是scope還有要能offline access
https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=https%3A%2F%2Fwww.rita.inapp.com%2Foauth2callback&client_id=00000000000-xxxx.apps.googleusercontent.com

api的用法可以直接參考官方文件,
其他需要注意的是:
針對同一個app, Google Play API一天可以打得次數是有上限的, 所以最好只在必要的時候用.
(而且我們也不希望因為request量太大影響整體系統效能吧)
官方文件的建議是希望我們能把查過的訂購資料記下來,
如果是單次性購買的產品, 就只要在使用者剛買完的時候查過一次就夠了,
訂閱型的產品則是在訂閱到期的時候重新查詢狀態.

ref:
- Google Play API
- Google Play In-app Billing

Wednesday, February 25, 2015

探索Playframework: 如何正確使用thread pool

在scala的世界, 我們常會用Future來處理某些非同步的工作,
用Future, 必需要透過implict的方式提供execution context, 來執行這些工作.

白話的說就是要指定你的code要在哪個thread pool跑.

使用Playframework, 在比較單純的情況下直接用預設的thread pool就可以了.
然後再透過application.conf設定適當的參數.

import play.api.libs.concurrent.Execution.Implicits._

def someAsyncAction = Action.async {
  import play.api.Play.current
  WS.url("http://www.playframework.com").get().map { response =>
    // This code block is executed in the imported default execution context
    // which happens to be the same thread pool in which the outer block of
    // code in this action will be executed.
    Results.Ok("The response code was " + response.status)
  }
}

play {
  akka {
    akka.loggers = ["akka.event.Logging$DefaultLogger", "akka.event.slf4j.Slf4jLogger"]
    loglevel = WARNING
    actor {
      default-dispatcher = {
        fork-join-executor {
          parallelism-factor = 1.0
          parallelism-max = 24
        }
      }
    }
  }
}

import play.api.libs.concurrent.Execution.Implicits._
先來找找我們用的execution context在哪裡,
defaultContext是ActorSystem的dispatcher
而create ActorSystem用的設定是"play"這個關鍵字下面的所有屬性.
接下來就是看ActorSystem如何設定dispatcher囉~
defaultGlobalDispatcher是用DefaultDispatcherId(即akka.actor.default-dispatcher)抓到設定值

大概是這樣囉!


針對不同類型的工作, 有時候我們會不希望所有事情都塞在預設的thread pool做,
要使用其他的thread pool, 只需要先在application.conf加上一組新的設定.
再用lookup讓Akka能找到它就好.
可以直接在產生Future的時候指定execution context或是直接import讓implicit發生作用
my-context {
  fork-join-executor {
    parallelism-factor = 20.0
    parallelism-max = 200
  }
}

object Contexts {
  implicit val myExecutionContext: ExecutionContext = Akka.system.dispatchers.lookup("my-context")
}

Future {
  // Some blocking or expensive code here
}(Contexts.myExecutionContext)

or

import Contexts.myExecutionContext
Future {
  // Some blocking or expensive code here
}

當然實際上要如何設定thread pool還是要參考測試結果啦!

ref:
- playframework source code (2.3.x)
- akka source code (2.3)
- understanding play thread pools (2.3.5)