I need to load Bitmaps into ArrayList, then convert it to Bitmap[] and pass into ArrayAdapter to inflate ListView. I use UniversalImageLoader library and here is my code:
final ArrayList<Bitmap> imgArray = new ArrayList<>(); //before the method scope, as a class field
//...some code...
File cacheDir = StorageUtils.getOwnCacheDirectory(
getApplicationContext(),
"/sdcard/Android/data/random_folder_for_cache");
DisplayImageOptions options = new DisplayImageOptions.Builder()
.cacheInMemory(true).cacheOnDisc(true).build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
getApplicationContext()).defaultDisplayImageOptions(options)
//.discCache(new FileCounterLimitedCache(cacheDir, 100)) - I commented it 'cause FileCounterLimitedCache isn't recognized for some reason
.build();
ImageLoader.getInstance().init(config);
for (int num=0;num<4;num++) {
ImageLoader imageLoader = ImageLoader.getInstance();
final int constNum = num;
imageLoader.loadImage("http://example.com/sample.jpg", new SimpleImageLoadingListener()
{
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
{
imgArray.add(constNum, loadedImage);
}
});
}
But I have some issues to struggle. Firstly, it often returns error (IndexOutOfBoundsException - current index exceeds size of ArrsyList) when first run. Then after some time (about a minute) the size of ArrayList is 1 (I check it with Toast), and then when run again right at once, it's already 4 (as it needs to be). Strange. But the main thing I need that the ArrayList is first filled and then all the other actions are done (that they be delayed and I don't have errors on the first run). How to do it?
And what to do if somebody doesn't have SD card? Btw, I couldn't find the created cache folder on my SD...
You can't add the elements in the way you're trying to. If for example the 2nd image finishes loading first, your code will correctly throw an IndexOutOfBoundsException as the location you're trying to add at is beyond the current size - see the documentation
You might be better off using an array initialised to the number of elements seeing as you know it's 4 elements - e.g.
final Bitmap[] imgArray = new Bitmap[4];
Then add the elements in your onLoadingComplete() using
imgArray[constNum] = loadedImage;
You can use a SparseArray instead of an ArrayList to avoid the IndexOutOfBoundsException.
I assume this is what you are after:
final SparseArray<Bitmap> imgArray = new SparseArray<>(); //before the method scope, as a class field
int numberOfImages;
int numberOfLoadedImages;
//...some code...
File cacheDir = StorageUtils.getOwnCacheDirectory(
getApplicationContext(),
"/sdcard/Android/data/random_folder_for_cache");
DisplayImageOptions options = new DisplayImageOptions.Builder()
.cacheInMemory(true).cacheOnDisc(true).build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
getApplicationContext()).defaultDisplayImageOptions(options)
//.discCache(new FileCounterLimitedCache(cacheDir, 100)) - I commented it 'cause FileCounterLimitedCache isn't recognized for some reason
.build();
ImageLoader.getInstance().init(config);
numberOfImages = 4;
numberOfLoadedImages = 0;
for (int num=0;num<4;num++) {
ImageLoader imageLoader = ImageLoader.getInstance();
final int constNum = num;
imageLoader.loadImage("http://example.com/sample.jpg", new SimpleImageLoadingListener()
{
#Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
{
imgArray.put(constNum, loadedImage);
numberOfLoadedImages++;
if(numberOfImages == numberOfLoadedImages)
{
//Do all the other actions
}
}
});
}
Universal Image Loader library contains the following method:
public void displayImage(java.lang.String uri, android.widget.ImageView imageView)
Which can be passes the image url and the ImageView to display the image on it.
plus to that, if later the same ImageView passed to the method but with a different url two thing can happen:
if old image already downloaded, normally the new image will be set to the ImageView.
if old image still downloading the library will cancel the http connection that is downloading the old image. and start downloading
the new image. (can be observed in the LogCat). this behaviour happens
when using an adapter.
Method call:
ImageLoader.getInstance().displayImage("http://hydra-media.cursecdn.com/dota2.gamepedia.com/b/bd/Earthshaker.png", imageView1);
Configuration:
Caching will not work if the defaultDisplayImageOptions not specified. which tells the library where to save those image. because this library has options to load images from Assets, Drawables or Internet:
DisplayImageOptions opts = new DisplayImageOptions.Builder().cacheInMemory(true).cacheOnDisk(true).build();
With this option the images will be saved in app. internal memory.
don't worry weather the device have an external memory or not.
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this)
.defaultDisplayImageOptions(opts)
.memoryCache(new LruMemoryCache(2 * 1024 * 1024))
.memoryCacheSize(2 * 1024 * 1024)
.diskCacheSize(50 * 1024 * 1024)
.diskCacheFileCount(100)
.writeDebugLogs()
.build();
ImageLoader.getInstance().init(config);
I created a working github repository for it. check it out.
Related
I'm trying to free memory when I don't need it any more but that doesn't seem to be easy. I create a ListView containing some images (Bitmaps). Every time I do that, the memory monitor shows increase of memory used by my app. This is how I do it:
private ArrayList<ImgMdl> populateList(){
ArrayList<ImgMdl> list = new ArrayList<>();
File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + settingsFileDir + "/" + imgLogFileDir);
if(folder.exists()) {
File[] listOfFiles = folder.listFiles();
listOfFiles = sortFileList(listOfFiles);
for (int i = 0; i < listOfFiles.length; i++) {
String imgFilePath = listOfFiles[i].toString();
Bitmap bmp = BitmapFactory.decodeFile(imgFilePath);
ImgMdl imgMdl = new ImgMdl();
imgMdl.set_itm_txt(getDateString(listOfFiles[i].getName()));
imgMdl.set_itm_img(bmp);
list.add(imgMdl);
}
}
return list;
}
public class MainGate_ImgMdl {
private String itm_txt;
private Bitmap itm_img;
public String get_itm_txt() {
return itm_txt;
}
public void set_itm_txt(String itm_txt) {
this.itm_txt = itm_txt;
}
public Bitmap get_itm_img() {
return itm_img;
}
public void set_itm_img(Bitmap itm_img) {
this.itm_img = itm_img;
}
}
If I open the activity containing that list several times, I'll sometime get an outOfMemoryException. How can I free memory if I don't need it any more? finish() doesn't help.
First, do not load images until they are needed. Suppose that you have 100 images in that directory. Most likely, the user cannot see all 100 on the screen — they would have to scroll to do that. If the user does not scroll, you have loaded the unseen images for no reason.
Second, do not load images that you already loaded. If you "open the activity containing that list several times", and you are loading all of the images each time, that is a waste, as you loaded many of those images previously.
Overall, do not load images yourself. There are plenty of image-loading libraries that are available, such as Picasso and Glide. A decent image-loading library will have a configurable cache (addressing my second point) and know how to load images on-demand in the background, even from rows in a ListView or RecyclerView (addressing my first point).
I am trying to create PDF file from xml layout view.
I have a listview in that layout, adding items and setting height based on child. PDF is creating but not filling the whole page.
What I have tried is,
PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(2250, 1400, 1).create();
// start a page
PdfDocument.Page page = document.startPage(pageInfo);
// draw something on the page
LayoutInflater inflater = (LayoutInflater)
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View content = inflater.inflate(R.layout.pdf_layout, null);
content.measure(2250, 1400);
content.layout(0,0, 2250, 1400);
tvName = (TextView)content.findViewById(R.id.tvName);
tvDate = (TextView)content.findViewById(R.id.tvDate);
tvAge = (TextView)content.findViewById(R.id.tvAge);
tvGender = (TextView)content.findViewById(R.id.tvGender);
tvPhone = (TextView)content.findViewById(R.id.tvPhone);
lvList = (ListView)content.findViewById(R.id.lvList);
lvList.setAdapter(adapter);
Utils.setListViewHeight(lvList, CreatePDFDemo.this);
tvName.setText(name);
tvAge.setText(age + "Y");
tvGender.setText(gender);
tvPhone.setText(phone);
content.draw(page.getCanvas());
// finish the page
document.finishPage(page);
// add more pages
// write the document content
try {
document.writeTo(output);
} catch (IOException e) {
e.printStackTrace();
}
This its output is like this image,
How can I write layout view covering full width of pdf page?
Change to this,
int measureWidth = View.MeasureSpec.makeMeasureSpec(page.getCanvas().getWidth(), View.MeasureSpec.EXACTLY);
int measuredHeight = View.MeasureSpec.makeMeasureSpec(page.getCanvas().getHeight(), View.MeasureSpec.EXACTLY);
content.measure(measureWidth, measuredHeight);
content.layout(0, 0, page.getCanvas().getWidth(), page.getCanvas().getHeight());
This will get page full height and width.
Use [PrintContent] (https://developer.android.com/reference/android/support/v4/print/PrintHelper.html)!
// Get the print manager.
PrintHelper printHelper = new PrintHelper(this);
// Set the desired scale mode.
printHelper.setScaleMode(PrintHelper.SCALE_MODE_FIT);
// Get the bitmap for the ImageView's drawable.
Bitmap bitmap = ((BitmapDrawable) mImageView.getDrawable()).getBitmap();
// Print the bitmap.
printHelper.printBitmap("Print Bitmap", bitmap);
I made a library to achieve this objective (Getting PDF from layout view).
The main code snippet is with the proper documentation -
PdfGenerator.getBuilder()
.setContext(context)
.fromLayoutXMLSource()
.fromLayoutXML(R.layout.layout_print,R.layout.layout_print)
/* "fromLayoutXML()" takes array of layout resources.
* You can also invoke "fromLayoutXMLList()" method here which takes list of layout resources instead of array. */
.setDefaultPageSize(PdfGenerator.PageSize.A4)
/* It takes default page size like A4,A5. You can also set custom page size in pixel
* by calling ".setCustomPageSize(int widthInPX, int heightInPX)" here. */
.setFileName("Test-PDF")
/* It is file name */
.setFolderName("FolderA/FolderB/FolderC")
/* It is folder name. If you set the folder name like this pattern (FolderA/FolderB/FolderC), then
* FolderA creates first.Then FolderB inside FolderB and also FolderC inside the FolderB and finally
* the pdf file named "Test-PDF.pdf" will be store inside the FolderB. */
.openPDFafterGeneration(true)
/* It true then the generated pdf will be shown after generated. */
.build(new PdfGeneratorListener() {
#Override
public void onFailure(FailureResponse failureResponse) {
super.onFailure(failureResponse);
/* If pdf is not generated by an error then you will findout the reason behind it
* from this FailureResponse. */
}
#Override
public void showLog(String log) {
super.showLog(log);
/*It shows logs of events inside the pdf generation process*/
}
#Override
public void onSuccess(SuccessResponse response) {
super.onSuccess(response);
/* If PDF is generated successfully then you will find SuccessResponse
* which holds the PdfDocument,File and path (where generated pdf is stored)*/
}
});
Try to convert your layout into image then set that image to PDF. read this, maybe you will get some idea.
Convert view to PDF
private void setImageloadConfig() {
defaultOption = new DisplayImageOptions.Builder()
.bitmapConfig(Config.RGB_565).resetViewBeforeLoading(false)
.cacheInMemory(true).cacheOnDisk(true);
configuration = new ImageLoaderConfiguration.Builder(context).denyCacheImageMultipleSizesInMemory()
.threadPoolSize(3).memoryCache(new LruMemoryCache(IMG_MAX_SIZE))
.memoryCache(new UsingFreqLimitedMemoryCache(IMG_MAX_SIZE)).memoryCacheSize(IMG_MAX_SIZE)
.memoryCacheSizePercentage(13).diskCache(new UnlimitedDiscCache(new File(IMAGE_FILE_PATH)))
.diskCacheSize(50 * 1024 * 1024).diskCacheFileNameGenerator(new HashCodeFileNameGenerator())
.defaultDisplayImageOptions(defaultOption.build())
.imageDownloader(new BaseImageDownloader(context, 5 * 1000, 30 * 1000)).build();
ImageLoader.getInstance().init(configuration);
}
In global activity, I have set DisplayImageOptions, but there is a problem, if I want to config showImageOnLoading, showImageForEmptyUri and showImageOnFail, how can solve it, because in difference issue. I want to set different image, but if I set it in specific activity, it will cover the global configuration.
The problem exists in code part2.
DisplayImageOptions options = new DisplayImageOptions.Builder()
.bitmapConfig(Config.RGB_565).resetViewBeforeLoading(false)
.cacheInMemory(true).cacheOnDisk(true)
.showImageOnLoading(R.drawable.home_list_img_picture)
.showImageForEmptyUri(R.drawable.home_list_img_picture)
.showImageOnFail(R.drawable.home_list_img_picture)
.build();
ImageLoader.getInstance().displayImage(imgPath,iamge_photo,options);
If I move the DisplayImageOptions configuration from global activity to specific activity, it's OK, but causes another problem. When I switch activities, the image component will flicker, which is OK, when configured in global activity, how can I solve this confict, any suggestions?
I have a news feed, where news items contain images. Each news item (in the table view) obtains a image url from a server and downloads the images asynchronously / from cache.
I will use a bit of pseudocode / Java to explain my process in the simplest terms possible.
NewsItemAdapter
Map<String, Bitmap> imageCache = new HashMap<String, Bitmap>(); // init cache
String imurl = get image url from appropriate NewsObject;
Bitmap value = imageCache.get(imurl);
if (value != null) { // if bitmap is in cache
load bitmap into image view from cache;
add bitmap to NewsObject for accessing later;
}else {
execute Asynchronous bitmap download task;
}
Asynchronous bitmap download task (The reason for scaleDownBitmap() is because of OutOfMemory errors I get)
doinBackground(Void...params){
myBitmap = download bitmap from imurl;
imageCache.put(imurl, scaleDownBitmap(myBitmap)); // put bitmap into cache
return scaleDownBitmap(myBitmap);
}
onPostExecute(Bitmap result){
load result into image view;
}
MainActivity
setOnNewsItemClickListener{
intent = get intent to mainNewsScreen; //after you click on news item
intent.putExta("newsBitmap", bitmap from NewsObject); // set in NewsItemAdapter
startActivity(intent);
}
MainNewsScreen
onCreate(){
load bitmap from intent extras into image view;
}
My main problem is if I remove the scaleDownBitmap() method found here I get OutOfMemory errors.
But I am losing a lot of image quality. As you can see I haven't used bitmap.recycle() at all, I'm not entirely sure where I'd use it as I need to keep the images in memory (I would have thought).
Any idea how to make this more efficient. I'm sure this would be helpful to a lot of people attempting to create a similar app.
Consider using an lrucache instead and storing the image on disk when it falls out of the cache.
Best practice is explained here:
http://developer.android.com/intl/es/training/displaying-bitmaps/cache-bitmap.html
Consider this code as an idea, some of it copied from the above link:
...
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
#Override
protected void entryRemoved (boolean evicted, K key, V oldValue, V newValue) {
// Save your entry to disc instead
}
};
Google has made a DiscLruCache that you can simply download and use in your project (its usage is described in the above link):
https://developer.android.com/intl/es/samples/DisplayingBitmaps/src/com.example.android.displayingbitmaps/util/DiskLruCache.html
Also don't keep an infinite amount of news / images in the view. You're going to have to remove news items as the user scrolls through them and reads them.
Consider using a Recycler view for this (along with Cards if you want a Material design feel to your app):
https://developer.android.com/intl/es/reference/android/support/v7/widget/RecyclerView.html
<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
android:id="#+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager)
Cards:
https://developer.android.com/intl/es/reference/android/support/v7/widget/CardView.html
<!-- A CardView that contains a TextView -->
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/card_view"
android:layout_gravity="center"
android:layout_width="200dp"
android:layout_height="200dp"
card_view:cardCornerRadius="4dp">
<TextView
android:id="#+id/info_text"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v7.widget.CardView>
More information about combining a Recycler view with Card views:
https://developer.android.com/training/material/lists-cards.html
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.
You can use Picasso library with a custom RequestHandler to load the icons in the background.
First create a RequestHandler which will handle the specific case where an app icon needs to be loaded.
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);
}
}
In your adapter, create a Picasso instance and add your RequestHandler.
// field variable
private Picasso mPicasso;
// in constructor
Picasso.Builder builder = new Picasso.Builder(context);
builder.addRequestHandler(new AppIconRequestHandler(context));
mPicasso = builder.build();
In your adapter's getView() load the icon using Picasso.
mPicasso.load(AppIconRequestHandler.getUri(app.packageName)).into(holder.mIcon);
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.
You can use Glide for automatic loading and caching and the URI of each application icon:
final RequestManager mGlide = Glide.with(activity);
final Uri appIconUri = applicationInfo.icon != 0 ?
Uri.parse("android.resource://" + packageName + "/" + applicationInfo.icon) :
null;
if (appIconUri != null) mGlide.load(appIconUri).into(holder.appIconImgView);
else {
mGlide.clear(holder.appIconImgView); // as suggested here: https://bumptech.github.io/glide/doc/getting-started.html
mGlide.load(android.R.drawable.sym_def_app_icon).into(holder.appIconImgView);
}
The reason I suggest Glide and not other image loading libraries is that Glide supports XML drawable (or dynamic/adaptive or vector icons) loading while others don't (see https://github.com/facebook/fresco/issues/2173)