3

I'm seeking for solutions to load 20 items from SQLite faster than 5 seconds (which is the seconds that I'm loading right now.) - First of all, I'm using a custom Listview Adapter.

I load 5 items in 1 second. I tried to load 20 items and I load them in 5 seconds. This are the fields I retrieve from database: int, String, String, int, Bytes, float, int.

As you may think, after getting the bytes I convert them into Bitmap.

Bitmap image = convertBlobToImage(cursor.getBlob(4));
// Using this function:
  public Bitmap convertBlobToImage(byte[] value){
        byte[] new_value = Base64.decode(value, 0);
        return BitmapFactory.decodeByteArray(new_value, 0, new_value.length);   
    }

So, in my class fields, they are going to get not the bytes but already the bitmap. One of the reasons of the amount of time to read, is probably the bitmap. I just did a test on Paint. I saved two equal images one in BMP and another in JPG. The JPG image have the size of 2,87KB and the BMP 420KB!!

With the code above, is that result I'm getting? And probably one of the solutions could be: http://www.mkyong.com/java/how-to-convert-byte-to-bufferedimage-in-java/ ?

What do you guys think? Thanks.


Edit: I was searching and I found about onDestroy(). Also I didn't have the "runOnUiThread" implemented, and I put that way. But I think it didn't give me any better result. What do you think? Could this increase the performance?

  @Override
    protected void onDestroy() {
        super.onDestroy();
        listView.setAdapter(null);
    }
// And I also tried to put runOnUiThread:
runOnUiThread(new Runnable() {
             @Override
             public void run() {
                 Bundle extras = getIntent().getExtras();
                 if (extras != null) {
                     DatabaseHandler db = new DatabaseHandler(Produtos.this);
                     display_products = db.get_all_oc_product(extras.getString("category_id"));

                     listView = (ListView) findViewById(R.id.product_listview);
                     inputSearch = (EditText) findViewById(R.id.product_inputSearch);

                     adapter = new itemAdapter(Produtos.this,R.layout.row, display_products);
                     listView.setAdapter(adapter);
                 }
             }
         });

Edit (2): I managed to decrease the time for 3 seconds on displaying 20 items. I accomplish this by closing all the connections to database after the queries. I was not doing this properly. Correct way:

cursor db.query(...)
try{
 // Code
} finally {
  cursor.close();
  db.close();
}

Edit (3): Further than the solution on Edit (2), one of the issues I had, which I was identified with, was the problem of the images. So, I started to look at them and I saw images the 2000x1800 and 300kb and even more, and I found rapidly that was here the problem.

So, in the PHP webservice I developed a function to resize the images to half and converting them to jpg.

function resize($filePath){
    list($width, $height) = getimagesize($filePath);

    $percent = 0.5;

        $newwidth = $width * $percent;
        $newheight = $height * $percent;

        $thumb = imagecreatetruecolor($newwidth, $newheight);

        $ext = pathinfo($filePath, PATHINFO_EXTENSION);
        $source = null;

        if($ext == "png"){
            $source = imagecreatefrompng($filePath);
        }else if($ext == "jpg" || $ext == "jpeg"){
            $source = imagecreatefromjpeg($filePath);
        }

        // Resize
        imagecopyresized($thumb, $source, 0, 0, 0, 0, $newwidth, $newheight, $width, $height);

        // Output
        $temporary = "C:\\xampp\\htdocs\\MyExample\\images\\temporary\\" . basename($filePath);
        imagejpeg($thumb, $temporary . ".jpg", 50);
        ImageDestroy($thumb);

        return $temporary . ".jpg";

}

With this solution, I decreased the time for a stunning 1 second loading 47 items!!

user2742861
  • 340
  • 1
  • 8
  • 17
  • 2
    Do you really need to store them in DB? – ppeterka Oct 04 '13 at 16:36
  • I think that you should learn how to use Traceview and figure out where your problem is, rather than just guessing: http://developer.android.com/tools/debugging/debugging-tracing.html – CommonsWare Oct 04 '13 at 16:38
  • usually blob's aren't stored in a database. – SnakeDoc Oct 04 '13 at 16:38
  • @SnakeDoc But it goes through the DB API, right? (Also, the database is stored as files, if I want to see it as so...) – ppeterka Oct 04 '13 at 16:40
  • Yes, I do really need to store them in DB. I retrieve them by Webservice, without any problem. After that, I show the items by categories. – user2742861 Oct 04 '13 at 16:41
  • @ppeterka66 i'm afraid i don't understand your last comment. if the OP didn't store the images as a blob in the database, then there is not db api to interact with. just raw files, which would reduce one overhead layer and consequently speed things up a little bit. – SnakeDoc Oct 04 '13 at 16:44
  • @SnakeDoc Yep. But [BLOBs](http://en.wikipedia.org/wiki/Binary_large_object) (which are database objects per definition, as far as I know) are stored using the DB engine, right? If you tried to suggest that _file content_ should not be stored in DB, but in the file system usually, then I might have misunderstood that - but I might not have been the only one... – ppeterka Oct 04 '13 at 16:47
  • @ppeterka66 yes, i was suggesting to store them on the local filessytem, as this would negate any performance penalty in having to fetch and convert them out of the database. but it seems the OP has stated they must store then in the database for whatever reason. So in that case, my suggestion is to look into using `NIO` for a more native performance in loading the images. – SnakeDoc Oct 04 '13 at 16:49
  • Try to avoid having to open the DB connection then close it on each query. this will be a major slowdown as jdbc driver must negotiate the connection each time. instead, open the db connection when your app starts, then close it as you shutdown (or when you get to a point in the app where you no longer need the db at all). – SnakeDoc Oct 04 '13 at 18:37
  • @SnakeDoc your point worths a shot, but, imagine that the user closes the application (by clicking in the 'home' button), the connection still remains active? I think your advice is worth in just one particular case, which is mine. I have a process: `Choose Categories -> Choose Sub-Categories -> Choose Products` In this process I open and close the connection three times. I should only do it one time. – user2742861 Oct 04 '13 at 22:08
  • @user2742861 to fix that is simple, you just setup a shutdownHook which will close your connection for you when the JVM terminates. Like: `Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { // you stuff to do when jvm is shutting down } });` -- it's not best to rely on that to do everything, so during a normal program termination you should cleanup your resources and connections normally, but registering a shutdownHook to close resources that absolutely must be closed even if it quits, this is a good way. – SnakeDoc Oct 04 '13 at 23:08
  • 1
    Except close the DB on Android lifecycle, not Java - i.e. in `Activity.onStop()`. JVM Termination doesn't necessarily happen in Android – JRaymond Oct 05 '13 at 00:16
  • @SnakeDoc but where should I put that code? In all activities? – user2742861 Oct 05 '13 at 10:04
  • @JRaymond good point, i haven't done much android yet... but `Activity.onStop()` does look promising. – SnakeDoc Oct 05 '13 at 17:41
  • @user2742861 this would depend on your design. In my work, I like to have any modules register themselves in a centralized Manager(class) that takes care of this as well as normal cleanup duties. Something like a RuntimeManager class. But that's just me... I've seen others have shutdown hooks registered locally within each class as part of the constructor, etc. It depends on what's best for your program design. – SnakeDoc Oct 05 '13 at 17:42
  • Thank you all. I solved my problem with the **Edit (3)**. – user2742861 Oct 14 '13 at 13:02

2 Answers2

0

Well It's a bit hard to help from the other side of this question, but some generic tips:

  1. Use profiling of some sort to determine which part is slowing you down. Is it loading the actual cursor back that's a problem? or is it the byte conversion? It's hard to say unless you measure - otherwise you could waste a lot of time optimizing something that isn't slowing you down. The simplest profiling example:

    long startTime = SystemClock.elapsedRealTime();
    suspectedSlowFunctionCall();
    long endTime = SystemClock.elapsedRealTime();
    Log.d(TAG, "Method duration: " + (endTime - startTime));
    

    As Mr. Murphy mentioned in the comments, there are also tools that help you with this task. The android developers site has a great post showing how to use traceview, which is included in the SDK.

  2. General wisdom is to not store Images in the database if you can avoid it. It is a lot easier on you to debug and work with (and for the system to load) if you store a file path instead and save the file.

  3. Android's BitmapFactory should be able to handle JPEGs. If you want to use JPG, feel free

  4. If something is unfixably slow, don't do it on the UI Thread! use something like an AsyncTask to do the slow operation, and show your results to the user one by one as they get loaded. This is always preferable to an operation that stalls whilst the user waits confused.

JRaymond
  • 11,625
  • 5
  • 37
  • 40
  • Hey. 1) - How can I use profiling? How do I install into eclipse or enable it? 2) - The images are retrieved by Webservice in BLOB format. Do you recommend to convert them automatically to images and store into SD card or device memory and then save the path into SQLite db? 3) - Is it possible, with the code I show in my post, to adapt it to JPG?? – user2742861 Oct 04 '13 at 16:47
  • @user2742861 Well that's interesting... if you don't know what kind of image they are, it probably isn't worth trying to convert it - they might be JPGs already, for all we know. I doubt the size of the image is your issue anyway - but we'll never know unless you profile it – JRaymond Oct 04 '13 at 16:57
  • try `jvisualvm` to see exactly where the big slowdown/hangup is in your code. it's a free profiler that is now included standard in the oracle jdk. – SnakeDoc Oct 04 '13 at 17:01
  • Most of the images are JPG but there are a few PNGs also. Please see my answer below. – user2742861 Oct 04 '13 at 17:01
0

I'd recommend checking into one of the java.nio.* packages to load your images. NIO is non-blocking which means you can load images asynchronously, resulting in more work done per time.

NIO also used native buffers on the system (outside the jvm) so it can prevent java from having to read things into it's heap, which again, makes things faster (native). There's a lot more benefits from using NIO here as well.

SnakeDoc
  • 13,611
  • 17
  • 65
  • 97
  • how would one use nio to load an image out of the DB and into a `Bitmap` object? – JRaymond Oct 04 '13 at 17:02
  • hes likely using an InputStream of sorts, to use NIO, you just open a channel on that stream, then read into a buffer, then spit it out to an object (image in this case). So he would need to modify his `convertBlobToImage()` method. – SnakeDoc Oct 04 '13 at 17:16
  • NIO is a solution to try. I also saw this solution: http://stackoverflow.com/questions/10081008/outofmemoryerror-in-game-with-many-small-images/10086141#comment12999643_10086141 Because, I can load like 5 images in Virtual Device, but if I try to load (in virtual device) 20 images I get the error: http://pastebin.com/LC9Aygzh. But in my Tablet I can show the data. – user2742861 Oct 04 '13 at 17:26
  • that error is because your ADK Virtual Device has run out of virtual RAM. Your tablet likely has more RAM on it (say 1GB instead of your virtual android which may be less like 512MB). The way you are doing things, it must read all that data into memory before it can do something with it. NIO will stream it through a buffer to help avoid that issue. – SnakeDoc Oct 04 '13 at 18:35
  • I will give a shot to NIO whenever I can, maybe this weekend. I found that the error was because of the RAM, and I increased the RAM but I still have the same error. But I'll test in another computer, with another emulator. See my edit (2). – user2742861 Oct 04 '13 at 18:38