Well, you’ve read the title of the article so there will be no secret about the contents. But why use File Descriptors and what is a File Descriptor?
First of all let’s think how we can even pass 10MB Bitmap object between processes in Android? Given a constraint that we can only use Android SDK there are 3 ways we can do that I am aware of:
While ContentProvider is probably most correct way to do that, and SharedMemory most complex one which might even require writing something in native code, using File Descriptors for this task seems reasonable since it certainly easier than using SharedMemory and for sure more interesting than using ContentProvider 😀. Well, that was a tad subjective. Anyway…
File Descriptor is a unique identifier (handle) for a file or other input/output resource, socket for example.
Let’s think of a File Descriptor as just some integer value that will uniquely identify some resource across all system. For instance if some file will have integer 1939949 as its file descriptor, that means if some other process will know this number 1939949 it will point to exact same file and other process will be able to read exact same file using this integer value.
In Android almost everything that can be passed between processes needs to be packaged in Parcel first, because for almost all communication happening between processes Android uses very powerful Binder framework and Parcel is a container developed specially for message passing using Binder.
Android provides us with a “special” file descriptor — AssetFileDescriptor. It implements Parcelable interface, which means it can be put inside Intent and passed to another component. But can it actually? Let’s try!
As an example we will try to pass this beautiful image of London (it’s free I checked😅), which originally is 9.3MB. We’ll store it just as jpeg image inside raw directory.
And to pass it we’ll create AssetFileDescriptor from raw resource and will put it into Intent.
Runtime crash 🙂
Its NOT allowed to pass file descriptors HERE. Nice 🙈
Unfortunately despite the fact that AssetFileDescriptor is indeed Parcelable and it’s safe to assume that it can be passed via Intent we can not actually do that. Why? I don’t have answer to that question unfortunately, Android is still full of mystery I guess…
But what if there is another way to do that? If AssetFileDescriptor implements Parcelable it should be able to be written into Parcel to be passed between processes, or why did they make it Parcelable then?
Binder to the rescue! Binder is capable of passing File Descriptors between processes, but to do that we need to define our own AIDL interface. AIDL is an interface definition language very similar to Java and AIDL supports Parcelable types therefore we can define method AssetFileDescriptor file();
simple AIDL interface
After then building our app AIDL tool which shipped with Android SDK will generate two classes Stub for server part, and Proxy for client. As developers we just have to implement both ant Binder framework will take care of low level implementation.
MainActivity which will receive our large bitmap 😱
Inside MainActivity we will bind to MyService which runs in separate process :child. Inside onServiceConnected callback we’ll cast remote IBinder object to IFileDescriptor interface we defined in AIDL file and then just fetch reference to AssetFileDescriptor.
Service part of AIDL implementation
Service part is very simple. From onBind(intent: Intent) we need to return IBinder object, and for our case it’s just simple implementation of IFileDescriptor interface.
And now we only left with bitmap fetching part and again there is no magic here.
Reading input stream form AssetFileDescriptor and decoding it to Bitmap
And voila! After doing all of that we can see this beautiful 9.3MB image of London passed to separate process.
Resulting image successfully passed to another process
Only one question left. How much overhead is causing this IPC interaction? How many milliseconds are we losing when passing image between processes compared with if we were to just read image from raw folder? Let’s find out!
To measure time I created this wrapper method, which is capable of measuring time of any block passed inside.
First of all let’s measure what time will it take to read and display image from raw resources in current process.
Reading image from resources in same process. NO IPC
Times we get reading image in same process
After launching this we can see approximate time to read 9.3MB bitmap from raw resource and displaying it is about 280 ms. Not bad, if you doing this on main thread and your device runs at 60 FPS you will loose almost 18 frames 😅
But what time will it take if we’ll read this image from another process? Could you try to guess at least? Before we will find that out? 😉
For more precise comparison we will only measure reading image part which happens after onServiceConnected callback invoked.
Measuring only part that happens after onServiceConnected called
Times we get reading image from remote process
Wow 😱😱😱. Did you expect that? I certainly not. Times reading image from remote process are roughly the same as we are reading it from current process. This means that in this particular case there is no overhead reading image from remote process at all!
That was an interesting experiment, now we know how to pass large images or files between processes and what tradeoffs there will be. You can always experiment with this yourself, all of the source code is available on Github.
I hope you enjoyed the reading, don’t forget to clap 😉
Cheers, Andrei ❤️
This article was originally published on proandroiddev.com on August 28, 2022