読者です 読者をやめる 読者になる 読者になる

MapView用のAPI Keyを署名から自動判別したい

Android上で、MapViewを使おうとすると、API Keyをlayout.xmlに書き込まないといけない。
さらに、このAPI Keyはアプリの署名から生成されるので、デバッグ用の署名(debug.keystore)とリリース用の署名で別々のモノを使わないといけない。
そうすると、リリース時にAPI Keyを入れ替えるだけのためにコードを修正しないといけなくなるわけで、スゲーめんどくさいし、間違えて、リリース用のAPI Keyをリポジトリにcommitしちゃったりして周りに迷惑を掛けたりして・・・みたいな事になる。
これに関して、

Android ApiキーをRelease版とDebug版で切り替える方法

の様なことをやっている方もいるのだけども、正直、android:debuggableを見るのも微妙な気がする。というか、android:debuggable使ってない・・・。

ので、apkの署名で使うAPI Keyを切り替えようと考えました。

要は、アプリ上で、

MapView mapView = new MapView(this, getMapApiKey());

のような感じで署名に対応したAPI Keyを自動的に返してくれるといいなぁと。

元ネタは、たまたま、この記事を見つけたから。

「アプリケーション(.apk)の自己署名を検証する」

今回は、apkの署名がデバッグ用とリリース用で違うから問題なわけで、それを検出して、対応するAPI Keyを貰えれば良い。で、Google様から貰えるAPI Keyは、署名に対応しているので、署名を文字列化したものを直接コードに挿入したいところなんだけど、署名をそのまま書き込むのは気が引けるのと、そもそも、文字列としては極めてでかいことなど、どうなんだろうという感じなので、

Sign Up for the Android Maps API

にあるように署名のMD5で比較するようにする。

ということで、ほぼ丸ままコピペ出来るコード。例外処理はいい加減ですが。コード中の、XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XXの部分は、Google様からAPI Keyを貰うときに渡したフィンガープリントの文字列をそのまま書いて貰って、その下のAPI Keyの部分は対応するキーに。複数のプロジェクトで共有しているモジュールの場合は、全部のプロジェクト用の対応をここに追加してもいいかもしれない。

// パッケージ署名に対応するAPI Keyを返す
String getMapApiKey() {
  PackageManager pm = getPackageManager();
  try {
    PackageInfo pi = pm.getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
    for (Signature sig : pi.signatures) {
      String key = getMapApiKey(sig);
      if (key != null)
        return key;
    }
  } catch (NameNotFoundException e) {
    e.printStackTrace();
  }
  return null;
}

// 与えられた署名に対応するAPI Keyを返す
static String getMapApiKey(Signature sig) {
  String sigStr = getSignatureFingerPrint(sig);
  
  // デバッグ用署名(debug.keystore)に対応するAPI Key
  if (sigStr.equals("XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"))
    return "<<デバッグ用署名用のAPI Keyをここに挿入>>";
  
  // リリース用署名に対応するAPI Key
  if (sigStr.equals("XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"))
    return "<<リリース用署名用のAPI Keyをここに挿入>>";

  // その他の署名とAPI Keyの対応など
  // ...

  return null;
}

// 署名のMD5を文字列形式で返す
static String getSignatureFingerPrint(Signature sig) {
  try {
    MessageDigest md = MessageDigest.getInstance("MD5");
    md.update(sig.toByteArray());
    return hex(md.digest());
  } catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
    return null;
  }
}

// バイト列の16進文字列化
static String hex(byte[] bin) {
  StringBuilder sb = new StringBuilder(bin.length * 3 - 1);
  for (int i = 0; i < bin.length; i++) {
    sb.append(String.format("%02X", bin[i]));
    if (i + 1 < bin.length)
      sb.append(':');
  }
  return sb.toString();
}