Monitor Your Application’s Network Calls
As our applications evolve and we add more functionality the chances for making redundant calls for the server grow. User’s activities, timers that work in the background, event handlers etc. can create overlapping operations. A nice trick which helps you monitor the network calls your application makes, is tracing each network call or tracing each response.
If you aggregated all your calls to a single class e.g. CNetworkComm which exposes the method sendHttpRequest(…) then you can add a trace in the beginning of the function specifying the type of the command which is sent to the server.
1 2 3 4 5 6 | public function sendHttpRequest(requestType:String, paramsList:ArrayCollection, url:String, listenToReply:Boolean):void { trace ('Sending ' + requestType + ' request to server'); } |
This will dramatically minimize the time you spend understanding what your application is trying to do or why various operations take so much time. You may find redundant calls, be able to trace where they are coming from and remove them.
If you didn’t aggregate all network calls into a single class, trace each callback function. For example,
<mx:HTTPService url="http://myserver/getUsersInfo.php" resultFormat="e4x" result="getUsersInfoResultHandler(event)" fault="getUsersInfoFaultHandler(event)"/> <mx:HTTPService url="http://myserver/getExtraInfo.php" resultFormat="e4x" result="getExtraInfoResultHandler(event)" fault="getExtraInfoFaultHandler(event)"/>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | private function getUsersInfoInResultHandler(event:ResultEvent):void { trace("getUsersInfoInResultHandler was called"); … } private function getExtraInfoResultHandler(event:ResultEvent):void { trace("getExtraInfoResultHandler was called"); … } private function getUsersInfoFaultHandler(event:FaultEvent):void { trace("getUsersInfoFaultHandler was called"); … } private function getExtraInfoFaultHandler(event:FaultEvent):void { trace("getExtraInfoFaultHandler was called"); … } |
In any event, it is best practice to trace both outgoing calls and incoming responses so you would be able to better understand what your application does.
Optimizing FLEX Application Performance
Is your FLEX application slow? Does it take a long time to load? Does every operation you do make the application sweat?
Let’s talk a bit about FLEX based application performance. Let’s take a simple user directory application in which I have an entry for each registered user which contains the user’s personal details such as name, address, DOB, his picture etc. The application is pretty basic and enables the application administrator to view all the registered users, add / delete / modify users etc.
Choose the right container
The first thing to take into consideration is which container to use. Different FLEX containers behave differently. In general, there are 2 types of containers – a child based container and an item renderer based container. The former includes the Canvas, HBox, VBox etc. These containers are basic in the sense that every additional child added to them, is allocated the required resources in the view and memory. Therefore, if you have 10,000 records of users and you load them into a VBox container, the VBox container will hold each one of them in memory. Assuming that every record is not tiny, then your screen will be able to display only a fraction of these 10,000 records – let’s say 50. In this case, there is no reason to keep the additional 9950 records in memory. Keeping these records in memory may consume a lot of system resources and make your application very slow. It can affect it both when loading (if you load the entire 10,000 records) and every time you try to scroll.
The second type of containers is more sophisticated – the item renderer based containers such as List, TileList, Grid, etc. These containers implement deferred loading and deferred instansiation so they don’t load all the items into memory. They measure how many items fit in the stage (the main drawing area) and allocate this number for items to be displayed. So even if the application needs to load 10,000 items to a List, it will only load and instantiate the number of items that fit into the screen. These containers also reuse renderers. Let’s say that the view has 50 items in the stage. When a user scrolls down, new items enter the stage. In the same time, old items leave the stage. The container will reuse the same item renderers for the new items that entered the stage so the application doesn’t instanitate or load any unnecessary items.
Take a look at the following code samples. While both produce the same visual result, the first way uses a VBox and loads all items to the stage, while the second way uses a List control which loads items on demand.
Using VBox
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | private function addItemToView(name:String, address:String, image:String):void { var vbox:VBox = new VBox(); var nameLabel:Label = new Label(); nameLabel.text = name; var addressLabel:Label = new Label(); addressLabel.text = address; var image:Image = new Image(); image.source = url; vbox.addChild(nameLabel); vbox.addChild(addressLabel); vbox.addChild(image); itemsContainer.addChild(vbox); } <mx:VBox id="itemsContainer" width="50%" height="100%" backgroundColor="0xffffff" /> |
Using List
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <mx:List dataProvider="{itemsList}" rowHeight="110"> <mx:itemRenderer> <mx:Component> <mx:VBox> <mx:Label text="{data.name}" /> <mx:Label text="{data.address}" /> ... <mx:Image source="{data.url}" /> </mx:VBox> </mx:Component> </mx:itemRenderer> </mx:List> |
Fetch meta data from the Server
So after we understand how to display our registered users we can simply load the information when the application starts. A potential bottle neck is loading the data provider in case the number of users is high. If the number of users is relatively large, then passing the information means passing an XML record for each user. Each XML record may look something like the following:
1 2 3 4 5 | <user id="1" first_name="John" last_name="Doe" address="1st Elm Street" phone_no="123456" image="thumbnail.jpg"/> |
Passing a large number of these records means a large amount of data traversing the network. Instead, when the application loads you can request the server to send a summary of the meta data, e.g. how many registered users are in the system. Then, start loading a fragment of those users, e.g. 100 at at time which will take a short time to complete and then fetch the next 100 registered users only when the user asks for it, i.e. when they scroll down or go to the next page. This design pattern is called lazy loading or deferred loading. In this constellation, there is a good chance that the average user may never trigger the loading of the entire data set from the server.
Aggregate server commands
Another common mistake is not aggregating client / server commands. For example, while the list of registered users is displayed, the administrator can delete a specific user. The first thing that comes into mind is simply send the server a “delete user” command along with the ID of the user to be deleted. If I translate it into XML command it will look like the following:
1 2 3 4 5 | <delete_user> <user id="1"/> </delete_user> |
While this will work, imagine that the administrator wants to delete 10 users (or 100 users or maybe even delete all users). Since you already
have a “deleteUser” method, you might be tempted to reuse that function and the code will probably look something like the following:
1 2 3 4 5 6 7 8 9 10 | private function deleteUserList(usersList:ArrayCollection) { for each (var user:Object in usersList) { // this is a call to the server deleteUser(user.id); } } |
What we have here is N discrete requests to the server and you want to avoid that. The reason you want to avoid that issuing a new request to the server for each user means that your application will become chatty and produce more traffic as every request has its request header not to mention that lower levels (TCP SYN / ACK etc.). The chattier your application, it means that it is more exposed to fluctuations in the network conditions and at the end of the day it could mean a longer time for each transaction to complete. On top of it, if you provide the ability to delete several users in a batch it would be much easier to the user too, so instead of hitting the delete button 10 times and confirming the deletion, they will check the user to be deleted and delete them in a single action. The delete XML would look something like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <delete_users> <users> <user id="1"/> <user id="2"/> <user id="3"/> </users> </delete_users> |