为什么 resolveInfo.loadLabel() 如此缓慢?

9
在我的主屏幕替代应用程序中,我需要获取所有已安装的应用程序列表,以将它们放入应用程序抽屉中。因此,以下方法会在每个应用程序上运行;
public static App fromResolveInfo (Context context, PackageManager pacMan, AppManager appManager, ResolveInfo resInf)
{
    String label = resInf.loadLabel (pacMan).toString ();
    String packageName = resInf.activityInfo.applicationInfo.packageName;
    String activityName = resInf.activityInfo.name;

    App app = new App (context, appManager);
    app.setLabel (label);
    app.setPackageName (packageName);
    app.setActivityName (activityName);

    AppIcon icon = null;
    if (appManager.isIconPackLoaded ())
        icon = appManager.getIconPack ().getIconForApp (app);
    if (icon == null)
        icon = appManager.getIconPack ().getFallbackIcon (resInf.loadIcon (pacMan));

    app.setIcon (icon);

    return app;
}

问题在于这里存在瓶颈,而且不是我最初预期的图标加载问题。方法的第一行 (String label = resInf.loadLabel(pacMan).toString();) 可以占用 0 到 250 毫秒 (在相对高端的设备上)。在旧设备上,这成为一个真正的问题。
在我的测试中,我注意到当较慢的设备正在进行多任务处理并且由于某种原因需要重新加载应用程序抽屉时,这个操作可能需要长达30秒才能完成(所有已安装的应用程序都要重新加载)。
缓存可能提供一个潜在的解决方案,但是如果应用程序的名称更改了怎么办(偶尔会发生)?我必须从缓存中获取标签,然后在单独的线程中循环遍历所有应用程序,并在标签更改的地方更正标签。这可能提供一个解决方案,但它似乎更像是一种肮脏的 hack 而不是一个实际的好解决方案。
有没有更快的方法来获取应用程序活动的标签?此外,为什么 Android 获取应用程序标签需要如此长时间,我能做些什么吗?

1
如果应用程序的名称更改怎么办(这种情况偶尔会发生)?--请听取相关广播并更新缓存以进行特定名称更改。这样的更改仅在安装、更新或删除应用程序时发生。"为什么Android获取应用程序标签需要如此漫长" --我没有看到这种行为。您是否使用Traceview确定这正是您的问题所在? - CommonsWare
@CommonsWare 我只有在我的应用程序运行时才会收到这样的广播吗?这是关于获取已安装应用程序的初始列表,我需要在我的应用程序启动时。我的观察结果来自于时间戳之间的比较(也许有点老式),即当某个操作开始和完成的时间^^。我刚刚通过Traceview进行了检查,它向我展示了相同的结论。虽然这是我第一次使用Traceview,请原谅我如果我误解了结果。http://stuff.robinj.be/stackoverflow/31188658-AsyncLoadApps.trace - RobinJ
“我只有在我的应用程序运行时才会收到这样的广播,对吗?” - 是的。您需要在进程启动时刷新此信息。“这是关于获取已安装应用程序的初始列表,我需要在我的应用程序启动时使用” - 希望如果您正在编写主屏幕,则更专注于获取可启动活动的列表,而不是已安装应用程序的列表。我没有遇到您描述的性能问题,使用我的Launchalot示例,而且该示例。 :-) - CommonsWare
@CommonsWare 是的,抱歉。我指的是活动,而不是应用本身。在您的示例中,我看到您仅从包管理器中检索标签,在适配器的getView()方法中。目前,当我的应用程序启动时,我只尝试检索所有活动及其图标和标签的列表。我想这可能有关系。我从未想过以这种方式做。另一方面,我必须进行很多更改才能使该方法起作用,因此如果可能的话,最好采用不同的解决方案。 - RobinJ
感谢您的迅速回复 :) - venkat
显示剩余2条评论
2个回答

1

回答原问题“为什么?”...正如其他人所指出的,问题在于标签是本地化的,这意味着它是从资源中读取的--从应用APK文件中,基于语言环境。但对我来说,这本身并不能解释这种影响。

我一直在开发一个查看已安装应用程序的应用程序,在一个带有大约150个应用程序的4核armeabi-v7a系统上,仅获取应用程序标签列表就需要大约10秒钟。这很痛苦。

我认为它使用ClassLoader从类文件等资源中读取资源--这可以非常快速--现代系统上可以处理数百个类文件。好吧,我不认为它再使用ClassLoader了。但原则上...

一百多个小文件应该花费少于10秒钟的时间来处理。这本身无法解释我看到的滞后。

所以,我沿着调用链下去,穿过Resources对象的创建,Assets的打开,最终检索到那个标签字符串。 我看到了一层层synchronized代码。每个应用标签都会被重新调用。现在,这样做可能会使Java变得很慢。

当然,如果你只获取单个标签,那就不太重要。但是,当您获取所有标签时,它会累积起来。

我希望找到底部,并查找可以在一个同步循环中调用的东西。但在android-29源码的底部,在另一个同步块中,我发现了对SDK特定的nativeGetResourceValue() 的调用。也就是说,没有任何方法可以将其重新设计为更有效率,并在其他SDK上运行。

另一方面...操作系统可以非常快速地按标签对应用程序进行排序并显示列表。这意味着它在某个地方维护了应用标签的表格。我们有访问权限吗?

如果有更快地获取应用标签列表的方法,我很乐意听取。 (不,我不需要听关于制作自己的缓存 -- 尽管我可能会这样做。)


1
您可以得到以下标签:
String label = (String) resInf.activityInfo.applicationInfo.loadLabel(pacMan);

如果你比较这两种方法的Android源代码,你会注意到applicationInfo方法的代码更少,可能瓶颈在额外的代码上。我个人从未比较过它们的执行时间,因为我从未观察到这样的问题。

2
这将获取实际应用程序的名称,而不是活动的标签(启动器),我认为。一些应用程序(如Google Drive)具有多个活动。 - RobinJ
1
是的,它确实从清单中返回<application>标记的标签,而不是启动器活动的标签,但你在问题中要求的是“应用程序的标签”。 - mhenryk
对我来说,这两种方法的执行时间大致相同,性能都不是很好。 - waseefakhtar

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