9

TL;DR:

On-device, we can navigate to Settings › Apps, and for each app separately can call up details on storage used: by the app itself, by its data, by its cache. Is it possible to obtain those details from the command line, ideally via ADB – and without having root access?

In detail:

I've already searched the web, but found no information on this. I've already tried walking the package list (pm list packages), obtaining the path to the APK file (adb shell pm path $pkgname | awk -F':' '{print $2}'), and getting the file size of that (adb shell stat $path | grep Size) – but that has a series of disadvantages:

  • it only gives me the size of the APK (for updated system apps, only of the latest update)
  • even if scripted, it takes ~2s per app to obtain even that. As today's devices already ship with far more than 100 system apps, it would take "ages" even if only a few user apps were installed (almost 6 minutes on an almost virgin Wiko Sunny 3 where I just tried that)
  • details on data and cache are completely missing

Ideally, Android has already somewhere collected those details; it doesn't take that long via the GUI. Maybe there's some database one could query without having root privileges – or some XML to parse, or some shell command I missed?

PS: if you know about a root-only solution, that'd be "better than nothing". Please indicate that in a comment then so I can come back and ask you to make that an answer if there are no non-root solutions.

2 Answers 2

10

My first instinct to dig out such information which is often available via programming route is to go through dumps of various system services. One such service is diskstats whose only purpose is to dump the status of the disk. Looking into the java code of DiskStatsService.java for various Android releases, I noticed that the information you are looking for is available since Android Oreo. Among other things, the dump shows all the package names, their package sizes, their app data size, and their cache sizes. That information is also dumped into the file /data/system/diskstats_cache.json but this file cannot be accessed without root access though.

The dump is pretty ugly so I wrote a bash script to show data neatly. Run this script in your Linux.

Here's the script (and it does not require root access too):

#!/bin/bash

USRDIR="$HOME"
F_DISK_STATS="$USRDIR"/diskstats.txt
F_PKG_NAMES="$USRDIR"/package_names.txt
F_PKG_SIZE="$USRDIR"/app_pkg_sizes.txt
F_DAT_SIZE="$USRDIR"/app_data_sizes.txt
F_CACHE_SIZE="$USRDIR"/app_cache_sizes.txt

# ADVISE: do a check whether ADB is working fine or not
adb shell dumpsys diskstats > "$F_DISK_STATS"

# Separating data into relevant named files
sed -n '/Package Names:/p' "$F_DISK_STATS" | sed -e 's/,/\n/g' -e 's/"//g' -e 's/.*\[//g' -e 's/\].*//g' > "$F_PKG_NAMES"
sed -n '/App Sizes:/p' "$F_DISK_STATS" | sed -e 's/,/\n/g' -e 's/.*\[//g' -e 's/\].*//g' > "$F_PKG_SIZE"
sed -n '/App Data Sizes:/p' "$F_DISK_STATS" | sed -e 's/,/\n/g' -e 's/.*\[//g' -e 's/\].*//g' > "$F_DAT_SIZE"
sed -n '/Cache Sizes:/p' "$F_DISK_STATS" | sed -e 's/,/\n/g' -e 's/.*\[//g' -e 's/\].*//g' > "$F_CACHE_SIZE"

# Printing package names and their sizes 
ttl_apps=$(wc -l "$F_PKG_NAMES" | cut -d ' ' -f1)
count=1
while [ $count -le $ttl_apps ]; do 
    pkg=$(sed -n "${count}p" "$F_PKG_NAMES")
    pkg_size=$(sed -n "${count}p" "$F_PKG_SIZE") 
    dat_size=$(sed -n "${count}p" "$F_DAT_SIZE")
    csh_size=$(sed -n "${count}p" "$F_CACHE_SIZE")
    echo -e "Pkg Name: $pkg" 
    echo -e "\t Pkg_size=$pkg_size bytes" 
    echo -e "\t Data_size=$dat_size bytes" 
    echo -e "\t Cache_size=$csh_size bytes" 
    echo -e "\t Total_size=$(($pkg_size + $dat_size + $csh_size)) bytes\n"
    count=$(( $count + 1)); 
done

Output when tested on OnePlus 6 (Android 9):

Pkg Name: org.xbmc.kodi
     Pkg_size=144297984 bytes
     Data_size=55771136 bytes
     Cache_size=54341632 bytes
     Total_size=254410752 bytes

Pkg Name: rikka.appops
     Pkg_size=10330112 bytes
     Data_size=1740800 bytes
     Cache_size=163840 bytes
     Total_size=12234752 bytes

Pkg Name: com.a0soft.gphone.aSpotCat
     Pkg_size=16228352 bytes
     Data_size=1101824 bytes
     Cache_size=163840 bytes
     Total_size=17494016 bytes
4
  • 1
    That's cool, thanks & +1! I didn't specify the Android version – but I guess I can live with that limitation. Saying that's much faster definitely is an understatement LOL – 1.6s for all apps in sum! // I just tried it on thw Wiko (Android Go 8.1), the dump lacks the "App Data Sizes:" line there unfortunately. "Normal" 8.1 has it, good. And, surprise: my Nougat device (7.1.1, LOS) has the same as the Go, i.e. all data without Data_size! That was the one dumpsys I was looking for and missed. Thanks a lot!
    – Izzy
    Commented Jan 19, 2020 at 1:33
  • Yeah, sorry about Android Go. I don't have access to any Go device so I couldn't try my solution there when testing it. It didn't even cross my mind that there are Android versions out there other than the mainstream big bloated one. :D
    – Firelord
    Commented Jan 19, 2020 at 3:10
  • I wasn't complaining :) Even the Go device comes with 160 pre-installed apps. And hey, this even works on Nougat. adb shell dumpsys diskstats command is available even on LP but without the app-specific details in its output (tried on an old tablet I've got here), but no idea (yet) for MM.
    – Izzy
    Commented Jan 19, 2020 at 10:44
  • 1
    The interface of that command seems a bit hard. Why in hell wouldn't they go for a simple list of lines such as: "appname",pkg_size,data_size,total_size ? A rethorical question :) Commented Jul 26, 2022 at 7:22
1

I've written a Python version. (Mainly intended for cases when you need to further work with the data.)

It works on its own as well. Without arguments, prints a table of all apps; you can only print some apps by providing their names as arguments.

#!/usr/bin/env python3

from subprocess import run, PIPE, DEVNULL
from json import loads

def get_appdata():
    output = run(['adb','shell','dumpsys','diskstats'], stdout=PIPE, stderr=DEVNULL, check=True, text=True).stdout
    datapoints = {
        'Package Names': 'name',
        'App Sizes': 'size',
        'App Data Sizes': 'data',
        'Cache Sizes': 'cache',
    }

    _data = {}

    for line in output.splitlines():
        for prefix, dataname in datapoints.items():
            if line.startswith(prefix + ': '):
                _data[dataname] = loads(line[len(prefix)+2:])

    for name, dataname in datapoints.items():
        if dataname not in _data:
            raise ValueError(f'dumpsys returned invalid output: missing `{name}:` line')


    assert len({len(x) for x in _data.values()}) == 1, 'dumpsys returned invalid output: unequal length of data fields'

    data = {}
    for i, app in enumerate(_data['name']):
        data[app] = {}
        for dp in ['size', 'data', 'cache']:
            data[app][dp] = _data[dp][i]

    return data

def show_table(appdata, apps=None):
    cwidth = max(len(x) for x in appdata.keys())

    print('{0:<{1}}'.format('App', cwidth), '         App', '        Data', '       Cache')
    for app, sizes in appdata.items():
        if apps is None or app in apps:
            print(f'{app:<{cwidth}} {sizes["size"]:>12} {sizes["data"]:>12} {sizes["cache"]:>12}')


if __name__ == '__main__':
    import sys
    appdata = get_appdata()
    if len(sys.argv) == 1:
        show_table(appdata)
    else:
        show_table(appdata, sys.argv[1:])

(Feel free to use and adapt. Hereby released to the Public Domain.)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .