Why a cheatsheet for Android commands exists at all
Most Android development happens in Android Studio. The IDE installs APKs, attaches debuggers, profiles methods, edits manifests, generates signing keys, and reads device logs. You don't spend much of your day in a terminal.
The other ten per cent is where this page comes in.
You open a terminal when you need to verify an App Link against the live signing fingerprint, peek inside a release AAB before pushing it, flip a Firebase Analytics debug flag without rebuilding, simulate Doze on a connected device, or chase a logcat tag the library author never bothered to document. Each one of those is a job where Android Studio either buries the relevant control three menu levels deep or simply doesn't expose it. The commands here are what's left when the IDE stops covering you.
Docs for any single one of them tend to be scattered across four Google pages, two Stack Overflow answers from 2018, the bottom of someone's gist, and a comment buried in AOSP. Nobody remembers them. This page is the looking-up step, collected in one place.
Three families of commands
The commands here look like one toolset but they come from three different binaries with three different design philosophies and three different release cadences. Knowing which family a command belongs to is how you debug it when it stops working.
adb: talking to a device
adb is a client-server program. The adb binary on your laptop is
the client. adbd is a daemon that runs inside Android with shell-user
privileges (UID 2000), and it's the server. adb devices and
adb install do their work on the client side. adb shell ... is
where control crosses over to the device and dispatches to whatever subsystem the rest of
the command names.
That dispatch is where the syntax gets ugly. adb shell am start talks to
Activity Manager. adb shell pm list packages talks to Package Manager.
adb shell dumpsys notification hits the framework's dumpsys interface.
adb shell content insert goes through a ContentProvider.
adb shell input tap 500 1200 runs through InputManager. Each of those
subsystems was added at a different time, by a different team, with different ideas about
how flags should look. Some take long flags like --user 0, some take single
dashes, some take positional arguments. The syntax feels like it's from 2002. It is from 2002.
Two-thirds of the commands on this page are adb shell invocations. Every entry
under ADB, App management, Activity & Intent, Power & background, and most of App
Links. The Firebase Analytics commands are adb-shell wrappers too. They just toggle
setprop flags before tailing logcat.
Build tools: keytool, apksigner, bundletool, gradle
The build and sign commands come from four separate binaries. keytool ships
with the JDK. apksigner and bundletool come with the Android SDK
build-tools. gradle (or gradlew, the wrapper checked into your
project) sits in the project itself. They're all there to inspect what you're about to
ship, what you've already shipped, or the keystore that's signing it.
The thing this section is really about is the Play App Signing fingerprint problem. When you upload an AAB to Play, Google strips your upload signature and re-signs the artifact with a key Google holds and never gives you. From that point on, three different SHA-256 fingerprints exist for the same app:
- The one your local upload keystore produces. Get it with
keytool -list -v -keystore upload-key.jks. - The one Google's app-signing key produces. You can see it in the Play Console under Setup → App integrity → App signing, and only there. This is what end-user devices actually see.
- The one your debug keystore at
~/.android/debug.keystoreproduces.gradlew signingReportwill print it. This signs every debug build you run.
All three are valid SHA-256 fingerprints, and they aren't interchangeable. Your
assetlinks.json needs the Play signing fingerprint, not the upload one your CI
happens to know about. Firebase and Google Sign-In need both the upload and Play
fingerprints registered, so debug builds keep working alongside store builds. Half the bug
reports that look like "App Links broke after our last release" turn out to be "you pasted
the upload fingerprint into assetlinks.json, and the live version is signed
with Google's key." keytool -list -v -keystore is the command to bookmark.
Logcat: reading what the device is shouting
logcat is a binary that runs inside Android, surfaced through
adb logcat. It streams everything. Framework lines. System server output.
Every app's stdout and stderr. Kernel messages. Native crash dumps. On a modern Pixel it
spits out 200 to 400 lines per second, and most of it has nothing to do with whatever
you're chasing.
Filtering is half the battle. The expression TagName:Priority *:S reads as
"show this tag at this priority and above, silence everything else." Priorities are V, D,
I, W, E, F, S. The three patterns worth memorising are adb logcat *:E for
errors only, adb logcat -s OkHttp for one tag, and
adb logcat --pid=$(adb shell pidof com.example.app) for one process.
-v threadtime is the format you usually want. It gives you timestamp, PID,
TID, priority, tag, and message. The default brief format drops the timestamp,
which makes correlating two events twenty seconds apart impossible.
The per-library tag quirk catches everyone the first time. Most libraries gate their
verbose output behind setprop log.tag.<TagName> VERBOSE, and the tag is
whatever string the library author passed to Log.v(TAG, ...) in their own
source. OkHttp's tag is OkHttp. Glide's is Glide. Firebase
Analytics is FA. Two letters, not documented in any obvious place. WorkManager
splits across WM-WorkerWrapper, WM-WorkSpec, and several others
depending on which lifecycle event you're chasing. There's no convention. If verbose
logging from a library isn't appearing, grep the library's source for
Log.v( and find the actual string.
After setprop you also need
adb shell stop && adb shell start before new processes will pick the
change up. That's restarting Zygote, in practice. Running setprop and then
wondering why nothing changed is the second-most-common logcat mistake.
What's not in here
A few things aren't on this page on purpose.
Anything Android Studio does better. The Layout Inspector beats
dumpsys activity top for view hierarchies. The Profiler beats
dumpsys meminfo for live memory and CPU. The APK Analyzer beats every
keytool and apksigner and unzip one-liner for poking
around inside a build artifact. If you're typing a long CLI to inspect something that's
inherently interactive, you're doing it the hard way.
Anything that requires root, a modified ROM, or instrumentation frameworks.
frida-server, Xposed, Magisk, and the rest of the device-modification
ecosystem are out of scope here. Every command on this page assumes a stock device with USB
debugging on, no special agents loaded.
Anything CTS, ATS, or Tradefed-shaped. The test harnesses that ship with AOSP for
certification and device-farm orchestration are their own world. The closest entries on
this page are screenrecord and uiautomator dump, which are the
manual equivalents of what those harnesses run at scale.
NDK and JNI debugging. adb shell ndk-stack shows up occasionally, but
diagnosing native crashes properly involves symbol servers, addr2line, and
Crashlytics symbol upload. That needs a page of its own. None of it is one-liners.
Where commands go wrong, predictably
Three failure modes are responsible for most of the time wasted in this part of the stack.
The first is permission asymmetry. A command runs cleanly under adb shell,
which is the shell user, UID 2000, with an elevated permission set baked into
adbd. The same command fails when an app tries to run it in its own process.
The classic case is adb shell content query --uri content://settings/secure.
It works fine from your terminal. The equivalent call from app code returns nothing until
the app declares READ_PRIVILEGED_SETTINGS, which only system apps can hold.
The shell user is more privileged than your app for some things and less for others. When a
command works for you and not for your app code, that asymmetry is usually why.
The second is output truncation. dumpsys is the worst offender.
adb shell dumpsys activity on a Pixel is easily 5,000 lines, and most
terminals will scroll the first 4,500 of them past your cursor before you can
grep. Pipe the output to a file with
adb shell dumpsys activity > activity.txt and read it there. The reverse
trap is logcat without -d. It streams forever, so commands that
should "show recent logs and exit" hang the terminal until you Ctrl-C. Fine the first
time, confusing the third time you hit it inside a script.
The third is version drift. ADB syntax changes more often than people realise.
adb pair and wireless ADB only exist from Android 11.
cmd package install-existing needs Android 10 or later.
pm grant only became reliably scriptable around API 23. If a command on this
page fails with "unknown command" or "Unknown verb," check the device's API level first.
adb shell getprop ro.build.version.sdk will print it in a single line.
How to use this page well
The search box at the top is the fastest way in. Type any phrase the way you'd Google it. "verbose analytics," "verify app links," "sha256 fingerprint," "tap coordinates," "wireless adb pair." The matcher scans titles, descriptions, and the command text itself, so partial matches and misspellings inside a word usually still hit.
When you know roughly which category you want, the chips below the search filter the list to one category at a time. A chip combined with a search term narrows things further.
Every category section has an anchor URL. /commands#firebase,
/commands#logcat, /commands#build, and so on for
the other six. Those are useful when you're linking from a Slack thread or a bug ticket.
Individual commands have their own # anchor too. The # icon to
the left of each command title copies its link to the clipboard.
If you spot a wrong command, an outdated flag, one you reach for often and don't see, or syntax that stopped working on a recent Android version, the contact form