Automating heap dumps from an Android device or emulator
First of all, your code passes on http://www.shellcheck.net/# with flying colors, congrats!

Simplify variable initializations

Here you can drop all the "", it will be the same thing but easier to write:


SERIAL=""
PACKAGE=""
FILE=""



That is, like this:

SERIAL=
PACKAGE=
FILE=


Check if a regular file exists with -f instead of -e

I admire your paranoia, with all the --:    


mkdir -p -- "$dir" &amp;&amp; touch -- "$file"  

if [ ! -e "$file" ]; then 
    err "File $file could not be created."
fi



But I would also recommend to use -f instead of -e,
as you are really expecting a regular file,
and -e is not necessary a regular file, can be a directory too.

Avoid putting commands into variables, especially when used only once

About this code:    


local adbinfo="$adbcmd shell ls -l $file"

$adbinfo | awk '{print $4}'



When the command in $adbinfo becomes anything non-trivial,
running the command with $adbinfo | ... may not work as expected.
As long as you keep that in mind, this is fine.
However, since you only use this command once in the file_size_on_device function,
I recommend to not use $adbinfo at all, but the command directly:

$adbcmd shell ls -l $file | awk '{print $4}'


Even so, just keep in mind that the same dangers exist for $adbcmd as for $adbinfo earlier. But at least we removed one layer of danger ($adbinfo),
so that's a good thing.

Simplify ls | grep, use just ls when it's good enough

In dump_heap, I'm wondering if this actually works as expected:


# Remove any existing tmp files we created. Ideally, there should be none.
local exists=$($adbcmd shell ls "$dir" | grep "$file")
if [ -n "$exists" ]; then
    $adbcmd shell rm "$file"
fi



In particular, it seems to me the rm command should be like this:

$adbcmd shell rm "$dir/$file"


Because ls "$dir" | grep "$file" suggests that $file is just a filename,
not an absolute path, inside the directory $dir.

Also, the call to grep seems unnecessary, a single ls command should do:

local exists=$($adbcmd shell ls "$dir/$file")


Simplifying conditions

Still in dump_heap,
when checking if $exists is non-empty, instead of -n "$exists",
you can just drop the -n, simply [ "$exists" ] is the same thing.

Lastly, I'm wondering if the whole thing can be simplified to:

# Remove any existing tmp files we created. Ideally, there should be none.
$adbcmd shell ls "$dir/$file" &amp;&gt;/dev/null &amp;&amp; $adbcmd shell rm "$dir/$file"


Prefer printf over echo for non-trivial printing

The various flags of echo are unreliable.
Depending on your system, they might work as expected or not.
Rather than using any of the flags of echo, I prefer printf.
So instead of:


echo -n "Dumping heap "



I recommend:

printf "Dumping heap "


However, I prefer echo when printing trivial text, 
without using any flags,
and when I want a terminating newline.

Prefer fewer processes

In this pipeline there are 3 processes: adb, grep and awk

local pid=$($adbcmd shell ps | grep $PACKAGE | awk '{print $2}')


Especially in cases when an awk follows a grep,
one begs the question if the task of grep can be included in the awk command.
And in this case yes:

local pid=$($adbcmd shell ps | awk -v pkg=$PACKAGE '$0 ~ pkg {print $2}')


However, a little laziness is not against the spirit of good shell scripting.
Your version with grep is shorter and simpler,
and on most days I would go with it too.
So this remark is just for the record, and for the meticulous.

Simplify condition

Instead of this:


if [ -z "${SERIAL}" ] || [ -z "${PACKAGE}" ] || [ -z "${FILE}" ]; then



I suggest to simplify like this:

if ! [ "$SERIAL" -a "$PACKAGE" -a "$FILE" ]; then