1

What I'm trying to do: I have several JPG files saved in a subfolder under the "assets" folder. My app should go into this subfolder, load the first file, display it for 30 seconds, then load the second file, display it for 30 seconds, and so on, ..., until the last file. This is done in a loop.

What problem I'm having: The app goes through all the iterations of the loop. It loads each file correctly, reports the width and height of each image correctly. However, the screen is black with no images being displayed, until after the whole loop finishes. Then the last image is displayed.

Here is my layout.xml file (I'd like to handle images of different sizes, but will worry about it later):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mylayout_id"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <ImageView
        android:id="@+id/image_display"
        android:layout_width="400dp"
        android:layout_height="400dp" >
    </ImageView>

    <Button
        android:id="@+id/button_start"
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="pressedStart"
        android:text="@string/btn_start" />
</LinearLayout>

Here is my code:

public class MyImageActivity extends Activity {
    private final String TAG = "MyImageActivity";

    private ImageView mIV;
    private Bitmap mFaceBitmap;
    private AssetManager mAssetMan = null;
    private String[] mImgFileNames = null;
    private int mBitmapWidth;
    private int mBitmapHeight;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.e(TAG, "onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mylayout);
        mIV = (ImageView) findViewById(R.id.image_display);
    }

    @Override
    protected void onResume() {
        Log.e(TAG, "onResume");
        super.onResume();
        mAssetMan = getAssets();
    }

    public void pressedStart(View view) {
        Log.e(TAG, "pressedStart");
        try {
            mImgFileNames = mAssetMan.list("my_subfolder");
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        Log.d(TAG, "mImgFileNames = " + mImgFileNames);
        Log.d(TAG, "number of image files = " + mImgFileNames.length);

        for (int i = 0; i < mImgFileNames.length; i++) {
            Log.d(TAG, "image file name = " + mImgFileNames[i]);

            mIV.setImageDrawable(null);
            mIV.invalidate();
            InputStream istr = null;
            try {
                istr = mAssetMan.open("my_subfolder" + mImgFileNames[i]);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            // load the photo
            Bitmap b = BitmapFactory.decodeStream(istr);
            mFaceBitmap = b.copy(Bitmap.Config.RGB_565, true); 
            b.recycle();

            mBitmapWidth = mFaceBitmap.getWidth();
            mBitmapHeight = mFaceBitmap.getHeight();  
            Log.d(TAG, "mBitmapWidth = " + mBitmapWidth);
            Log.d(TAG, "mBitmapHeight = " + mBitmapHeight);

            Drawable drawable = new BitmapDrawable(getResources(), mFaceBitmap);
            mIV.setImageDrawable(drawable);
            mIV.invalidate();
            SystemClock.sleep(5000);
        }
    }
}

I found some other discussions about similar problems here and here. I tried a few suggested solutions, e.g. adding setImageDrawable(null) to force ImageView to update; that did not work for me.

Anything wrong with my approach? I really appreciate your helps and suggestions.

Thanks in advance & Have a nice weekend.

Community
  • 1
  • 1
hubeir
  • 1,279
  • 2
  • 13
  • 24

3 Answers3

1

This is happening because you are placing the new image within the holder and calling invalidate to make it display but then you turn around and put the UI thread to sleep for 5 seconds so I cannot update it. By the time the system goes to update it, it is already moving on to the next image. I would use a AsyncTask to load the next Image in the background and then put a timer of 5 seconds in the the background thread before it updates the UI with the new data.

EDIT
Here is the reworked version of your code that is operational if this is your exact code. If not you should be fine. (if the formatting is bad, it is because my work uses IE8 still and it doesn't let me see the formatting. I will fix when I get home.)

public class MyImageActivity extends Activity {
        private final String TAG = "MyImageActivity";
        private ImageView mIV;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            Log.e(TAG, "onCreate");
            super.onCreate(savedInstanceState);
            setContentView(R.layout.mylayout);
            mIV = (ImageView) findViewById(R.id.image_display);
        }

        @Override
        protected void onResume() {
            Log.e(TAG, "onResume");
            super.onResume();
        }

        public void pressedStart(View view) {
        new SlideShowTask().execute();
        }

    private class SlideShowTask extends AsyncTask<Void, Drawable, Void>{

      private Bitmap mFaceBitmap;
          private AssetManager mAssetMan = null;
          private String[] mImgFileNames = null;
          private int mBitmapWidth;
          private int mBitmapHeight;

      @Override
      Protected Void doInBackground(Void... params){
        mAssetMan = getAssets();
        Log.e(TAG, "pressedStart");
                try {
                    mImgFileNames = mAssetMan.list("my_subfolder");
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
                Log.d(TAG, "mImgFileNames = " + mImgFileNames);
                Log.d(TAG, "number of image files = " + mImgFileNames.length);

            for (int i = 0; i < mImgFileNames.length; i++) {
                Log.d(TAG, "image file name = " + mImgFileNames[i]);

                InputStream istr = null;
                try {
                    istr = mAssetMan.open("my_subfolder/" + mImgFileNames[i]);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

                // load the photo
                Bitmap b = BitmapFactory.decodeStream(istr);
                mFaceBitmap = b.copy(Bitmap.Config.RGB_565, true); 
                b.recycle();

                mBitmapWidth = mFaceBitmap.getWidth();
                mBitmapHeight = mFaceBitmap.getHeight();  
                Log.d(TAG, "mBitmapWidth = " + mBitmapWidth);
                Log.d(TAG, "mBitmapHeight = " + mBitmapHeight);

                Drawable drawable = new BitmapDrawable(getResources(), mFaceBitmap);
        publishProgress(drawable);
                SystemClock.sleep(5000);
            }
      }

      @Override
      Protected Void onProgressUpdate(Drawable... values){
        super.onProgressUpdate(values);
        mIV.setImageDrawable(values[0]);
        mIV.invalidate();
      }
    }
}
ObieMD5
  • 2,684
  • 1
  • 16
  • 26
  • An AsyncTask cannot be used in a loop. Does that mean I have to put the loop in the AsyncTask? – hubeir Jul 29 '13 at 18:11
  • @hubeir No what you do is pass in the image you want loaded within your application to do in background. Get the image then use publish progress to update the image on your background. Then do sleep for 5 seconds after. It will look something in psudocode: `doInBackgroupd(String params....){ for (int i = 0; i <=5; i++){ bitmap b = getimage(imagename); publishprogress(b); sleep(5000);}}`This way the loop will happen in the AsyncTask until it shows 5 pictures with 5 seconds between each one. Ill update my answer with example in a second. – ObieMD5 Jul 29 '13 at 18:24
  • @hubeir I just finished rewriting your code on my laptop. I will be home in about an hour so I will post it in an edit. I tested it and it is working fine all pictures show for 5 seconds. – ObieMD5 Jul 29 '13 at 19:48
  • @hubeir the newcode is in my edit section. If it doesn't work let me know and we will find out where the bug is. It is hard to rewrite someones code to let them copy and paste it. – ObieMD5 Jul 29 '13 at 20:03
  • Thanks a lot for the code. Separately I also had something working after I got the idea from your initial reply. I use an AsyncTask too but the display updating is done in runOnUiThread(). Your solution is more elegant. Thanks again! – hubeir Jul 29 '13 at 20:14
1

This happens because you put the main Ui Thread to sleep. And you should never do that. As it means that the whole application will hang for 5seconds which is wrong.

You should create a new thread and update the UI from there like this code which is the easiest.

runOnUiThread(new Runnable(){

    @Override
    public void run(){
         <--Your Code Here -->
         <--sleep for 5000ms-->
    }
});

or use a Handler or an ASynkTask. There are many good tutorials for each of them here.

Manolis Proimakis
  • 1,787
  • 16
  • 22
  • I tried runOnUiThread() but the code didn't change its behavior. I might be do it right. Between a Handler and an AsyncTask, which is the preferred way? – hubeir Jul 29 '13 at 18:09
1

Try using a new thread or Runnable and add your code to the run function and than make that thread sleep. Since you are updating your UI you would also need a handler. check the link below

www.vogella.com/articles/AndroidBackgroundProcessing/article.html

Maverick
  • 961
  • 9
  • 13