如何在gridView中更快地加载应用程序的图像(图标)?

por*_*der 1 android gridview

我正在显示gridView中安装的所有应用程序.加载大量应用时,可以说30或更多,图标将显示在默认的Android图标上,然后几秒后更新为正确的图标.我想知道我可以对我的代码进行哪些改进,以使图标图像显示得更快.

加载以下内容: new LoadIconsTask().execute(mApps.toArray(new AppsInstalled[]{}));

这就是我的工作.

private class LoadIconsTask extends AsyncTask<AppsInstalled, Void, Void>{

        @Override
        protected Void doInBackground(AppsInstalled... params) {
            // TODO Auto-generated method stub
            Map<String, Drawable> icons = new HashMap<String, Drawable>();
            PackageManager manager = getApplicationContext().getPackageManager();

            // match package name with icon, set Adapter with loaded Map
            for (AppsInstalled app : params) {
                String pkgName = app.getAppUniqueId();
                Drawable ico = null;
                try {
                    Intent i = manager.getLaunchIntentForPackage(pkgName);
                    if (i != null) {
                        ico = manager.getActivityIcon(i);
                    }               
                } catch (NameNotFoundException e) {
                    Log.e(TAG, "Unable to find icon match based on package: " + pkgName 
                            + " : " + e.getMessage());
                }
                icons.put(app.getAppUniqueId(), ico);
            }
            mAdapter.setIcons(icons);
            return null;
        }
Run Code Online (Sandbox Code Playgroud)

在我使用loadIconsTask()之前填充我的应用程序列表

private List<App> loadInstalledApps(boolean includeSysApps) {
    List<App> apps = new ArrayList<App>();

    // the package manager contains the information about all installed apps
    PackageManager packageManager = getPackageManager();

    List<PackageInfo> packs = packageManager.getInstalledPackages(0); // PackageManager.GET_META_DATA

    for (int i = 0; i < packs.size(); i++) {
        PackageInfo p = packs.get(i);
        ApplicationInfo a = p.applicationInfo;
        // skip system apps if they shall not be included
        if ((!includeSysApps)
                && ((a.flags & ApplicationInfo.FLAG_SYSTEM) == 1)) {
            continue;
        }
        App app = new App();
        app.setTitle(p.applicationInfo.loadLabel(packageManager).toString());
        app.setPackageName(p.packageName);
        app.setVersionName(p.versionName);
        app.setVersionCode(p.versionCode);
        CharSequence description = p.applicationInfo
                .loadDescription(packageManager);
        app.setDescription(description != null ? description.toString()
                : "");
        apps.add(app);
    }
    return apps;
}
Run Code Online (Sandbox Code Playgroud)

关于我的Adapter类,它是标准的.我的getView()如下所示:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        AppViewHolder holder;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.row, null);

            // creates a ViewHolder and stores a reference to the children view
            // we want to bind data to
            holder = new AppViewHolder();
            holder.mTitle = (TextView) convertView.findViewById(R.id.apptitle);
            holder.mIcon = (ImageView) convertView.findViewById(R.id.appicon);
            convertView.setTag(holder);
        } else {
            // reuse/overwrite the view passed assuming that it is castable!
            holder = (AppViewHolder) convertView.getTag();
        }

        App app = mApps.get(position);

        holder.setTitle(app.getTitle());
        if (mIcons == null || mIcons.get(app.getPackageName()) == null) {
            holder.setIcon(mStdImg);
        } else {
            holder.setIcon(mIcons.get(app.getPackageName()));
        }

        return convertView;
    }
Run Code Online (Sandbox Code Playgroud)

有没有更好的办法?我可以以某种方式将图标的图像存储在数据结构中,当我返回此活动时,我可以跳过loadIconsTask吗?那可能吗?先感谢您.

Ben*_*oli 12

您可以将Picasso库与自定义RequestHandler一起使用以在后台加载图标.

  1. 首先创建一个RequestHandler将处理需要加载应用程序图标的特定情况.

    public class AppIconRequestHandler extends RequestHandler {
    
        /** Uri scheme for app icons */
        public static final String SCHEME_APP_ICON = "app-icon";
    
        private PackageManager mPackageManager;
    
        public AppIconRequestHandler(Context context) {
            mPackageManager = context.getPackageManager();
        }
    
        /**
         * Create an Uri that can be handled by this RequestHandler based on the package name
         */
        public static Uri getUri(String packageName) {
            return Uri.fromParts(SCHEME_APP_ICON, packageName, null);
        }
    
        @Override
        public boolean canHandleRequest(Request data) {
            // only handle Uris matching our scheme
            return (SCHEME_APP_ICON.equals(data.uri.getScheme()));
        }
    
        @Override
        public Result load(Request request, int networkPolicy) throws IOException {
            String packageName = request.uri.getSchemeSpecificPart();
            Drawable drawable;
            try {
                drawable = mPackageManager.getApplicationIcon(packageName);
            } catch (PackageManager.NameNotFoundException ignored) {
                return null;
            }
    
            Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
            return new Result(bitmap, Picasso.LoadedFrom.DISK);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在适配器中,创建一个Picasso实例并添加您的实例RequestHandler.

    // field variable
    private Picasso mPicasso;
    
    // in constructor
    Picasso.Builder builder = new Picasso.Builder(context);
    builder.addRequestHandler(new AppIconRequestHandler(context));
    mPicasso = builder.build();
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在适配器中getView()使用加载图标Picasso.

    mPicasso.load(AppIconRequestHandler.getUri(app.packageName)).into(holder.mIcon);
    
    Run Code Online (Sandbox Code Playgroud)

  • 只是对贝尼托答案的一些改进:https://gist.github.com/guavabot/57da5064e93260ee35a2ef9cfbb6bec2添加1.一个静态方法来获取此RequestHander处理的Uri.2.当无法加载图标时返回null以避免在Result的构造函数中使用NPE.3.更好地处理Uri以避免"SCHEME_APP_ICON +":"`的事情. (3认同)

rup*_*pps 5

令人惊讶的是,系统花了这么多时间来获取这些列表,您可能想添加带有时间戳的日志,以查看哪个是要求苛刻的操作。

我不知道该程序是否可以进一步优化,我还没有使用过这些系统API,但是您可以做的当然是缓存此列表

  • 将其创建onResume / onCreate为静态列表,并(出于正确性考虑)将其销毁,onPause / onStop如果您要考虑用户可以在您的应用程序中安装应用程序的情况(将调用onPause),但是您当然可以跳过此步骤步。

  • 您可能还希望将列表永久缓存在sdcard中,并找到一些简单而快速的试探法来确定列表是否已更改以便重新创建。可能与已安装软件包的数量以及其他内容一样(为了避免用户卸载3个应用程序并安装3个不同应用程序的情况,软件包的数量将相同,因此您必须以某种方式进行检测)。

编辑-要推荐一种缓存机制,您应该确定哪个是慢速操作。只是猜测一下,从您的问题“图标需要几秒钟的时间才能出现”,看来缓慢的操作是:

ico = manager.getActivityIcon(i);
Run Code Online (Sandbox Code Playgroud)

但我可能错了。假设我是对的,那么便宜的缓存可以是:

1)将Map<String, Drawable> icons = new HashMap<String, Drawable>();doInBackground 的外部移到类的根目录并使其静态,如:

private static Map<String, Drawable> sIcons = new HashMap<String, Drawable>()
Run Code Online (Sandbox Code Playgroud)

2)在您的loadIconsTask中,考虑您已经具有此图标的情况:

 for (AppsInstalled app : params) {

                String pkgName = app.getAppUniqueId();
                if (sIcons.containsKey(pkgName) continue;
                .
                .
                .
    }
Run Code Online (Sandbox Code Playgroud)

这是因为,只要您的应用程序还处于活动状态,sIcons它现在static就将处于活动状态。

3)作为经典的事情,您可能需要将sIcons从更改DrawableBitmap。为什么?因为a Drawable可能保留对Views和的内部引用,Context并且这是潜在的内存泄漏。你可以得到Bitmap从一个Drawable很容易,调用drawable.getBitmap(),(假设drawableBitmapDrawable,但它显然是因为它是一个应用程序图标),所以suming了你必须:

        // the static icon dictionary now stores Bitmaps
        static Map<String, Bitmap> sIcons = new HashMap<String, Bitmap>();
        .
        .
        // we store the bitmap instead of the drawable
        sIcons.put(app.getAppUniqueId(), ((BitmapDrawable)ico).getBitmap());
        .
        .
        // when setting the icon, we create the drawable back
        holder.setIcon(new BitmapDrawable(mIcons.get(app.getPackageName())));
Run Code Online (Sandbox Code Playgroud)

这样,您的静态哈希图将永远不会泄漏任何内存。

4)您可能需要检查是否值得在磁盘上存储这些位图。请注意,这是一些额外的工作,如果从磁盘加载图标的时间与加载图标调用的时间相似ico = manager.getActivityIcon(i);则可能不值得。可能是(我不知道manager.getActivityIcon()是否从APK中提取图标),但肯定不是。

如果您检查出它是否值得,则在创建列表时,可以将位图保存到sdcard中,如下所示:

  // prepare a file to the application cache dir.
    File cachedFile=new File(context.getCacheDir(), "icon-"+app.getPackageName());
    // save our bitmap as a compressed JPEG with the package name as filename
    myBitmap.compress(CompressFormat.JPEG, quality, new FileOutputStream(cachedFile);
Run Code Online (Sandbox Code Playgroud)

...然后在加载图标时,检查图标是否存在并改为从sdcard加载:

String key=app.getPackageName();

File localFile=new File(context.getCacheDir(), "icon-"+key);

if (localFile.exists()) {
    // the file exists in the sdcard, just load it
    Bitmap myBitmap = BitmapFactory.decodeStream(new FileInputStream(localFile));

    // we have our bitmap from the sdcard !! Let's put it into our HashMap
    sIcons.put(key, myBitmap)
} else {
    // use the slow method
}
Run Code Online (Sandbox Code Playgroud)

就像您看到的那样,这只是识别慢速操作的问题。如果我们的上述假设是正确的,则存储的位图将在应用程序销毁后继续存在,并且有望优化图标加载。