首次启动带有Google地图的活动非常缓慢。

46
我想在我的Activity中使用SupportMapFragment。我直接将此片段添加到布局XML中,并将此布局设置为内容视图。但是,当首次启动活动时,需要太长时间(超过1秒)。下一次启动很好,并且只需几毫秒。
我尝试了:
- 删除任何初始化 - 使用MapFragment代替SupportMapFragment - 以编程方式添加MapFragment
但都没有帮助。地图显示正常,没有任何问题或可疑日志。
您有什么建议,它是什么原因以及如何改进它?
编辑: 我有一个ListView,当用户点击项时,它会启动具有MapFragment的DetailActivity。单击项目后,在DetailActivity显示之前会出现明显的延迟。只有onCreate方法在运行setContentView时才运行了超过1秒钟。而且,在活动处于onCreate方法中时,来自该活动的任何可见内容都不可见。单击和显示内容之间的这个延迟对用户来说并不友好。
谢谢

1
“不可能”和“假”的意思是什么? - nonahex
当然啦... Mapview 需要一些时间来加载,大约需要10秒钟。还有一件事情需要澄清,如果你使用了显式意图,那么切换到另一个屏幕也需要时间,大约需要10秒钟。 - M D
好的,现在我明白你的意思了。MapFragment的初始化不需要1秒钟,但是启动带有MapFragment的Activity需要超过1秒钟。我在问题中添加了应用程序中正在发生的事情以及我所说的1秒延迟的内容。 - nonahex
没错。这就是我所谈论的。 - M D
抱歉,我不认同这个解释和答案。特别是当其他应用程序没有这个问题时。例如Google I/O 2014应用程序。 - nonahex
我也遇到了这个问题。在我的应用中,我在多个活动中显示地图。第一次启动任何带有地图的活动时,会比后续启动需要更长的时间。 - Marcelo Noguti
9个回答

24
第一次加载时间很长的原因是因为Play Services API需要加载,如日志行所示:
I/Google Maps Android API﹕ Google Play services client version: 6587000
I/Google Maps Android API﹕ Google Play services package version: 6768430

很不幸,“package”需要大约一秒钟才能加载,只使用MapsInitializer仅会得到“client”。因此,这里有一个不太美观的解决方法:在您的主要启动器活动中初始化一个虚拟地图。

mDummyMapInitializer.getMapAsync(new OnMapReadyCallback() {
  @Override
  public void onMapReady(GoogleMap googleMap) {
    Log.d(TAG, "onMapReady");
  }
});

现在,当您稍后加载实际地图时,它不应初始化Play服务API。由于异步方法在主线程之外执行,因此这也不会导致主活动中的任何延迟。

由于必须在某个地方进行初始化,无论如何,我认为在应用程序启动时立即进行初始化是有意义的,这样当您加载实际需要地图的活动时,您就不必等待。

注意:mDummyMapInitializer必须是MapFragment或SupportMapFragment,并且必须添加到活动中,否则Play Services API将无法加载。 getMapAsync方法本身也必须从主线程调用。


关于原因,似乎你是正确的。在我的另一个项目中,加载地图片段是可以的。不同的是,第二个项目在更多的事情上使用了Google Play,而不仅仅是地图。在那里,我在应用程序启动时初始化了Google Play服务。因此,延迟是由Google Play服务的初始化引起的。它可以比第一次使用地图片段更早地完成。 - nonahex
7
我希望能够在Application.java文件中初始化这些服务,而不必在Activity中加载地图... - clocksmith
我尝试了这个解决方案,但是在Activity中getMapAsync永远不会返回。这真的很奇怪,因为在我实际显示地图的Fragment中,在调用getMapAsync之前我没有做任何特殊的事情。有什么想法吗? - JDenais
@JDenais,听起来你漏掉了对mDummyMapInitializer.onCreate()的调用。这将会非常慢。 - Brian Attwell

20

好的,我刚遇到了同样的问题,并且在查看这个问题后认为,没有“好的”解决方法。

我的当前解决方案是延迟添加片段,让 Activity 在添加地图之前有机会渲染其他所有内容。

现在,我将地图嵌入到一个子片段中,所以我的代码看起来像这样:

    // inside onCreateView
    new Handler().postDelayed(new Runnable() {

        @Override
        public void run() {
            if (isAdded()) {
                FragmentManager fm = getChildFragmentManager();
                GoogleMapFragment mapFragment = GoogleMapFragment
                        .newInstance();
                fm.beginTransaction()
                        .replace(R.id.mapContainer, mapFragment).commit();
            }
        }
    }, 1000);

如果直接添加到活动中,代码可能如下所示:

    // inside onCreate
    new Handler().postDelayed(new Runnable() {

        @Override
        public void run() {
            if (!isFinishing()) {
                FragmentManager fm = getFragmentManager();
                GoogleMapFragment mapFragment = GoogleMapFragment
                        .newInstance();
                fm.beginTransaction()
                        .replace(R.id.mapContainer, mapFragment).commit();
            }
        }
    }, 1000);

然而,在Runnable内部需要进行检查以确保我们不会尝试将地图添加到不存在的Activity或Fragment中。

我并不喜欢像这样的硬编码延迟,所以如果我有更好的方法,我会返回。1秒应该足够了,甚至可能会更少。


1
谢谢。我很高兴,有其他人也遇到了这个问题,并且对它的工作方式不满意。是的,这是可能的解决方案,我的解决方案非常相似。我能够避免硬编码延迟。在holder视图上初始化MapFragment posrt方法。在onResume中调用它。 - nonahex
@nonahex 谢谢,但我的问题是我的地图在一个片段中,而这个片段又被保留在ViewPager中。因此,如果我将初始化移动到onResume中,它将在滑动时重新实例化,这不是我想要的。否则是个好主意。 - cYrixmorten
但是只有在该片段尚未初始化时才能对其进行初始化。应该有一个简单的条件就足够了。 - nonahex
@nonahex,我想你说得对。我会试一试的 :) - cYrixmorten
这是唯一一个对我有效的。100毫秒对我来说足够了。 - Davi Alves
这是最佳解决方案! - sebasira

18

我通过在我的Application.onCreate()中使用MapsInitializer来解决了这个问题:

MapsInitializer.initialize(this);

好的结果和更加简洁(而不是hacky)的解决方案!


我的1秒加载时间中,这个节省了我100毫秒。 - anon
我测量了没有该命令的地图初始化时间,第一次大约为700毫秒,之后为200毫秒左右。然后我进行了两个实验:首先,我将此行代码放到MainActivity的onCreate中,之后放到Fragment的onViewCreated方法中,就在地图前面。两者结果相同,都是约700毫秒,没有任何变化。尝试使用本地和应用程序上下文,但均无效。 - Kirill Starostin

11

我也一直在解决这个问题,通过以下步骤得到了显着的改善:

1)拔掉USB电缆(或者断开调试会话),然后再尝试。在调试会话处于活动状态时,应用程序中的Google Maps速度要慢得多。 断开调试器后,速度会变快......虽然肯定不是最快的,但至少还可以接受。

2)不要调用setMapType(),除非您已经调用了getMapType()并确认它与您要设置的内容不同。对同一地图类型的多次调用仍将导致每次重置,这可能需要时间。

3)以编程方式添加地图片段,类似于@cYrixmorten发布的内容,但我从onResume()的末尾启动一个后台线程,然后等待50毫秒,然后在UI线程上运行它。这样可以避免立即命中UI线程,从而使Activity有时间加载和显示; 您应该始终在屏幕上,同时地图可能会阻塞一切。

关键在于您希望为每个Activity仅创建一个新的MapFragment实例,而不是每次屏幕方向旋转都创建一个新的实例。 我所做的就是调用“getFragmentManager().findFragmentById(R.id.mapContainer)”,这将为我提供上次的地图片段句柄,如果是第一次,则为null(在这种情况下,我将创建地图片段并进行FragmentManager.replace())。


非常感谢。拔掉USB线可以让地图运行更快。 - Jarana Manotumruksa
你为什么要等到onResume()时才添加地图片段?相比于onCreate(),这有什么速度优势吗? - IgorGanapolsky

3

我有一个“主”活动和一个带有地图视图的活动。当该活动第一次启动时,速度非常慢。

clocksmith的帖子 给了我一个想法,可以在单独的线程中从主活动开始初始化。这确实解决了问题。

这是我的“主”活动代码:

public void onCreate(Bundle savedInstanceState) {
    ...

    Runnable initMap = () -> {
        BaseApplication.d("Start init mapView");
        MapView mapView = new MapView(MainActivity.this);
        mapView.onCreate(null);
        BaseApplication.d("... done");
    };
    new Thread(initMap).start();
}

mapView从未被使用 - 它只用于初始化目的。

这里有一个堆栈跟踪 - 仅供参考:

12-09 19:31:54.442 17172-17341/my.app D/XXX: Start init mapView
12-09 19:31:54.525 17172-17341/my.app I/zzy: Making Creator dynamically
12-09 19:31:55.007 17172-17341/my.app D/ChimeraCfgMgr: Reading stored module config
12-09 19:31:55.153 17172-17341/my.app D/ChimeraCfgMgr: Loading module com.google.android.gms.maps from APK /data/user/0/com.google.android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/MapsModule.apk

12-09 19:31:55.154 17172-17341/my.app D/ChimeraModuleLdr: Loading module APK /data/user/0/com.google.android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/MapsModule.apk
12-09 19:31:55.262 17172-17341/my.app D/ChimeraFileApk: Primary ABI of requesting process is armeabi-v7a
12-09 19:31:55.271 17172-17341/my.app D/ChimeraFileApk: Classloading successful. Optimized code found.
12-09 19:31:55.316 17172-17341/my.app W/System: ClassLoader referenced unknown path: /data/user/0/com.google.android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/native-libs/armeabi-v7a

12-09 19:31:55.317 17172-17341/my.app W/System: ClassLoader referenced unknown path: /data/user/0/com.google.android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/native-libs/armeabi
12-09 19:31:55.618 17172-17341/my.app I/Google Maps Android API: Google Play services client version: 7571000
12-09 19:31:55.630 17172-17341/my.app I/Google Maps Android API: Google Play services package version: 8489438
12-09 19:31:55.969 17172-17341/my.app I/e: Token loaded from file. Expires in: 423267993 ms.
12-09 19:31:55.969 17172-17341/my.app I/e: Scheduling next attempt in 422967 seconds.
12-09 19:31:56.338 17172-17341/my.app D/XXX: ... done

如我们所见,这确实需要很多时间...

我在我的主活动的onResume中添加了一个简单的布尔检查(isMapPreInitializationComplete)。这完全解决了帧跳过问题,也没有引起任何UI延迟。谢谢! - Jaymes Bearden
2
我收到了一个异常,它说onCreate应该从主线程调用。 - Zvonimir Peran
你可以不用lambda表达式来完成吗? - temirbek

3

对于我来说,速度比1秒慢得多,因为我正在使用:

mapFragment.getMap();

然后我更改为:

 mapFragment.getMapAsync(new OnMapReadyCallback() {
        @Override
        public void onMapReady(GoogleMap googleMap) {
            map = googleMap;
        }
 });

使用getMapAsync()不会阻塞UI,因此您的活动将在地图加载之前加载。虽然仍然很慢,但至少您可以显示一个加载消息。

2
虽然这个问题已经存在多年,但问题仍然存在,没有一个干净且直接的解决方案。我看到了Rx的解决方案,但它已经不再起作用了,所以这是我的两分钱:
  1. 我的启动器/闪屏界面没有由特殊活动支持 - 它只是带有图像的背景XML(没有花哨的动画,没有在背景上进行路由,只是系统加载资源的普通方式)。这使我可以自由地锁定主线程,因为闪屏界面无论如何都不会执行任何交互式操作。
  2. SDK已更改,函数getMapAsync现在实际上需要您在主线程上调用。因此,将其卸载到Schedulers.io()线程中不再起作用。
  3. Rx的API已更改,自3.0版本以来,不允许将null作为可观察对象的“合法”输出发送,因此我只发送true

我的Kotlin解决方案位于主活动中,在重写的函数onCreate(savedInstanceState: Bundle?)中由Hilt注释为@AndroidEntryPoint

val mainThread = AndroidSchedulers.mainThread()
Observable.fromCallable {
    val mapView = MapView(this)
    mapView.onCreate(null)
    true
}.subscribeOn(mainThread).observeOn(mainThread).subscribe(
    // on success
    { Log.i("MAPS", "Initialized Google Maps.") },
    // on error
    { Log.w("MAPS", "Warming up of Google Maps failed: " + it.message) }
)

1
与其他解决方案类似,但使用RxJava + RxAndroid。只需从启动器活动的onCreate中调用此代码片段即可。
Observable.fromCallable(new Callable<Void>() {
    @Override
    public Void call() {
        MapView mapView = new MapView(LaunchActivity.this); // Replace LaunchActivity with appropriate activity's name
        mapView.onCreate(null);
        return null;
    }
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(
    new Action1<Void>() {
        @Override
        public void call(Void ignorable) {
            Log.i("Google Maps", "Initialised Google Maps.");
        }
    }, new Action1<Throwable>() {
        @Override
        public void call(Throwable ignorable) {
            Log.w("Google Maps", "[EXPECTED] Initialized Google Maps but got: " + ignorable.getMessage());
        }
    });

0

我遇到了相同的问题,MapsInitializer 的技巧对我没有用。

在我看来,这个问题的最佳解决方法是像其他用户描述的那样手动加载 Map Fragment。这不是一个 hacky 的解决方案,您只需自己处理 fragment 实例即可。

mMapFragment = MapFragment.newInstance();
fragmentManager.beginTransaction().replace(R.id.map_fragment_container, fragment, FRAGMENT_GOOGLEMAPS_TAG).commit();

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