I am trying to display the images in galleryViews which are in ListView. I am downloading images from server to SDCard and then displaying it, first I am checking images in cache if image is not there then I am loading it from sdcard.
when the user starts the app for the first time I am downloading the images from server and saving them to sdcard in the mean time iam showing the activity with text only
I want if the image is not there in sdcard the after downloading the image it should display the image as soon as the image is download here is what I am doing.
public class AsyncImageLoader {
private boolean isImageView;
private final LinkedHashMap<String, Bitmap> cache =
new LinkedHashMap<String, Bitmap>(60, (float) 1.0, true);
Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
LoadImageFromSdCard loadImage = (LoadImageFromSdCard) msg.obj;
if (isImageView) {
loadImage.imageView.setImage(loadImage.bmp);
} else {
Thread thread = new Thread(new LoadImageFromSdCard(loadImage.uri, loadImage.imageView));
try {
Log.i("AsyncImageLoader", "SECOND THREAD STARTED");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.start();
}
}
};
public Bitmap loadImage(String uri,
ImageTextComboControl imageView) {
if (cache.containsKey(uri)) {
return cache.get(uri);
} else {
handler.post(new LoadImageFromSdCard(uri, imageView));
}
return null;
}
private class LoadImageFromSdCard implements Runnable {
String uri;
ImageTextComboControl imageView;
ImageView image;
Bitmap bmp = null;
public LoadImageFromSdCard(String uri, ImageTextComboControl imageView) {
this.uri = uri;
this.imageView = imageView;
}
public void run() {
FileInputStream fis;
try {
fis = new FileInputStream(new File(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
bmp = BitmapFactory.decodeStream(fis);
if (imageView != null) {
isImageView = true;
cache.put(uri, bmp);
Message message = new Message();
message.obj = this;
handler.sendMessage(message);
}
}
}
}
Thanx
Is much better use Async Task class http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html (using AsyncTask section) and, important, take care about large bitmap
http://developer.android.com/training/displaying-bitmaps/index.html
I solved my problem using onContentChanged() method in adapter. I am saving images to sdCard and getting the sdCard path saving it into sqlite database so when ever the data is changing in sqlite database onContentChanged method is called because I am using Cursor Adapter.
Related
I'm developing music player and I use picture from audio files to display it in UI of my app, if I change music very fast (previous, next button) then I can get java.lang.RuntimeException: Canvas: trying to use a recycled bitmap, but popular players from Play market doesn't have this problem if I change music very fast. How can I avoid this error as well as in other music apps?
Similar question with the same error didn't help me
MediaService class
MediaMetadataRetriever mMetaRetriever = new MediaMetadataRetriever();
mMetaRetriever.setDataSource(songPath);
byte[] art = mMetaRetriever.getEmbeddedPicture();
Bitmap iconUrl = null;
try {
if (art != null) {
iconUrl = BitmapFactory.decodeByteArray(art, 0, art.length);
}
} catch (Exception e) {
}
UI class
try {
// with or without it error happens anyway
/*if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
mBitmap = null;
mLogo.setImageBitmap(null);
}*/
mBitmap = metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART);
mLogo.setImageBitmap(mBitmap);
} catch (Exception e) {
}
I just decided to use AsyncTask and cancel execution (mImageViewAsync.cancel(true);) for current instance when a new instance is created and executed, so in this case onPostExecute is triggered less then doInBackground, so now it be difficult for app to crash (but it still can in some cases, but it's not important so)
private ImageViewAsync mImageViewAsync;
private void updateMetaData(MediaMetadataCompat metadata) {
//...
mLogo.setImageBitmap(null);
if (mImageViewAsync != null) {
mImageViewAsync.cancel(true);
}
mImageViewAsync = new ImageViewAsync();
mImageViewAsync.execute(metadata);
}
private class ImageViewAsync extends AsyncTask<MediaMetadataCompat,Void,Bitmap> {
#Override
protected Bitmap doInBackground(MediaMetadataCompat... meta) {
return meta[0].getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART);
}
#Override
protected void onPostExecute(Bitmap bitmap) {
mLogo.setImageBitmap(bitmap);
}
}
I am a beginner in Android app dev. Recently I am trying to make a face detection app using FaceDetector.
In the code below is my main activity class. I have a Runnable to loop through a set of images, convert to bitmaps and send them to FaceOverlayView to find and draw faces. The loop can go through all 4 items.
However, as I tried to make it loop all over again by setting index to 0 after the last item, the app crashes. I observed the behavior that the app will crash trying to reload an image loaded before. Would you please give me any insight on this? Many thanks in advance.
public class MainActivity extends AppCompatActivity {
private FaceOverlayView mFaceOverlayView;
Handler handler;
Thread thread;
ArrayList<Integer> imgID = new ArrayList<Integer>(Arrays.asList(R.raw.face1, R.raw.face2, R.raw.face3, R.raw.face4));
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mFaceOverlayView = (FaceOverlayView) findViewById(R.id.face_overlay);
thread = new Thread(new myThread());
thread.start();
handler = new Handler() {
#Override
public void handleMessage(Message msg) {
if (msg.arg1 == -1) {
//thread.interrupt();
}
Bitmap bitmap = (Bitmap) msg.obj;
mFaceOverlayView.setBitmap(bitmap);
}
};
}
class myThread implements Runnable {
#Override
public void run() {
int index = 0;
while (index < imgID.size()) {
InputStream stream = getResources().openRawResource(imgID.get(index));
Bitmap bitmap = BitmapFactory.decodeStream(stream);
Message msg = Message.obtain();
msg.obj = bitmap;
handler.sendMessage(msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
index++;
if (index >= imgID.size()) {
index = 0;
}
bitmap.recycle();
bitmap = null;
}
}
}
}
I' m using Picasso to help the cache of images.
The question is, how can I access the downloaded image to make a share intent?
any ideas? thanks!
I hope you can understand my question :-)
Sorry for my delay, I found a solution, but, not a good one...
First, I really searched for a while and looked at the code of Picasso. It seems like you should provide your own downloader and other stuff. But then, why should I use the lib...
And then, I suppose it's Picasso's design / architecture to just cache the file in the internal storage. Maybe because the external storage is not always available (like the user may plug in his SD card to his computer), or maybe because the external storage is not as fast as the internal... That's my guess. In a word, other apps cannot access the internal storage of the current app, so the share cannot be done.
Thus, I made a really ordinary solution. I just wait for Picasso to give the Bitmap, and compress it to a file in the external file, then do the share. It seems like a bad solution, but it really solves the problem, yes...
You should be aware of whether the external cache directory is available or not. If not, you cannot do the share. And you need to put the compress task in a background thread, so, waiting the external file cached... Does it seem like a bad solution? I think so...
Below is my project code, you can have a try...
private boolean mSaved; // a flag, whether the image is saved in external storage
private MenuItem mShare;
private Intent mIntent;
private ShareActionProvider mShareActionProvider;
private File mImage; // the external image file would be saved...
private Target target = new Target() {
#Override
public void onBitmapLoaded(final Bitmap bitmap, Picasso.LoadedFrom from) {
new Thread(new Runnable() {
#Override
public void run() {
FileOutputStream os = null;
try {
String dir = CatnutUtils.mkdir(getActivity(), Constants.FANTASY_DIR); // check the exteral dir avaiable or not...
String[] paths = Uri.parse(mUrl).getPath().split("/");
mImage = new File(dir + File.separator + paths[2] + Constants.JPG); // resoleve the file name
} catch (Exception e) { // the external storage not available...
Log.e(TAG, "create dir error!", e);
return;
}
try {
if (mImage.length() > 10) { // > 0 means the file exists
// the file exists, done.
mIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(mImage));
mSaved = true;
return;
}
os = new FileOutputStream(mImage);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
mIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(mImage));
mSaved = true;
} catch (FileNotFoundException e) {
Log.e(TAG, "io error!", e);
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
Log.e(TAG, "io closing error!", e);
}
}
}
}
}).start();
mFantasy.setImageBitmap(bitmap);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
mFantasy.setImageDrawable(errorDrawable);
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
if (placeHolderDrawable != null) {
mFantasy.setImageDrawable(placeHolderDrawable);
}
}
};
#Override
public void onPrepareOptionsMenu(Menu menu) {
mShare.setEnabled(mSaved);
}
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fantasy, menu);
mShare = menu.findItem(R.id.action_share);
mShareActionProvider = (ShareActionProvider) mShare.getActionProvider();
mShare.setActionProvider(mShareActionProvider);
mShareActionProvider.setShareIntent(mIntent);
}
Finally, call Picasso.with(getActivity()).load(mUrl).into(target);
When the file is saved, the user can click the share menu do the share.
public static File getImageFile(Context context, String url)
{
final String CACHE_PATH = context.getCacheDir().getAbsolutePath() + "/picasso-cache/";
File[] files=new File(CACHE_PATH).listFiles();
for (File file:files)
{
String fname= file.getName();
if (fname.contains(".") && fname.substring(fname.lastIndexOf(".")).equals(".0"))
{
try
{
BufferedReader br=new BufferedReader(new FileReader(file));
if (br.readLine().equals(url))
{
File imgfile= new File(CACHE_PATH + fname.replace(".0", ".1"));
if (imgfile.exists())
{
return imgfile;
}
}
}
catch (FileNotFoundException|IOException e)
{
}
}
}
return null;
}
I've just found out this guide with a very good solution.
https://guides.codepath.com/android/Sharing-Content-with-Intents
The code will be like this:
// Can be triggered by a view event such as a button press
public void onShareItem(View v) {
// Get access to bitmap image from view
ImageView ivImage = (ImageView) findViewById(R.id.ivResult);
// Get access to the URI for the bitmap
Uri bmpUri = getLocalBitmapUri(ivImage);
if (bmpUri != null) {
// Construct a ShareIntent with link to image
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, bmpUri);
shareIntent.setType("image/*");
// Launch sharing dialog for image
startActivity(Intent.createChooser(shareIntent, "Share Image"));
} else {
// ...sharing failed, handle error
}
}
// Returns the URI path to the Bitmap displayed in specified ImageView
public Uri getLocalBitmapUri(ImageView imageView) {
// Extract Bitmap from ImageView drawable
Drawable drawable = imageView.getDrawable();
Bitmap bmp = null;
if (drawable instanceof BitmapDrawable){
bmp = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
} else {
return null;
}
// Store image to default external storage directory
Uri bmpUri = null;
try {
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS), "share_image_" + System.currentTimeMillis() + ".png");
file.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(file);
bmp.compress(Bitmap.CompressFormat.PNG, 90, out);
out.close();
bmpUri = Uri.fromFile(file);
} catch (IOException e) {
e.printStackTrace();
}
return bmpUri;
}
It basically consists in retrieving the bitmap from the imageview and saving it to a local temp file and then using it for sharing. I've tested it and it seems to work fine.
I am writing this application where I get live images from a tcp connection and I need to display them on an ImageViev.
What I am doing is calling the asynctask inside the button click. but it seem to create a number of background threads.
this is the code for the button click event
btnLive.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
try
{
String name = ((Button)v).getText().toString();
if(name.equalsIgnoreCase("Live"))
{
DataOutputStream dos;
DataInputStream dis;
String reply;
if(config.conn.isConnected())
{
dos = new DataOutputStream(config.conn.getOutputStream());
dos.writeBytes("STREAM-LIVE-IMAGES");
dos.flush();
//dis = new DataInputStream(in);
in = config.conn.getInputStream();
while (true)
{
new myTask().execute(in);
}
}
}
}
catch(Exception ex)
{
Log.d("Live Button ", "Exception " + ex.getMessage() );
}
}
});
and this the code for the asyncTask
class myTask extends AsyncTask<InputStream, Integer, Bitmap> {
protected Bitmap doInBackground(InputStream...in)
{
Bitmap bmp = null;
try
{
//Do some none UI stuff here and return a value *result
byte[] rcvPacket = ReadJpegBinaryAndRemoveDelimiter(in[0]);
bmp = BitmapFactory.decodeByteArray(rcvPacket, 0, rcvPacket.length);
Log.d("Live Image Streaming ", "Recieved Images: " + rcvPacket.length + " " + bmp);
} catch (Exception e) {
e.printStackTrace();
}
return bmp;
}
while (true) {
new myTask().execute(in);
}
because you are executing AsyncTask in loop, in your case in infinite loop, it's not sounds very well, you need to change it.
You need to execute in outside of loop. You shouldn't, musn't do it.
Just call
new myTask().execute(in);
without loop.
I managed to sort it out with a background thread because asynctask wasnt the right option for my issue.
So I created a separate background thread and used a post runnable method on the imageview to update the UI.
My app is a Wifi chat app with which you can communicate between two Android units with text messages and snap camera pictures and send them. The pictures are stored to the SD-card.
I used to have an OutOfMemoryError thrown after a couple of sent images, but I solved that problem by sending the
options.inPurgeable = true;
and
options.inInputShareable = true;
to the BitmapFactory.decodeByteArray method. This makes the pixels "deallocatable" so new images can use the memory. Thus, the error no longer remains.
But, the internal memory is still full of images and the "Low on space: Phone storage space is getting low" warning appears. The app no longer crashes but there's no more memory on the phone after the app finishes. I have to manually clear the app's data in Settings > Applications > Manage Applications.
I tried recycling the bitmaps and even tried to explicitly empty the app's cache, but it doesn't seem to do what i expect.
This function receives the picture via a TCP socket, writes it to the SD-card and starts my custom Activity PictureView:
public void receivePicture(String fileName) {
try {
int fileSize = inStream.readInt();
Log.d("","fileSize:"+fileSize);
byte[] tempArray = new byte[200];
byte[] pictureByteArray = new byte[fileSize];
path = Prefs.getPath(this) + "/" + fileName;
File pictureFile = new File(path);
try {
if( !pictureFile.exists() ) {
pictureFile.getParentFile().mkdirs();
pictureFile.createNewFile();
}
} catch (IOException e) { Log.d("", "Recievepic - Kunde inte skapa fil.", e); }
int lastRead = 0, totalRead = 0;
while(lastRead != -1) {
if(totalRead >= fileSize - 200) {
lastRead = inStream.read(tempArray, 0, fileSize - totalRead);
System.arraycopy(tempArray, 0, pictureByteArray, totalRead, lastRead);
totalRead += lastRead;
break;
}
lastRead = inStream.read(tempArray);
System.arraycopy(tempArray, 0, pictureByteArray, totalRead, lastRead);
totalRead += lastRead;
}
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pictureFile));
bos.write(pictureByteArray, 0, totalRead);
bos.flush();
bos.close();
bos = null;
tempArray = null;
pictureByteArray = null;
setSentence("<"+fileName+">", READER);
Log.d("","path:"+path);
try {
startActivity(new Intent(this, PictureView.class).putExtra("path", path));
} catch(Exception e) { e.printStackTrace(); }
}
catch(IOException e) { Log.d("","IOException:"+e); }
catch(Exception e) { Log.d("","Exception:"+e); }
}
Here's PictureView. It creates a byte[ ] from the file on the SD-card, decodes the array to a Bitmap, compresses the Bitmap and writes it back to the SD-card. Lastly, in the Progress.onDismiss, the picture is set as the image of a full screen imageView:
public class PictureView extends Activity {
private String fileName;
private ProgressDialog progress;
public ImageView view;
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
Log.d("","onCreate() PictureView");
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
view = new ImageView(this);
setContentView(view);
progress = ProgressDialog.show(this, "", "Laddar bild...");
progress.setOnDismissListener(new OnDismissListener() {
public void onDismiss(DialogInterface dialog) {
File file_ = getFileStreamPath(fileName);
Log.d("","SETIMAGE");
Uri uri = Uri.parse(file_.toString());
view.setImageURI(uri);
}
});
new Thread() { public void run() {
String path = getIntent().getStringExtra("path");
Log.d("","path:"+path);
File pictureFile = new File(path);
if(!pictureFile.exists())
finish();
fileName = path.substring(path.lastIndexOf('/') + 1);
Log.d("","fileName:"+fileName);
byte[] pictureArray = new byte[(int)pictureFile.length()];
try {
DataInputStream dis = new DataInputStream( new BufferedInputStream(
new FileInputStream(pictureFile)) );
for(int i=0; i < pictureArray.length; i++)
pictureArray[i] = dis.readByte();
} catch(Exception e) { Log.d("",""+e); e.printStackTrace(); }
/**
* Passing these options to decodeByteArray makes the pixels deallocatable
* if the memory runs out.
*/
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inInputShareable = true;
Bitmap pictureBM =
BitmapFactory.decodeByteArray(pictureArray, 0, pictureArray.length, options);
OutputStream out = null;
try {
out = openFileOutput(fileName, MODE_PRIVATE);
/**
* COMPRESS !!!!!
**/
pictureBM.compress(CompressFormat.PNG, 100, out);
pictureBM = null;
progress.dismiss(); }
catch (IOException e) { Log.e("test", "Failed to write bitmap", e); }
finally {
if (out != null)
try { out.close(); out = null; }
catch (IOException e) { }
} }
}.start();
}
#Override
protected void onStop() {
super.onStop();
Log.d("","ONSTOP()");
Drawable oldDrawable = view.getDrawable();
if( oldDrawable != null) {
((BitmapDrawable)oldDrawable).getBitmap().recycle();
oldDrawable = null;
Log.d("","recycle");
}
Editor editor =
this.getSharedPreferences("clear_cache", Context.MODE_PRIVATE).edit();
editor.clear();
editor.commit();
}
}
When the user presses the back key, the picture isn't supposed to be available anymore from within the app. Just stored on the SD-card.
In onStop() I recycle the old Bitmap and even try to empty the app's data. Still the "Low on space" warning appears. How can I be sure the images won't allocate the memory anymore when they're not needed?
EDIT: It appears the problem is the compress method. If everything after compress is commented, the problem remains. If I delete compress, the problem disappears. Compress seems to allocate memory that's never released, and it's 2-3 MB per image.
Ok, I solved it. The problem was, I was passing an OutputStream to compress, which is a stream to a private file in the app's internal memory. That's what I set as the image later. This file is never allocated.
I didn't get that I had two files: one on the SD-card and one in the internal memory, both with the same name.
Now, I'm just setting the SD-card file as the ImageView's image. I never read the file into the internal memory as a byte[], thus never decoding the array to a bitmap, thus never compressing the bitmap into the internal memory.
This is the new PictureView:
public class PictureView extends Activity {
public ImageView view;
private String path;
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
Log.d("","onCreate() PictureView");
path = getIntent().getStringExtra("path");
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
view = new ImageView(this);
setContentView(view);
Uri uri = Uri.parse( new File(path).toString() );
view.setImageURI(uri);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
Log.d("","Back key pressed");
Drawable oldDrawable = view.getDrawable();
if( oldDrawable != null) {
((BitmapDrawable)oldDrawable).getBitmap().recycle();
oldDrawable = null;
Log.d("","recycle");
}
view = null;
}
return super.onKeyDown(keyCode, event);
}
}
Is it bad practice to put an external file as the image of an ImageView? Should I load it into internal memory first?
If you specifically want the image to be nullified from memory for sure when a user presses back you could override the back button and make your image clean up calls there. I do that in some of my apps and it seems to work. maybe something like this:
#Override
protected void onBackPressed() {
super.onBackPressed();
view.drawable = null;
jumpBackToPreviousActivity();
}
Im pretty sure there are some view methods that clear other caches and things like that. You can recycle the bitmap but that doesnt guarantee that it will be dumped right then but only at some point when the gc gets to it.....but Im sure you probably know that already :)
EDIT: You could also do the same thing in the onPause method. That one is guaranteed to get called. The other two may never get called according to the android docs.
http://developer.android.com/reference/android/app/Activity.html