<<上篇 | 首页 | 下篇>>

Hibernate 的 10 个常见面试问题及答案 - 博客 - 伯乐在线

Hibernate中get和load有什么不同之处? 把get和load放到一起进行对比是Hibernate面试时最常问到的问题,这是因为只有正确理解get()和load()这二者后才有可能高效地使用Hibernate。get和load的最大区别是,如果在缓存中没有找到相应的对象,get将会直接访问数据库并返回一个完全初始化好的对象,而这个过程有可能会涉及到多个数据库调用;而load方法在缓存中没有发现对象的情况下,只会返回一个代理对象,只有在对象getId()之外的其它方法被调用时才会真正去访问数据库,这样就能在某些情况下大幅度提高性能。你也可以参考 Hibernate中get和load的不同之处, 此链接给出了更多的不同之处并对该问题进行了更细致的讨论。

Hibernate中save、persist和saveOrUpdate这三个方法的不同之处? 除了get和load,这又是另外一个经常出现的Hibernate面试问题。 所有这三个方法,也就是save()、saveOrUpdate()和persist()都是用于将对象保存到数据库中的方法,但其中有些细微的差别。例如,save()只能INSERT记录,但是saveOrUpdate()可以进行 记录的INSERT和UPDATE。还有,save()的返回值是一个Serializable对象,而persist()方法返回值为void。你还可以访问 save、persist以及saveOrUpdate,找到它们所有的不同之处。

Hibernate中的命名SQL查询指的是什么? Hibernate的这个面试问题同Hibernate提供的查询功能相关。命名查询指的是用<sql-query>标签在影射文档中定义的SQL查询,可以通过使用Session.getNamedQuery()方法对它进行调用。命名查询使你可以使用你所指定的一个名字拿到某个特定的查询。 Hibernate中的命名查询可以使用注解来定义,也可以使用我前面提到的xml影射问句来定义。在Hibernate中,@NameQuery用来定义单个的命名查询,@NameQueries用来定义多个命名查询。

Hibernate中的SessionFactory有什么作用? SessionFactory是线程安全的吗? 这也是Hibernate框架的常见面试问题。顾名思义,SessionFactory就是一个用于创建Hibernate的Session对象的工厂。SessionFactory通常是在应用启动时创建好的,应用程序中的代码用它来获得Session对象。作为一个单个的数据存储,它也是 线程安全的,所以多个线程可同时使用同一个SessionFactory。Java JEE应用一般只有一个SessionFactory,服务于客户请求的各线程都通过这个工厂来获得Hibernate的Session实例,这也是为什么SessionFactory接口的实现必须是线程安全的原因。还有,SessionFactory的内部状态包含着同对象关系影射有关的所有元数据,它是 不可变的,一旦创建好后就不能对其进行修改了。

Hibernate中的Session指的是什么? 可否将单个的Session在多个线程间进行共享? 前面的问题问完之后,通常就会接着再问这两个问题。问完SessionFactory的问题后就该轮到Session了。Session代表着Hibernate所做的一小部分工作,它负责维护者同数据库的链接而且 不是线程安全的,也就是说,Hibernage中的Session不能在多个线程间进行共享。虽然Session会以主动滞后的方式获得数据库连接,但是Session最好还是在用完之后立即将其关闭。

hibernate中sorted collection和ordered collection有什么不同? T这个是你会碰到的所有Hibernate面试问题中比较容易的问题。sorted collection是通过使用 Java的Comparator在内存中进行排序的,ordered collection中的排序用的是数据库的order by子句。对于比较大的数据集,为了避免在内存中对它们进行排序而出现 Java中的OutOfMemoryError,最好使用ordered collection。

Hibernate中transient、persistent、detached对象三者之间有什么区别? 在Hibernate中,对象具有三种状态:transient、persistent和detached。同Hibernate的session有关联的对象是persistent对象。对这种对象进行的所有修改都会按照事先设定的刷新策略,反映到数据库之中,也即,可以在对象的任何一个属性发生改变时自动刷新,也可以通过调用Session.flush()方法显式地进行刷新。如果一个对象原来同Session有关联关系,但当下却没有关联关系了,这样的对象就是detached的对象。你可以通过调用任意一个session的update()或者saveOrUpdate()方法,重新将该detached对象同相应的seesion建立关联关系。Transient对象指的是新建的持久化类的实例,它还从未同Hibernate的任何Session有过关联关系。同样的,你可以调用persist()或者save()方法,将transient对象变成persistent对象。可要记住,这里所说的transient指的可不是 Java中的transient关键字,二者风马牛不相及。

Hibernate中Session的lock()方法有什么作用? 这是一个比较棘手的Hibernate面试问题,因为Session的lock()方法重建了关联关系却并没有同数据库进行同步和更新。因此,你在使用lock()方法时一定要多加小心。顺便说一下,在进行关联关系重建时,你可以随时使用Session的update()方法同数据库进行同步。有时这个问题也可以这么来问:Session的lock()方法和update()方法之间有什么区别?。这个小节中的关键点也可以拿来回答这个问题。

Hibernate中二级缓存指的是什么? 这是同Hibernate的缓存机制相关的第一个面试问题,不出意外后面还会有更多这方面的问题。二级缓存是在SessionFactory这个级别维护的缓存,它能够通过节省几番数据库调用往返来提高性能。还有一点值得注意,二级缓存是针对整个应用而不是某个特定的session的。

Hibernate中的查询缓存指的是什么? 这个问题有时是作为上个Hibernate面试问题的后继问题提出的。查询缓存实际上保存的是sql查询的结果,这样再进行相同的sql查询就可以之间从缓存中拿到结果了。为了改善性能,查询缓存可以同二级缓存一起来使用。Hibernate支持用多种不同的开源缓存方案,比如EhCache,来实现查询缓存。

为什么在Hibernate的实体类中要提供一个无参数的构造器这一点非常重要?

每个Hibernate实体类必须包含一个 无参数的构造器, 这是因为Hibernate框架要使用Reflection API,通过调用Class.newInstance()来创建这些实体类的实例。如果在实体类中找不到无参数的构造器,这个方法就会抛出一个InstantiationException异常。

可不可以将Hibernate的实体类定义为final类?
是的,你可以将Hibernate的实体类定义为final类,但这种做法并不好。因为Hibernate会使用代理模式在延迟关联的情况下提高性能,如果你把实体类定义成final类之后,因为 Java不允许对final类进行扩展,所以Hibernate就无法再使用代理了,如此一来就限制了使用可以提升性能的手段。不过,如果你的持久化类实现了一个接口而且在该接口中声明了所有定义于实体类中的所有public的方法轮到话,你就能够避免出现前面所说的不利后果。

阅读全文……

标签 :

jQuery ajax在GBK编码下表单提交终极解决方案(非二次编码方法) - JavaScript - web - ITeye论坛

http://www.open-lib.com/Forum/Read_69_1.action

前言:

当jquery ajax在utf-8编码下(页面utf-8,接收utf-8),无任何问题。可以正常post、get,处理页面直接获取正确的内容。

但在以下情况下:

GBK -> AJAX POST ->GBK

UTF-8 -> AJAX POST ->GBK

后台代码无法获取正确的内容,通常表现为获取到奇怪字符、问号。

经典解决方法:

1:发送页面、接收页面均采用UTF-8编码。

2:发送页面在调用ajax post方法之前,将含有中文内容的input用encodeURIComponent编码一次,而接收页面则调用解码方法( 如:java.net.urldecoder.decode("接收到内容","utf-8")  )。


其中,第一种方法无疑是最简单、最直接,但往往不符合实际,因为很多项目并不是使用utf-8编码,例如国内大部分使用gbk编码,也不可能为了解决这样一个问题,而将整个项目转换为utf-8编码,成本太大,风险太高。

第二方法,是现在最多人使用的方法,俗称二次编码,为什么叫二次编码,等下会解释。客户端编码两次,服务端解码两次。但这种方法不好的地方,就是前台手动编码一次,后台再手动解码一次,稍不留神就会忘记,而且代码掺和前台逻辑。

交互过程:

当我们使用表单按照传统方式post提交时候(非AJAX提交),浏览器会根据当前页面编码,encode一次,然后发送到服务端,服务端接收到表单,会自动dencode一次,通常这个过程是对程序是透明的,因此加上手动编码、解码,就变成上面所说的二次编码。

但当我们使用AJAX方式提交时候,浏览器并不会自动替我们encode,因此在jquery中有这样的一段代码:

Js代码  收藏代码
  1. ajax: function( s ) {  
  2.     // Extend the settings, but re-extend 's' so that it can be  
  3.     // checked again later (in the test suite, specifically)  
  4.     s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));  
  5.   
  6.     var jsonp, jsre = /=?(&|$)/g, status, data,  
  7.         type = s.type.toUpperCase();  
  8.   
  9.     // convert data if not already a string  
  10.     if ( s.data && s.processData && typeof s.data !== "string" )  
  11.         s.data = jQuery.param(s.data);  
  12. ........      
  13. }  

 

以上是jquery的ajax方法的代码片段,下面是正常调用jquery ajax post的代码:

Js代码  收藏代码
  1. $.ajax({  
  2.  url: ajaxurl,  
  3.  type: 'POST',  
  4.  dataType: 'html',  
  5.  timeout: 20000,//超时时间设定  
  6.  data:para,//参数设置  
  7.  success: function(html){  
  8.   
  9.  }  
  10. });  

 

通过上面代码可以知道,当设置了data时候,jquery内部会调用jQuery.param方法对参数encode(执行本应浏览器处理的encode)。

Js代码  收藏代码
  1. jQuery.param=function( a ) {  
  2.     var s = [ ];  
  3.     function add( key, value ){  
  4.         s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value);  
  5.     };  
  6.     // If an array was passed in, assume that it is an array  
  7.     // of form elements  
  8.     if ( jQuery.isArray(a) || a.jquery )  
  9.         // Serialize the form elements  
  10.         jQuery.each( a, function(){  
  11.             add( this.name, this.value );  
  12.         });  
  13.   
  14.     // Otherwise, assume that it's an object of key/value pairs  
  15.     else  
  16.         // Serialize the key/values  
  17.         for ( var j in a )  
  18.             // If the value is an array then the key names need to be repeated  
  19.             if ( jQuery.isArray(a[j]) )  
  20.                 jQuery.each( a[j], function(){  
  21.                     add( j, this );  
  22.                 });  
  23.             else  
  24.                 add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] );  
  25.   
  26.     // Return the resulting serialization  
  27.     return s.join("&").replace(/%20/g, "+");  
  28. }//jquery.param end  

 

上面是jQuery.param的代码,细心点可以留意到encodeURIComponent这方法,这是javascript内置的方法,对目标字符串执行utf-8 encode,因此,当页面使用gbk编码时候,服务端会使用gbk进行解码,但实际提交的数据是以utf-8编码的,所以造成接收到内容为乱码或者为问号。

解决方法:

encodeURIComponent会以utf-8编码,在gbk编码下,可不可以以gbk进行编码呢?

如果还在打encodeURIComponent主意的话,那不好意思,encodeURIComponent只会utf-8编码,并没有其他api进行其他编码;不过,别担心,看看下面:

encodeURIComponent,它是将中文、韩文等特殊字符转换成utf-8格式的url编码。

escape对0-255以外的unicode值进行编码时输出%u****格式,其它情况下escape,encodeURI,encodeURIComponent编码结果相同。

哈哈,看到希望吧?没错,就是用escape代替encodeURIComponent方法,不过必须注意:

escape不编码字符有69个:*,+,-,.,/,@,_,0-9,a-z,A-Z

encodeURIComponent不编码字符有71个:!, ',(,),*,-,.,_,~,0-9,a-z,A-Z

使用了escape之后必须对加号进行编码,否则,当内容含有加号时候会被服务端翻译为空格。

终于知道解决办法了,重写jquery代码:

Js代码  收藏代码
  1. jQuery.param=function( a ) {  
  2.     var s = [ ];  
  3.     var encode=function(str){  
  4.         str=escape(str);  
  5.         str=str.replace(/+/g,"%u002B");  
  6.         return str;  
  7.     };  
  8.     function add( key, value ){  
  9.         s[ s.length ] = encode(key) + '=' + encode(value);  
  10.     };  
  11.     // If an array was passed in, assume that it is an array  
  12.     // of form elements  
  13.     if ( jQuery.isArray(a) || a.jquery )  
  14.         // Serialize the form elements  
  15.         jQuery.each( a, function(){  
  16.             add( this.name, this.value );  
  17.         });  
  18.   
  19.     // Otherwise, assume that it's an object of key/value pairs  
  20.     else  
  21.         // Serialize the key/values  
  22.         for ( var j in a )  
  23.             // If the value is an array then the key names need to be repeated  
  24.             if ( jQuery.isArray(a[j]) )  
  25.                 jQuery.each( a[j], function(){  
  26.                     add( j, this );  
  27.                 });  
  28.             else  
  29.                 add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] );  
  30.   
  31.     // Return the resulting serialization  
  32.     return s.join("&").replace(/%20/g, "+");  
  33. }  

 

 

上面那段代码并不需要在jquery的源文件重写,可以在你项目的javascript贴上,覆盖它原有的方法,不过必须在jquery加载之后。

经初步验证,上面那段代码在utf-8编码也可以工作正常,大概是编码成unicode的缘故吧。

这样,就不是需要使用什么二次编码,即影响前台,又影响后台。gbk编码下ajax post不再是问题了,此乃是终极解决方法。哈哈。

阅读全文……

Sencha Touch Performance Tips and Tricks

How we worked with Sencha Touch 2 - some performance hints and other tips

Our team has been developing with Sencha Touch 2 since its pre-release days late 2011. We have spent a lot of time tweaking components and trying different techniques in order to speed up responsiveness - in an attempt to make it feel as native as possible. With the release of IOS6 on iPhone5 our latest version in development is pretty damn quick.

Why Sencha Touch 2?

In my opinion Sencha Touch 2 is the easy winner (when compared to jQuery Mobile).
Initially spending a few weeks investigating and prototyping with jQuery Mobile it was found to be no where near as production ready as Sencha Touch 2. If you are simply looking to mobile-ify a website, jQuery mobile may be a better alternative due to its quicker learning curve. However, if you are looking to create a more native look and feel web app - I wouldn't recommend anything but Sencha Touch 2 (for now).
 
The initial downside with the Sencha Touch 2 framework, is it's learning curve - it will take a little while getting used to, but its well worth it.  The coding practices it promotes within JavaScript are really promising (hopefully a good step in the right direction in bringing better/well architected structure to the JavaScript language). It took our team about a month or so to really understand the frameworks core concepts and components.

The other great point to make is it's 'just JavaScript', you have full access to the source code, and with the use of the ExtJS 'Override' functionality you can really do just about anything to the core Sencha Touch 2 library, without modifying the library source code.

However, if you're not interested in the challenge and learning something new, maybe Sencha Touch 2 isn't for you..

What sort of applications should you use Sencha Touch 2 for?

  • Content based apps
  • News listings apps
  • Daily deal apps

What sort of applications maybe not to use Sencha Touch 2 for?

  • 3D games
  • Any other sort of game
  • Applications that require high processing power (and potentially a lot of high resolution imagery).
*Not to say the above can't work.. But your are setting yourself up for a challenge.

Final point before we get started

I'm going to assume you have a good understanding of the key concepts of Sencha Touch 2. If you don't I highly suggest having a look at the great documentation at sencha.com:

All docs:
http://docs.sencha.com/touch/2-0/.

Kitchecn sink demo:
http://docs.sencha.com/touch/2-0/touch-build/examples/production/kitchensink/index.html

This article is aimed at developers who have been using Sencha Touch 2 and want to find out some of the approaches we made to improve performance within our upcoming version 2 of our application.

Disclaimer:
A lot of what I mention can be found within the Sencha Touch 2 docs, or by looking directly into the codebase of Sencha Touch 2.
 

Improving performance with views/panels

You basically have 2 approaches with view

Load all of your views/panels into the viewport at application startup:

Advantages
  • Your views/panels are in memory and ready to display when triggered by the users input.
  • If you have only a few views (around 5-10) you can most likely get away with this approach without many performance issues.
Disadvantages
  • As soon as your application grows, you will start suffering massive performance issues, slow button tap responsiveness, slow list scrolling, memory warnings (in Xcode) etc..
  • A much slower application bootup time.

Create and load your views/panels into the viewport on demand:

This is the approach we have now adopted (after we initially implemented option 1 above in our initial release version). We are now essentially creating 2 views within our viewport on application boot up (the loading screen, and the home screen). Every other view is created on demand.
 
Advantages
  • You are only loading the views/panels that are required into memory.
  • Faster button responsiveness.
  • Less in memory which means a more responsive application.
  • No more memory warnings from XCode.
Disadvantages
  • A little harder to implement.
  • It will take an extra bit of time to create and render the view within the app when triggered by the user (simple tests show anywhere from 40ms to about 300ms).
However, you are probably thinking after the user has viewed every view/panel within your application, your application will end up storing all of the views in memory anyway.. Wrong.. Lets take this approach a step further (you can see the below approach used in the kitchen sink example on http://docs.sencha.com/touch/2-0/touch-build/examples/production/kitchensink/index.html).
What you want to do is only keep your most popular views in memory at any one time.
The below code shows how you can manage all of your created views/panels within your viewport, and only keep the most used 5 views in memory, the rest get destroyed, meaning your application is using less memory.



/**
 * app/views/viewport.js
 */
Ext.define('Demo.view.Viewport', {
    extend: 'Ext.Container',
    xtype: 'demo-viewport',
    requires: [
        // Home Loading
        'Demo.view.home.Loading',
    ],
    /**
     * Configuration
     * @property {object}
     */
    config: {
        layout: {
            type: 'card',
            animation: {
                type: 'slide',
                direction: 'left',
                duration: 250
            }
        },
        fullscreen: true,
        items: [
            // Home Loading
            {xtype: 'home-loading'},
        ]
    }
});

/**
 * app/controller/Base.js
 * We use a Base Controller to do the logic that is used throughout
 * the application (eg: back button, memory handling of views etc..)
 */
Ext.define('Demo.controller.Base', {
    extend: 'Ext.app.Controller',

    /**
     * Configuration
     * @property {object}
     */
    config: {

        /**
         * Refs
         * @property {object}
         */
        refs: {
            /**
             * @ignore
             * Panels
             */
            viewport: 'demo-viewport',
            /**
             * @ignore
             * Buttons
             */
            back: 'button[action=back]'
        },
        previousPanels: [],
        /**
         * Controls
         * @property {object}
         */
        control: {
            /**
             * Generic back to button (if you want one)
             */
            back: {
                tap: function(el, e){
                    var viewport = this.getViewport();
                    var activeItem = viewport.getActiveItem();
                    var view = activeItem.oldItem;

                    // Unset oldItem as its now been used
                    activeItem.oldItem = null;

                    // Set back animation
                    viewport.getLayout().setAnimation({type: 'slide', direction: 'right'});

                    // Go back to oldItem
                    viewport.setActiveItem(view);

                    if( e.event ) {
                        // Stop events from bubbling
                        e.stopEvent();
                    }
                }
            }
        }
    },

    launch: function() {

        var viewport = this.getViewport();
        var baseController = this;

        viewport.addBeforeListener(
            'activeitemchange',
            function( card, newItem, oldItem ){

                // Fix for overlapping panels (explanation later)
                // allow the panel to change
                if (oldItem.aboutToBeVisible === true) {
                    newItem.aboutToBeVisible = false;
                    newItem.hide();
                    return false;
                }

                // flag that the new item is now in the process of being
                // transitioned to
                newItem.aboutToBeVisible = true;

                // For the "Back" functionality
                // Only set oldItem if its null,
                // If its not null it means that there is an already set oldItem that
                // should be used instead.
                if( null == newItem.oldItem ){
                    newItem.oldItem = oldItem;
                }
            }
        );

        viewport.addAfterListener(
            'activeitemchange',
            function( card, newItem, oldItem ){

                var index,
                    panels = baseController.getPreviousPanels(),
                    newId = newItem.getItemId();

                // The below code ensures that only 5 view panels are kept in memory
                // This is to speed up the application
                for( index in panels ){
                    // If panel already exists in array, remove it
                    if (panels[index].getItemId() == newId) {
                        panels.splice(index, 1);
                        break;
                    }
                }

                // Add new item to top of array
                panels.splice(0, 0, newItem);

                // remove one panel from the back of the array
                if( panels.length > 5 ) {
                    var panel = panels.pop();
                    panel.destroy();
                }

                // The new item is no longer transitioning, so flag it as such
                newItem.aboutToBeVisible = false;
            }
        );
    }
});

/**
 * app/controller/Home.js
 * Example of a controller in your application
 *
 */
Ext.define('Demo.controller.Home', {
    extend: 'Ext.app.Controller',
    requires: [
        'Demo.view.home.Index'
    ],
    /**
     * Configuration
     * @property {object}
     */
    config: {
        /**
         * Refs
         * @property {object}
         */
        refs: {
            viewport: 'demo-viewport',
            /**
             * Defining all of your views to autoCreate: true will ensure
             * Sencha will create them if the dont already exist in memory
             */
            homeIndex: {
                selector: 'home-index',
                xtype: 'home-index',
                autoCreate: true
            }
        },
        /**
         * Controls
         * @property {object}
         */
        control: {

            /**
             * Home index
             */
            homeIndex: {
                hide: function(){
                },
                show: function(){
                }
            }
        }
    }
});
 
The above example also shows a generic back button for your application, simple add
action: 'back'
on your button within your view and the rest is taken care for you. However, if you are using the routes in Sencha Touch 2 you will not need this (as the framework already will take care of this for you).

 

Improving list scrolling and performance

Lists are supposed to scroll smoothly and fast - if they don't, your user will undoubtedly know your application is not native. Below are some points to take into consideration.

Gradients and other CSS3 styles

Avoid gradients/box shadows as much as you can within lists and list items. Our application initially used linear gradients for each list item, scrolling was a little jerky. Replacing this with a flat background color really improved the responsiveness of the scrolling (especially in the iPhone4).
 
[Screens coming soon.]

High Resolution images in lists

Try to use low resolution images in list items (if your list has images), this will help to increase scrolling speed within your lists. I can identify 2 reasons for this:
  • Low resolution images are smaller in file size:
    • Requires less bandwidth to download each image.
    • Smaller amount of data stored in local memory cache.
  • Lower quality images animate faster (less processor intense).
  • You can look into staggering the loading of images (so as to make less concurrent web requests). We do this, if you would like a code sample contact me.

Amount of items in list

This is still an unresolved problem with our application. Sencha Touch 2 seems to handle about 30-40 items in a list nicely (each list item has an image). Any more than this the phone begins to struggle. I would suggest looking at the pagination (infinite scroll plugins that are available). Due to our underlying API web service we cannot implement this easily at the moment.
 

 

Improving panel transitions and responsiveness

Ensuring when a user taps/clicks a component within your application you want an immediate,or as close to immediate response as possible. The more complicated your application becomes, the more you tend to try to accomplish within the 'show' events of a panel. For examples you may be doing one or more of the following:
  • Hiding/showing components
  • Populating form data
  • Populating dynamic data into panel.
  • etc..
If your view/panel is doing this on the 'show' event it can slow down the response/transition of that panel when the user triggers the event. An approach our application is using, is keeping the show events as light weight as possible (usually doing nothing), and creating a delayed function to actually do the complex logic.

This approach allows your application to transition immediately to the next panel, giving a more native feel, and then doing the processing. See below:
 
/**
 * app/controller/Portfolio.js
 *
 */
Ext.define('Demo.controller.Portfolio', {
    extend: 'Ext.app.Controller',
    requires: [
        'Demo.view.portfolio.Index'
    ],
    /**
     * Configuration
     */
    config: {
        /**
         * Refs
         */
        refs: {
            viewport: 'demo-viewport',
            portfolioIndex: {
                selector: 'portfolio-index',
                xtype: 'portfolio-index',
                autoCreate: true
            }
        },
        /**
         * Controls
         */
        control: {
            /**
             * Product index events
             */
            index: {
                show: function(){
                    var me = this;

                    Ext.create('Ext.util.DelayedTask', function () {
                        me.getIndex().fireEvent('showDelay');
                    }).delay(500);
                    // 500 should be set to more than your card layout transition time
                },
                showDelay: function(){

                    /**
                     * More processor intense processing can be done
                     * here, as panel will have been in view
                     */
                }
            }
        }
    }
});
 

Screen overlay bug

Sometimes Sencha Touch 2 cannot keep up with user input. I have noticed this happens on larger more complex applications. An example looks similar to below:

[Screens coming soon.]

I have only noticed this when using a card layout. Basically, this can happen if you click on multiple list items within a single list which both try to set different views as the active item within the card layout. Sencha attempts to show both views and can sometimes result in multiple panels being displayed over one another.

[Screens coming soon.]


Another way to duplicate is (you may need 2 hands for this), if you have a view/panel with a button (potentially in the header) and a list item. If you repeatably tap both the list item and the button you will most likely be able to trigger this view/panel overlay bug.

 
 
 
 

Approaches we have used to resolve.

BaseController (preferred):
Within the Base Controller we listen to the before and after events of the 'activeitemchange' for the viewports card layout. We manually track the when an item has being set as 'active item' within the card layout, by setting:
newItem.aboutToBeVisible = true;
Then once the after listener has been triggered we set:
newItem.aboutToBeVisible = false;

If 2 items are triggered in the before event as event, the secondary one hides itself and returns false.
/**
 * app/controller/Base.js
 *
 */
Ext.define('Demo.controller.Base', {
    extend: 'Ext.app.Controller',
    launch: function() {

        var viewport = this.getViewport();
        var baseController = this;
        // Listen to the before event for setting activeItem in viewport.
        viewport.addBeforeListener(
            'activeitemchange',
            function( card, newItem, oldItem ){

                // Fix for overlapping panels
                // If a panel is already in process of becoming visible,
                // hide this new panel and return false to stop the transition.
                if (oldItem.aboutToBeVisible === true) {
                    newItem.aboutToBeVisible = false;
                    newItem.hide();
                    return false;
                }

                // Set that the new item is now in the process of being transitioned to
                newItem.aboutToBeVisible = true;

                // If not null set oldItem so we can use the back button
                if( null == newItem.oldItem ){
                    newItem.oldItem = oldItem;
                }
            }
        );
        // Listen to the before event for setting activeItem in viewport.
        viewport.addAfterListener(
            'activeitemchange',
            function( card, newItem, oldItem ){

                var index,
                    panels = baseController.getPreviousPanels(),
                    newId = newItem.getItemId();

                // The below code ensures that only 5 view panels are kept in memory
                // This is to speed up the application
                for( index in panels ){
                    // If panel already exists in array, remove it
                    if (panels[index].getItemId() == newId) {
                        panels.splice(index, 1);
                        break;
                    }
                }

                // Add new item to top of array
                panels.splice(0, 0, newItem);

                // remove one panel from the back of the array
                if( panels.length > 5 ) {
                    var panel = panels.pop();
                    panel.destroy();
                }

                // The new item is no longer transitioning, so flag it as such
                newItem.aboutToBeVisible = false;
            }
        );
    }
});

Buttons:

Another solution to a similar issue in 2.0 of Sencha is tapping a button multiple times can sometimes trigger multiple tap events (Im not sure if this happens in the 2.0.1 yet). This can slow your application if your application is listening for the tap event and doing a complex task. We have implemented a timer on each button (by overriding the button component). What this new functionality does is listen for when a button is tapped, uses a variable to disable any future tap events from bubbling up to the application for the next 2 seconds.
 
/**
 * app/override/Button.js
 */
Ext.define('Demo.override.Button', {
    override: 'Ext.Button',
    lastTapped: 0,
    /**
     * Update button icon to support masking
     * @return {String} icon
     */
    updateIcon: function(icon) {
        var me = this,
            element = me.iconElement;

        if (icon) {
            me.showIconElement();

            if( this.getIconMask() ){
                element.setStyle('-webkit-mask-image', icon ? 'url(' + icon + ')' : '');
            } else {
                element.setStyle('background-image', icon ? 'url(' + icon + ')' : '');
            }
            me.refreshIconAlign();
            me.refreshIconMask();
        }
        else {
            me.hideIconElement();
            me.setIconAlign(false);
        }
    },
    /**
     * On slower devices Sencha registers multiple button taps
     * which ends up causing strange behaviour. Ensure only a single
     * button tap every 2seconds per button
     */
    onTap: function(e) {

        var now = Date.now();

        if (this.getDisabled()) {
            return false;
        }

        if ( (now - this.lastTapped) > 2000 ) {
            this.lastTapped = now;

            this.fireAction('tap', [this, e], 'doTap');
        }
    }
});

 

 

Native components

Although our application is bundled as an IOS application and listed on the apple store (and hopefully soon Android) we took a specific approach so as to not tie ourselves too heavily to native functionality. 
 
"Make it work as a web app within a browser first"

Essentially what this means is our application is coded to work entirely within the browser first. All functionality must work in a browser. We then add the specifics eg: 

  • Passbook functionality (on IOS6) - only visible on IOS6 devices.
  • Local notifications (eg: setting up calendar reminders) - Only available on the iPhone - to any other user (web browser) this functionality is not available/hidden.
  • Email composer - only available if within Cordova application.
With this approach you are not locking yourself into a specific platform, more so supporting the web browser first, then progressively adding more support to devices/platforms that support it.
 
Why? I'm a firm believer that native functionality (that we use Cordova to get access to) will slowly become available from directly within the browser. As this happens you can start removing Cordova plugins that provide connection to the native libraries. Yes its a bit of a gamble, but it cannot hurt to architect your application in this way since you have already chosen a HTML5/JavaScript pathway.

Compile your JavaScript

Always compile your javascript into app-all.js.

Our application has multiple environments, below is an example of 2, development and production index files.
  • index-dev.html
    <html>
        <head>
            <link href="css/application.css" rel="stylesheet" type="text/css"></link>
        </head>
        <body>
            <script>
                // Define environment
                var settings = {
                    environment: 'development'
                };
            </script>
    
            <script src="app/ConfigurationDev.js"></script>
            <script src="lib/sencha/sencha-touch-debug.js"></script>
            <script src="app.js"></script>
        </body>
    </html>
    
    
  • index.html
    This is our production environment.
    Includes the cordova plugins and the compiled Sencha Touch 2 files:
        <head>
            <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" />
            <meta name="apple-mobile-web-app-capable" content="yes" />
            <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    
            <link href="css/application.css" rel="stylesheet" type="text/css" />
        </head>
        <body>
            <script>
                console.log = function(){};
            </script>
    
            <!-- Load up cordova plugins -->
            <script type="text/javascript" src="lib/cordova/cordova-1.8.1.js"></script>
            <script type="text/javascript" src="lib/cordova/NotificationEx.js"></script>
            <script type="text/javascript" src="lib/cordova/LocalNotification.js"></script>
            <script type="text/javascript" src="lib/cordova/PushNotification.js"></script>
            <script type="text/javascript" src="lib/cordova/PixAuth.js"></script>
            <script type="text/javascript" src="lib/cordova/ChildBrowser.js"></script>
            <script type="text/javascript" src="lib/cordova/SMSComposer.js"></script>
            <script type="text/javascript" src="lib/cordova/EmailComposer.js"></script>
    
            <script type="text/javascript" src="app-all.js"></script>
        </body>
    </html>
 
 

 

Configuration setup

Managing configuration is a core component within any application. Below is a sample of how our configuration is setup. We use 4 environments as below:
  • Production (default)
  • Staging
  • Emulator (Used for automated user testing)
  • Development (included only within the index-dev.html file)
There are many ways configuration can be implemented, this one for now works well for us.
 
/**
 * app/Configuration.js
 */
var settings = settings || {
    environment: 'production'
};

/**
 * Production environment
 */
settings.production = {
    'version': '2.0',
    'api.user': '',
    'api.key': '',
    'api.timeout': 30000,
    'logger.writer.db': true,
    'logger.writer.alert': false,
    'logger.writer.console': false,
    'logger.writer.remote': true,

    // Cache
    'cache.user.login': 3600,
    'cache.configuration.list': 86400,

    // Our system has 2 performance profiles
    // High profile - for newer phones (iPhone 4S & 5)
    'performance.high.base.transition.time': 250,
    'performance.high.scrolltotop.time': 500,
    'performance.high.list.acceleration': 15,
    'performance.high.list.friction': 0.3,

    // Low profile - for older phones (iPhone 3S, 4)
    'performance.low.base.transition.time': 400,
    'performance.low.scrolltotop.time': 750,
    'performance.low.list.acceleration': 10,
    'performance.low.list.friction': 0.4
};

/**
 * Staging overrides
 */
settings.staging = {
    'api.user': '',
    'api.key': ''
};

/**
 * Development environment
 * @ignore
 *
 * See app/ConfigurationDev.js
 */

/**
 * Override configuration with merged config.
 * This is so there is no possible way settings
 * from other environments can be accessed unless
 * through the configuration object
 */

for( var i in settings.production ) {
    settings[i] = settings.production[i];
}

// This defines which environment will be deployed
for( var i in settings[settings.environment] ) {
    settings[i] = settings[settings.environment][i];
}

settings.production = undefined;
settings.staging = undefined;
settings.development = undefined;
settings.emulator = undefined;

/**
 * All configuration within application goes through this object
 */
Ext.define('Demo.Configuration', {

    /**
     * Performance profile high
     */
    PERFORMANCE_HIGH: 'high',

    /**
     * Performance profile low
     */
    PERFORMANCE_LOW: 'low',

    /**
     * Configuration
     * @property {object}
     */
    config: {
        settings: settings,
        performance: null
    },
    /**
     * Singleton
     */
    singleton: true,
    /**
     * Constructor
     * @contructor
     */
    constructor: function(config) {

        if( undefined == config ) {
            config = {};
        }

        // Default performance to high
        config.performance = this.PERFORMANCE_HIGH;

        // If devicePixelRatio is 1, it implies low res display (on iphone)
        // Drop performance to low for this device
        if( 1 == window.devicePixelRatio ) {
            config.performance = this.PERFORMANCE_LOW;
        }

        this.initConfig(config);
    },

    /**
     * Get property
     *
     * @param {String} name
     * @return {String}
     */
    getProperty: function( name )
    {
        return this.getSettings()[name];
    },

    /**
     * Set property
     * Not is use yet
     *
     * @param {String} name
     * @param {String} value
     */
    setProperty: function( name, value )
    {
        this.getSettings()[name] = value;
    },

    /**
     * Get properties
     */
    getProperties: function()
    {
        return this.getSettings();
    },

    /**
     * Get environment
     */
    getEnvironment: function()
    {
        return this.getSettings().environment;
    },

    /**
     * Is environment development
     */
    isEnvironmentDevelopment: function()
    {
        return ('development' == this.getSettings().environment);
    },

    /**
     * Is environment staging
     */
    isEnvironmentStaging: function()
    {
        return ('staging' == this.getSettings().environment);
    },

    /**
     * Is environment production
     */
    isEnvironmentProduction: function()
    {
        return ('production' == this.getSettings().environment);
    },

    /**
     * Get a config value based off profile in use
     */
    setPerformanceHigh: function()
    {
        // Set performance to high
        this.setPerformance(this.PERFORMANCE_HIGH);
    },

    /**
     * Get a config value based off profile in use
     */
    getPerformanceProperty: function(name)
    {
        return this.getProperty('performance.' + this.getPerformance() + '.' + name);
    }
});
 

Design Requests:

The entire product team (designers, developers, product) need to be smart when it comes to HTML mobile development with Sencha Touch 2. Sencha Touch 2 is a great step forward in web application development, but there are many areas you need to focus on. Based off the last year of mobile app development a lot of care needs to be taken into account when designing for Sencha Touch 2. Designers and UX needs to be focused on delivering a fast performing application rather than an application full of CSS3 effects that will slow down performance dramatically.

My advice, start simple, add new features with care. Benchmark and test each change on actual devices to be certain there are no huge performance issues. 

Develop in Chrome for fast development - we dramatically increased our development speed by being able to develop directly within chrome. However, constantly test your features in the simulator and the device - otherwise you will find yourself with problems later on in the project. 
 
 

Working code sample

Here is a very basic working code sample project that uses the code mentioned in this post.
Demo application

Create a virtual host
For development go to
http://virtualhost/index-dev.html

For production go to
http://virtualhost/index.html
I use the above one with Cordova (previously PhoneGap). If there is any interest I will include the Cordova configuration within the code sample.

Hope you learned something from my blog post, any questions let me know..

Dion Beetson
Founder of www.ackwired.com
Linked in: www.linkedin.com/in/dionbeetson
Twitter: www.twitter.com/dionbeetson
Website: www.dionbeetson.com
 

 

阅读全文……

标签 : , ,