9

In my home screen replacement app, I have to get a list of all installed apps to put them inside the app drawer. Hence, the following method gets run on every app;

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;
}

The problem is that there is a bottleneck here, and it's not he loading of the icons as I had originally anticipated. The first line of the method (String label = resInf.loadLabel (pacMan).toString ();) can take up anywhere between 0 and 250 milliseconds (on a relatively high-end device). On older devices, this becomes a real issue.
In my tests, I have noticed that when a slower device is multitasking and for some reason the app drawer has to be reloaded, it can take up to 30 seconds for this action to be completed (on all of the installed apps).

Caching could offer a potential solution for this, but then what if the name of an app changes (which occasionally happens)? I'd have to take the labels from the cache, and then loop over all of the apps in a separate thread and correct the labels where they have changed. This may offer a solution, but it seems more like a dirty hack than an actual good solution.

Is there any faster way to get the label of an app's activity? Also, why does it take so ridiculously long for Android to get an app's label, and/or is there anything that I can do about it?

RobinJ
  • 5,022
  • 7
  • 32
  • 61
  • 1
    "what if the name of an app changes (which occasionally happens)?" -- listen for the relevant broadcasts and update your cache for the specific name change. Such a change will only occur when apps are installed, updated, or removed. "why does it take so ridiculously long for Android to get an app's label" -- I haven't seen this behavior. Are you using Traceview to determine that this is exactly where your problem is? – CommonsWare Jul 02 '15 at 15:26
  • @CommonsWare Wouldn't I only receive such broadcasts while my app is running? This is about getting the initial list of installed apps, which I need when my app is started. My observations came from a (somewhat oldfashioned, perhaps) comaprison of timestamps between when an action started and when it was completed ^^ I have just checked via Traceview, and it shows me the same conclusion. This is my first time using Traceview, though. So please excuse me if I'm misinterpreting the results. http://stuff.robinj.be/stackoverflow/31188658-AsyncLoadApps.trace – RobinJ Jul 02 '15 at 16:59
  • "Wouldn't I only receive such broadcasts while my app is running?" -- yes. You would need to refresh this information when your process starts up. "This is about getting the initial list of installed apps, which I need when my app is started" -- hopefully, if you are writing a home screen, you are focused more on getting the list of launchable activities, not the list of installed apps. I have not experienced the performance issues that you describe with [my Launchalot sample](https://github.com/commonsguy/cw-omnibus/tree/master/Introspection/Launchalot), and that sample is *old*. :-) – CommonsWare Jul 02 '15 at 17:12
  • @CommonsWare Yes, sorry. I mean activities, not the apps themselves. In your example I see that you only retrieve the label from the package manager in the `getView ()` method of your adapter. Currently, I just try to retrieve a list of all activities and their icons and labels when my app starts up. I'd say that might be related. I'd never thought of doing it that way. Then again I'd have to make a lot of changes to make that approach work, so a different solution would be preferable, if at all possible. – RobinJ Jul 02 '15 at 17:26
  • @CommonsWare i facing the performance issue while sorting the ResolvInfo details based on their name using Collections.sort(appInfoArrayList, new ResolveInfo.DisplayNameComparator(packageManager)); Any help to make it better other than caching it locally? – venkat Sep 19 '16 at 10:18
  • @venkat: Not really. It takes time to load the resources, such as the display name that you are comparing on. – CommonsWare Sep 19 '16 at 10:57
  • Thanks for the prompt reply :) – venkat Sep 19 '16 at 18:33

2 Answers2

1

You can get label as:

String label = (String) resInf.activityInfo.applicationInfo.loadLabel(pacMan);

If you compare Android source code for those two methods you will notice the one from applicationInfo has less code to execute. Maybe the bottleneck sits in the extra code. I personally never compared execution times for those as I never observed such issue.

mhenryk
  • 551
  • 5
  • 13
  • 2
    That would get the name of the actual application, not the label of the activity (launcher), I think. Some applications (like Google Drive) have multiple activities. – RobinJ Jul 02 '15 at 15:52
  • 1
    Yes, it does return label of the tag from manifest, not the launcher's activity label, but you asked for "app's label" in your question. – mhenryk Jul 02 '15 at 16:10
  • For me, both methods return in approx. the same time, which is not very performant. – waseefakhtar May 14 '20 at 16:08
1

To answer the original question "why?" ... As others have noted, the issue is that the label is localized, meaning it is read from resources -- from the app APK files, based on locale. But to me, this by itself doesn't explain the effect.

I have been developing an app that looks at installed apps, and on a 4-core armeabi-v7a system with some 150 apps, just getting a list of app labels alone takes around 10 seconds. It's painful.

I assumed it was using the ClassLoader to read resources from something like class files -- which can be very fast indeed -- hundreds of class files can be processed almost instantly on modern systems. Well, I don't think it uses the ClassLoader any more. But in principle...

A hundred or so little files should take much less than 10 seconds to process. That by itself couldn't account for the lag I see.

So I followed the calls down, through the creation of Resources objects, the opening of Assets, to finally retrieving that one label String. What I saw was layer upon layer upon layer of synchronized code. And all that is being called anew for each app label. Now, there is something that can make Java grind to a halt.

Of course it doesn't matter much if you're just fetching a single label. But it adds up, when you're fetching all of them.

I hoped to get to the bottom of it, and find something that I could call within one synchronized loop. But at the bottom of the android-29 source, within yet another synchronized block, I found a call to the SDK-specific nativeGetResourceValue(). That is, there is no way somehow re-roll that to be more efficient, and also run on other SDKs.

On the other hand... the OS can display lists of apps sorted by label very quickly. This means it maintains a table of app labels somewhere. Do we have access to that?

If there is some approach to obtain a list of app labels more quickly, I would love to hear it. (No, I don't need to hear about making my own cache -- which I might do anyway.)

Steve White
  • 373
  • 5
  • 9