1

To navigate through the pages of my app i use:

UiApplication.getUiApplication().invokeLater(new Runnable(){
    public void run(){
                    UiApplication.getUiApplication().pushScreen(screen);
    }
});

This is working well on simple screens with not too many fields. But when I try to go to a Screen more elaborated (it contains a custom "listField" made out of multiple managers with multiple fields) the UI takes A LOT of time to update. I've noticed that the page has actually finished constructing but it just doesn't show up, I have to move the trackpad or do a click and then the screen will show.

Code for the screen:

public class List extends MainScreen{

public List(Categoria categoria){
    Bitmap bmap = Bitmap.getBitmapResource("img/fondo.png");
    getMainManager().setBackground(BackgroundFactory.createBitmapBackground(bmap, Background.POSITION_X_LEFT, Background.POSITION_Y_TOP, Background.REPEAT_SCALE_TO_FIT));
    setTitle("Establecimientos");
    vfm = new VerticalFieldManager(Manager.VERTICAL_SCROLL | Manager.VERTICAL_SCROLLBAR);
    try {
        DbHelper dbHelper = new DbHelper();
        dbHelper.open();
        EstablecimientoDAO eDao = new EstablecimientoDAO(dbHelper.db);
        OfertaDAO oDao = new OfertaDAO(dbHelper.db);
        Vector ests = eDao.getEstablecimientosPorCategoria(categoria);
        for(int i = 0; i < ests.size(); i++){
            Establecimiento est = (Establecimiento) ests.elementAt(i);
            Oferta oferta = oDao.getOferta(est.getIdEstablecimiento());
            EstablecimientosListField elf1 = new EstablecimientosListField();
            Bitmap bm = null;
            String location = "";
            FileSystem fs = Utilities.getFileSystem();
            if(fs.hasSdCard()){
                location = "/SDCard/Blackberry/pictures/";
            }
            else if(fs.hasMCard()){
                location = "/store/";
            }
            FileConnection fc = (FileConnection)Connector.open("file://" + location + "e_" + est.getIdEstablecimiento() + ".jpg");
            if(fc.exists() && fc.fileSize() > 0){
                InputStream is = fc.openInputStream();
                byte [] data = new byte[(int) fc.fileSize()];
                data = IOUtilities.streamToBytes(is);
                is.close();
                fc.close();
                //bm = Bitmap.createBitmapFromBytes(data, 0, data.length, 1);
                EncodedImage eImage = EncodedImage.createEncodedImage(data, 0, data.length);
                eImage = Utilities.resizeToWidth(eImage, Display.getWidth() / 3, 1, true);
                Bitmap tmp = eImage.getBitmap();
                if(tmp.getHeight() > 150){
                    eImage = Utilities.resizeToHeight(eImage, 1, 150, true);
                }
                bm = eImage.getBitmap();
            }
            else{
                EncodedImage eImage = EncodedImage.getEncodedImageResource("img/default_esta.png");
                eImage = Utilities.resizeToHeight(eImage, 1, 150, true);
                bm = eImage.getBitmap();
            }
            String desc = "";
            if(oferta != null && oferta.getDescripcion() != null){
                desc = oferta.getDescripcion();
            }
            if(est.getDescEstablecimiento() != null){
                desc += est.getDescEstablecimiento();
            }


            ButtonField verMas = new ButtonField("VER MÁS"){
                protected boolean touchEvent( TouchEvent message )    {
                    int x = message.getX( 1 );        
                    int y = message.getY( 1 );        
                    if( x < 0 || y < 0 || x > getExtent().width || y > getExtent().height ) {
                        // Outside the field            
                        return false;       
                    }        

                    switch( message.getEvent() ) {                  
                    case TouchEvent.UNCLICK:                
                        fieldChangeNotify(0);               
                        return true;        
                    }        
                    return super.touchEvent( message );    
                }

                protected boolean navigationClick(int status, int time) {
                    if (status != 0) {        // you did not have this check
                        fieldChangeNotify(0);
                    }
                    return true;
                }

                protected boolean trackwheelClick( int status, int time )
                {        
                    if (status != 0) fieldChangeNotify(0);    
                    return true;
                }
            };
            verMas.setCookie(est);
            verMas.setChangeListener(new FieldChangeListener(){
                public void fieldChanged(Field field, int context) {
                    final Establecimiento est = (Establecimiento)field.getCookie();
                    //if(field instanceof )
                    /*UiApplication.getUiApplication().invokeLater(new Runnable(){
                        public void run(){
                            UiApplication.getUiApplication().pushScreen(new DetalleEstablecimientoScreen(est));
                        }
                    });*/
                    Runnable runnable = new Runnable(){
                        public void run(){
                            UiApplication.getUiApplication().pushScreen(new DetalleEstablecimientoScreen(est));
                        }
                    };
                    PleaseWaitPopupScreen.showScreenAndWait(runnable, "Cargando...");
                }
            });

            EncodedImage eImage = EncodedImage.getEncodedImageResource("img/det_fb.png");
            eImage = Utilities.resizeToWidth(eImage, (int) (Display.getWidth() / 16), (int) (Display.getWidth() / 16), true);
            BitmapField bmFb = new BitmapField(eImage.getBitmap(), Field.FOCUSABLE){
                protected boolean touchEvent( TouchEvent message )    {
                    int x = message.getX( 1 );        
                    int y = message.getY( 1 );        
                    if( x < 0 || y < 0 || x > getExtent().width || y > getExtent().height ) {
                        // Outside the field            
                        return false;       
                    }        

                    switch( message.getEvent() ) {                  
                    case TouchEvent.UNCLICK:                
                        fieldChangeNotify(0);               
                        return true;        
                    }        
                    return super.touchEvent( message );    
                }

                protected boolean navigationClick(int status, int time) {
                    if (status != 0) {        // you did not have this check
                        fieldChangeNotify(0);
                    }
                    return true;
                }

                protected boolean trackwheelClick( int status, int time )
                {        
                    if (status != 0) fieldChangeNotify(0);    
                    return true;
                }
            };
            bmFb.setCookie(est);
            bmFb.setChangeListener(new FieldChangeListener(){
                public void fieldChanged(Field field, int context){
                    try{
                        DbConnection dbHelper = new DbConnection();
                        dbHelper.open();
                        OfertaDAO oDao = new OfertaDAO(dbHelper.db);
                        final Establecimiento est = (Establecimiento)field.getCookie();
                        final Oferta oferta = oDao.getOferta(est.getIdEstablecimiento());
                        dbHelper.close();
                        UiApplication.getUiApplication().invokeLater(new Runnable(){
                            public void run(){
                                try{
                                    String post = "Banco de Bogotá";
                                    if(oferta.getDescripcion() != null){
                                        post = oferta.getDescripcion();
                                    }
                                    //String [] opciones = {"Aceptar", "Cancelar"};
                                    int respuesta = Dialog.ask(Dialog.D_OK_CANCEL, "Usted está a punto de compartir la siguiente oferta:\n" + post, Dialog.YES);
                                    if(respuesta == Dialog.D_OK){
                                        new FacebookPost(oferta, est);
                                    }
                                }
                                catch(Exception ex){
                                    Dialog.alert("ERROR: " + ex.toString());
                                }
                            }
                        });
                    } catch(Exception ex){
                        UiApplication.getUiApplication().invokeLater(new Runnable(){
                            public void run(){
                                Dialog.alert("Error al compartir");
                            }
                        });
                    }
                }
            });

            eImage = EncodedImage.getEncodedImageResource("img/det_tw.png");
            eImage = Utilities.resizeToWidth(eImage, (int) (Display.getWidth() / 16), (int) (Display.getWidth() / 16), true);
            BitmapField bmTw = new BitmapField(eImage.getBitmap(), Field.FOCUSABLE){
                protected boolean touchEvent( TouchEvent message )    {
                    int x = message.getX( 1 );        
                    int y = message.getY( 1 );        
                    if( x < 0 || y < 0 || x > getExtent().width || y > getExtent().height ) {
                        // Outside the field            
                        return false;       
                    }        

                    switch( message.getEvent() ) {                  
                    case TouchEvent.UNCLICK:                
                        fieldChangeNotify(0);               
                        return true;        
                    }        
                    return super.touchEvent( message );    
                }

                protected boolean navigationClick(int status, int time) {
                    if (status != 0) {        // you did not have this check
                        fieldChangeNotify(0);
                    }
                    return true;
                }

                protected boolean trackwheelClick( int status, int time )
                {        
                    if (status != 0) fieldChangeNotify(0);    
                    return true;
                }
            };
            bmTw.setCookie(est);
            bmTw.setChangeListener(new FieldChangeListener(){
                public void fieldChanged(Field field, int context){
                    try{
                        DbConnection dbHelper = new DbConnection();
                        dbHelper.open();
                        OfertaDAO oDao = new OfertaDAO(dbHelper.db);
                        final Establecimiento est = (Establecimiento)field.getCookie();
                        final Oferta oferta = oDao.getOferta(est.getIdEstablecimiento());
                        dbHelper.close();
                        UiApplication.getUiApplication().invokeLater(new Runnable(){
                            public void run(){
                                String tuit = "tweeteando desde fidelity";
                                if(oferta.getDescripcion() != null){
                                    tuit = oferta.getDescripcion();
                                }
                                //String [] opciones = {"Aceptar", "Cancelar"};
                                int respuesta = Dialog.ask(Dialog.D_OK_CANCEL, "Usted está a punto de compartir la siguiente oferta:\n" + tuit, Dialog.OK);
                                if(respuesta == Dialog.OK){
                                    UiApplication.getUiApplication().pushScreen(new BrowserFieldScreen(tuit));
                                }
                            }
                        });
                    } catch(Exception ex){
                        UiApplication.getUiApplication().invokeLater(new Runnable(){
                            public void run(){
                                Dialog.alert("Error al compartir");
                            }
                        });
                    }
                }
            });

            BitmapField pin = new BitmapField(Bitmap.getBitmapResource("img/pin.png"), Field.FOCUSABLE){
                protected boolean touchEvent( TouchEvent message )    {
                    int x = message.getX( 1 );        
                    int y = message.getY( 1 );        
                    if( x < 0 || y < 0 || x > getExtent().width || y > getExtent().height ) {
                        // Outside the field            
                        return false;       
                    }        

                    switch( message.getEvent() ) {                  
                    case TouchEvent.UNCLICK:                
                        fieldChangeNotify(0);               
                        return true;        
                    }        
                    return super.touchEvent( message );    
                }

                protected boolean navigationClick(int status, int time) {
                    if (status != 0) {        // you did not have this check
                        fieldChangeNotify(0);
                    }
                    return true;
                }

                protected boolean trackwheelClick( int status, int time )
                {        
                    if (status != 0) fieldChangeNotify(0);    
                    return true;
                }
            };
            pin.setCookie(new Long(est.getIdEstablecimiento()));
            //pin.setChangeListener(this);
            pin.setChangeListener(new FieldChangeListener(){
                public void fieldChanged(Field field, int context) {
                    final long idEstablecimiento = ((Long)field.getCookie()).longValue();
                    //if(field instanceof )
                    UiApplication.getUiApplication().invokeLater(new Runnable(){
                        public void run(){
                            UiApplication.getUiApplication().pushScreen(new MapaScreen(idEstablecimiento, "establecimiento"));
                        }
                    });
                }
            });
            elf1.add(new BitmapField(bm));
            elf1.add(new LabelField(est.getNombre(), LabelField.ELLIPSIS));
            elf1.add(new LabelField(desc, LabelField.ELLIPSIS));
            elf1.add(verMas);
            elf1.add(pin);
            elf1.add(bmFb);
            elf1.add(bmTw);
            vfm.add(elf1);
        }
    } catch (Exception ex){

    }
    add(vfm);
    //UiApplication.getUiApplication().requestForeground();
    //invalidate();
}
}
rod_torres
  • 336
  • 2
  • 9

4 Answers4

3

I would be interested to know what device and OS level you are seeing this on.

I don't think we can figure out your problem from the information presented, we are just guessing. If you want some more detailed assistance, you will need to give example code. I appreciate that you may not want to post your own code, but perhaps you can create a small sample using an array of Data.

That said, my experience is that on the initial load of the screen, if there is a delay, then it is most likely a delay in loading content rather than the screen itself. So for example, if you are reading data and images off the SD Card to populate the screen, then that will slow it down. In fact you should not be doing this, you should populate your screen with 'filers' and then update by reading the required data off the SD Card and updating the screen as it goes from a Background Thread.

The other thing that comes to mind is that updating a screen with lots of managers present, can cause each of these managers to have to reposition. Once you get a large number of Managers, this update can introduce noticeable lag. But you would probably needs 100's for Managers/Fields to notice this. And this laying out occurs when the Screen is on display, not during construction.

Finally have you seen any signs of storage shortage, for example an hour glass or watch face icon on the screen during this slow processing.

I hope the above helps, if not, you may have to give us representative code.

Update

Looking at your code, it would seem you could do a lot of optimisation of this code before converting to asynchronous loading. Remember Bitmap manipulation is expensive in terms of performance. I would recommend proceeding in two stages:

  1. Move the repeated code out of the loop
  2. Do the loading that is outside the loop only once.

Here as example of step 1.

Currently inside the loop you have:

eImage = EncodedImage.getEncodedImageResource("img/det_tw.png");
eImage = Utilities.resizeToWidth(eImage, (int) (Display.getWidth() / 16), (int) (Display.getWidth() / 16), true);
BitmapField bmTw = new BitmapField(eImage.getBitmap(), Field.FOCUSABLE){  

Outside the loop do

int displayWidth = Display.getWidth(); // I was told this was an expensive call so minimise its use
EncodedImage det_twEImage = EncodedImage.getEncodedImageResource("img/det_tw.png");
det_twEImage = Utilities.resizeToWidth(det_twEImage, (int) (displayWidth / 16), (int) (displayWidth / 16), true);
Bitmap bmTwBitmap = eImage.getBitmap();

and in the loop:

BitmapField bmTw = new BitmapField(bmTwBitmap, Field.FOCUSABLE){ 

And an example of step 2.

Currently you have this:

public List(Categoria categoria){
    Bitmap bmap = Bitmap.getBitmapResource("img/fondo.png");

You could look at doing this:

static Bitmap bmap = null;

public List(Categoria categoria){
    if ( bmap == null ) {
        bmap = Bitmap.getBitmapResource("img/fondo.png");
    )

This specific example is perhaps not so relevant, but you can appreciate that this approach can stop doing work twice.

Most large applications that I have worked on have implement some sort of caching for Bitmaps, which generally will involve preloading (while a splash screen is being displayed perhaps) all the static Bitmaps and dynamically loading but retaining a reference to other used Bitmaps so that they are only loaded once. Bitmaps are immutable, so you can reference them as many times as you like in BitmapFields.

There are few other things that might be contributing but one bit of code you need to look at is replacing this:

byte [] data = new byte[(int) fc.fileSize()];
data = IOUtilities.streamToBytes(is);

with

byte [] data = IOUtilities.streamToBytes(is);

which saves allocating and then throwing away a large chunk of memory.

If you do all this, and asynchronously load the images you can't load once, I think you will find performance improves significantly. Also as an experiment, always use the (loaded once) default image for your list items and that will give you an impression of how fast things will be.

Peter Strange
  • 2,640
  • 11
  • 11
  • Thanks for the help. I've added the most significant part of the code, in summary it's a manager with 1 bitmapfield, 2 labelfield, 1 buttonfield and 3 focusable bitmapfield. – rod_torres May 31 '14 at 02:48
  • I will do what you suggest about readin images on a background thread but I don't think that is the problem, sometimes the screen can be just three custom managers and if I'm using the trackpad the screen will refresh in 3-5 seconds but if I'm not it could take minutes in show. The screen IS actually there because if I click it will do one of the actions of the buttons from the managers – rod_torres May 31 '14 at 02:52
  • To add to Peter's suggestions: you can be more precise around the logic that calls Utilities.resizeToHeight and Utilities.resizeToWidth. You shouldn't need to call both, just use one. Then your code path will have exactly one invocation of EncodedImage.getBitmap(). the getBitmap() function is one of the most expensive parts here, and you're calling it twice for each image. – Michael Donohue Jun 01 '14 at 23:52
2

Have you checked the event log on the device or simulator to see if an exception is thrown? You can see it by going to the device home screen, holding 'alt' and pressing L G L G. It could be something that goes wrong on the first paint, and then the user interaction with the trackpad causes a refresh.

Michael Donohue
  • 11,776
  • 5
  • 31
  • 44
2

All of the answers given are very useful to have a better app and I'll do them, but the actual "error" I've found after debugging the code couldn't be guessed by the code I had given. The error was in a LabelField I passed to the manager, inside the manager I was changing the font of the Labelfield and was causing this crazy error. To resolve it I simply changed the font in the constructor of the mainscreen rather than inside the manager.

rod_torres
  • 336
  • 2
  • 9
  • Thanks for letting us know. There are many possible ways you can cause repeated invocations of painting, so the principle from the answer below - only do things once - applies here too. And just for next time, if you could supply a piece of code that actually demonstrates the problem, that will help us help you. – Peter Strange Jun 04 '14 at 08:20
1

Potential problems with your code:

  • Performing IO (DB/file/network connections) on the Main thread
  • FieldchangeListeners without checking for programmatic context (yes, that second param to fieldChanged is actually useful).
  • Multiple events handled for a single user input (touchEvent + navigationClick + trackwheelClick)

Even if doing IO in the Main thread is not the problem for your current data set, it is a bad practice, and you might run into problems in the future.

What you should be doing:
A)

  1. Show loading dialog
  2. Fetch data in background
  3. Initialize list screen
  4. Hide loading dialog
  5. Change to list screen

or

B)

  1. Change to list screen
  2. Show loading dialog or message
  3. Fetch data in BG
  4. Initialize list
  5. Hide loading dialog/message
Mister Smith
  • 27,417
  • 21
  • 110
  • 193