安卓:自动选择调试/发布 Maps API 密钥?

41

已过时:此旧问题涉及过时的Google Maps v1 API。使用v2 API时,您可以在一个Google API控制台条目中使用多个证书指纹。API密钥不再存储在清单或代码中。


是否可能自动检测用于签名APK的证书?我想在应用程序中同时拥有调试和发布地图证书,并将有效证书传递给MapView构造函数。

通过这样的设置,我在发布应用程序时不会犯错误 - 在模拟器和我的设备上使用调试证书,然后使用发布证书签名,然后将应用程序发送到市场。

我考虑过检测我的特定设备或调试器是否已连接,但这并不完美。也许需要为调试证书标记某些文件?有更好的方法吗?


你也许会发现这里提供的答案同样适用。 - Viktor Brešan
对于Map Api V2,如何动态使用密钥,请查看我的答案http://stackoverflow.com/a/40462174/1348522 - Zumry Mohamed
11个回答

48

SDK工具17版中有一种新的方法来确定是调试版本还是发布版本。以下是新功能概述的摘录:

现在,生成一个名为BuildConfig的类,其中包含一个根据您的构建类型自动设置的DEBUG常量。您可以在代码中检查(BuildConfig.DEBUG)常量以运行仅调试的函数。

因此,现在您可以简单地编写如下内容:

if (BuildConfig.DEBUG)
{
    //Your debug code goes here
}
else
{
    //Your release code goes here
}

更新:我在ADT中遇到了一个bug:有时候在导出应用程序包后,BuildConfig.DEBUG会变成true。问题描述在这里:http://code.google.com/p/android/issues/detail?id=27940


是的,现在看起来是正确的方式。我会在获得第17版后转移,只是在下载之前先等待几天。 - Ifor
随着SDK rev 17的推出,更改已接受的答案,在某些情况下,其他高票答案仍然有价值。 - tomash
我只是想用这个代码替换以前的调试检测代码,但是所提到的错误似乎仍然存在。 - Bachi
问题27940于2012年3月被关闭 - JJD

27

跟 API 密钥遇到了同样的问题。这里是一个完整的解决方案,基于上面的链接和Bijarni 的示例(对我来说不知道为什么不起作用),我现在使用这种方法:

// Define the debug signature hash (Android default debug cert). Code from sigs[i].hashCode()
protected final static int DEBUG_SIGNATURE_HASH = <your hash value>;

// Checks if this apk was built using the debug certificate
// Used e.g. for Google Maps API key determination (from: http://whereblogger.klaki.net/2009/10/choosing-android-maps-api-key-at-run.html)
public static Boolean isDebugBuild(Context context) {
    if (_isDebugBuild == null) {
        try {
            _isDebugBuild = false;
            Signature [] sigs = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
            for (int i = 0; i < sigs.length; i++) {
                if (sigs[i].hashCode() == DEBUG_SIGNATURE_HASH) {
                    Log.d(TAG, "This is a debug build!");
                    _isDebugBuild = true;
                    break;
                }
            }
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }      
    }
    return _isDebugBuild;
}
你需要找到你的调试签名的hashValue(),只需输出sigs[i].hashCode()。
然后,我不想动态添加MapView,而是使用xml文件。你不能在代码中设置api密钥属性并使用xml布局,因此我使用了这个简单的方法(尽管复制xml布局不太美观):
在我的MapActivity中:
    public void onCreate(Bundle savedInstanceState)
    {       
    super.onCreate(savedInstanceState);

    // Select the proper xml layout file which includes the matching Google API Key
    if (isDebugBuild(this)) {
        setContentView(R.layout.map_activity_debug);
    } else {
        setContentView(R.layout.map_activity_release);
    }

4
一句话提醒:我不喜欢你的两个布局,因为需要同时在两个文件中进行任何更改。相反,我通过 new MapView(this, isDebug?keyDebug:keyRelease); 创建 MapView,并将其添加到当前布局中。 - tomash
1
是的,起初我也是动态创建它的,但由于我的地图活动布局不会改变,而且我希望所有布局都在一个地方(XML定义),没有混淆,所以我选择了上述方法。当然,两种方法都很好用。 - Bachi
4
你可以始终拥有3个布局文件,其中两个是你切换的,只需在这两个布局文件中使用两个<include>标签引用位于MapView上方和下方的实际布局实现即可。 - Blundell

10

判断是否为调试版本的更简单方法是通过检查应用程序信息中的调试标志,而不是签名哈希。

public boolean isDebugBuild() throws Exception
{
   PackageManager pm = _context.getPackageManager();
   PackageInfo pi = pm.getPackageInfo(_context.getPackageName(), 0);

   return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
}

一旦找到调试版本,您可以使用不同的资源来显示地图,或者在应用程序内创建MapView并将其添加到布局中。

    if(isDebugBuild())
    {
        _mapView = new MapView(this, getString(R.string.debugmapskey));
    }
    else
    {
        _mapView = new MapView(this, getString(R.string.releasemapskey));
    }

如果您在开发时始终将android:debuggable设置为true,而在不需要时将其设置为false,则这是一个很好的解决方案。然而,我发现我只在开发和测试的最后阶段关闭debuggable,但通常会在此之前向客户发送已签名的.apk文件。因此,在这种情况下,签名密钥对我更有效。 - Bachi
3
嗨 Bachi,从 SDK Tools revision 8 开始,http://developer.android.com/sdk/tools-notes.html(常规说明)中不需要在 AndroidManifest 中添加 debuggable 标志,工具会根据发布或开发自动设置它们。 - Suriya
我认为第16版停止了这样做...我在我的最新应用程序中遇到了这个问题,不得不改变我的方法: https://dev59.com/4V7Va4cB1Zd3GeqPHjjD - TacB0sS

5
所有回答都已经过时了,如果您正在使用Android Studio,则Gradle是正确的选择。
在您的build.gradle中使用不同的密钥。
android {
  .. .. ...
    buildTypes {
       debug {
          resValue "string", "google_maps_api_key", "[YOUR DEV KEY]"
       }
       release {
           resValue "string", "google_maps_api_key", "[YOUR PROD KEY]"
       }
    }
  }

在你的 AndroidManifest.xml 文件中

<meta-data
        android:name="com.google.android.maps.v2.API_KEY"
        android:value="@string/google_maps_api_key"/>

来源

如果你想为调试和发布保存不同的密码,那么你应该遵循这个方法。


3

我曾经遇到过将API密钥与构建流程和源代码控制不正确地整合在一起的问题,但现在我已经通过将其作为属性存储在local.properties中来解决了。我需要在build.xml中添加以下内容:

<property name="mapviewxml" value="res/layout/mapview.xml" />
<target name="-pre-build">
    <fail unless="mapsApiKey">You need to add mapsApiKey=... to local.properties</fail>
    <copy file="mapview.xml.tpl" tofile="${mapviewxml}" overwrite="true">
        <filterchain>
            <replacetokens>
                <token key="apiKey" value="${mapsApiKey}"/>
            </replacetokens>
        </filterchain>

    </copy>
</target>

现在,我当然需要在项目根目录中创建mapview.xml.tpl(它不能放置在res/layout中,因为这会破坏构建过程):

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.maps.MapView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:clickable="true"
    android:apiKey="@apiKey@"
    />

在预编译过程中,模板被复制到正确的位置,并将@apiKey@替换为实际密钥。不幸的是,我还没有找到一种方法来区分调试版和发布版本,因此要编译发布版本,我只需要将发布apiKey添加到ant参数中:

ant -DmapsApiKey=.... release 

这种方法与SCM(我不需要检查密钥)很好地集成在一起,并且在构建过程中也可以接受。


3
如果你还感兴趣,我刚刚写了一篇关于另一种方法的博客。通过对Android构建脚本进行简单更改,你可以切换Map API密钥以及所有其他所需的发布更改。我喜欢这个方法的原因是没有任何与调试相关的内容进入发布版,而且你可以保持XML布局的原样。详情请见:http://blog.cuttleworks.com/2011/02/android-dev-prod-builds/

谢谢Andy,我更新了响应。(我真的得修复那个缺失的重定向) - Romain

3
我认为在Google API的控制台中创建一个条目,其中包括您的发布密钥和调试密钥(两者都映射到相同的软件包)非常有效,并且是一个更简单的方法,不必担心您是在调试还是编译发布版本。解决方案在这里概述。

在Maps API 2中添加多个sha1密钥完全没有问题 - 所有在此提到的解决方案都是针对以前的API 1,那里没有舒适的解决方案。 - Bachi

1

我不知道这是否对任何人有帮助,但我已经合并了一些其他建议,以生成以下MapViewActivity。

在此示例中,仅当存在该文件(将该文件添加到您的.gitignore中)时,R.layout.map_dbg才会被使用,且仅在调试版本中使用。

此方法的优点是:

  1. 您无需编写ant目标(如果使用eclipse,则很好)
  2. 正确的发布密钥始终在map.xml中(希望不会因为错误而检查调试密钥)
  3. 始终为发布构建使用发布密钥
  4. 可以使用多个调试密钥

该方法的缺点是:

  1. 每次更新map.xml时,您需要记得更新map_dbg.xml

    public class MapViewActivity extends MapActivity {
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //
            // 将map.xml复制到map_dbg.xml并更新API密钥。
            //
            int id = getLayoutId("map_dbg");
            if(id ==0)
                id = R.layout.map;
    
            setContentView(id);
        }
    
        int getLayoutId(String name) {
            return isDebugBuild() ? getResources().getIdentifier(name, "layout", getPackageName()) : 0;
        }
    
        public boolean isDebugBuild() 
        {
            boolean dbg = false;
            try {
                PackageManager pm = getPackageManager();
                PackageInfo pi = pm.getPackageInfo(getPackageName(), 0);
    
                dbg = ((pi.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
            } catch (Exception e) {
            }
            return dbg;
        }
    
    }
    

1

我最终使用SD卡上的特殊文件 - 如果存在,则使用调试密钥;如果缺少,则使用发布密钥。这样做可以运行。

编辑:请查看新接受的答案,它效果更好。


0

我设置了一个简单的Ant目标,用调试密钥或发布密钥替换apikey。这非常简单,使代码不受不必要的逻辑干扰。

<target name="apikey">
    <!-- Location of target layout file -->
    <first id="first">
        <fileset dir="." includes="res/layout/kondi_training_templates.xml" />
    </first>
    <property name="layout-file" value="${toString:first}"/>
    <echo>template-file: ${template-file}</echo>

    <replaceregexp file="${template-file}"
        match="android:apiKey=.*"
        replace='android:apiKey="${mapview.apikey}"'
        byline="true"
    />
</target>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接