Xamarin and Android - KitKat and Your External SD Card
This is an update on my previous blog post about locating and using your external, removable SD card on versions of Android based on Jellybean (and below).
Android KitKat (version 4.4) introduced some extra security around writing to your external SD card, and in a nutshell, it really just comes down to not being able to place files where you like any more. You can still write to it, but there is a rather nasty caveat which I’ll get to later on.
Pre-KitKat, you could still create directories and files at the “root” of the mount point of the SD card.
But not any more. You can still do this on the INTERNAL disk. I don’t know if this will change in future, but I am going to assume it will.
So:
- You can still write to the external, removable SD card, just not in the root.
- On the external SD card, you have to place your files in a folder based off:
/storage/(external_sd_mount)/Android/data/(your.package.name)/
(you have full access rights in here, you can do pretty much anything you need to)
- You can write to the internal disk exactly as you’ve always been able to (as far as I’ve been able to test it). This part hasn’t changed.
- You don’t appear to need the
WRITE_EXTERNAL_STORAGE
permission any more either, if you’re targeting only KitKat and above. Best to leave it in though, if targeting devices lower on the food chain.
So in my spare time, I’ve done some fiddling, some research, built and broke a bunch of things, and coerced a couple of people (@dfrencham and @meligy) who have KitKat devices into helping me out. Thanks guys! :)
If you’ve been struggling with this, yourself, then I’ll save you some time, and show you how to go about it.
The key to accessing the external, removable SD card was there all along (or at least since the SDK version 19 for KitKat was released). Why did I have so much trouble getting it to work? Well, I was overthinking it, and trying to force the solution down the proc/mounts
path I’d had to choose before. A one-size-fits-all approach struck me as more elegant, but it was not to be…
Things have actually become easier though…
Google introduced a new API method call in KitKat called GetExternalFilesDirs
. This returns an array of all ‘permanently’ mounted ‘external’ paths to where you can write data. I use the term ‘external’ in quotes because I think it’s a bit of a misnomer (and I had a bit of a grumble about it in the earlier post).
You’ll have noticed the similarity of GetExternalFilesDirs
to GetExternalFilesDir
(added in level 8). One letter difference, and probably why I missed it.
You’ll be pleased to know that GetExternalFilesDirs
does pretty much the same thing. The very first item in the list of paths it returns is exactly the same path as would have been returned by GetExternalFilesDir
.
However, any paths in the list after that will be paths for externally mounted media, like your external, removable SD card.
If I make this call on an LG G3 running KitKat, I get:
Hooray!
Note that drives which are mounted “transiently”, such as those which might be connected via a USB cable (or something else) are not listed. It appears that the disk needs to be physically attached to the device, for instance in an SD card slot, inside the case.
Does this mean you can simply switch out GetExternalFilesDir
for GetExternalFilesDirs
, and just use the first item for your internal disk, as a one-call-to-rule-them-all method? Not quite. Because the API was only introduced with KitKat, you have to do an Android version check, to see which version of Android you’re running, or it’ll crash.
Viva fragmentation.
The check is easily done tho. All we have to do is something along the lines of:
And what does ExternalSdStorageHelper.GetExternalSdCardPathEx()
do?
There’s more info and comments in the code file in the repo, I took some of it out brevity’s sake. You can also take a look at the ContextCompat shim which Google provides for some backward compatibility tweaking, but this adds a hefty chunk to your final APK, which may be an issue for you.
And that’s pretty much all there is to it.
Note that there is also another API method call introduced in Android Lollipop (level 21), named GetExternalMediaDirs
, but I haven’t got around to upgrading any of my devices to give this a try (yet!). And no, I don’t trust the emulators for anything hardware related.
Warning! Data loss!
Back to the caveat I mentioned at the start of this post. If you decide to store your information on the external, removable SD card in KitKat (or above), the only location you can write to will be removed if the application is uninstalled! There will be no warning, that data will simply be clobbered. So you may want to give your users the option of either backing up their files or moving them to somewhere else (e.g. on the internal) before uninstalling. Application updates are fine, everything is left as is, but if your user uninstalls your app, their data is history!
If you are a developer, and using this SD card location, then every time you relaunch your app using the debugger will also wipe everything out, since the debugger uninstalls the old version and installs the new one. Ask me how I know :)
The newest version of the testing app I built before is now up on GitHub, with the additional functionality built in. About the only other tweak I made to it was to explicitly call the IsWritable
method after getting the path, instead of making calling it inside the path discovery routine.
References
Some references I found useful:
Everything in here is released as OSS under the MIT license, so feel free to use it any way you like.