2013年1月30日 星期三

[Android] 如何在程式中分辨開發版跟正式版

好久沒有寫部落格了
理由是去年初之前都是專心寫iOS
但最近還需要同時寫Web跟Android
導致我同時弄的東西太多
光看文件就沒空了
絕對不是因為之前沈迷D3
也絕對不是因為之前狂看冰與火之歌
也絕對不是因為下班後活動太多..... (踢飛..)

這篇是第一次拿Android來寫,就拿我最近遇到的小問題來做分享吧
情境是有些功能我們在開發版跟正式版會有不同的行為
這時候會是開發版用一套code
正式版需要另一套code
那需要怎麼做才可以用程式判斷出執行環境的差異呢?

首先因為Android是java-based的
並沒有像Objective-C有define來作選擇性編譯
而在Android的架構中也沒有明顯的development/release兩個版本的設計
我google了一下,比較類似的討論有
這篇stackoverflow的討論
當中我比較傾向是用Signature來去達到這個目標
因為只有這個是runtime可以分辨出差異,而且是程式中可以取得的
再來是我們發佈到google play (或是其他market)
應該都會用的是release key做簽署
所以只要能夠在runtime跟release key的signature做比較
就可以判斷執行環境是development或是production

有了這個方向,
先查到Android中取得Signature的方法如下
PackageInfo pkgInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
for(Signature signature : pkgInfo.signatures)
{
     System.out.println("cert: " + sigature.toCharsString());
}
你只要把你的production的signature印出來,並且hardcode進你的code
之後你做個字串比對就可以判斷是不是production environment了。

但是你會發現印出來的字串很長,大概3-400個bye吧 (隨便估@@)
總覺得不是很精簡。
這時候假掰的個性又出現了
那就來個SHA-1 hash之後再base64吧~ \(^O^o)
其實這是Facebook Android SDK的key hash給的靈感 XD
方法是找到你用來簽署所用的keystore,對他下以下的指令 (以下為Mac環境為例)
keytool -exportcert -alias youralias -keystore yourkeystore.keystore | openssl sha1 -binary | openssl base64
這時候你應該會印出一個類似下面這種字串
+YyPzaeOKYkleq9Rwtk7+Rett/o=

把這個hash放在code中,
並且加上以下代碼
private static final String SIG_HASH_PRODUCTION = "+YyPzaeOKYkleq9Rwtk7+Rett/o=";

public boolean isProduction()
{
     try
     {
          PackageInfo pkgInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
          for(Signature signature : pkgInfo.signatures)
          {
               String sigHash =getSignatureHash(signature);
               if(SIG_HASH_PRODUCTION.equals(sigHash))
               {
                    return true;
               }
          }
     }catch(Exception e){}

     return false;
}


public String getSignatureHash(Signature signature)
{
     try {
          MessageDigest md = MessageDigest.getInstance("SHA-1");
          byte[] hash = md.digest(signature.toByteArray());
          String hashb64 = Base64.encodeToString(hash, Base64.NO_WRAP);
          return hashb64;
     } catch (NoSuchAlgorithmException e) {
          e.printStackTrace();
     }

     return null;
}

如此一來,只要用給google play上架簽署的apk,
執行時期的isProduction()都會回傳的是true
如此你可以在任何程式去用這個Utility function判斷是否為production mode。



2013/2/4 Updated
上面的方法有一個缺點就是,預設是development。
比較好的方法還是預設市production mode,
因為避免"意外"發生而變成development mode。
而developement的key是在~/.android/debug.keystore
command如下
keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64
而程式中改成用debug has key做負面表列來判斷是否為production mode。
或是直接改成isDebugMode()