如何在gridView中更快加载应用程序的图像(图标)? [英] How to load image (icons) of apps faster in gridView?

查看:75
本文介绍了如何在gridView中更快加载应用程序的图像(图标)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在显示安装在gridView中的所有应用程序。当加载很多应用程序时,比如说30或更多,图标将显示在默认的Android图标上,然后几秒钟后更新为正确的图标。



加载以下内容: new LoadIconsTask()。我的代码如何改进可以使我的代码更加快速地显示图标图像。 execute(mApps.toArray(new AppsInstalled [] {}));



这就是我所做的。

 私人类LoadIconsTask扩展了AsyncTask< AppsInstalled,Void,Void> {

@Override
protected void doInBackground(AppsInstalled ... params){
// TODO自动生成的方法存根
Map< String,Drawable> icons = new HashMap< String,Drawable>();
PackageManager manager = getApplicationContext()。getPackageManager();

//将软件包名称与图标匹配,将Adapter设置为已加载Map
(AppsInstalled app:params){
String pkgName = app.getAppUniqueId();
Drawable ico = null;
尝试{
Intent i = manager.getLaunchIntentForPackage(pkgName);
if(i!= null){
ico = manager.getActivityIcon(i);
}
} catch(NameNotFoundException e){
Log.e(TAG,无法找到基于包的图标匹配:+ pkgName
+:+ e。的getMessage());
}
icons.put(app.getAppUniqueId(),ico);
}
mAdapter.setIcons(图标);
返回null;
}

在loadIconsTask()和

之前填充我的应用程序清单

 私人列表< App> loadInstalledApps(boolean includeSysApps){
List< App> apps = new ArrayList< App>();

//包管理器包含所有已安装应用程序的信息
PackageManager packageManager = getPackageManager();

列表< 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;
//跳过系统应用(如果不包含)
if((!includeSysApps)
&&((a.flags& ApplicationInfo.FLAG_SYSTEM)== 1)){
继续;
}
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);
}
返回应用;
}

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

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

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

//创建一个ViewHolder并存储对子视图的引用
//我们要将数据绑定到
holder = new AppViewHolder();
holder.mTitle =(TextView)convertView.findViewById(R.id.apptitle);
holder.mIcon =(ImageView)convertView.findViewById(R.id.appicon);
convertView.setTag(holder);
} else {
//重用/覆盖通过假设它是可强制转换的视图!
持有人=(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()));
}

返回convertView;
}

有更好的方法吗?我可以以某种方式将图标的图像存储在数据结构中,并且当我返回到此活动时,我可以跳过loadIconsTask?那可能吗?

解决方案

令人惊讶的是,系统在获取这些列表时需要很多时间,您可能需要添加一些日志用时间戳来查看哪一个是苛刻的操作。

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




  • onResume / onCreate 作为静态列表,并且(为了正确性)在 onPause / onStop 中销毁它,如果你想考虑用户可以在你的应用程序中安装应用程序的情况app(onPause会被调用),但你当然可以跳过这一步。 您可能还想永久缓存sdcard中的列表,并找到一些简单而快速的启发式来确定列表是否具有为了重新创建而更改。例如,可能包含已安装软件包的数量以及其他内容(如果用户卸载3个应用程序并安装3个不同的应用程序,则软件包数量将相同,您必须以某种方式检测到这种情况才能放弃该情况)。




编辑 - 为了推荐一个缓存机制,你应该确定哪一个是缓慢的操作。只是猜测,并从您的问题图标需要几秒钟的时间出现,它看起来像是缓慢的操作是:

  ico = manager.getActivityIcon(i); 

但我可能是错的。假设我是对的,那么便宜的缓存可以是:
$ b <1>移动 Map< String,Drawable>图标=新的HashMap< String,Drawable>(); 在doInBackground之外的类的根目录并使其成为静态的,如:

  private static Map< String,Drawable> sIcons = new HashMap< String,Drawable>()

2)在您的loadIconsTask中,有这个图标:

$ p $ for(AppsInstalled app:params){

字符串pkgName = app.getAppUniqueId ();
if(sIcons.containsKey(pkgName)continue;



}

这是因为 sIcons 现在 static 3)作为一个优雅的东西,你可能希望将sIcons从 Drawable 到位图。为什么?因为 Drawable 可能会保留在对 Views Context的引用中这是潜在的内存泄漏。你可以非常容易地从 Drawable 获取 Bitmap ,调用 drawable.getBitmap() / code>,(假设 drawable 是一个 BitmapDrawable ,但显然这是因为它是一个应用程序图标),所以总结一下你会得到:

  //静态图标字典现在存储位图
静态图<字符串,位图> sIcons = new HashMap< String,Bitmap>();


//我们存储位图而不是可绘制的
sIcons.put(app.getAppUniqueId(),((BitmapDrawable)ico).getBitmap());


//设置图标时,我们创建可绘制的背部
holder.setIcon(new BitmapDrawable(mIcons.get(app.getPackageName())));

这样你的静态hashmap永远不会泄漏任何内存。



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

如果你看看它是否值得,当你创建列表时,你可以像下面这样将位图保存到SD卡中:

  / /准备一个文件到应用程序缓存目录。 
文件cachedFile = new File(context.getCacheDir(),icon - + app.getPackageName());
//将我们的位图保存为压缩的JPEG,包名称为文件名
myBitmap.compress(CompressFormat.JPEG,quality,new FileOutputStream(cachedFile);



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

  String key = app.getPackageName(); 

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

if(localFile.exists()){
//该文件存在于SD卡中,只需加载它
位图myBitmap = BitmapFactory.decodeStream(new FileInputStream(localFile)) ;

//我们有我们的sdcard的位图!!让我们把它放到我们的HashMap
sIcons.put(key,myBitmap)
} else {
/ /使用缓慢的方法
}

就像你看到的,只是一个识别如果我们上面的假设是正确的,你的存储位图将会在你的应用程序被破坏的情况下生存下来,并且它会希望优化图标加载。


I am displaying all apps installed in a gridView. When loading a lot of apps, lets say 30 or more, the icons will display at the default Android icon and then several seconds later update to the correct icon. I am wondering about improvements I can make to my code to make the icon images display faster.

Load the following with: new LoadIconsTask().execute(mApps.toArray(new AppsInstalled[]{}));

Here is what I do.

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

Also populate my listing of apps before I loadIconsTask() with

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

In regards to my Adapter class it is standard. My getView() looks like the following:

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

Is there a better way? Can I somehow store the images of the icons in a data structure and when I return back to this Activity I can skip the loadIconsTask? Is that possible? Thank you in advance.

解决方案

it's surprising the system takes that much time in getting these lists, you may want to add some logs with timestamping to see which one is the demanding operation.

I don't know if that procedure can be further optimized, I haven't used these system API's very much, but what you can certainly do is to cache this list

  • Create it in onResume / onCreate as a static list, and (for the sake of correctness) destroy it in onPause / onStop if you want to consider the case where the user may install an application while in your app (onPause will be called), but you can certainly skip this step.

  • You may want to also permanently cache the list in the sdcard and find some simple and fast heuristic to decide if the list has changed in order to recreate it. Something like maybe the number of installed packages together with something else (to discard the case when the user uninstalls 3 apps and install 3 different apps, the number of packages will be the same and you have to detect this somehow).

EDIT- To recommend a caching mechanism, you should identify which one is the slow operation. Just guessing, and from your question "the icons take some seconds to appear" it looks like that the slow operation is:

ico = manager.getActivityIcon(i);

but I might be wrong. Let's suppose I'm right, so a cheap caching can be:

1) Move the Map<String, Drawable> icons = new HashMap<String, Drawable>(); outside of doInBackground to the root of the class and make it static, like:

private static Map<String, Drawable> sIcons = new HashMap<String, Drawable>()

2) In your loadIconsTask consider the case you already have this icon:

 for (AppsInstalled app : params) {

                String pkgName = app.getAppUniqueId();
                if (sIcons.containsKey(pkgName) continue;
                .
                .
                .
    }

This is because sIcons is now static and will be alive as long as your application is alive.

3) As a classy thing, you may want to change sIcons from Drawable to Bitmap. Why? Because a Drawable may keep inside references to Views and Context and it's a potential memory leak. You can get the Bitmap from a Drawable very easily, calling drawable.getBitmap() , (Assuming drawable is a BitmapDrawable, but it will obviously be because it's an app icon), so suming up you'll have:

        // 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())));

This way your static hashmap will never leak any memory.

4) You may want to check if it's worth to store those bitmaps on disk. Mind this is some additional work and it might not be worth if the time to load the icon from disk is similar to the time to load the icon calling ico = manager.getActivityIcon(i);. It may be (i don't know if manager.getActivityIcon() extracts the icon from the APK) but it certainly may be not.

If you check out it's worth, when you create the list, you can save the bitmaps to the sdcard like this:

  // 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);

... then when loading the icons, check if the icon exists and load from the sdcard instead:

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
}

Well as you see it's just a matter of identifying the slow operation. If our above assumption is correct, your stored bitmaps will survive your application destroy and it will hopefully optimize the icon loading.

这篇关于如何在gridView中更快加载应用程序的图像(图标)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆