Hi I am new to android:
I am displaying image thumbs in GridView. For better performance I am loading it asynchronously.
My AsyncTask is as:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
private String image_path;
public BitmapWorkerTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
#Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
Bitmap picture = BitmapFactory.decodeFile(image_path);
int width = picture.getWidth();
int height = picture.getHeight();
float aspectRatio = (float) width / (float) height;
int newWidth = 98;
int newHeight = (int) (98 / aspectRatio);
return picture = Bitmap.createScaledBitmap(picture, newWidth,
newHeight, true);
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
public void onDismiss(DialogInterface dialog) {
this.cancel(true);
}
}
and Calling form:
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater layoutInflater = (LayoutInflater) ctx
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ListRowHolder listRowHolder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.ll_sponsor_list_item,
parent, false);
listRowHolder = new ListRowHolder();
listRowHolder.imgSponsor = (ImageView) convertView
.findViewById(R.id.imggrid_item_image);
convertView.setTag(listRowHolder);
} else {
listRowHolder = (ListRowHolder) convertView.getTag();
}
try {
BitmapWorkerTask task = new BitmapWorkerTask(
listRowHolder.imgSponsor);
task.image_path = ImageName.get(position);
task.execute(1);
} catch (Exception e) {
Toast.makeText(ctx, e + "", Toast.LENGTH_SHORT).show();
}
return convertView;
}
The problem here is the tasks are running in background even I clicked on back button.
Use cancel() to stop a AsyncTask:
task.cancel(true);
The documentation of AsyncTask provides some additional details in the 'Cancelling a task' section:
A task can be cancelled at any time by invoking cancel(boolean). Invoking
this method will cause subsequent calls to isCancelled() to return true.
After invoking this method, onCancelled(Object), instead of
onPostExecute(Object) will be invoked after doInBackground(Object[]) returns. To ensure that a task is cancelled as quickly as possible, you should always check the return value of isCancelled() periodically from doInBackground(Object[]), if possible (inside a loop for instance.)
First, you need to keep references to every asynctask you execute. When the activity pauses, you should iterate through the references to the asynctask and cancel them with cancel(). You should also call get() on each of the asynctasks. This will make sure that the UI thread waits until the asynctask is finished before changing activities.
Use cancel(true) if you want the asynctask's thread to be interrupted, or use cancel(false), and at points in your doInBackground() method you should check isCancelled() and return.
For safety you must be very careful with asynctasks. Check out this article on safely handling them.
Make something like this
private class myAsyncTask extends AsyncTask<Void, String, Void> {
private boolean isTaskCancelled = false;
public void cancelTask(){
isTaskCancelled = true;
}
private boolean isTaskCancelled(){
return isTaskCancelled;
}
protected Void doInBackground( Void... ignoredParams ) {
//Do some stuff
if (isTaskCancelled()){
return;
}
}
protected void onPostExecute( Void array )
{
//Do something
}
protected void onProgressUpdate(String... values)
{
//Do something
}
}
When you press back on the Activity, put the asynctask in cancelled mode, so it will stop executing things.
Check for the cancelled method in every step you make in your asynctask, to stop it on the next line
Related
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
I am using an async call inside my Custom Adapter class to load images in a Listview. However, the images are not shown till I scroll up/down atleast slightly. Please let me know what might be the reason. Thanks for your help.
private class LoadImage extends AsyncTask<Void, Void, Void>
{
private String fileName = null;
private ImageView imgViewToLoad = null;
LoadImage(String fileName, ImageView imgViewToLoad)
{
this.fileName = fileName;
this.imgViewToLoad = imgViewToLoad;
}
#Override
protected Void doInBackground(Void... args)
{
return null;
}
//This is executed on main UI thread
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
Utils.mImageFetcher.loadImage(fileName, imgViewToLoad);
}
}
This is in my getView()
----------------------
ViewHolderIncomingImg holderInImg = null;
if (null != convertView &&
(convertView.getTag() instanceof ViewHolderIncomingImg && !isFirstMsg))
{
holderInImg = (ViewHolderIncomingImg) convertView.getTag();
if (!multiSelectionMode)
{
if (!mSelectedItemsIds.get(position) && convertView.isActivated())
convertView.setActivated(false);
}
}
else
{
convertView = mInflater.inflate(R.layout.conv_list_item_incoming_img, parent, false);
holderInImg = new ViewHolderIncomingImg();
..............
new LoadImage(convRowItems.get(position).getMessage(), holderInImg.image).execute();
As Listview use the concept of recycling its view. The images are not showing when the async call finish.
So go for listview lazy loading approach it will help you.
I am using asynckTask to decode a file and onPostExecute I setImageBitmap and call progressDialog.dismiss() but after the progressDialog is dismissed the imageView take a few seconds to show the image. What I want is the progressDialog to disappear only when the image view is ready for the user. My code is below:
class MyTask extends AsyncTask<String, Integer, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private String mainImageString;
private Context context;
ProgressDialog progressDialog;
public MyTask(ImageView imageView, Context mContext) {
imageViewReference = new WeakReference<ImageView>(imageView);
context = mContext;
}
#Override
protected void onPreExecute() {
progressDialog= ProgressDialog.show(MyClass.this, "Progress Dialog Title Text","Process Description Text", true);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(String... params) {
mainImageString = params[0];
return BitmapFactory.decodeFile(mainImageString);
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
progressDialog.dismiss();
}
}
}
}
When you are setting your Bitmap basically you are "building" it on the UI thread. What I recommend is to call setImageBitmap in your doInBackground callback and then dismiss the dialog.
You can do this...
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
handler.postDelayed(new Runnable() {
public void run() {
progressDialog.dismiss();
}
}, 1000); //time for image to appear in image view
}
}
Please note this is not a foolproof way..but this what-easy way-currently I can think of..
Other hack I can think about is to extend ImageView and override onDraw().call super() in onDraw(),once the call to super() returns then dismiss the dialog box.This is more accurate way of doing it.
something like this:
onDraw(){
super();
onDismissDialogBox()//send an even to activity to dismiss dialog box
}
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) {
}
}
I need to cancel my asyncthread . In my application I am doing some heavy calculations, and I want to give user ability to cancel calculations(and then retry). I read on forums, that you can't just stop task from what is it doing, and that you need to check if task isCancelled=true inside your DoinBackground code. But that doesn't work for me.
Task itself is working great and it outputs correct data if I leaved it to end on itself.
In my App first I call function naredi_pdf_start(view), then when the task is running, if I call close_pdf1(view), it gives me an error.(I am changing views and app can't find my pdf_text1 Textview when calling publishProgress- null pointer exception). I really dont know how to use task.cancel(true) method (in my case: start_pdf.cancel(true))).
Here is my code:
String progress_pdf;
naredi_pdf start_pdf;
public void naredi_pdf_start(View view) {
start_pdf=new naredi_pdf();
start_pdf.execute();
}
public void close_pdf1(View view) {
if(start_pdf!=null) {
Log.v("not null","not null");
start_pdf.cancel(true);
setContentView(R.layout.other_view); //This is where
//I don't have TextView pdf_text1
}
}
private class naredi_pdf extends AsyncTask<Void, String, Void> {
protected Void doInBackground( Void... ignoredParams ) {
progress_pdf="Calculating Statistical Data";
//A LOT OF CODING
for(int i = 0; i < 1; i++) {
if(isCancelled()) {
break;
}
else {
publishProgress("Calculating team statistics");
}
}
//MORE OF CODING
for (int i = 0; i < 1; i++) {
if (isCancelled()) {
break;
}
else {
publishProgress("Calculating player's BIO");
}
}
//MORE OF CODING
for (int i = 0; i < 1; i++) {
if (isCancelled()) {
break;
}
else {
publishProgress("Calculating player's individual performance");
}
}
return null;
}
protected void onPostExecute( Void array ) {
//saving to database
}
protected void onProgressUpdate(String... values) {
progress_pdf=values[0]+"\n"+progress_pdf;
if (isCancelled()) {
}
else {
TextView pdf_text1 = (TextView) findViewById (R.id.pdf_text1);
pdf_text1.setText(progress_pdf);
// dialog(view);
}
}
}
Your problem is not that you can't cancel the AsyncTask. You probably get NullPointerException because your call to setContentView() goes through before AsyncTask.cancel() has been successful. A onProgressUpdate() gets called, only to find that the layout is now changed and there is no Viewwith id=R.id.pdf_text1!
From documentation on AsyncTask.
A task can be cancelled at any time by invoking cancel(boolean). Invoking this method will cause subsequent calls to isCancelled() to return true. After invoking this method, onCancelled(Object), instead of onPostExecute(Object) will be invoked after doInBackground(Object[]) returns. To ensure that a task is cancelled as quickly as possible, you should always check the return value of isCancelled() periodically from doInBackground(Object[]), if possible (inside a loop for instance.)
Since onCancelled() runs on the UI thread, and you are certain that no subsequent calls to onProgressUpdate() will occure, it's is a great place to call setContentView().
Override onCancelled() in you AsyncTask
private class naredi_pdf extends AsyncTask<Void, String, Void> {
protected Void doInBackground( Void... ignoredParams ) { // YOUR CODE HERE}
protected void onPostExecute( Void array ) { // YOUR CODE HERE}
protected void onProgressUpdate(String... values) {// YOUR CODE HERE}
// ADD THIS
#Override
protected void onCancelled() {
// Do not call super.onCancelled()!
// Set the new layout
setContentView(R.id.other_layout);
}
}
Change close_pdf1()
public void close_pdf1(View view) {
if(start_pdf!=null) {
Log.v("not null","not null");
start_pdf.cancel(true);
}
}
And you should have an AsyncTask that automatically changes your layout when cancelled. Hopefully you should not encounter any NullPointerException either. Haven't tried the code though :)
Edit
If you feel fancy, follow Rezooms advice on using return.
for(int i = 0; i < 1; i++) {
if(isCancelled()) {
return null;
}
.
.
.
}
The return statement cancels the execution of the doInBackground method, not break.
isCancelled is a propietary method of AsyncTask class.
You should define a private boolean property on your extended class, do something like this
private class myAsyncTask extends AsyncTask<Void, String, Void> {
private boolean isTaskCancelled = false;
public void cancelTask(){
isTaskCancelled = true;
}
private boolean isTaskCancelled(){
return isTaskCancelled;
}
protected Void doInBackground( Void... ignoredParams ) {
//Do some stuff
if (isTaskCancelled()){
return;
}
}
protected void onPostExecute( Void array )
{
//Do something
}
protected void onProgressUpdate(String... values)
{
//Do something
}
}