Pages

Tuesday, October 18, 2016

Android's bad company: IntentService, ResultReceiver and Configuration Changes

| Intro |

I has a simple task: convert the given location (latitude and longitude coordinates) into the human-readable address strings (city, street, post code etc.). On iOS platform this could be solved with the help of just several lines of code. On Android it turned out to be a complicated and error-prone problem with a lot of pitfalls.

At the first glance, this task can be very easily accomplished using the official code sample. However, since we don't live in a perfect world, this sample is not suitable for the production code. It may look good for some Android newcomers but if we have a goal to develop a real-world app a lot of different edge-cases (as much as possible) must be taken into account.

Here are just some issues we would encounter in case of blind copy-pasting the Google's code:
  • As all we know, after the configuration changes your Activities (or non-retained Fragments) are recreated (I suppose you don't use this nasty hack android:configChanges="orientation|screenSize"). When they have been recreated the old ones must be garbage collected and if not - say hello to a memory leak. As will be discussed on the next paragraphs, the IntentService from the Google's sample during ongoing operation holds the indirect reference to the Activity associated with the ResultReceiver and hence prevents its deconstruction. Check also this StackOverflow question.
  • What if the IntentService sends the result at the time when the activity has been temporarily destroyed (e.g. after the device rotation)? Yes, it will go to nowhere and that means the whole work is simply lost. 
  • If the device quickly changes the location then each time it needs to obtain the new address data. But the IntentService doesn't provide us opportunity to cancel previous or pending tasks. So the device must waste resources for the operations which are completely useless now.
Ok, let's go ahead. I'll show you what I've found regarding this subject after some research.

How does IntentService send the results

To obtain the results from the IntentService Google suggests us to use a ResultReceiver subclass. The ResultReceiver implements Parcelable interface that's why it can be easily added to the Intent that is used to start the service. The following code snippet depicts this process:

1
2
3
4
5
6
7
8
        // Create an intent for passing to the intent service responsible for fetching the address.
        Intent intent = new Intent(this, FetchAddressIntentService.class);

        // Pass the result receiver as an extra to the service.
        intent.putExtra(Constants.RECEIVER, mResultReceiver);

        // Start the service
        startService(intent);

Every Android developer must be familiar with such procedure - it's a common task to start services or activities using intents containing some data. Usually we put data to the intent using Intent class putExtra method. For example, it can be some primitive type (int, boolean etc.) or an object that implements Parcelable interface. The data then can be extracted using such code:

mReceiver = intent.getParcelableExtra(Constants.RECEIVER);

Intuitively we expect to get a new copy of the parcelable object from the getParcelableExtra method. And it worth noting that this object should not influence the life-cycle of the original object we put in to the parcel. There are even answers on StackOverflow which suggest to use Parcelable interface as a deep copying method: link1, link2.

But it turns out this is not always the case. After a closer look at the Parcel docs I found a very interesting paragraph, here is the quote:
An unusual feature of Parcel is the ability to read and write active objects. For these objects the actual contents of the object is not written, rather a special token referencing the object is written. When reading the object back from the Parcel, you do not get a new instance of the object, but rather a handle that operates on the exact same object that was originally written. 
Wow! And how is that possible we do not get a new instance of the object? Does it mean we obtain just another reference to the original object and hence this reference will prevent it from being garbage collected? After a lot of searching, googling, stackoverflowing I simply didn't find any comprehensive answer.

As a last resort, I decided to look at the ResultReceiver.java source to find out what's really going on here. Of course, you can analyse it yourself, but here are the most important parts. According to the parcelable interface, we should implement writeToParcel and static CREATOR field which in turn requires a constructor taking a Parcel as an argument. So how does our ResultReceiver is getting unparceled? Look at the snippet:

1
2
3
4
5
    ResultReceiver(Parcel in) {
        mLocal = false;
        mHandler = null;
        mReceiver = IResultReceiver.Stub.asInterface(in.readStrongBinder());
    }

As you can see, the readStrongBinder() method is used to extract the original object from the parcel. And what the parcel docs state about it? Yay! That method deals with the aforementioned active objects which means we do not get a new instance!

To understand what is the IResultReceiver.Stub.asInterface line for look at the official docs regarding AIDL technology. Take a closer look at this statement:
  • Objects are reference counted across processes.
Finally I found the info I was looking for. It's easy to see now that our friend IntentService does not make a deep copy of the result receiver. Instead the rather complicated approach called AIDL is used here.

In simple words, what all this means is that as long as the IntentService is doing its job it will hold the indirect reference to the original result receiver object. And if the activity which started the service also points to the same object after the configuration change we have a memory leak. Nice.

Weak References and Retained Fragment

There are different methods you can find in the internet to overcome this issue. But most of them have disadvantages that are not suitable for the production code. For example, some guys suggest to save the ResultReceiver subclass into the Bundle and then extract it after the configuration change. But in this case only the base class ResultReceiver will be extracted and all our callbacks will be lost (callbacks are just interfaces, they cannot be added to a parcel!).

The only good advise I've found is to use LocalBroadcastManager and forget about this IntentService + ResultReceiver company.

Nonetheless, I finally managed to solve this problem using the good old weak references and a retained fragment. Here is the step-by-step instruction:
  • To allow the old activity garbage collection it must hold a weak reference to the result receiver object that's why the result receiver is stored in the retained fragment like this:

  •  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    /**
     * A fragment that retains the stateful objects during a configuration change.
     */
    public final class MainRetainedFragment extends Fragment {
        /**
         * A key used by the fragment manager to set/retrieve the fragment.
         */
        public static final String FRAGMENT_TAG = "MAIN_RETAINED_FRAGMENT_TAG";
    
        /**
         * An object used to receive the result of the intent service work.
         */
        @Nullable
        private ResultReceiver mResultReceiver;
    
        /**
         * A factory method used to create a fragment in accordance with Android development best practices.
         * @return A new instance of MainRetainedFragment.
         */
        public static MainRetainedFragment newInstance() {
            return new MainRetainedFragment();
        }
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            // Retain this fragment.
            setRetainInstance(true);
        }
    
        @Override
        public String toString() {
            return "MainRetainedFragment{" +
                    "mResultReceiver=" + mResultReceiver +
                    '}';
        }
    
        /**
         * Gets the result receiver object.
         *
         * @return ResultReceiver object.
         */
        @Nullable
        public ResultReceiver getResultReceiver() {
            return mResultReceiver;
        }
    
        /**
         * Set the result receiver object.
         *
         * @param resultReceiver An instance of the ResultReceiver object.
         */
        public void setResultReceiver(@NonNull ResultReceiver resultReceiver) {
            mResultReceiver = resultReceiver;
        }
    }
    

  • Now the result receiver survives during the configuration changes and, moreover, it has a chance to cache the results obtained from the intent service and send them back to the activity when it is available again. The last code snippet depicts how we can access the result receiver object from the activity:

  •  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            final FragmentManager fragmentManager = getSupportFragmentManager();
    
            MainRetainedFragment mainRetainedFragment = (MainRetainedFragment) fragmentManager.
                    findFragmentByTag(MainRetainedFragment.FRAGMENT_TAG);
    
            AddressResultReceiver addressResultReceiver;
    
            if (mainRetainedFragment == null) {
    
                // Create a new fragment if it is null (in case it is accessed for the first time).
                mainRetainedFragment = MainRetainedFragment.newInstance();
                fragmentManager.beginTransaction().
                        add(mainRetainedFragment, MainRetainedFragment.FRAGMENT_TAG).commit();
    
                // Create a new result receiver.
                addressResultReceiver = new AddressResultReceiver(new Handler());
    
                 // Save the result receiver in the retained fragment.
                mainRetainedFragment.setResultReceiver(addressResultReceiver);
    
                // Set the activity as a callback handler.
                // Note: this setter must set the callback handler as a weak reference.
                addressResultReceiver.setReceiverCallbackHandler(this);
            } else {
                // Use the retained result receiver.
                try {
                    addressResultReceiver = (AddressResultReceiver) mainRetainedFragment.getResultReceiver();
                } catch (ClassCastException e) {
                    e.printStackTrace();
                    throw new ClassCastException(" The mResultReceiver must be of AddressResultReceiver type.");
                }
            }
    
            // Set the result receiver as a weak reference.
            mResultReceiverWeakRef = new WeakReference<>(addressResultReceiver);
        }
    

I have tested this approach using LeakCanary - a memory leak detection library and everything seems to work fine.

As always, I hope this will help someone. If something is still unclear, please feel free to leave a comment.

Friday, October 7, 2016

Android Studio 9-Patch Images

| Intro |

Developing my new App which targets Android platform and currently has a codename MyPlaceApp (I'm pondering about changing it to something more cool) I have encountered a lot of different issues and some of them were not so easy resolvable. Obviously, due to time constraints I won't discuss every solution I used in my project in this blog post. Today I'm gonna talk about an amazing way of preparing and integrating with Android Studio a bunch of the raster images that can be resized during runtime preserving the original quality.

Image scaling

This is a common task during mobile development to scale the image resources for the different screen sizes/densities regardless of the platform (Android or iOS). There are several techniques with the help of which we can obtain the required scaling. In particular, Android Developer Portal recommend us to use the following options:
  • Save the resized .png images into folders which define the abstract generalised screen densities: ldpi, mdpi, hdpi, xhdpi etc. By following this link you can check matching between the popular devices and the screen density groups. Android system automatically detects the current screen properties and loads the resources from the proper folders;
  • Import vector drawable into app/src/main/res/drawable/ folder. In this case images will be automatically resized without degrading the quality.

    Note: Android Support Library since version 23.2 provides support of vector drawables for the older API levels.

  • Draw custom shapes which can be defined in XML (e.g., res/drawable/filename.xml) with the specified colours or gradients.

Every option has its own pros and cons. Providing an image for each possible screen density can be very time consuming. At the same time, as the docs state
the initial loading of a vector drawable can cost more CPU cycles than the corresponding raster image
As per my case, I needed to find the solution that would allow me to use the raster images and, on the other hand, resize them without quality loss at the runtime (in accordance with the text content). At the first glance, this seems like magic. But finally I've found such solution. And it is called 9-Patch Images.

9-Patch Images

Here is the step-by-step instruction on how you can create and use the 9-Patch Images in your Android Studio projects:
  1. Using your favourite graphic editor create an image you want to use in your project. Here is what I got with the help of Affinity Designer:

  2. Background image created in Affinity Designer

    Since this is a vector editing program the image can be easily resized to .png with any resolution. But keep in mind that 9-Patch Images can be only stretched (not shrunk) so you should start with the smallest version of your icon.

  3. When you have the image ready copy it to the specific drawable folder. For example, this can be app/src/main/res/drawable-mdpi/.

    Note: For best results you should repeat this process for each screen density (xhdpi, xxhdpi etc.).

    Now right click the imported .png file in Android Studio and choose "Create 9-Patch File" option. You should see the open built-in 9-Patch editor like in the image below:

    Image in the built-in 9-Patch editor

  4. And now the most important part. To make it possible to scale this image while preserving the good quality you must draw 1px width black lines which would define the stretchable areas. The editor should already have added the 1px space around each image side. The topmost and leftmost lines specify the part of the original image that must be stretched when needed. Feel free to divide this line into multiple segments if you want to prevent scaling some specific elements (e.g., rounded corners) to save image quality. During the stretching process the Android platform just repeats the same colour pixels so the complex image parts must be excluded from this with the help of the aforementioned black lines.The right and bottom 1px black lines define the content area. For instance, if you have some dynamically generated text these lines would specify the exact rectangle where this text should be placed. If it cannot fit the defined area the image will be stretched in accordance with the specified stretchable parts.

    Let's look closer at the options which the built-in editor propose us:

    9-Patch editor with all checkbox selected

    Show lock - If this is selected on mouse hovering you will see the whole image covered by the diagonal red lines and only the 1px width lines on each side will remain transparent. This is just a reminder of where you are allowed to draw the meta black lines.

    Show patches - This option allows you to see the stretchable areas of your image. The green regions define the patches that will stretch either horizontal or vertical (not both). The pink define the fully stretchable patch.

    Show content - Shows the rectangle area where the content will be placed above the image.

    Show bad patches - This is the interesting one. When it is selected the problem parts of the image will be highlighted with the red rectangles. The term "problem parts" in this case means such image segments that can be distorted during scaling. As I said earlier, Android just repeats the same pixels when stretching the 9-Patch image. So if you have defined such stretchable areas where the adjacent pixels are different (even a little bit) the 9-Patch will doughtily report you that it is something wrong with your current image configuration. To ensure that the image will look good in all possible scenarios just select the stretchable areas where all the adjacent pixels are the same (have the same colour).


Basically, that's it. If you want to simplify the process of creating multiple 9-Patch images check out this nice third-party tool - Nine-patch Generator.

Hope this info will help someone.