I have a slight problem. Need a nudge in the right direction.
I am doing a video editor like Vine and/or instagram. Where they show a timeline with screencaps from the video
It just adds more pictures depending on the videos duration. What i did for my app is that i added a recyclerView. This recyclerview has an adapter that calls the following function every time onBindViewHolder
public Bitmap getFrameFromCurrentVideo(int seconds) {
Bitmap bitmap = null;
if(mMediaMetadataRetriever != null) {
bitmap = mMediaMetadataRetriever.getFrameAtTime(seconds * 1000000, MediaMetadataRetriever.OPTION_CLOSEST);
}
return bitmap;
}
This works and it adds the proper amount of images that i want. But the problem is that it is too heavy on the UI thread. Since the recyclerView is recycling everything. It then lags up every time it has to get a frame.
So i thought that i have to do some async task and then cache the images. But what i read is that AsyncTask is not recommended for recycler views since it recycles.
So what should i do to enchance the performance? Any good idea?
This is what i did to solve my problem.
I created async task and memory cache my result.
My adapter checks if the image already exist. If it does. Then we skip doing the background work. Otherwise i do the async task and try to load the image. We also tag the view just in case the user scrolls while the task is not finished.
This helps us check if the Tag is different from what the task have. If it is the same. Then we can safely put the right image in the imageview.
Snippet from my adapter
#Override
public void onBindViewHolder(PostVideoRecyclerViewHolder holder, int position) {
holder.mImageView.getLayoutParams().width = mScreenWidth / mMaxItemsOnScreen;
holder.mImageView.setImageDrawable(null);
int second = position * 3 - 3;
String TAG = String.valueOf(second);
holder.mImageView.setTag(TAG);
Bitmap bitmap = mFragment.getBitmapFromMemCache(TAG);
if(bitmap == null) {
PostVideoBitmapWorkerTask task = new PostVideoBitmapWorkerTask(holder.mImageView, TAG, mFragment);
task.execute(second);
}
else {
holder.mImageView.setImageBitmap(bitmap);
}
}
My AsyncTask class
public class PostVideoBitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private ImageView mImageView;
private String TAG;
private PostVideoFeedFragment mFragment;
public PostVideoBitmapWorkerTask(ImageView imageView, String TAG, PostVideoFeedFragment fragment) {
mImageView = imageView;
this.TAG = TAG;
mFragment = fragment;
}
#Override
protected Bitmap doInBackground(Integer... params) {
Bitmap bitmap = mFragment.getFrameFromCurrentVideo(params[0]);
mFragment.addBitmapToCache(TAG,bitmap);
return bitmap;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if(mImageView.getTag().toString().equals(TAG)) {
mImageView.setImageBitmap(bitmap);
}
}
}
Snippet from my fragment class
public void addBitmapToCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
I can recommend https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html if you want to read up on caching
And also https://developer.android.com/reference/android/os/AsyncTask.html for reading up on asyncTask's
Related
hello im loading lots of little images (ex: 180x180 10.21kb) from a LOCAL database into lots of different recycler views in fragments in viewpagers in tab layouts (nested fragments.) for instance each tab has a new fragment with a recycler view with these little cards and images everything works fine but eventually it all seems to get a bit much for the system and my app is force closed with an out of memory error the error points to my adapters onBindViewHolder, which looks like this
#Override
public void onBindViewHolder(MyViewHolder holder, int position){
addNewCard cardmaker = cardMakerListDB.get(position);
holder.cardText.setText(cardmaker.getCardName());
holder.speechText.setText(cardmaker.getCardSpeech());
Drawable drawable = new
BitmapDrawable(Utility.getPhoto(cardmaker.getCardIcon()));
holder.cardImage.setImageDrawable(drawable);
}
specifically its pointing at this line
BitmapDrawable(Utility.getPhoto(cardmaker.getCardIcon()));
this is getting a byte array from the database and converting it into a bitmap here is the method from my Utility class
public static Bitmap getPhoto(byte[] image) {
return BitmapFactory.decodeByteArray(image, 0, image.length);
}
can anyone see something that i cant, or that i could be doing to avoid this here im quite new to all this, or is there a different workaround any suggestions welcome
EDIT
Thought id try and be clever and have all the images load from an async task, I dont actually know if this would resolve my issue or not but i get this error
07-10 14:18:28.594 18486-18832/ss.sealstudios.com.socialstories D/skia: --- SkImageDecoder::Factory returned null
i understand it i just dont know what i need to be passing the method to fix it heres what ive tried
MY ENTIRE VIEW HOLDER TRYING TO IMPLEMENT ASYNC TASK
public class CardAdapterDB extends
RecyclerView.Adapter<CardAdapterDB.MyViewHolder> {
private List<addNewCard> cardMakerListDB;
public class MyViewHolder extends RecyclerView.ViewHolder{
public TextView cardText, speechText;
public ImageView cardImage;
public ProgressBar progress;
public MyViewHolder(View view){
super(view);
cardText = (TextView) view.findViewById(R.id.cardText);
speechText = (TextView) view.findViewById(R.id.speechText);
cardImage = (ImageView) view.findViewById(R.id.cardimage);
progress = (ProgressBar) view.findViewById(R.id.progressBarFetch);
}
}
public CardAdapterDB(List<addNewCard> cardMakerListDB){
this.cardMakerListDB = cardMakerListDB;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.card, parent, false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position){
addNewCard cardmaker = cardMakerListDB.get(position);
holder.cardText.setText(cardmaker.getCardName());
holder.speechText.setText(cardmaker.getCardSpeech());
holder.progress.setVisibility(View.VISIBLE);
startNewAsyncTask(cardmaker.getCardIcon(),holder.progress,holder);
}
private class MyAsyncTask extends AsyncTask<Bitmap, Void, Bitmap> {
private byte[] data;
private ProgressBar progress;
private MyViewHolder holder;
public MyAsyncTask(byte[] data,ProgressBar progress,MyViewHolder holder)
{
this.data = data;
this.progress = progress;
this.holder = holder;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
progress.setVisibility(View.VISIBLE);
progress.setIndeterminate(true);
}
#Override
protected Bitmap doInBackground(Bitmap... data) {
return BitmapFactory.decodeByteArray(this.data, 0, data.length);
}
#Override
protected void onPostExecute(Bitmap response) {
super.onPostExecute(response);
{
Drawable drawable = new BitmapDrawable(response);
holder.cardImage.setImageDrawable(drawable);
// progress.setVisibility(View.GONE);
}
}
}
public void startNewAsyncTask(byte[] image,ProgressBar progress,MyViewHolder
holder) {
MyAsyncTask asyncTask = new MyAsyncTask(image,progress,holder);
asyncTask.execute();
}
#Override
public int getItemCount(){
return cardMakerListDB.size();
}
}
Apparently the huge number of images causes your app to crash with OutOfMemoryError, and unfortunately you can do nothing if your app got this error, it will forced to be closed.
So, all you can do is to avoid the OutOfMemoryError, and this can be done by a lot of ways:
1. assign a largeHeap for your application:
you can do that by adding android:largeHeap="true" to the <application> tag inside your manifest.xml file.
2. override the onLowMemory method of your activity, which will enable you to take an action if the system feels like the memory is very low at some point:
#Override
public void onLowMemory() {
// you can here remove the Bitmaps or stopping the process of generating images or do whatever you want to survive being trapped into 'OutOfMemoryError'.
};
3. you can use any of the Image Libraries to changing the settings of your retrieved images, like reducing its resolution, reducing its size, and even reducing its color set, and the most common ones that are being used in this manner will be Universal-Image-Loader and Picasso, for example in Universal-Image-Loader you can use it in any process of downloading and displaying your Bitmap:
In your case, you have the Bitmap already loaded and all you want is to use an Image library to edit its options(here we will use UniversalImageLoader), in this case you can save the image as mentioned is this answer and after that load it from the memory with the options you gave to it:
// Saving image to disk
String filename = "some_name.jpg";
File sd = Environment.getExternalStorageDirectory();
File dest = new File(sd, filename);
Bitmap bitmap = (Bitmap)data.getExtras().get("data");
try {
FileOutputStream out = new FileOutputStream(dest);
bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
// this is an example of the used options
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
options.inJustDecodeBounds = true;
options.inDither = false;
options.inPurgeable = true;
options.inInputShareable = true;
// create ImageLoader instance
ImageLoader loader = ImageLoader.getInstance();
loader.loadImageSync(dest.getAbsolutePath(), options);
Try using Picasso Dependency : compile 'com.squareup.picasso:picasso:2.5.2
Picasso
.with(context)
.load(your_image)
.fit()
// call .centerInside() or .centerCrop() to avoid a stretched image
.into(your_imageview);
I have a RecyclerView adapter with many different ViewHolders. One of the ViewHolders contains an ImageView, which needs to be able to take a picture, resize it, then display it. For modularity, I want the ViewHolder to be self-contained: it and not the parent activity should handle everything concerning the photo taking and displaying process. Also the file path is constant (it will never change). In fact, it is /storage/emulated/0/com.company.app/myst/cat.jpg. As a result, here is my implementation of the ImageView’s onClick method.
#Override
public void onClick(View v) {
final FragmentManager fm = ((MyActivity) getContext()).getSupportFragmentManager();
Fragment auxiliary = new Fragment() {
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
resizeResaveAndDisplayPhoto();
super.onActivityResult(requestCode, resultCode, data);
fm.beginTransaction().remove(this).commit();
}
};
fm.beginTransaction().add(auxiliary, "FRAGMENT_TAG").commit();
fm.executePendingTransactions();
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (null != takePictureIntent.resolveActivity(view.getContext().getPackageManager())) {
((MyActivity)view.getContext()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
auxFragment.startActivityForResult(takePictureIntent, Constants.REQUEST_CODE_PHOTO);
}
}
When resizeResaveAndDisplayPhoto is called it executes the following AsyncTask
public static class ResizeThenLoadImageTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewWeakReference;
private final WeakReference<File> fileWeakReference;
private final WeakReference<Context> weakContext;
private final int reqHeight;
private final int reqWidth;
public ResizeThenLoadImageTask(Context context, ImageView imageView, File file, int reqHeight, int reqWidth) {
weakContext = new WeakReference<Context>(context);
imageViewWeakReference = new WeakReference<>(imageView);
fileWeakReference = new WeakReference(file);
this.reqHeight = reqHeight;
this.reqWidth = reqWidth;
}
#Override
public Bitmap doInBackground(String... params) {
File file = fileWeakReference.get();
Bitmap bitmap = null;
if (null != file) {
bitmap = ImageUtils.reduceImageSize(file, reqHeight, reqWidth);
ImageUtils.saveBitmapToGivenFile(bitmap, file);
}
return bitmap;
}
#Override
public void onPostExecute(Bitmap bitmap) {
if (null != imageViewWeakReference && null != fileWeakReference) {
ImageView imageView = imageViewWeakReference.get();
File file = fileWeakReference.get();
if (null != imageView) {
if (null != bitmap) {
imageView.setImageBitmap(bitmap);
}
else {
imageView.setImageResource(R.drawable.photo);
}
imageView.postDelayed(new Runnable() {
#Override
public void run() {
if (null != weakContext.get()) {
((MyActivity) weakContext.get()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
}
}, 10000);
}
}
}
}
You may notice that I lock the orientation before taking the photo and unlock it 10 seconds after displaying the photo. That trick is part of my troubleshooting. So here is the situation.
The system described above works very well. Problems happen in the following case
Say I already have a photo in the ImageView but want to replace it.
So I click on the ImageView to take a new photo.
If I rotate the device to take the new photo, then when I return the new photo displays briefly before the old photo returns.
So I lock to orientation to see what was happening. Here is what I found.
The new photo displays for as long as I lock the orientation. As soon as the orientation unlocks (10 sec), the old photo returns.
If I leave the activity and the returns, the old photo is still displaying.
If I close the app completely and then return, then I see the new photo.
When the device is rotated, the running activity is recreated as well the asyncTask attached to it, probably causing a leak.
You probably need to call the asyncTask inside of a service instead in an activity so the asyncTask is going to be attached to the life cycle of the service.
I'm trying to clear the cache memory of Picasso via Android coding.
Can anyone please help me in this issue..?
I have tried using the following code, but this was not useful in my case:
Picasso.with(getActivity()).load(data.get(pos).getFeed_thumb_image()).skipMemoryCache().into(image);
Use this instead :
Picasso.with(getContext()).load(data.get(pos).getFeed_thumb_image()).memoryPolicy(MemoryPolicy.NO_CACHE).into(image);
Remove cache of Picasso like this.
public class Clear {
public static void clearCache (Picasso p) {
p.cache.clear();
}
}
This util class can clear the cache for you. You just have to call it:
Clear.clearCache(Picasso.with(context));
EDIT:
The class Clear must be in the package :
package com.squareup.picasso;
Because cache is not accessible from outside that package.
Like in this answer: https://stackoverflow.com/a/23544650/4585226
if you are trying to load an image through Json(from db) try clearing the networkCache for a better result.
Picasso.with(context).load(uri).networkPolicy(NetworkPolicy.NO_CACHE)
.memoryPolicy(MemoryPolicy.NO_CACHE)
.placeholder(R.drawable.bv_logo_default).stableKey(id)
.into(viewImage_imageView);
Instead of clearing the complete cache if one wants to refresh the image with the given Uri. try this Picasso.with(context).invalidate(uri); it internally removes the key from the cache maintained by Picasso.
Excerpt from Picasso.java
/**
* Invalidate all memory cached images for the specified {#code uri}.
*
* #see #invalidate(String)
* #see #invalidate(File)
*/
public void invalidate(Uri uri) {
if (uri == null) {
throw new IllegalArgumentException("uri == null");
}
cache.clearKeyUri(uri.toString());
}
When activity destroy, unfortunately bitmap was not recycled if we're using Picasso. I try to programmatically recycle bitmap, what's loaded in to image view. There is a way to reference to loaded bitmap by using Target.
Target mBackgroundTarget = new Target() {
Bitmap mBitmap;
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
if (bitmap == null || bitmap.isRecycled())
return;
mBitmap = bitmap;
mBgImage.setImageBitmap(bitmap);
mHandler.post(new Runnable() {
#Override
public void run() {
// Do some animation
}
});
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
recycle();
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
/**
* Recycle bitmap to free memory
*/
private void recycle() {
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
mBitmap = null;
System.gc();
}
}
};
And when Activity destroy, I call onBitmapFailed(null) to recycle loaded bitmap.
#Override
protected void onDestroy() {
super.onDestroy();
try {
if (mBackgroundTarget != null) {
mBackgroundTarget.onBitmapFailed(null);
Picasso.with(context).cancelRequest(mBackgroundTarget);
}
} catch (Exception e) {
e.printStackTrace();
}
}
But remember, DON'T CACHE IMAGE IN MEMORY by this case, It will cause Use recycled bitmap exception.
Picasso.with(context)
.load(imageUrl)
.resize(width, height)
.memoryPolicy(MemoryPolicy.NO_CACHE)
.into(mBackgroundTarget);
Hope this help.
If you keep reference of your custom Downloader implementation you can clear cache.
public class PicassoUtil {
private static Picasso sInstance;
private static OkHttp22Downloader sDownloader;
public static Picasso getPicasso(Context context){
if(sInstance == null) {
sDownloader = new OkHttp22Downloader(context)
Picasso.Builder builder = new Picasso.Builder(context);
builder.downloader(sDownloader);
sInstance = builder.build(sDownloader);
}
return sInstance;
}
public static void clearCache(){
if(sDownloader != null){
sDownloader.clearCache();
}
}
}
It is important to have access to your http client and its Cache. In my implementation there is access to the cache, hence clearing cache with clearCache() method.
i had the same problem.
It worked for me.
I used Picasso in RecycleView inside a dialog. When i closed dialog, picasso doesnt clear cache. But while you are using the dialog it clears image cache. However there is some cache that is not cleared. Maybe the cache that was not cleared is the last you seen in dialog before dialog.dismiss().
use this
memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE)
Picasso.with(activity).load(file).resize(100,100).centerCrop().memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE).into(contactImage, new com.squareup.picasso.Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError() {
}
});
Picasso.with(this.getContext()).load(gamePlayer.getPlayerProfileUrl()).skipMemoryCache().into(iv);
This also works
I'm experiencing some choppyness when loading bitmaps into ImageViews from an AsyncTask.
I have a fragment in which I display information loaded from SQLite, and said information often times have photos attached. When my fragment is launched, it takes up to a second before it is displayed (the app appears to hang for a bit),
probably because of the heavy data loading, which tells me I might be doing something wrong.
Here is a stripped down version of my implementation:
public class InformationFragment extends Fragment {
private ArrayList<MyPhoto> mPhotos;
private LinearLayout mPhotoContainer;
private View mView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ....
mView = inflater.inflate(R.layout.fragment_information, container, false);
mPhotoContainer = (LinearLayout) mView.findViewById(R.id.fragment_information_photos_container);
return mView;
}
#Override
public void onResume() {
super.onResume();
loadInformation();
}
private void loadInformation() {
// Loads information from database and puts it into TextViews and such.
// Relatively performance heavy operations, should perhaps run off main, but it's not the cause
// of my problems as it was pretty smooth before I implemented photo attachments
}
private ArrayList<MyPhoto> getPhotos() {
// Loads photos from database. Not sure but could be pretty performance heavy, you tell me :)
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if(mPhotoContainer.getChildCount() == 0) {
mPhotos = getPhotos();
for (MyPhoto p : mPhotos) {
addImageViewForPhoto(p);
}
}
}
private void addImageViewForPhoto(MyPhoto p) {
final ImageView iv = new ImageView(getActivity());
mPhotoContainer.addView(iv);
new MyPhotoLoaderTask(iv).execute(p.getBytes());
}
}
The following is the MyPhotoLoaderTask class
public class MyPhotoLoaderTask extends AsyncTask<byte[], Void, Bitmap> {
private final WeakReference<ImageView> mWeakImageView;
public MyPhotoLoaderTask(ImageView iv) {
mWeakImageView = new WeakReference<ImageView>(iv);
}
#Override
protected Bitmap doInBackground(byte[]... params) {
return MyPhotoUtils.createBitmap(params[0], 100, 100);
}
#Override
protected void onPostExecute(final Bitmap result) {
if(mWeakImageView != null && result != null) {
final ImageView iv = mWeakImageView.get();
iv.setImageBitmap(result);
}
}
}
And lastly, the MyPhotoUtils.createBitmap() method
public static Bitmap createBitmap(byte[] bytes, int reqWidth, int reqHeight) {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
opts.inSampleSize = getInSampleSize(opts, reqWidth, reqHeight);
opts.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
}
I don't mind the fact that loading the images takes a while, but I would love for the fragment to load with all of the text information already present,
and then have the images appear one by one after the user has begun interaction with the fragment. The current situation is pretty bad in my opinion, and I fear it will become even worse if the user will attach even more photos to the information.
I am working with some Acitivitys , it's working pretty well. But i am having a strange delay on it.
And I figure it out that, it was about this part of the code, where I am loading stored image in the SDCard.
if(p.getAdress() != null){
Bitmap bitmap = BitmapFactory.decodeFile(p.getAdress());
new_image.setBackgroundDrawable(null);
new_image.setImageBitmap(bitmap);
}
Why this simple code is taking too long to execute?
How to solve it?
If I take this code off, everything works as i wished.
You shouldn't load large bitmaps directly on the UI thread.
Actually, the best reference to loading large Bitmaps efficiently can be found here.
And right here you can find good information on how you can load them using an AsyncTask.
This is the method they show you there (you can even download the sample!)
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
public void loadBitmap(int resId, ImageView imageView) {
if (cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
Your can try this way. It help you
private class LongOperation extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... params) {
if(p.getAdress() != null){
Bitmap bitmap = BitmapFactory.decodeFile(p.getAdress());
new_image.setBackgroundDrawable(null);
new_image.setImageBitmap(bitmap);
}
return "Executed";
}
#Override
protected void onPostExecute(String result) {
}
#Override
protected void onPreExecute() {
}
#Override
protected void onProgressUpdate(Void... values) {
}
}