Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

Tip: Accessing Newer Android SDK Version APIs Using Java Reflection

5.00/5 (2 votes)
9 Oct 2015CPOL2 min read 11.5K  
Android code shortcuts to explore newer SDK APIs

Introduction

With every release of the Android SDK, new API is introduced for developers. If you want to use the new API, then you may have to drop support for some older SDK versions. If you wish to continue work with an older SDK version, then you have to use Java reflection to access newer APIs.

Background

My web browser & feed reader app can save web pages to image files. Recently, I wanted to add the ability to save the images to PDF - just for the heck of it.

This meant that I needed to use the android.graphics.pdf.PdfDocument class introduced in KitKat (SDK v19). My project had always been linked to Donut (SDK v4) JAR file and this API was out of reach for my code. So, I added a new Java reflection class to my public domain AndroidWithoutStupid Java library and used it in the app.

You can use the new class MvReflection.java individually or use the whole JAR.

Using the Code

The code for saving a bitmap of an image to PDF was pretty straightforward.

Java
void saveBitmapAsPDF(Bitmap aoBitmap, String asPDF) {
    
  FileOutputStream fos;
  try {
      fos = new FileOutputStream(asPDF);
  } catch (FileNotFoundException e) {
    e.printStackTrace();    
    return;
  }

  // create a new document
  PdfDocument document = new PdfDocument();

  // crate a page description
  PageInfo pageInfo = new PageInfo.Builder(aoBitmap.getWidth(), aoBitmap.getHeight(), 1).create();

  // start a page
  Page page = document.startPage(pageInfo);

  // draw something on the page
  page.getCanvas().drawBitmap(aoBitmap, 0, 0, null);

  // finish the page
  document.finishPage(page);
    
  // write the document content
  try {
    document.writeTo(fos);
    fos.flush();
    fos.close();
  } catch (IOException e) {
    e.printStackTrace();
  }

  // close the document
  document.close();
}

Rewriting even this simple code using Java reflection wouldn't be easy unless the API lookup strategy was moved to another class. (Java reflection was not fun to say the least.) The MvReflection.java class has three methods:

  1. getNewInstance(String asClassName, Object... aoConstructorArguments) - to access a specified class and create a new instance from it.
  2. invokeMethod(Object aoInvokingInstance, String asMethodName, Object... aoMethodArguments) - to call a specified method using the new instance created from the first method.
  3. getNewNestedInstance(Object oEnclosingInstance, String asClassName, Object... aoConstructorArguments) - to create an instance of a nested class using the enclosing instance created using the first method.

With the rewritten code, the number of lines increased but it would have become unreadable if the Java reflection part was all inline. If a method fails, it returns null. On older devices, it does return null, as expected, and the code exits.

Java
boolean saveBitmapAsPDF(Bitmap aoBitmap, String asPdfPath) {
  Canvas oCanvas;
  Object oPdfDocumentInstance, oPdfDocumentPageInfoBuilderInstance, 
         oPdfDocumentPageInfoInstance, oPdfPageInstance;

  if (Build.VERSION.SDK_INT >= 19) {
    oPdfDocumentInstance = 
        MvReflection.getNewInstance("android.graphics.pdf.PdfDocument");

    if (oPdfDocumentInstance != null) {
      oPdfDocumentPageInfoBuilderInstance =
      MvReflection.getNewNestedInstance(
          oPdfDocumentInstance, 
          "android.graphics.pdf.PdfDocument$PageInfo$Builder",
          aoBitmap.getWidth(), 
          aoBitmap.getHeight(), 1);

      if (oPdfDocumentPageInfoBuilderInstance != null) {
        oPdfDocumentPageInfoInstance = 
            MvReflection.invokeMethod(
                oPdfDocumentPageInfoBuilderInstance, 
                "create");
                
        if (oPdfDocumentPageInfoInstance != null) {
          oPdfPageInstance = 
              MvReflection.invokeMethod(oPdfDocumentInstance, 
                                        "startPage", 
                                        oPdfDocumentPageInfoInstance);

          if (oPdfPageInstance != null) {
            oCanvas = 
              (Canvas) MvReflection.invokeMethod(
                   oPdfPageInstance, 
                   "getCanvas");			 			        
            oCanvas.drawBitmap(aoBitmap, 0, 0, null);

            MvReflection.invokeMethod(oPdfDocumentInstance, 
                                      "finishPage", 
                                      oPdfPageInstance);

            try {
              OutputStream os = new FileOutputStream(asPdfPath);
              MvReflection.invokeMethod(oPdfDocumentInstance, 
                                        "writeTo", 
                                        (OutputStream) os);
              os.flush();
              os.close();
              MvReflection.invokeMethod(oPdfDocumentInstance, 
                                        "close");		     			
            } catch (FileNotFoundException e) {
              MvMessages.logMessage(e.getClass().getCanonicalName());
            } catch (IOException e) {
              MvMessages.logMessage(e.getClass().getCanonicalName());
            }
            MvMessages.logMessage("PDF saved"); 
          }
        }
      }
    }
  } else {
    MvMessages.logMessage(
        "android.graphics.pdf API is not available in this SDK version - " + 
        Build.VERSION.SDK_INT);
  }
  return(false);
}

Now my app is able to access APIs in Android Lollipop or Marshmallow while continuing to maintain support for Android Donut.

The code save the bitmap to a PDF on newer devices.

Points of Interest

A serious issue I encountered was with derived class types. Methods such as PdfDocument.writeTo() required an exact match. A FileOutputStream instance that my code had would not pass for an OutputStream type that the method signature wanted. A brute force tactic worked - iterating through all overloads to find the one that worked with a given set of argument types. It looks risky but will work all right if the overloads are limited.

The MvReflection.java class could be used in other forms of Java too, not just Android Java.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)