Google+ Badge

Wednesday, December 21, 2011

JIDE Table as a base for the scheduler

For the moment I am going to punt on MigCalendar and give JIDE a shot for our scheduler. We already have a license for JIDE so might as well get some use out of it and we can drop a custom control and JAR files from the project with the switch.

The JIDE table supports various row sizes, multilevel columns, custom rendering, row labels, etc. Pretty much everything we need I hope as long as I can get a really nice looking custom cell renderer in place. Row / Column spanning is in there but night be tricky to handle what we need. I also need to pivot things but will probably do that with a table reconfiguration instead of using their pivot stuff due to special layout needs.

I did run into some issues with the new MarginArea as the demo they provide was missing that file. I almost got the code running by guessing at they layout but it was not working. A quick email to them got me the example code I needed to complete the task. Very happy with the turn around time on that request.

Going to take some effort to get it all in place but for the first days work I feel I got a lot of progress done. The documentation is still a bit iffy, English is a second language for them and web support is light but I have a feeling I can fight through this one.

I hate to give up on MigCalendar but so many here have tried to make it work the way we need it that it is really time to move on to something else that hopefully solves most of our needs and does not introduce too many new headaches.

Tuesday, December 20, 2011

MigCalendar = slow progress

While MigCalendar might be powerful and flexible it is poorly documented and there is pretty much non-existent support on the Web. The company locked down their forums so you can only submit bug reports. I did just submit one so I hope I get an answer to what seems something simple really soon. I hate it when you don't have access to a forum to share problems and solutions with other users.

I did break down what I am attempting to do into a single source code file. I want to get as much running as possible in that before I integrate with the main product. I have been doing work in the main product too and running into the same issues. With all the other work I am attempting in the main product I don't want to get my lack of knowledge of MigCalendar to get in the way of my lack of knowledge on the rest of the scheduler.

I have looked for other products that support our needs and have found nothing. I can find simple calendars that show a day, week or month of events for a single person and with time on left and day above but we need to show times for facilities / room, providers, etc. I need a flexible header and activities driven by categories. I need to show overlapping appointments.

It is getting close to fish or cut bait time. This happens with a lot of 3rd party controls. You get a nice demo going or get a first cut of your code running then you start to run into walls. I am not sure if some of the walls can be broken down here or if they are solid limitations. Writing a header, footer, time range and full grid with drag and drop would take a lot of time but in the end you get exactly what you need. I have written plenty of custom controls in Java and other languages so I can tackle this. Start with a small piece and build it up until it all works.

Others in the office have had the same experience. They can fix an issue here and there, tweak the look and then get out quickly. The open issues have been left open because no one has been able to fully understand what MigCalendar does. I have it working better in some places - especially with two level deep categories in the header - but broken in so many others. We know it can run fast, you can see that in the demos, but our code is rebuilding too many things as things are selected. The demos tend to just have one layout where we allow the user to toggle how they are looking at things similar to pivot tables in Excel. It has to recalculate too many things about the activity when this happens. That also leads me toward doing a custom solution that is optimized for the data we have. Generic is wonderful when you can pull it off but there are times you need things to do exactly what you want and do them very fast.

When I was working in the stock market industry I wrote a custom grid in Java to do high speed painting of real-time market data. It could handle a lot of updates using dirty cell paint management and doing paints on a quarter second timer. For a long time it was used by one of the big "do your own trading" places that you see advertise on TV. Not a scam place, a legit trading company that is still around. Sure I can look over that code and get some ideas. It was all Java 1.1 AWT and I did a conversion to 1.2 Swing at one point. Old stuff but highly optimized.

Monday, December 19, 2011

New application version out on both Markets on to MigCalendar

I finished up the latest release for both Android and iOS and QA accepted it as ready to release on Friday. I submitted it to the Android Market where it appeared nearly instantly. To my surprise it appeared in the Apple App Store on Sunday - I got an email from Apple on its updated status. I did not think they actually worked over the weekend. This is the second time it appeared two calendar days after submission.

Based on activity they just gave it the illegal method check once over and let it go to the store as it is an update to an existing application that requires a login to use. It is only useful to our clients. It is not like we are selling it and forcing you to buy a login outside of the App Store so I don't think Apple gives a rip about what it does, if it leaks memory or how clients use it. I don't they even care if it follows their UI guidelines. As far as I know it is not doing anything out of the norm. I triple make sure I am not using any private API calls, run it through the leak detector as I add features and really the app is used to access our servers and get to the data the client owns and to send updates to the data as needed. I am very happy it went through the process quickly.

Now that everything is out of beta we can turn on access at our server level and let the doctors have some real fun with the application. I am really curious to see how many installs we have for each platform and how much data traffic it generates. Doctors can take pictures of the medical paperwork on site so we should start to see images flying about soon enough. Well I won't see the images, I don't look at live medical records, but I can see file sizes and what not from the log files.

We will await feedback from the clients before we plan the next release. I have some items I would love to add to the program but if no user requests them it is better to work on the real needs and not my guesses. This means I am on break from the mobile side of things for a bit. Good thing to happen just before the holiday season.

I will be working on our front office appointment scheduling program. It makes heavy use of MigCalendar. I have messed around with that a bit but am a total novice. The original developer is not here so I will have to figure out most of it on my own. MigCalendar is a bit of an odd bird. Things are stored in statics that I don't feel belong there. The API is very powerful but not easy to understand. I started work on this a few months back and made a lot of notes so I am reviewing those and diving into the code. Various object constructors have numerous parameters which can get hard to follow.

Our current UI is lacking in usability. There is a tabbed interface but the tabs are really just controlling what is visible in the calendar. There are two buttons inside each tab that control the filtering operations. I am converting these into a combobox with 4 items mirroring the tab / button combinations listed above. This gives more vertical room in the UI and gets rid of a tab that is not really a tab and buttons that toggle but don't. It allows me to add more root + filter modes in the future and cleans up the code nomenclature as the buttons were namee after their functionality in the first tab which has no relationship to what they do in the second tab. It also leaves one instance of MigCalendar running instead of two. Yes, this whole application was in dire need of help.

Can't say I am looking forward to getting this all up and running again. One of those projects you just want to scrape and start from scratch. I feel like it should appear on "Extreme Code Makeover" and we should blow up a computer with C4 while some dude with OCD runs around screaming. Once I get over the initial learning curve it should not be too bad. Of course once it starts working like it should more requests will come in and I will be tagged as "the guy" that knows the appointment stuff. Since I am taking off the last week of the year I also get a chance to forget most of what I figure out this week. Nothing like starting over twice in a few weeks period.

Thursday, December 15, 2011

New ADT 16 includes Lint - very nice

Grabbed the new Android ADT version 16 and it includes Lint. I like to keep my code as clean as possible so I found this very useful. My initial warning list was 374 items which can seem a bit scary. I have knocked that down to 252 items doing some very simple things. All of the information provided by Lint is useful but not all of it is something you must fix.

I have some strings that are hard coded in my XML files. They say things like "date placeholder" and the like. I have them to help me visualize things when I am using the preview window. Actual values will be filled in at run-time. There are also a lot of "Missing contentDescription attribute on image". I might just shut this one off. This program is used by doctors. Being visually disabled pretty much means they can't do their job. I don't need to support that functionality in this project.

I was able to cleanup some "useless layout" issues. I converted single row table layouts to liner layouts. I was setting the background color and cache color hint in a lot of my XML files. It recommended using a theme instead as everything was getting painted twice - once with the them background color and then with my override color. I set up an application level theme in the manifest to solve that problem. It also forced me to define the background color in the style.xml file so I did a quick search across all XML files and updated them to use the define. I can now change the color in one place and have the entire app use it. I should have done it this way from the start but somehow cut and paste seems more fun than doing it right when you are in a hurry. We are hitting final QA cycle and I want things to be as clean as possible meaning this was a good time to fix past oversights.

I still have a couple of "children can be replaced by one and a compound drawable." items to clean up. That will involve code changes and not just XML changes as the image in font of the text changes at run-time depending on the status of the item. Makes sense to use the setCompoundDrawables instead of having two controls in a LinearLayout when the TextView already has full support for that. I will probably wait to change those for the next release as I could find subtle issues to work around since I have not used this particular functionality in a layout used for table rows.

It is highly recommended that you grab the latest ADT and see what warnings it has in store for you. Between the new Lint and the utility I wrote to scan my project for orphan resources I am feeling pretty darn good about the next release. QA has not found any issues in a few days so it looks like we will ship in the next few business days and allow our clients to enjoy all the new functionality.

Wednesday, December 14, 2011

A better understanding of iOS memory management

I got a free eBook called "Objective-C Fundamentals" from Manning. Chapter 9 covers memory management, something we all love. While I have read various things about viewDidLoad viewDidUnload and didReceiveMemoryWarning I had not tied them all together. Since my app can get into low memory conditions when using the camera reading this chapter gave me a better understanding of things. It was time to tackle this beast.

The home screen in the app consists of 6 images the user can press. Below those images are labels and behind all of that are shelves that the images sit on. The labels are pasted onto the shelf front. Pretty basic stuff but the images can be large on the iPad or retina iPhone so we need to be a good neighbor and throw them away during low memory conditions.

First mistake I was making - loading all the images in the init method. I should load things in viewDidLoad. I moved all that code which was easy enough.

Second mistake - thinking viewDidLoad and viewDidUnload are called in pairs. Gee, the names sure lead you to believe that will happen but it does not. Apple added viewDidUnload support in iOS 3.0 but did not add pair calling code. Since this view in my app is not using an XIB because I totally change the layout between portrait and landscape I don't get viewDidUnload support even when I call self.view = nil in didReceiveMemoryWarning. If I set it to null then I will get multiple viewDidLoad calls but never a viewDidUnload call.

Here is what happens:

Initial view appears
   viewDidLoad is called
As I go back and forth between the Home view and other views - no other calls made.
Go to a view off the home view (home view not visible at this point)
Force low memory condition in simulator
   didReceiveMemoryWarning is called
     I set self.view = nil
     I thought viewDidUnload would be called - it is not
   Hit "back" button on second view
   Home view becomes visible
   viewDidLoad is called on Home View

Since I am not using an XIB file I have to do all my memory clean up in the didReceiveMemoryWarning call. It all works fine now and running the memory leak detector shows all is good.

I can tell things are working because the images "fly" into place when the view first appears or you rotate the device. First time up they fly from 0,0 to their spot on the shelf. Toggle back and forth between the Home view and another view and they don't fly as they were already in right spot. Toggle to second view, force low memory condition and return to Home view and they fly back in place again. Of course I put in breakpoints to verify all the calls are being done in order too.

The app is being a good citizen. Things are loading in the correct methods. Memory is being dumped in the correct methods. Now if only the methods were actually called in pairs to avoid all this confusion. Such is life when you don't use Interface Builder and IBOutlets due to your interface being different between portrait and landscape. I run 3 shelves in a 2x3 layout in portrait and 2 shelves in a 3x2 layout in landscape.

Monday, December 12, 2011

First OSX Utility - find unreferenced image files

Today I wrote my first OSX application. I have been writing iPhone apps in Objective C but I had not done an OSX application. I needed a utility to tell me what image files were unreferenced in my iOS project so I can remove them and clean things up.

Started out with a base OSX Application in Xcode. Added some basic fields to the XIB file - Directory name, [Browse...] button, [Find Issues] button and a text area to show the results.

Next I figured out how to browse for a single directory and get the URL back. Take that URL and do a recursive search for all other directories. In each directory find image files using an image extension list and file all code files (*.m, *.h).

Once all the file information is available start looking in the *.xib, *.h and *.m code for images either by NSResourceName, extension or by imageNamed: as that call will automatically look for PNG files. As we find references in code increment the image name reference. I have code to ignore commented out lines and blocks of code although the block indicator needs to start the line not be buried within a line at this time.

At the end of the run it builds a string of all orphan image files (those without reference in code) and populates the text area with the list. This leaves some false positives for icons that you need to publish the app but those are pretty easy to ignore at this point.

I added a progress indicator as the program run can take a few seconds to run, clean up of the menu system to show only valid items, update the about dialog text and adding a real program icon.

Finally I added a check for {name}.png / {name}@2x.png to show what missing retina images or orphan retina images you happen to have. I found eight images that I had not done a retina version of so this was a handy validation. I also found an image that was not being used and was being referenced from an unrelated directory.

This does find false positives but mainly on the numerous icon files you need for all iOS device flavors. I might need to add another text field for you to type in "files that start with {string}" to ignore but only people who are OCD enough to crave an error free run would care. Everyone else will go "yeah, forget those" and move on.

I am using some code I found thanks to Stack Overflow to read basic text files a line at a time. Totally surprised, although at this point I should not be, that is missing from the core library. So odd to me how many very basic things are missing from the iOS / OSX SDK. It still appears that Apple likes to add new API areas but not make existing API calls better.

My overall understanding of Xcode / XIB files / Objective C put the whole project in at under a day. I learned how to query for a directory, roll through a directory structure, read lines from a text file and do widget anchoring in XIB so my window resizes / relayouts out controls as expected.

At this point the program is not as powerful as the Java version I wrote for Android project validation. On the Android side I can check images, strings, layouts, animations, etc. and I don't have any false positives as all my icons are referenced in one of my XML files. For OSX I am making a bit more of a guess about the code to see if you are using an image. The utility will help me do this round of clean up and will keep it clean as time goes on. I really wish the SVN integration with Xcode was better. I know I can delete the file from the project but it does not delete it from SVN. I will have to manually clean that up or do the work under AppCode from Jetbrains as it does full integration.

Friday, December 9, 2011

New Android Utility - find orphan references

I decided to write a new utility to help clean up my Android project. It is a simple Java command line only program, no JFrame / no GUI, that takes a base directory as its only parameter. From that it builds a list of all sub-directories (ignoring .svn directories) then finds the Android generated R.java file.

R.java is parsed based on "public static final class" and "public static final int" to build a list of ID strings.

Each .JAVA and .XML file is then scanned in the directory tree looking for resource IDs in their various formats such as @string/ in XML or R.string. in Java. The reference count is incremented. At the end of the run I print out all the items that have a reference count of zero. I was able to clear up a dozen strings, a couple of layouts and 10 images from the project. Strings and layouts don't amount to much but images sure can when it comes to download size. Some of the images were in multiple directories to handle various Android sizes. Getting rid of layouts is always good as you might open one, edit it a bunch, the find out you edited something that is never used. Killing a layout might kill other image and string references too.

I wrote something similar to this at a previous job to find orphan strings before we sent the Java and C++ programs out to be localized. That saved us from paying money to localize a string that was never used in the program. I also checked for orphan images against the Java code and some other orphan resources against the C++ code. It would be nice if this was just built into Eclipse.

Now I need to do something similar for the iOS side. It will not be as straight forward as the images are directly referenced in the code as string names and I need to add a special check for @2x images to make sure they have a non-retina mate. I guess this would be a good time for me to write my first non-iPhone based Objective C program. Might put a GUI around it with a [Browse..] button to pick the directory and a list control to show the results. Seems to be cheating to write just a command line program on the Mac.

I want to run the program a few more times against various XML / Java code formats before I release it to the wild. It appears to work like a champ against the code I write but I am the only author and follow a consistent code format which does not make for a well rounded test. I kept getting false positives until I tweaked various bits of the parser. Trust but Verify was the rule until the final run when it was 100% accurate for my coding style.

Anyone have a big interest in this program and want to be a beta tester? Leave a comment and I can send you the source code (it is all of 288 lines in a single file) so you can see what you think and request improvements.

 ** Update ** Code appears below for anyone to run and check out

package org.peck;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Simple command line program to find the R.java Android generated code and
 * look for orphan references against *.java and *.XML files  
 * 
 * @author kevin.peck
 * @date December 9, 2011
 */
public class AndroidResourceScan {
    
    private static List<String> directories = new ArrayList<String>();
    private static List<RefInfo> references = new ArrayList<RefInfo>();
    
    private static List<XmlMapping> xmlMapping = new ArrayList<XmlMapping>();
    
    private static final String RESOURCE_FILE = "R.java";
    private static final String CLASS_START = "public static final class";
    private static final String ITEM_START = "public static final int";
    
    /**
     * Android resource scanner
     * 
     * Find and parse the R.java file and see if there items in that file
     * that are not referenced in any *.java or *.xml files
     * 
     * @param args  Arguments, base directory
     */
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("Android Resource Scan by Kevin Peck");
            System.out.println();
            System.out.println("Parse the Android generated R.java file then look");
            System.out.println("for orphan references - any defined IDs that don't");
            System.out.println("appear in a JAVA or XML file in same directory tree.");
            System.out.println("Comment lines are ignore in all files");
            System.out.println("NOTE: .svn directories are ignored.");
            
            System.out.println();
            System.out.println("Usage: AndroidResourceScan {base path}");
            return;
        }
        
        xmlMapping.add(new XmlMapping("@string/", "R.string."));
        xmlMapping.add(new XmlMapping("@drawable/", "R.drawable."));
        xmlMapping.add(new XmlMapping("@color/", "R.color."));
        xmlMapping.add(new XmlMapping("@style/", "R.style."));
        xmlMapping.add(new XmlMapping("@id/", "R.id."));
        xmlMapping.add(new XmlMapping("@+id/", "R.id."));
        xmlMapping.add(new XmlMapping("@anim/", "R.anim."));
        xmlMapping.add(new XmlMapping("style name=\"", "R.style."));
        xmlMapping.add(new XmlMapping("<attr name=\"", "R.attr."));
        System.out.println("Scanning " + args[0] + " for R.java file");
        buildDirTree(args[0]);
        
        boolean haveResFile = false;
        File resFile = null;
        for (String dirName : directories) {
            resFile = new File(dirName + RESOURCE_FILE);
            if (resFile.exists()) {
                haveResFile = true;
                break;
            }
        }
        
        if (!haveResFile || (resFile == null)) {
            System.out.println("Unable to find " + RESOURCE_FILE + " in " + args[0] + " directory tree");
            return;
        }
        System.out.println("Found resource file " + resFile.getAbsolutePath());
        buildRefList(resFile.getAbsolutePath());
        
        System.out.println("Scanning JAVA files for references");
        scanFiles();
        
        int orphanCount = 0;
        System.out.println("Orphans found");
        for (RefInfo refInfo : references) {
            if (refInfo.refCount == 0) {
                orphanCount++;
                System.out.println(refInfo.name);
            }
        }
        System.out.println("total " + orphanCount);
    }
    
    /**
     * Scan the directory tree .java and .XML files 
     * Send them to proper parser as found
     */
    private static void scanFiles() {
        for (String dir : directories) {
            File fDir = new File(dir);
            String [] files = fDir.list();
            for (String fileName : files) {
                if (fileName.endsWith(".java")) {
                    scanJavaFile(dir + '/' + fileName);
                } else if (fileName.endsWith(".xml")) {
                    scanXmlFile(dir + '/' + fileName);
                }
            }
        }
    }
    
    /**
     * Open a java file and scan it for R. references
     *  
     * @param fileName
     */
    private static void scanJavaFile(String fileName) {
        BufferedReader reader;
        try {
            reader = new BufferedReader(new FileReader(fileName));
            String line;
            boolean inComment = false;
            
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (inComment && line.endsWith("*/")) {
                    inComment = false;
                } else if (!inComment && line.startsWith("/*")) {
                    inComment = true;
                } else if (!line.startsWith("//")) {
                    int iStart = line.indexOf("R.");
                    while (iStart != -1) {
                        if (iStart != 0 && " (=".indexOf(line.charAt(iStart - 1)) != -1) {
                            int iEnd = iStart;
                            while (iEnd < line.length() && " ,);".indexOf(line.charAt(iEnd)) == -1) {
                                iEnd++;
                            }
                            String refName = line.substring(iStart, iEnd);
                            for (RefInfo refInfo : references) {
                                if (refInfo.name.equals(refName)) {
                                    refInfo.refCount++;
                                    break;
                                }
                            }
                        }
                        iStart = line.indexOf("R.", iStart + 1);
                    }
                }
            }
            reader.close();
        } catch (IOException exp) {
            exp.printStackTrace();
        }
    }
    
    /**
     * Scan XML file looking for XML mapped entries
     * 
     * @param fileName  Name of file to scan
     */
    private static void scanXmlFile(String fileName) {
        BufferedReader reader;
        try {
            reader = new BufferedReader(new FileReader(fileName));
            String line;
            
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (!line.startsWith("<!--")) {
                    for (XmlMapping xmlMap : xmlMapping) {
                        int iStart = line.indexOf(xmlMap.xmlName);
                        if (iStart != -1) {
                            int iEnd = iStart + xmlMap.xmlName.length();
                            while (iEnd < line.length() && " \"><".indexOf(line.charAt(iEnd)) == -1) {
                                iEnd++;
                            }
                            String refName = xmlMap.resName + line.substring(iStart + xmlMap.xmlName.length(), iEnd).trim();
                            for (RefInfo refInfo : references) {
                                if (refInfo.name.equals(refName)) {
                                    refInfo.refCount++;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            reader.close();
        } catch (IOException exp) {
            exp.printStackTrace();
        }
    }

    /**
     * Build directory tree based on a root node
     *
     * @param baseDir  Root node to scan for sub directories
     */
    private static void buildDirTree(String baseDir) {
        String topDir = baseDir;
        if (!topDir.endsWith("/") && !topDir.endsWith("\\")) {
            topDir += "/";
        }
        File fDir = new File(topDir);
        if (fDir.isDirectory()) {
            directories.add(topDir.replace("//", "/"));
            String [] files = fDir.list();
            for (String fileName : files) {
                String subDir = topDir + '/' + fileName;
                File testDir = new File(subDir);
                if (testDir.isDirectory() && !subDir.contains("/.svn")) {
                    buildDirTree(subDir);
                }
            }
        }
    }
    
    /**
     * Build a list of public static final class references found in given file
     * 
     * @param fileName  Name of file to process ({path}/R.java)
     */
    private static void buildRefList(String fileName) {
        BufferedReader reader;
        try {
            reader = new BufferedReader(new FileReader(fileName));
            String line;
            String className = "";
            boolean inComment = false;
            boolean inClass = false;
            
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (inComment && line.endsWith("*/")) {
                    inComment = false;
                } else if (!inComment && line.startsWith("/*")) {
                    inComment = true;
                } else if (!line.startsWith("//")) {
                    if (!inClass && line.startsWith(CLASS_START)) {
                        className = line.substring(CLASS_START.length()).trim();
                        if (className.endsWith("{")) {
                            className = className.substring(0, className.length() - 2);
                        }
                        inClass = true;
                    } else if (inClass) {
                        if (line.startsWith(ITEM_START)) {
                            int nameEnd = line.lastIndexOf('=');
                            if (nameEnd != -1) {
                                references.add(new RefInfo("R." + className + '.' + line.substring(ITEM_START.length() + 1, nameEnd)));
                            }
                        }
                        if (line.endsWith("}")) {
                            inClass = false;
                        }
                    }
                }
            }
            reader.close();
        } catch (IOException exp) {
            exp.printStackTrace();
        }
    }
    
    /**
     * XML to Resource format mapping 
     */
    static class XmlMapping {
        public String xmlName;
        public String resName;
        
        public XmlMapping(String xmlName, String resName) {
            this.xmlName = xmlName;
            this.resName = resName;
        }
    }

    /**
     * Resource name and reference count (all we care about is count != 0) 
     */
    static class RefInfo {
        public String name;
        public int refCount;
        
        public RefInfo(String name) {
            this.name = name;
        }
    }
}

Feel free to report any bugs you find back to me.

Wednesday, December 7, 2011

New machine built - data xfer time

I built a new machine as an early Christmas present for both my son and I over the past couple of days. My son helped me build it which was a cool experience for him. He is getting my current machine as a nice upgrade. His older AMD machine has been having all sorts of issues from not wanting to boot to booting but then not seeing the NIC or having the sound be bad. It could be a flaky power supply but I was due an upgrade anyway. Since I program for a living a 5 year old machine running a 32bit OS was starting to show its age.

It was a fun experience for him. I have a feeling we are not very far off from never building again so he may never use these skills. I looked for a pre-built box just to avoid the potential build headaches but could not find anything set up the way I wanted for a good price. If the price was good it was some crappy brand. If the parts were good it was $350-$500 more than the parts. I also drive a manual transmission. Cars and computers are like my coding - closer to the bare metal that many like to go. I write custom controls and never shy away from writing gut level code.

Here is the configuration

Intel Core i5 2500K
Thermaltake TR2 RX 750W Modular Power Supply
Samsung 22X DVD‘RW Burner with Dual Layer Support
Gigabyte GA-Z68XP-UD3 LGA 1155 Z68 ATX Intel Motherboard
Sentey Extreme Division Optimus GS-6000R Mid Tower
OCZ Technology Vertex 3 Series VTX3-25SAT3-120G
Zotac ZT-50303-10M GTX 560 Ti Overclocked 1024MB
ASUS Xonar DG 5.1 PCI Sound Card
Corsair 8GB DDR3-1600 (PC3-12800)

I went i5 instead of i7 after seeing in various websites that the i5 is a better gaming CPU due the hyperthreading getting in the way on the i7. I am not one of those "must have the top of everything" guys. I want a system that is stable and comes at a reasonable price. This is also why I did not get the $600 video card or the $1,200 dual video card set up. I bought all of it at the local MicroCenter. I normally order off NewEgg but they had a very limited processor selection and the price was higher than just picking it up local. NewEgg were cheaper on the sound card and DVD player but only by a few bucks. I just wanted to get it all at the same time ready to build.

I did go with a sound card even though the MB has built in sound. I have never been super impressed with motherboard sound and research shows the ASUS $30 to be a nice upgrade for a really good price. It is the 5.1 card, not 7.1, but I have a 5.1 setup I am happy with so it was the proper fit.

The SSD was a bit of a splurge. I have SSD at work and it is so nice to boot quickly and move right along after that. I am also moving over a normal HD to this setup to store music, images, etc. The case has a ton of fans but they are all pretty large and spinning slowly making the setup very quiet. The sound of a HD spinning is so familiar this just seems like it is not working at all. Guess I will even hear more of my game sounds when I get the chance to play.

Once it burns in for a few days I will fill out all the rebate paperwork and get that sent off. I hate rebates but I do have $100 coming back to me so it will be worth filling it out. I also need to download Assassin's Creed Brotherhood which I got for free with the video card.

I installed Win7 Ultimate 64bit and the Windows Experience comes to 7.5 with 7.8 being the highest and everything pretty much in that zone. Install was quick from DVD to SSD and boot time is faster than any machine I have ever used.

The build had two difficult areas. One was getting the stinking CPU cooling fan to seat properly. Seems I could get 3 corners in and have one pop out. The second was getting it to boot. Initially fans would kick on, single beep from post and then no video. I tried all sorts of things and gave up for the night thus making this a two day build. Finally used a screwdriver to short the CMOS jumper and then it booted. Not sure what in CMOS / BIOS was so annoyed it needed a reset especially since this is a brand new board and setup but that worked.

I turned off on-board sound in BIOS then watched the CPU health for a bit. CPU temp kept climbing so I shut it down and reseated the CPU fan again. Fired it up and CPU stayed at 45C for 20 minutes so I was happy and did the OS install.

I copied various files over from my server, installed updated video, sound and MB drivers along with Java and Eclipse. I still have a ton of other work to do to get it all in shape. I need to pull the HD out of my son's original machine and put it in my old machine so he can copy over files as he needs them. He will get to reinstall some of his games too. I did a backup of my Steam games and will move that over.

The new MB does not have IDE on it and I was planning on moving my secondary IDE drive into this machine. Guess I need a IDE / SATA adapter to do that. Right now all HD prices are up due to flooding so I hate to buy a SATA drive when I have usable drives to store music and what not. They might not be super new, fast and shiny but they spin up and hold data. I have access to what I need on our shared server drive so it is not a critical item but I hate to get in and out of the case too many times. I need to go in again to route power supply cables in a cleaner manner at the very least.

Going to be a number of days / weeks before I figure out everything I am missing. Always some utility you use rarely but need from time to time. Since my old machine is just going to my son and we are not reformatting it I will be able to move across anything I need with ease. That machine has dual HD right now so I will copy over everything I can think of from one drive to the other and put that drive in my new machine then put his old drive in there as his D: drive so he has access to his stuff.

Now I am ready to play some new games to see how this thing really works. I have Gears of War that I got for $20 a long time back. It always stuttered on the old box so I could never get into playing it. Now it should run just fine. Might be the first thing I install to prove this new beast was worth it.

Means I wont have much to open under the tree this year but really the holidays are all about watching other people open stuff you bought them. Shame I did not have the new setup over Thanksgiving when I got a second copy of Portal 2 during the Steam sale. My son and I played the co-op all day long finishing all levels. Near the end his machine would not show where the white goo was on the level so he could not place portals. My machine did things just fine. Could be a reboot of his machine would have kicked it in, could be his flaky machine would have never worked or his video card was just too old or memory shy to every handle it. He is so ready to play single player Portal 2 on a "real" machine that getting him to do homework is going to be rough. When daddy gets a new machine everyone wins.

* UPDATE *
Did a ton of data transfer last night. I now have my backed up Steam games installed (overnight process) and am downloading my free copy of Assassin's Creed Brotherhood today as it refused to accept $0.00 payment last night but did allow it this morning following exact same steps.

I have my son's old IDE drive in his new / my old machine and I copied over all his documents to the C: drive. As he reinstalls games we will move over the save game data from that drive and eventually we will remove it from the system as it is a waste of power to spin it up. I set up a new account for him and left my account so I can copy up to the network if I would happen to need something I forgot over there.

There were two IDE DVD drives in the case but only one is set to run right now as I stole the other IDE channel for the old HD. He is running without a burner at this point but with a slot load reader. Lets him install games but I need to get the burner back in place so he can burn videos and other school projects in the future. Of course we can burn them from the server, my wife's laptop, my machine or my other son's laptop meaning it is a hassle but not a big deal.

I installed Gears of War. It would not run saying I had a modified EXE. I installed the patch and it ran but it will not let me leave the video set up screen. I assume it is a Win7 64bit issue but gave up and went to bed instead of trying over and over. I will try a Steam game tonight but the ones I have use the old source engine so I doubt they show off the graphics card. I need to break down and by Skyrim but I really want it to go on sale. I have never paid $60 for a game and hate to start now.

Thursday, December 1, 2011

Android Out of Memory issues

The dreaded "Out of Memory error" with corresponding "Force Close" on your Android device is very frustrating. It was happening to our users from time to time but today I think I may have found the issue. I won't know for sure if this is a true fix until it is out in the field.

I have a couple of ways to acquire images in our app - you can take a picture with the camera or grab an existing image from the gallery. In either case I have to check out the image size and shrink it down to less than 1 meg of data via scaling, quality compression or both. It has to be less than 1 meg as I am holding the images in a SQLite blob to be sent to the server at a later time. I can't leave things in the gallery due to HIPAA rules and I can't trust the user to not delete the original image between pick it and transmit it time frames.

You can also view the image at any time within my app using a viewer I wrote that allows pinch zoom, drag scrolling, rotation and fit to window functionality. This is where things started to go awry.

If I opened and existing image, exited that activity then tried to capture an image from the camera there was a good chance I would get the out of memory issue. If I just took pictures but never viewed them or waited some amount of time before viewing them it might work just fine. This also depended on the device and version of the OS I was using. To find the issue I opened an image in the viewer then closed the viewer and did a heap dump via DDMS in Eclipse, converted that dump via hprof-conv in the android sdk\tools directory and loaded it up in MAT (memory analysis tool) under Eclipse. The Dominator Tree showed the ImageView of the closed activity to still be holding on to a weak referenced large bitmap. I guess the reference is not weak enough for the GC to kick it out of memory during the camera processing even though I was calling System.gc() before I started the camera bitmap manipulation.

To solve the issue I added the following to the image viewing activity:


/**
 * On the way out dump our images
 */
@Override
protected void onDestroy() {
  imageView.setOnTouchListener(null);
  imageView.setImageBitmap(null);
  System.gc();
  super.onDestroy();
}


Now after hitting the back button from the image activity I don't have a large weak reference to an image floating about in the heap. I have been able to view, take pictures, view, lather, rinse, repeat without any issues. I was running these test on a ASUS Transformer tablet running OS 3.2.1

I am also making sure I call recycle on the bitmaps I use during my scale / quality reduction routines during the post camera processing.

Probably a good idea to manually release any large images you use in your application as activities go away to avoid these problems. Right now I am not releasing all the smaller images I am using but it might be a good idea to make a sweep through the heap dump to see what else is sneaking around.

Not sure if the System.gc() call is even necessary and I know they recommend you don't call it but at this point I can't see it hurting anything and I am only doing it on activity destroy so I am leaving it in there for good luck and hoping I don't hear of any more OOM crashes in the field.