I'm trying to implement a slide show for my App and I seem to be getting stuck on what seems like a small detail.
I'm new to Android and haven't been programming long so I'm not great with threading yet.
Anyways I have an ArrayList which is great. When I try to loop through the ArrayList replacing the images in my ImageView with images in the ArrayList however, I only end up seeing the final image in the List.
I can see in my LogCat that the images are being set (at least that section of the code is running. It is sleeping as I asked (I can notice the 1000ms in the LogCat entries.
I'm doing this in an AsyncTask and trying to set the results from the onPostExecute(). Here is that code...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_slide_show);
screenSize = MediaHelper.getScreenSize(this);
Bundle extras = getIntent().getExtras();
Long projectId;
imageList = new ArrayList<Bitmap>();
ivSlide = (ImageView)findViewById(R.id.iv_slide);
tvLoading = (TextView)findViewById(R.id.tv_loading);
tvLoading.setVisibility(View.INVISIBLE);
if(extras != null){
projectId = extras.getLong("projectId");
new GetImagesTask().execute(projectId);
}
}
class GetImagesTask extends AsyncTask<Long, Void, ArrayList<Bitmap>>{
#Override
protected ArrayList<Bitmap> doInBackground(Long... id) {
long projectId = id[0];
Project project = getProject(projectId);
ArrayList<Bitmap> i = new ArrayList<Bitmap>();
File[] files = MediaHelper.getProjectFilenames(project.getImagePath());
if(files != null && files.length > 0){
for(File aFile : files){
Bitmap bitmap = MediaHelper.getScaledImage(aFile, screenSize.y / 2, screenSize.x / 2);
i.add(bitmap);
Log.d(TAG, "image added...size = " + bitmap.getByteCount());
}
}
return i;
}
#Override
protected void onPostExecute(ArrayList<Bitmap> result) {
if(result != null && result.size() > 0){
imageList = result;
if(imageList.size() > 0){
ivSlide.post(new Runnable(){
#Override
public void run(){
try{
for(Bitmap bm : imageList){
ivSlide.setImageBitmap(bm);
Thread.sleep(1000);
Log.d(TAG, "Setting ImageView image");
}
}catch (InterruptedException e){
Log.d(TAG, "Thread interrupted unexpectedly.");
}
}
});
}
}
Any help is much appreciated!!
I haven't used View.post(Runnable) before so I'm not sure about its behaviour but if it's not running on the UI Thread that "setImageBitmap" is not applied instantly, I think it's only applied when some other thread asks your view to reload its layout.
If it runs on the UI Thread, the Thread.sleep(1000) is asking the UI to stop working for a whole second, making the setImageBitmap effect impossible to notice to the user.
While facing a similar problem some time ago I used a Thread with a while in it that calls to runOnUiThread(Runnable) each time it needed to swipe the pictures. That way, the loading task was ran on the UI Thread and the waiting on a background one.
Hope it helps.
EDIT:
Could you try replacing your AsyncTask with this one, as suggested on my comment? gist.github.com/Arasthel/9788601
Your problem is likely Thread.sleep(). This will sleep the main thread and likely prevent the ui from updating till the last image when you don't sleep the thread.
Use Handler.postDelayed(Runnable, long) instead of sleeping the thread.
Obviously only the last item will be updated, Because you are running the loop and updating the same variable "ivSlide". For each iteration the same variable is getting updated with the different Bitmap at last it stops with the last Bitmap updated.
To overcome this you have to update each ivslide view variable with the arraylist index.
Please update full code so I can answer better.
Related
I am Recording Framelayout using AsyncTask for a certain amount of time. (suppose 5 seconds)
The code is working fine but it's making my activity freeze.
private class AsyncTask extends AsyncTask<String, Integer, String> {
#Override
protected String doInBackground(String... strings) {
try {
framlayout.setDrawingCacheEnabled(true);
for (int i = 0; i < 5; i++) {
Bitmap bitmap = Bitmap.createBitmap(framlayout.getDrawingCache());
// collecting bitmaps and at the end making video
}
framlayout.setDrawingCacheEnabled(false);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
There is no issue with the code for creating video
My issue is activity is freezing even if I am using Asynctask
Please help
Thank you in advance.
this line do its job in UI thread, as framlayout (probably) belongs to some Activity or Fragment
framlayout.setDrawingCacheEnabled(true);
I'm surprised that this doesn't throw "wrong non-UI thread" exception, but on the other hand it doesn't change any param of this View...
setDrawingCacheEnabled itself makes View generate bitmap (save/store some byte-array) with own look, this is heavy and may provide some small freeze
if you need this print screen keep drawing cache enabled from start, don't toggle this on runtime (from UI or any other thread)
use this code and manage your loop inside it
var exec = Executors.newScheduledThreadPool(1)
var check = 0
exec!!.scheduleAtFixedRate({
if (check < 5) {
Bitmap bitmap =Bitmap.createBitmap(framlayout.getDrawingCache());
}
check++
}, 0, 100, TimeUnit.MILLISECONDS)
I want to show a listview with some texts and images. When i'm creating a view for listview, i'm calling method show of my PictureImageView, that downloads and showing image. Download is running in new thread in AsyncTask. But while image downloading i can't normally scroll listview, it's twitches.
To run AsyncTask in new thread i call executeOnExecutor method. I tried to call execute method, but then scroll stops at all till download is over.
Here my class.
public class PictureImageView extends LinearLayout {
private Drawable image_drawable = null;
private ImageView image = null;
...
protected String getImageURL() {
...
return uri;
}
public void show() {
if (image_drawable != null) {
image.setImageDrawable(image_drawable);
addView(image);
} else {
// target Android API >= 14 so executeOnExecutor works in another thread
new RequestTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getImageURL());
}
}
protected void onResponse(Drawable image) {
if (image != null) {
image_drawable = image;
show();
}
}
class RequestTask extends AsyncTask<String, String, Drawable> {
#Override
protected Drawable doInBackground(String... urls) {
Drawable image = null;
HttpURLConnection connection = null;
InputStream connection_stream = null;
try {
URL url = new URL(urls[0]);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setUseCaches(true);
connection.connect();
int response_code = connection.getResponseCode();
//#see http://libs-for-android.googlecode.com/svn/reference/com/google/android/filecache/FileResponseCache.html
if (response_code == HttpURLConnection.HTTP_OK || response_code == -1) {
connection_stream = connection.getInputStream();
image = Drawable.createFromStream(connection_stream, null);
}
} catch (MalformedURLException e) {
} catch (IOException e) {
} finally {
if (connection != null) {
connection.disconnect();
}
if (connection_stream != null) {
try {
connection_stream.close();
} catch (IOException e) {
}
}
}
return image;
}
#Override
protected void onPostExecute(Drawable image) {
PictureImageView.this.onResponse(image);
}
}
}
How can i fix it? I guess, the problem is that there is no any another thread, but how to check it?
I've delt with this exact problem first hand. The twitching comes from updating the ListView each time a picture is downloaded. There are 2 different approaches I took to fix this. Depending on your project set up one my work
Approach 1: Minimize twitching by only updating once
In my case I used an AsyncTask as a seperate class with a call back to the starting activity. What I did was use a singleThreadExecutor so that the task to download each user's picture were serialy executed and a counter to track how many treads were started/left - increamenting each time I added one to the executor, decrementing each time the call back was called. For example
#Override
public void userPic(Bitmap pic){
if(pic != null){
//use picture
}
taskCounter--
if(taskCounter == 0){
updateUserListView();
}
}
By updating once all threads were done I was able to minimize the twitching by only refreshing the list once, thus allowing scroll and jumping back to the top only once all picutres were done
Approach 2: eliminate twitch by using mem cache
Eventually what I ened up doing was using a cache to store bitmaps. This approach completely eliminated the jumping issue beacuse the list was no longer being refreshed, rather the adapter was loading bitmaps from the cache only when views were recycled. I still used a seperate task with a call back
#Override
public void userPic(Bitmap pic){
if(pic != null){
memCache.addPicture(pic);
}
}
only this time rather than update the list directly, if a picture was downloaded I stored it to the cache. Then in my adapter code, I set the picutre field to update from cache if present
if(picture_view != null){
if(memCache.contains(u.getId()){
picture_view.setImageBitmap(memCache.getPicture(u.getId()));
} else {
picture_view.setImageBitmap(memCache.getPicture("default"));
}
this approach takes advatage of the fact that views are updated in a ListView automaticaly once they are recycled. As you scroll and the views are rebuilt, the adapter will automatically populate the fields with new data if it has changed.
Downsides - the list does not auto upate. If pictures are downloaded for fields that are currently visible, they will not be updated until you scroll away from that view. Also, slightly more set up in creating a cache. I chose to use a singelton pattern to do this since I was accessing the cache from multiple places (e.g. adding pictures in one place and getting in another).
In my android application, on a certain activity I need to create screenshots of views without actually displaying them. I have been successful in achieving this by inflating the views and saving them as bitmaps.
But in some scenarios the number of these bitmaps is large enough and it takes a lot of time to create them. As such the UI on the phone becomes non responsive. Is there any way I can do this whole process in the background? I have already tried implementing it in an Async Task but that does not work because its not allowed to inflate views in an Async task.
Any suggestions are highly appreciated.
AsyncTask doBackground method works on another Thread, That's the reason you are not able to inflate the views.
First whether u have one layout or many. If you have many then try below.
I have not test this. Just a sample for you.
public class Task extends AsyncTask<Void, Integer, Void>
{
private ArrayList<Integer> layoutIds;
private View currentView;
private LayoutInflater inflater;
private Object lock = new Object();
public Task(Context context) {
super();
inflater = LayoutInflater.from(context);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(Void... params) {
Bitmap temp;
for (int i = 0; i < layoutIds.size(); i++) {
temp = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Canvas canvas = new Canvas(temp);
synchronized (lock) {
publishProgress(i);
try {
// Wait for the UI Thread to inflate the layout.
lock.wait();
}
catch (InterruptedException e) {
}
}
currentView.draw(canvas);
// Now save this bitmap
try {
FileOutputStream stream = new FileOutputStream(new File(Environment.getExternalStorageDirectory(), "File_" + i + ".png"));
temp.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.flush();
stream.close();
} catch (Exception e) {
}
finally
{
if(temp != null)
{
temp.recycle();
temp = null;
}
}
}
return null;
}
#Override
protected void onProgressUpdate(Integer... values) {
synchronized (lock) {
currentView = inflater.inflate(layoutIds.get(values[0]), null);
// Notify async thread that inflate is done.
lock.notifyAll();
}
}
}
EDITED
Here we have two thread one is AsyncTask which is a Thread Pool and another is UI Thread.
With synchronized block we could make sure that only one thread could use the object lock as long as it is not in sleeping or waiting for another thread.
So if one thread is executing the block inside synchronize then it will start monitoring that object and make sure no other thread which also has a synchronize block for that object will be executed. i.e., another thread has to wait for as long as the active thread goes to sleep or completed its execution inside synchronized block.
For more explanation, See this
Here, we used the synchronize block to wait for UI thread to complete.
So as it execute lock.wait(), the AsyncThread will wait till another thread calls notify on the same object. And when lock.notifyAll() is called all the thread (AsyncThread) which are waiting will be resumed.
AsyncTask is divided to onPreExecute(), onProgressUpdate() and onPostExecute(), all happens in the main UI thread allowing you to inflate the view. Only doInBackground() is where things actually happen in the other thread. If you can do al your calculation on this method and only inflate onProgressUpdate() or onPostExecute() it might help.
Also if you can give us your code it might help us to find a way to make it more CPU efficient.
Anyhow, android will try to force close your app if the UI thread isn't responding for more than 5 seconds, and theres isn't much to do about it (As far as I know).
So I have an activity with an ImageView. My goal is to have the activity start and asynchronously fetch the image I need in there. While the image is being fetched, I'd like to display some sort of spinning loading symbol in place of the image. I've tried using AsyncTask as a separate class, but ran into issues relating to modifying the views of another activity. I tried an anonymous Runnable class inside the activity, but it doesn't seem to be yielding the effects I expect. Can anyone advise?
SOLVED: Simply needed a handler in the activity
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
ImageView qrCode = (ImageView)findViewById(R.id.qr);
qrCode.setImageBitmap((Bitmap)msg.obj);
}
};
new Thread(new Runnable(){
public void run() {
BitmapFactory.Options bmOptions;
bmOptions = new BitmapFactory.Options();
bmOptions.inSampleSize = 1;
int retry = 0;
Bitmap bm = null;
while(retry < 50 && bm == null){
bm = LoadImage(image_URL, bmOptions);
if(bm == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
retry++;
}
Message msg = new Message();
msg.obj = bm;
handler.sendMessage(msg);
return;
}
}).start();
`
Place a progress dialog inside of the view with the imageview.
set the imageview ImageView.visibility(View.INVISIBLE);
In your preExecute() in your AsyncTask
Show the spinning dialog, and then do the fetching in the doInBackground() of the AsyncTask
In onPostExecute() show your imageview by changing the visibility and dismiss the dialog or sets its visibility to View.GONE
Android's developer guide for threading should help you out. It has AsyncTask examples that pretty much do exactly what you're asking for:
Android Threading Guide
If you don't implement the publishProgress() method, and only implement doInBackground to get the data, and onPostExecute to update the UI, then it should by default give you the spinning icon to indicate that it's loading. Keep in mind that onPostExecute runs on the main UI thread so you can't make any network calls from there
I did something similar and did according to this tutorial:
http://evancharlton.com/thoughts/lazy-loading-images-in-a-listview
First the problem:
I'm working on the application that uses multiple FragmentLists
within a customized FragmentStatePagerAdapter. There could be,
potentially substantial number of such fragments say between 20 and 40.
Each fragment is a list in which each item could contain text or image.
The images need to be uploaded asynchronously from the web and cached to temp memory cache and also to SD if available
When Fragment goes off the screen any uploads and current activity should be cancelled (not paused)
My first implementation followed well known image loader code from Google. My problem with that code is that it basically creates one instance of AsyncTask per image. Which in my case kills the app real fast.
Since I'm using v4 compatibility package I thought that using custom Loader that extends AsyncTaskLoader would help me since that internally implements a thread pool. However to my unpleasant surprise if I execute this code multiple times each following invocation will interrupt the previous. Say I have this in my ListView#getView method:
getSupportLoaderManager().restartLoader(0, args, listener);
This method is executed in the loop for each list item that comes into view. And as I stated - each following invocation will terminate the previous one. Or at least that's what happen based on LogCat
11-03 13:33:34.910: V/LoaderManager(14313): restartLoader in LoaderManager: args=Bundle[{URL=http://blah-blah/pm.png}]
11-03 13:33:34.920: V/LoaderManager(14313): Removing pending loader: LoaderInfo{405d44c0 #2147483647 : ImageLoader{405118a8}}
11-03 13:33:34.920: V/LoaderManager(14313): Destroying: LoaderInfo{405d44c0 #2147483647 : ImageLoader{405118a8}}
11-03 13:33:34.920: V/LoaderManager(14313): Enqueuing as new pending loader
Then I thought that maybe giving unique id to each loader will help the matters but it doesn't seem to make any difference. As result I end up with seemingly random images and the app never loads even 1/4 of what I need.
The Question
What would be the way to fix the Loader to do what I want (and is there a way?)
If not what is a good way to create AsyncTask pool and is there perhaps working implementation of it?
To give you idea of the code here's stripped down version of Loader where actual download/save logic is in separate ImageManager class.
public class ImageLoader extends AsyncTaskLoader<TaggedDrawable> {
private static final String TAG = ImageLoader.class.getName();
/** Wrapper around BitmapDrawable that adds String field to id the drawable */
TaggedDrawable img;
private final String url;
private final File cacheDir;
private final HttpClient client;
/**
* #param context
*/
public ImageLoader(final Context context, final String url, final File cacheDir, final HttpClient client) {
super(context);
this.url = url;
this.cacheDir = cacheDir;
this.client = client;
}
#Override
public TaggedDrawable loadInBackground() {
Bitmap b = null;
// first attempt to load file from SD
final File f = new File(this.cacheDir, ImageManager.getNameFromUrl(url));
if (f.exists()) {
b = BitmapFactory.decodeFile(f.getPath());
} else {
b = ImageManager.downloadBitmap(url, client);
if (b != null) {
ImageManager.saveToSD(url, cacheDir, b);
}
}
return new TaggedDrawable(url, b);
}
#Override
protected void onStartLoading() {
if (this.img != null) {
// If we currently have a result available, deliver it immediately.
deliverResult(this.img);
} else {
forceLoad();
}
}
#Override
public void deliverResult(final TaggedDrawable img) {
this.img = img;
if (isStarted()) {
// If the Loader is currently started, we can immediately deliver its results.
super.deliverResult(img);
}
}
#Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
#Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
// At this point we can release the resources associated with 'apps'
// if needed.
if (this.img != null) {
this.img = null;
}
}
}
Ok, so first things first. The AsyncTask that comes with android shouldn't drown out your app or cause it to crash. AsyncTasks run in a thread pool where there is at most 5 threads actually executing at the same time. While you can queue up many tasks to be executed , only 5 of them are executing at a time. By executing these in the background threadpool they shouldn't have any effect on your app at all, they should just run smoothly.
Using the AsyncTaskLoader would not solve your problem if you are unhappy with the AsyncTask loader performance. The AsyncTaskLoader just takes the loader interface and marries it to an AsyncTask. So it's essentially mapping onLoadFinished -> onPostExecute, onStart -> onLoadInBackground. So it's the same exact thing.
We use the same image loader code for our app that causes an asynctask to be put onto the threadpool queue each time that we try to load an image. In google's example they associate the imageview with its async task so that they can cancel the async task if they try to reuse the imageview in some sort of adapter. You should take a similar strategy here. You should associate your imageview with the async task is loading the image in the background. When you have a fragment that is not showing you can then cycle through your image views associated with that fragment and cancel the loading tasks. Simply using the AsyncTask.cancel() should work well enough.
You should also try to implement the simple image caching mechanism the async image view example spells out. We simply create a static hashmap that goes from url -> weakreference . This way the images can be recycled when they need to be because they are only held on with a weak reference.
Here's an outline of the image loading that we do
public class LazyLoadImageView extends ImageView {
public WeakReference<ImageFetchTask> getTask() {
return task;
}
public void setTask(ImageFetchTask task) {
this.task = new WeakReference<ImageFetchTask>(task);
}
private WeakReference<ImageFetchTask> task;
public void loadImage(String url, boolean useCache, Drawable loadingDrawable){
BitmapDrawable cachedDrawable = ThumbnailImageCache.getCachedImage(url);
if(cachedDrawable != null){
setImageDrawable(cachedDrawable);
cancelDownload(url);
return;
}
setImageDrawable(loadingDrawable);
if(url == null){
makeDownloadStop();
return;
}
if(cancelDownload(url)){
ImageFetchTask task = new ImageFetchTask(this,useCache);
this.task = new WeakReference<ImageFetchTask>(task);
task.setUrl(url);
task.execute();
}
......
public boolean cancelDownload(String url){
if(task != null && task.get() != null){
ImageFetchTask fetchTask = task.get();
String downloadUrl = fetchTask.getUrl();
if((downloadUrl == null) || !downloadUrl.equals(url)){
fetchTask.cancel(true);
return true;
} else
return false;
}
return true;
}
}
So just rotate through your image views that are in your fragment and then cancel them when your fragment hides and show them when your fragment is visible.