谷歌地图Android API v2抛出GooglePlayServicesNotAvailableException异常,已过时,SupportMapFragment.getMap()返回null。

18

我正在尝试将新的Google Maps Android API v2集成到使用Android支持库的应用程序中。

我经历了许多迭代,尝试着使它正常工作,但最终放弃了并认为我应该在这里提问。

我去了API控制台,创建了一个项目,启用了适用于Android的Google Maps API v2,在清单上粘贴了所需的调试KEY。没有身份验证问题或其他任何问题。我非常确定我做得对。

在我的项目的libs文件夹中,我通过项目右键单击->Android->Add Support Library...添加了android-support-v4.jar。在Eclipse中,我执行了File->Import->Android->Existing Android Code Into Workspace,并导入了最新的(rev 3)android-sdk/google/google_play_services/libproject/google-play-services_lib。然后我将其作为依赖库添加到我的项目中(项目右键单击->Properties->Android->在底部添加它作为库依赖项)。

在我的清单中,我有:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
    <permission
      android:name="myapp.permission.MAPS_RECEIVE"
      android:protectionLevel="signature"/>

    <All the uses-permission required like INTERNET, ACCESS_NETWORK_STATE, WRITE_EXTERNAL_STORAGE, FINE AND COARSE LOCATION, etc>
    <uses-permission android:name="myapp.permission.MAPS_RECEIVE"/>
    <uses-feature android:glEsVersion="0x00020000" android:required="true"/>
    <application ...>
        ...
        <uses-library android:name="com.google.android.maps" />
        <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="AI..."/>
    </application>

在我的mainview.xml文件中,我有:

<fragment
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/map"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   class="com.google.android.gms.maps.SupportMapFragment" />

现在在MainView.java中我有以下代码:

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;

public class MainView extends FragmentActivity ... {
    private GoogleMap mMap;

    @Override
    protected void onResume() {
        ...
        Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.map);
        SupportMapFragment mapFragment = (SupportMapFragment)fragment;
        mMap = mapFragment.getMap();
        //PROBLEM: mMap is null here even though mapFragment is not
    }
}

接着我想我可能需要初始化碎片,所以在设置内容视图后,我在我的onCreate中添加了以下代码:

//Fragments
FragmentManager manager = getSupportFragmentManager(); 
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.map, SupportMapFragment.newInstance());
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.commit();

我说,也许不是SupportMapFragment引起了问题,而是我应该引用实际的片段,在我的onResume中这样做:

Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.map);
SupportMapFragment mapFragment = (SupportMapFragment)fragment;

//Fragments
FragmentManager manager = getSupportFragmentManager(); 
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.map, mapFragment);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.commit();

mMap = mapFragment.getMap();

mMap 在这之后又变成了 null。

然后我发现有一个 MapsInitializer 类,所以我觉得可能应该先调用那个。

于是我在上面的代码中获取片段之前添加了以下代码:

try {
    MapsInitializer.initialize(this);
} catch (GooglePlayServicesNotAvailableException e) {
    e.printStackTrace();
}

使用Logcat时报告了警告(第313行是MapsInitializer.initialize(this)):

com.google.android.gms.common.GooglePlayServicesNotAvailableException
    at com.google.android.gms.internal.n.c(Unknown Source)
    at com.google.android.gms.internal.n.a(Unknown Source)
    at com.google.android.gms.maps.MapsInitializer.initialize(Unknown Source)
    at myapp.activities.MainView.inflateSelectedViewAndSetData(MainView.java:313)
    at myapp.activities.MainView.onClick(MainView.java:642)
    at android.view.View.performClick(View.java:3153)
    at android.view.View$PerformClick.run(View.java:12148)
    at android.os.Handler.handleCallback(Handler.java:587)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:152)
    at android.app.ActivityThread.main(ActivityThread.java:4615)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:491)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
    at dalvik.system.NativeStart.main(Native Method)

尝试期间,Logcat会报告以下警告:

Google Play services out of date.  Requires 2010100 but found 1013

这可能是罪魁祸首,但我没有找到解决方案。

现在我已经束手无策了。我阅读了几个线程这里这里这里 ,但是没有一个建议解决了问题。在后者中,他们建议不包括项目依赖关系,只包括jar文件。嗯,使用google-play-services.jargoogle-play-services_lib/libs文件夹或从该项目导出我的自己的jar文件都不起作用。

顺便说一句,我正在实际设备上运行(2.3.5和带有3.2的平板电脑),而不是模拟器。

非常感谢任何帮助。 :)

更新:

qubit提供了以下解决方案。

至于导出库依赖项,不必处理它的一个巧妙方法(在处理存储库时很痛苦)是使用FatJar并导出google_play_services_lib项目(我没有创建本地副本,因为当它更新时我不想重新导入),并将输出fat jar文件作为另一个jar文件包含在您的项目中。 注意:在选择要包含在fat jar中的jar文件时,我必须排除annotations.jar文件,因为它已经被预先包含并且会导致重复错误。 现在,我只需将google_play_services_rev_3.jar包含在我的项目的libs文件夹中,就可以开始了。


由于这不是您问题的答案,我将其仅作为评论提供:当您不想做更多基于GPS / WLAN的本地化,显示地图,绘制到显示的地图时,我建议使用http://sourceforge.net/projects/libwlocate/中的类。在我看来,它们更容易处理,并支持多种地图类型。 - Elmi
这个“google_play_services_rev_3.jar”应该是通用的,适用于任何项目和配置,我想是吧?感谢FatJar的提示,我现在使用这个程序可以让我的项目在Jenkins CI中工作了。 - JaakL
@JaakL 是的,它是通用的,你可以在任何项目中导入它。 :) - iliask
3个回答

15
作为底部链接所描述的内容;http://developer.android.com/google/play-services/setup.html 下面是处理此问题的示例代码;
int checkGooglePlayServices =    GooglePlayServicesUtil.isGooglePlayServicesAvailable(mContext);
    if (checkGooglePlayServices != ConnectionResult.SUCCESS) {
    // google play services is missing!!!!
    /* Returns status code indicating whether there was an error. 
    Can be one of following in ConnectionResult: SUCCESS, SERVICE_MISSING, SERVICE_VERSION_UPDATE_REQUIRED, SERVICE_DISABLED, SERVICE_INVALID.
    */
       GooglePlayServicesUtil.getErrorDialog(checkGooglePlayServices, mActivity, 1122).show();
    }

6
我在我的项目中遇到了同样的问题。解决方案非常简单,只需处理 ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap(); 可能返回 null 的情况。如果你处理了 null ,内置的 SupportMapFragment 将处理 Google Play services out of date. 错误,并向您显示本地化消息和更新 Google Play 服务的按钮,就像 @qubz 在示例项目中所讲述的那样。
示例代码:
@Override
protected void onResume() {
    super.onResume();
    setUpMapIfNeeded();
}

/**
 * Sets up the map if it is possible to do so (i.e., the Google Play services APK is correctly
 * installed) and the map has not already been instantiated.. This will ensure that we only ever
 * call {@link #setUpMap()} once when {@link #mMap} is not null.
 * <p>
 * If it isn't installed {@link SupportMapFragment} (and
 * {@link com.google.android.gms.maps.MapView MapView}) will show a prompt for the user to
 * install/update the Google Play services APK on their device.
 * <p>
 * A user can return to this FragmentActivity after following the prompt and correctly
 * installing/updating/enabling the Google Play services. Since the FragmentActivity may not have been
 * completely destroyed during this process (it is likely that it would only be stopped or
 * paused), {@link #onCreate(Bundle)} may not be called again so we should call this method in
 * {@link #onResume()} to guarantee that it will be called.
 */
private void setUpMapIfNeeded() {
    // Do a null check to confirm that we have not already instantiated the map.
    if (mMap == null) {
        // Try to obtain the map from the SupportMapFragment.
        mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
                .getMap();
        // Check if we were successful in obtaining the map.
        if (mMap != null) {
            setUpMap();
        }
    }
}

/**
 * This is where we can add markers or lines, add listeners or move the camera. In this case, we
 * just add a marker near Africa.
 * <p>
 * This should only be called once and when we are sure that {@link #mMap} is not null.
 */
private void setUpMap() {
    mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
}

结果如下:

enter image description here

根据文档所述

只有在底层地图系统加载并且片段中的底层视图存在时,才能使用getMap()获取GoogleMap。此类自动初始化地图系统和视图;但是您无法保证它何时准备就绪,因为这取决于Google Play服务APK的可用性。如果GoogleMap不可用,则getMap()将返回null。SupportMapFragment


有点困惑如何调用地图应用程序的更新,就像你提到的“获取Google Play服务”的调用。 - Abdul Wahab
如果你能处理 ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap(); 可能返回 null 的情况,那么 Google Play 服务库将显示更新屏幕而不是 MapFragment。 - Roman Nazarevych
嗯,我明白了,更新屏幕是从Google Play服务库中的本地调用,我在想,如果getMap()返回null,那么在自定义更新按钮上,我需要解析哪个意图才能转到更新页面,就像我们有这样:Intent viewIntent = new Intent("android.intent.action.VIEW", Uri.parse("http://www.stackoverflow.com/")); startActivity(viewIntent); - Abdul Wahab
@AbdulWahab 这里有一个说明,告诉你如何手动调用Play服务更新:http://developer.android.com/google/play-services/setup.html 和 http://developer.android.com/reference/com/google/android/gms/common/GooglePlayServicesUtil.html#getErrorDialog(int, android.app.Activity, int)。 - Roman Nazarevych

5

从Play商店下载并安装最新的Google Play服务。由于某些原因,我无法通过搜索找到它。通过在SDK中运行文件夹/extras/google/google_play_services/samples/maps中的示例应用程序,我被提示安装升级,并被带到Play商店进行操作。


它们默认安装。这是一款商业(非root)的HTC Flyer,而已经root的HTC Desire S已经安装了gapps(并且Google Play服务也在其中)。 - iliask
1
您需要最新版本。您发布的错误是指默认的“出厂安装”版本。 - qubz
2
我刚刚在这里阅读到:http://code.google.com/p/gmaps-api-issues/issues/detail?id=4627#c1,通过向活动页面添加新的MapFragment将触发更新。如果您已经构建了应用程序,那么这可能比配置示例应用程序运行要容易。 - qubz
你,我的朋友,是一个救命恩人。由于某种原因,我禁用了后台数据,这导致谷歌播放服务无法自动更新。一旦启用,它就会自动更新,一切都正常了(虽然我看不到地图,只有右下角的+-,但这本身就是另一个问题)。不需要片段事务或MapsInitializer。再次感谢! - iliask
2
作为对访问者的一般参考,本页面的后半部分概述了Google Play服务APK安装方案及其处理方法。[http://developer.android.com/google/play-services/setup.html] - qubz

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