Tuesday, April 9, 2013

Optimizing your Java code in Android (part 2)

It took me a bit longer than I had hoped, but I was finally able to write this next post in my series on Android Optimization. Some of the things in this post may seem obvious for people that have some experience in Java, but I have noticed that there are a lot of people who haven't seen that much (or none at all) Java code that get started on Android right away. So I'll mention these things in here as well.

I/O streams

I/O stands for Input and Output streams. We use these streams to read from and write to files on the device or on other devices. The classes for this can be found in the java.io package.

Closing streams

The first thing I want to start is closing streams. I just spent quite some time today at work figuring out why some files could not get deleted. The person who wrote the code forgot to close the InputStream of a file :/

So, whenever you are using an Input or OutputStream, always close it afterwards. The best way to do this is in a finally block. This way it will always be closed when the job is done, even if something went wrong. Here's an example:

FileInputStream fileInputStream = null;
  try
  {
   fileInputStream = new java.io.FileInputStream(someFile);
   fileInputStream.read ();
  }
  catch (java.io.FileNotFoundException e1)
  {
   System.out.println("Exception : File not found");
  }
  catch (java.lang.Exception e)
  {
   e.printStackTrace();
  }
  finally
  {
   fileInputStream.close ();
  }

Remember this. This will save you (and others) a lot of trouble.



Buffering

Most of the streams read and write one byte at a time. Unfortunately, this causes bad performance because it takes a lot of time to read/write when you are dealing with large amounts of data. But have no fear, java.io provides Buffered streams that can override this byte by byte behavior.

These classes are BufferedInputStream and BufferedOutputStream. The BufferedInputStream stores all the bytes that the FileInputStream sends until it reaches a limit. This limit is by default 512 bytes, but you can change this. Once this limit is reached, the BufferedInputStream sends the data.

If you want, you can also create a custom buffer. This can be done really easy by adding a byte[] to the constructor. This is probably one of the fastest ways to send a file. It all depends on the size of your file. Since I haven't got a lot of time, I looked for an example of using the streams online instead of writing one myself. But nevertheless it should explain everything.

This first method uses no Buffered stream, so sends everything byte by byte.
public static void readWrite(String fileFrom, String fileTo) throws IOException{

             InputStream in = null;

             OutputStream out = null;

             try{

                          in = new FileInputStream(fileFrom);

                          out = new FileOutputStream(fileTo);

                          while(true){

                                          int bytedata = in.read();

                                          if(bytedata == -1)

                                          break;

                                          out.write(bytedata);

                          }

             }

             finally{

              if(in != null)

                          in.close();

              if(out !=null)

                          out.close();

             }

}

This second method uses a Buffered stream with the default buffer of 512 bytes
public static void readWriteBuffer(String fileFrom, String fileTo) throws IOException{

             InputStream inBuffer = null;

             OutputStream outBuffer = null;

             try{

                          InputStream in = new FileInputStream(fileFrom);

                          inBuffer = new BufferedInputStream(in);

                          OutputStream out = new FileOutputStream(fileTo);

                          outBuffer = new BufferedOutputStream(out);

                          while(true){

                                          int bytedata = inBuffer.read();

                                          if(bytedata == -1)

                                          break;

                                          out.write(bytedata);

                          }

             }

             finally{

              if(inBuffer != null)

                          inBuffer.close();

              if(outBuffer !=null)

                          outBuffer.close();

             }

}

This last methods uses the method available() to define the size of the byte array. This is not always the best way, but it will do for this example.
         

public static void readWriteArray(String fileFrom, String fileTo) throws IOException{

             InputStream in = null;

             OutputStream out = null;

             try{

                          in = new FileInputStream(fileFrom);

                          out = new FileOutputStream(fileTo);

                          int availableLength = in.available();

                          byte[] totalBytes = new byte[availableLength];

                          int bytedata = in.read(totalBytes);

                          out.write(totalBytes);

                            

             }

             finally{

              if(in != null)

                          in.close();

              if(out !=null)

                          out.close();

             }

}          

}

A possible outcome of these methods (depending on the file):

  • the first method took 660 ms
  • the second method took 270ms
  • the third method took only 1 ms
Very important note: Custom buffering takes lot of memory if your file size is large, you should be careful about the memory capability of your system. If custom buffering takes lot of memory, try to reduce the array size so that the memory usage will not be large.