This may be more of a Java question than an Android question, but I'm having trouble retrieving a Bitmap created within an AsyncTask to store in another class (an Activity) so that I can recycle it when I'm done using it.
The AsyncTask creates the Bitmap in doInBackground() and sets it as the bitmap for an ImageView in onPostExecute(), the ImageView being passed in through the constructor. But after completion I want the Bitmap to be accessible in the Activity. The Activity has an ArrayList of ImageViews and another of Bitmaps, but since the AsyncTask creates a new Bitmap, I can't find an easy way to get this new object in the ArrayList of Bitmaps in the Activity. Currently I have it working by passing in the ArrayList along with an index into the list to the AsyncTask constructor, and doInBackground just sets that entry in the array to the newly created bitmap.
I don't like this solution though, because I want to be able to use this AsyncTask for different things, perhaps where the Activity doesn't have an ArrayList of Bitmaps. And I can't simply give the AsyncTask constructor a Bitmap because Java passes the reference by value, and setting it to a new Bitmap object wouldn't allow access for the caller.
How can I do this more elegantly?
Here is the relevant code. Lines not pertaining to this question have been omitted for clarity.
public class LoadCachedImageTask extends AsyncTask<String, Void, Void> {
private Context context;
private ImageView image;
private ArrayList<Bitmap> bitmaps;
int index;
public LoadCachedImageTask(Context context, ImageView image, ArrayList<Bitmap> bitmaps, int index) {
this.context = context;
this.image = image;
this.bitmaps = bitmaps;
this.index = index;
}
protected Void doInBackground(String... urls) {
String url = urls[0];
Bitmap bitmap = null;
// Create the bitmap
File imageFile = new File(context.getCacheDir(), "test");
bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath());
// Set the bitmap to the bitmap list
bitmaps.set(index, bitmap);
return null;
}
protected void onPostExecute(Void arg) {
// Display the image
image.setImageBitmap(bitmaps.get(index));
}
protected void onCancelled() {
if (bitmaps.get(index) != null) {
bitmaps.get(index).recycle();
bitmaps.set(index, null);
}
}
}
And here's a sample Activity that uses it.
public class SampleActivity extends Activity {
private ArrayList<ImageView> images;
private ArrayList<Bitmap> bitmaps;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
images = new ArrayList<ImageView>();
bitmaps = new ArrayList<Bitmap>();
int numImages = 15;
// Create the images and bitmaps
for (int i = 0; i < numImages; i++) {
images.add(new ImageView(this));
bitmaps.add(null);
}
// Load the bitmaps
for (int i = 0; i < numImages; i++) {
new LoadCachedImageTask(this, images.get(i), bitmaps, i).execute("http://random.image.url");
}
}
}
I haven't tested the above code, so it might not work, but I think it gets the point across.
As I understand it, you're trying to load a large number of bitmaps into a large number of ImageViews asynchronously. I would think this can be done with a single AsyncTask class that you use multiple times for each ImageView.
You're AsyncTask should be something like this:
public class LoadCachedImageTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> mImgView;
public LoadCachedImageTask(ImageView image) {
mImageView = new WeakReference<ImageView>(image);
}
protected Void doInBackground(String... urls) {
if(urls == null || urls.length < 1)
return;
// Create the bitmap
final Bitmap bitmap = BitmapFactory.decodeFile(url);
return bitmap;
}
protected void onPostExecute(Bitmap bmp) {
// Display the image
if(bmp != null) {
final ImageView imageView = (ImageView) mImgView.get();
if(imageView != null) // needed in case the weakreference is removed
imageView.setImageBitmap(bmp);
}
}
Then to fill your array of ImageViews with something like this:
for(ImageView imgView : images) {
(new LoadCachedImageTask<String, Void, Bitmap>)(imgView).execute(getBitmapUrl());
}
The for-loop will iterate through each ImageView reference and pass it to a brand new AsyncTask reference. It will then execute the AsyncTask with the given url to whatever bitmap you need. The asynctask will hold on to a reference of the ImageView so long as the ImageView exists. If, for some reason, your ImageView got destroyed, the bitmap will still load then immediately get thrown away.
Related
Hi guys trying to implement AsyncTask here in my project, there seems to be no error shown by Android Studio either and have debugged to see if the bitmaps are downloaded and yes it is. I dont know what the problem is so here is my code.
My Activity:
public class MainActivity extends AppCompatActivity {
public ImageView imageView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView =findViewById(R.id.justAnImage);
String[] strings = new String[2];
strings[0] = "blah.com/sds.jpg";
strings[1] = "blah.com/sds2.jpg";
new AsyncDownloader(this,imageView).execute(strings);
}
public void StartAnimation(ImageView imageView, Bitmap[] bitmaps)
{
AnimationDrawable animation = new AnimationDrawable();
for (int i=0;i<bitmaps.length;i++) //replaced erroneous code-> (int i=0;i>=bitmaps.length;i++)
{
animation.addFrame(new BitmapDrawable(getApplicationContext().getResources(),bitmaps[i]),1000);
}
animation.setOneShot(false);
imageView.setBackground(animation);
// start the animation!
animation.start();
}
}
My Async downloader class
public class AsyncDownloader extends AsyncTask<String[],Void,Bitmap[]> {
private Bitmap[] bitmapArray;
private ImageView imageView;
MainActivity mainActivity;
public AsyncDownloader(MainActivity mainActivity,ImageView imageView) {
this.imageView = imageView;
bitmapArray = new Bitmap[2];
this.mainActivity = mainActivity;
}
#Override
protected Bitmap[] doInBackground(String[]... strings) {
for(int i=1;i>=0;i--)
{
try {
URL url = new URL(strings[0][i]);
bitmapArray[i] = BitmapFactory.decodeStream(url.openConnection().getInputStream());
} catch (Exception e) {
Log.e("DOWNLOAD ASYNC", e.getMessage());
}
}
return bitmapArray;
}
#Override
protected void onPostExecute(Bitmap[] bitmapArray) {
super.onPostExecute(bitmapArray);
//imageView.setImageBitmap(bitmapArray[0]);
mainActivity.StartAnimation(imageView,bitmapArray);
}
}
I cant find the problem here, debugging is so terrible in Android Studio. If any one could help, i shall be thankful.
EDIT 1: After suggestions from fellow stack guys, it is clear that the animation drawable is not working as desired and nonetheless there is no image showing up on the ImageView control.
EDIT 2: Code works after correcting the loop specified in the answer. Found an alternative to display images after certain interval. The code goes like this :
public void StartAnimation(final ImageView imageView, final Bitmap[] bitmaps)
{
handler = new Handler();
Runnable runnable = new Runnable() {
int i = 0;
public void run() {
imageView.setImageBitmap(bitmaps[i]);
i++;
if (i > bitmaps.length - 1) {
i = 0;
}
handler.postDelayed(this, 1200);
}
};
handler.postDelayed(runnable, 1200);
}
Your code is perfect, except loop condition inside method StartAnimation.
issue is with loop condition which is always false.
To fix the issue change
From
for (int i = 0; i >= bitmaps.length; i++)
To
for (int i = 0; i < bitmaps.length; i++)
use fresco or picosso or glide library
these libraries automatically caching and managing memory.
it's easy to work with picosso and glide but fresco adds more features to developers.
EDITED
I have this class:
public class Item {
private Bitmap image;
public Item(Bitmap image) {
this.image = image;
}
I have also the main activity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//the question is about this line
Item item = new Item(???);
}
}
How to call the constructor of the Item From MainActivity (inside onCreate)? I do not know how to refer to a Bitmap from Resources.
The image I want to pass to the constructor is located in:
app >> res >> mipmap >> cat.png
Here you can convert bitmap to drawable
Drawable myDrawable = getResources().getDrawable(R.drawable.cat);
Bitmap bitmap= ((BitmapDrawable) myDrawable).getBitmap();
in java , you can call constructor By building an instance of your class .
in your Question :
new Item(bitmap);
you can refer to resource bitmap in your Activity by doing this :
Bitmap bitmap = BitmapFactory.decodeResource(getResources() , R.drawable.cat);
and in Fragment :
Bitmap bitmap = BitmapFactory.decodeResource(getAvtivity().getResources() , R.drawable.cat);
Bitmap image = BitmapFactory.decodeResource(getResources() , R.drawable.cat);
I'm trying to load some images from my server to New App, but i don't have any result :
This is my MainActivity code :
import java.io.InputStream;
import static android.net.Uri.parse;
public class MainActivity extends Activity {
private ImageView iv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new DownloadImageTask((ImageView)findViewById(R.id.loadimg)).execute(getString(R.string.link));
private class DownloadImageTask extends AsyncTask<String,Void,Bitmap>{
private final ImageView bmImage;
ImageView bmImg;
public DownloadImageTask (ImageView bmImage){
this.bmImage=bmImage;
}
protected Bitmap doInBackground (String...urls){
String underplay = urls[0];
Bitmap mIcon11 =null;
try {
InputStream in =new java.net.URL(underplay).openStream();
mIcon11 = BitmapFactory.decodeStream(in);
} catch (Exception e){
Log.e("Error",e.getMessage());
e.printStackTrace();
}
return mIcon11 ;
}
}
The Result in emulator is not appear any thing just my Button ?
Add the following dependency to your build.gradle file under app:
dependencies {
compile 'com.squareup.picasso:picasso:2.3.2'
}
Then in your code you can simply load a bitmap or image from a server like so:
Picasso.with(this)
.load(IMAGE_URL)
.into(yourImageView);
Where this is the activity context. IMAGE_URL is the url of the image, example: http://yourapi.com/image_1034.png, and yourImageView is the ImageView, ImageButton, or other Custom View where you want to upload the image into.
Doing it this way is considered best practice, and reduces a lot of the boilerplate code you've written. Try building a scalable app writing AsyncTasks for every time you upload a Bitmap.
In your AsyncTask you need to implement:
protected void onPostExecute(Bitmap icon) {
iv.setImageBitmap(icon)
}
Can you try this code?
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.image);
I create a Bitmap using Android Query like this:
ArrayList<Bitmap> bitmapArray = new ArrayList<Bitmap>();
aq = new AQuery(HomeCategoryActivity.this);
aq.ajax(currentUrl,Bitmap.class,0,new AjaxCallback<Bitmap>(){
#Override
public void callback(String url, Bitmap object, AjaxStatus status) {
if(object != null)
{
bitmapArray.add(object);
}
}
});
But when I Log.d the size of the arraylist, it returns 0. The Bitmap is NOT NULL, I checked by setting it as an image resource. It simply doesn't get added to the arraylist. What do you think is wrong?
public class XPBN extends Activity{
private Map _map;
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
final Map data = (Map)getLastNonConfigurationInstance();
if (data == null) {
Toast.makeText(this, "New Map", Toast.LENGTH_SHORT).show();
_map = new Map(this);
} else {
Toast.makeText(this, "Old Map", Toast.LENGTH_SHORT).show();
_map = (Map)data;
}
setContentView(_map);
}
#Override
public Object onRetainNonConfigurationInstance() {
Toast.makeText(this, "Saving Configuration...", Toast.LENGTH_LONG).show();
return _map;
}
}
I am going to suppose that the title I gave this forum-thread states the problem I am experiencing pretty thoroughly. I have also edited the code to try to save a string object and then recover the string object through the getLastNonConfigurationInstance(), just to see to what extent I could get it to work for me, but it still seemed to return null. I have not tried calling it from onStart() or onRestart() or onResume(), but from what I have read, it is usually only called from onCreate(Bundle) anyways. This has got me really confused... :/
I figured it may be of some help to know a little about my Map class object, so here's (some of) the code from it:
public class Map extends SurfaceView implements SurfaceHolder.Callback{
private MapThread _mapThread;
private int _terrain[][];
private ArrayList<Player> playerList;
private Bitmap _blueback;
private Bitmap _bluefront;
private Bitmap _blueleft;
private Bitmap _blueright;
private Bitmap _greenback;
private Bitmap _greenfront;
private Bitmap _greenleft;
private Bitmap _greenright;
private Bitmap _redfront;
private Bitmap _redback;
private Bitmap _redleft;
private Bitmap _redright;
private Bitmap _robot1;
private Bitmap _forest;
private Bitmap _grass;
private Bitmap _mountain;
private Bitmap _tree;
#SuppressWarnings("unused")
private boolean _mapLoaded = false;
private int _viewX = 0;
private int _viewY = 0;
Map(Context context){
super(context);
getHolder().addCallback(this);
_mapThread = new MapThread(this);
setFocusable(true);
_terrain = new int[100][100];
playerList = new ArrayList<Player>();
addPlayer(0, 0, 0);
_blueback = BitmapFactory.decodeResource(context.getResources(), R.drawable.blueback);
_bluefront = BitmapFactory.decodeResource(context.getResources(), R.drawable.bluefront);
_blueleft = BitmapFactory.decodeResource(context.getResources(), R.drawable.blueleft);
_blueright = BitmapFactory.decodeResource(context.getResources(), R.drawable.blueright);
_greenback = BitmapFactory.decodeResource(context.getResources(), R.drawable.greenback);
_greenfront = BitmapFactory.decodeResource(context.getResources(), R.drawable.greenfront);
_greenleft = BitmapFactory.decodeResource(context.getResources(), R.drawable.greenleft);
_greenright = BitmapFactory.decodeResource(context.getResources(), R.drawable.greenright);
_redback = BitmapFactory.decodeResource(context.getResources(), R.drawable.redback);
_redfront = BitmapFactory.decodeResource(context.getResources(), R.drawable.redfront);
_redleft = BitmapFactory.decodeResource(context.getResources(), R.drawable.redleft);
_redright = BitmapFactory.decodeResource(context.getResources(), R.drawable.redright);
_robot1 = BitmapFactory.decodeResource(context.getResources(), R.drawable.robot1);
_forest = BitmapFactory.decodeResource(context.getResources(), R.drawable.forest);
_grass = BitmapFactory.decodeResource(context.getResources(), R.drawable.grass);
_mountain = BitmapFactory.decodeResource(context.getResources(), R.drawable.mountain);
_tree = BitmapFactory.decodeResource(context.getResources(), R.drawable.tree);
TouchScreenHandler handler = new TouchScreenHandler(); // This includes 2 other nested-class threads
this.setOnTouchListener(handler);
}
Perhaps there lies something in the complexity of the Object returned by onRetainNonConfigurationInstance() that might be contributing to my problems?
Or lastly is there something (like a Activity or Application property) in my Manifest.xml file that is the problem?
If any further information is required, please let me know, as I will be checking back on this post frequently until I can get past this little bump in the road.
PS: I have this problem with both ADB and my device.
PSS: most importantly, a big THANKS to this community for the help and support, as it is greatly appreciated! :D
Have you confirmed that onRetainNonConfigurationInstance() is actually being called? I see you have a toast being displayed in it, but you don't actually say it is being shown. (And why a toast instead of just a Log.i()?)
As the documentation says, "this function is called purely as an optimization, and you must not rely on it being called." You can't rely on this actually happening. The only time it will happen in fact is when a configuration change happens while your activity is in the foreground, in which case the framework will in one feel swoop call onRetainNonConfigurationInstance() and destroy the current instance and immediately create a new instance with the retained object available to it. There should be pretty much nothing you can do that would prevent the object you return from appearing in the new instance... though I guess if you call finish() or such on the old one, that might do it.
Not sure about the solution to your problem there is another problem with your code. You shouldn't use onRetainNonConfigurationInstance for anything like that holds a reference to the activity context, otherwise you will leak the activity later.