LanDenLabs - Java, Android and Mac Weakness (2018)

Android Weakness - what to look out for

The following is my personal list of weaknesses (issues) with Android. For some background I am using MacBook Pro to develop Android Apps using Android Studio and Java 1.8 [Go Back]


Android

Android Weaknesses and Points of Interest (2018):

  1. AsyncTask must be recreated for each execution
  2. AsyncTask execute single threaded
  3. Debugging issues
  4. ListView divider height of 0dp or 0px does not hide divider
  5. XML Sax parser does not parse XML character encoding declaration
  6. SimpleDateFormat changed in API24 with no warning
  7. Bitmap may be cached so don't call recycle to free memory
  8. Animation, or Animate or Transitions ...
  1. AsyncTask must be recreated each time. http://codetheory.in/android-asynctask

    You can't make a single AsyncTask instance and reuse it. Android has several objects like this and it seems Java and Android like to force you to reallocate objects ever time you use them. Feels like a waste to re-allocate to reuse.

    [To Top]


  2. AsynTask execution defaults to single thread pool
  3. AsyncTask.execute()

    Executes the task with the specified parameters. The task returns itself (this) so that the caller can keep a reference to it.

    Note: this function schedules the task on a queue for a single background thread or pool of threads depending on the platform version. When first introduced, AsyncTasks were executed serially on a single background thread. Starting with DONUT, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting HONEYCOMB, tasks are back to being executed on a single thread to avoid common application errors caused by parallel execution. If you truly want parallel execution, you can use the executeOnExecutor(Executor, Params...) version of this method with THREAD_POOL_EXECUTOR; however, see commentary there for warnings on its use.

    Use AsyncTaskCompat.executeParallel to execute Async Task in parallel.

    [To Top]


  4. Debugger

    [To Top]


  5. ListView divider height of 0dp or 0px does not hide divider, you have to also set its color to transperent.

       
     <ListView
        android:id="@+id/application_root_view_drawer_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:choiceMode="singleChoice"
        android:dividerHeight="0dp"
        android:divider="@android:color/transparent"
        android:scrollbars="none" />
     

    [To Top]


  6. XML Sax parser does not parse XML character encoding declaration

    The standard Android XML SAX parser NEVER parses the character encoding from the xml declaration encoding attribute.

    
    <?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
    <person>
      Dennis Lang
    </person>
    

    The Android XML Pull parser does support parsing the Xml declaration encoding. Example parsing the declaration encoding with Pull parser:

    
    XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
    XmlPullParser parser = factory.newPullParser();
    ...
    FileInputStream stream = null;
    stream = new FileInputStream(file);
    parser.setInput(stream, null);  // <-- pass null as encoding to force parsing of declaration.
    
    

    The Android XML SAX parse does not parse the declaration line. Custom implementation to extract encoding from the declaration line.

    
    public static void executeHTTPGetAndParseXML(String urlString, DefaultHandler xmlContentHandler)
            throws ConnectionException, XmlParseException {
    
        Response response = executeGetHTTPRequest(urlString);
    
        try {
            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser parser = spf.newSAXParser();
            InputSource source = getEncodedInputSource(response); // <-- get encoded input source.
            parser.parse(source, xmlContentHandler);
    
        } catch (IOException ioe) {
            throw new ConnectionException(String.format(
                    "Failed to load an xml for URL [%s]", urlString), ioe);
        } catch (SAXException se) {
            throw new XmlParseException(String.format(
                    "Failed to parse an xml resource obtained for the URL [%s]", urlString), se);
        } catch (ParserConfigurationException pce) {
            throw new XmlParseException(String.format(
                    "Failed to parse an xml resource obtained for the URL [%s]", urlString), pce);
        }
    }
    
    /**
     * Hack - stupid Java XML SAX parser does not process the XML declaration (prolog)
     * This method will extract and  set character encoding based on optional XML declaration..
     *
     *  
     *
     * @param response
     * @return Correctly set character encoding InputSource
     * @throws IOException
     */
    private static InputSource getEncodedInputSource(Response response ) throws IOException {
        byte[] xmlData = response.body().bytes();
        InputSource source  = new InputSource(new ByteArrayInputStream(xmlData));
        int xmlDeclLen = Math.min(100, xmlData.length);
        String xmlDeclStr = new String(xmlData, 0, xmlDeclLen);
        String encodingStr = xmlDeclStr
                .replaceAll("[\r\n]","")
                .replaceAll("<[?]xml.+?encoding=[\"']([^\"']+)[\"'].*?[?]>.*", "$1");
        try {
            Charset charSet = Charset.forName(encodingStr);
            if (charSet != null) {
                source.setEncoding(charSet.name());
            }
        } catch (Exception ex) {
        }
    
        return source;
    }
        
    

    [To Top]


  7. SimpleDateFormat changed in API24 with no warning

    As of API 24 the Android SimpleDateFormat added support for ISO 8601 time zone by adding the format character 'X'. At the same time it changed what the 'Z' format supported.

    Table from current SimpleDateFormat online documentation:

    Z Time zone RFC 822 time zone -0800
    X Time zone ISO 8601 time zone -08; -0800; -08:00

    The following time string:

    017-01-13 22:06:00+00

    Was parsable in pre API 24 using:
    SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ", Locale.ENGLISH)
    

    As of API 24 the 'Z' format no longer tolerates or parses +00 time zone. The new 'X' does but it is only available as of API 24, so you need to pick the correct format at runtime.

    
    if (Build.VERSION.SDK_INT >= 24) {
        GEO_DATA_VALID_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssX", Locale.ENGLISH);
    } else {
        GEO_DATA_VALID_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ", Locale.ENGLISH);
    }
    

    [To Top]


  8. Bitmap - may be cached internally

    It is dangerous to call recycle() on Bitmap because it may be cached internally. See below for code samples which cache BitmapDrawable.

    Android description of Bitmap recycle method -

    Free the native object associated with this bitmap, and clear the reference to the pixel data. This will not free the pixel data synchronously; it simply allows it to be garbage collected if there are no other references. The bitmap is marked as "dead", meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing. This operation cannot be reversed, so it should only be called if you are sure there are no further uses for the bitmap.

    This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.

    Below are several ways to extract a Bitmap from your package resources. Two of them will cache the BitmapDrawable. This is handy to know if this bitmap is loaded frequently where caching would help. You may also want to avoid the caching if it is a large Bitmap which is rarely loaded. The point is more that you may not know what Android is doing with your Bitmap so calling recycle to free up memory is dangerous and not recommended. A well written Java app will garbage collect any unused objects. Calling recycle in hopes of freeing memory will likely result in an unstable app.

    
        /**
         * Load bitmap resource - no internally caching.
         */
        public static class Loader1 implements  Loader {
    
            @Override
            public Bitmap getBitmap(Context context,int resId) {
                return BitmapFactory.decodeResource(context.getResources(), resId);
            }
        }
    
        /**
         * Load bitmap resource - drawable cached internally
         */
        public static class Loader2 implements  Loader {
    
            @Override
            public Bitmap getBitmap(Context context,int resId) {
                BitmapDrawable bmDrawable;
                if (Build.VERSION.SDK_INT >= 21) {
                    bmDrawable = (BitmapDrawable) context.getResources().getDrawable(resId, context.getTheme());
                } else {
                    bmDrawable = (BitmapDrawable)context.getResources().getDrawable(resId);
                }
                return bmDrawable.getBitmap();
            }
        }
    
        /**
         * Load bitmap resource - drawable cached internally
         */
        public static class Loader3 implements  Loader {
    
            @Override
            public Bitmap getBitmap(Context context,int resId) {
                if (Build.VERSION.SDK_INT >= 21) {
                    return ((BitmapDrawable) context.getDrawable(resId)).getBitmap();
                } else {
                    return ((BitmapDrawable)context.getResources().getDrawable(resId)).getBitmap();
                }
            }
        }
    

    See LanDen Labs GitHub for sample program all PerfTester

    [To Top]


  9. Animation, or Animate or Transitions ...

    Android has a growing set of animation solutions. This large set can make it tricky to know and pick the best solution:

    Problems with Animation :

    ... there are a couple of major pieces of functionality lacking in the original View Animation system.

    For one thing, you can animate Views... and that's it. To a great extent, that's okay. The GUI objects in Android are, after all, Views. So as long as you want to move a Button, or a TextView, or a LinearLayout, or any other GUI object, the animations have you covered. But what if you have some custom drawing in your view that you'd like to animate, like the position of a Drawable, or the translucency of its background color? Then you're on your own, because the previous animation system only understands how to manipulate View objects.

    The previous animations also have a limited scope: you can move, rotate, scale, and fade a View... and that's it. What about animating the background color of a View? Again, you're on your own, because the previous animations had a hard-coded set of things they were able to do, and you could not make them do anything else.

    Finally, the previous animations changed the visual appearance of the target objects... but they didn't actually change the objects themselves. You may have run into this problem. Let's say you want to move a Button from one side of the screen to the other. You can use a TranslateAnimation to do so, and the button will happily glide along to the other side of the screen. And when the animation is done, it will gladly snap back into its original location. So you find the setFillAfter(true) method on Animation and try it again. This time the button stays in place at the location to which it was animated. And you can verify that by clicking on it - Hey! How come the button isn't clicking? The problem is that the animation changes where the button is drawn, but not where the button physically exists within the container. If you want to click on the button, you'll have to click the location that it used to live in. Or, as a more effective solution (and one just a tad more useful to your users), you'll have to write your code to actually change the location of the button in the layout when the animation finishes.

    It is for these reasons, among others, that we decided to offer a new animation system in Honeycomb, one built on the idea of "property animation."

    
        // Example of original View Animation fade out, does not modify view object. 
        target.setAnimation(loadAnimation(R.anim.fad_out));
        
        // Example of new Property Animate fade out - modifies View object
        target.animate().alpha(0).setDuration(2000).start();
    

    File fad_out in anim resource directory:

    
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:anim/linear_interpolator">
        <alpha
            android:fromAlpha="1.0"
            android:toAlpha="0.1"
            android:duration="2000"
            />
    </set>
    

    Example to monitor original View Animation state changes.

    
    target.getAnimation().setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
            status.setText("Animation Start alpha=" + target.getAlpha());
        }
    
        @Override
        public void onAnimationEnd(Animation animation) {
            status.setText("Animation End alpha=" + target.getAlpha());
        }
    
        @Override
        public void onAnimationRepeat(Animation animation) {
            status.setText("Animation Repeat");
        }
    });
    

    Example to monitor new Properity Animate state changes:

    
    // Note the withEndAction() is only called once then removed from the animate.
    // Use animate().setUpdateListener(...) for permanent callback.
    target.animate().withEndAction(new Runnable() {
        @Override
        public void run() {
            status.setText("animate End alpha=" + target.getAlpha());
        }
    });