Skip to content

Instantly share code, notes, and snippets.

@OleksandrKucherenko
Created February 1, 2018 08:18
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OleksandrKucherenko/f79b0fd93d009ba41ae6e9ff07b6506a to your computer and use it in GitHub Desktop.
Save OleksandrKucherenko/f79b0fd93d009ba41ae6e9ff07b6506a to your computer and use it in GitHub Desktop.
Dump Android View hierarchy (very good for unit tests)
package your.name;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.*;
import android.support.v4.app.Fragment;
import android.support.v4.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Stack;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func2;
public final class Helper {
/** Pint into log activity views hierarchy. */
@NonNull
public static String logViewHierarchy(@NonNull final Activity activity) {
return logViewHierarchy(activity.findViewById(android.R.id.content));
}
/** Print into log view hierarchy. */
@NonNull
public static String logViewHierarchy(@NonNull final View root) {
final StringBuilder output = new StringBuilder(8192).append("\n");
final Resources r = root.getResources();
final Stack<Pair<String, View>> stack = new Stack<>();
stack.push(Pair.create("", root));
boolean nextLevel = false;
while (!stack.empty()) {
final Pair<String, View> p = stack.pop();
final View v = p.second;
final boolean isLastOnLevel = stack.empty() || !p.first.equals(stack.peek().first);
final String graphics = "" + p.first + (isLastOnLevel ? "└── " : "├── ");
final String className = v.getClass().getSimpleName();
final String line = graphics + className + " id=" + v.getId() + resolveIdToName(r, v);
output.append(line).append("\n");
if (v instanceof ViewGroup) {
final ViewGroup vg = (ViewGroup) v;
for (int i = vg.getChildCount() - 1; i >= 0; i--) {
stack.push(Pair.create(p.first + (isLastOnLevel ? " " : "│ "), vg.getChildAt(i)));
}
}
}
final String msg = output.toString();
return msg;
}
/** @see <a href="https://stackoverflow.com/questions/10137692/how-to-get-resource-name-from-resource-id">Lookup resource name</a> */
@NonNull
/* package */ static String resolveIdToName(@Nullable final Resources r, @NonNull final View v) {
if (null == r) return "";
try {
return " / " + r.getResourceEntryName(v.getId());
} catch (Throwable ignored) {
return "";
}
}
}
@OleksandrKucherenko
Copy link
Author

Output results:

screen shot 2018-02-01 at 09 14 43

@OleksandrKucherenko
Copy link
Author

Usage:

RobolectricHelper.logViewHierarchy(activity);
RobolectricHelper.logViewHierarchy(fragment);
RobolectricHelper.logViewHierarchy(view);

@stankinzl
Copy link

stankinzl commented Nov 27, 2018

Kotlin:

import android.app.Activity
import android.content.res.Resources
import androidx.annotation.*
import android.view.View
import android.view.ViewGroup

import java.util.Stack
import android.util.Pair

object RobolectricHelper {

/**For some useful utils for Robolectric check https://gist.github.com/OleksandrKucherenko (need conversion to Java)*/
/** Pint into log activity views hierarchy.  */
@NonNull
fun logViewHierarchy(@NonNull activity: Activity) {
    logViewHierarchy(activity.findViewById<View>(android.R.id.content))
}

/** Print into log view hierarchy.  */
@NonNull
fun logViewHierarchy(@NonNull root: View) {
    val output = StringBuilder(8192).append("\n")
    val r = root.resources
    val stack = Stack<Pair<String, View>>()
    stack.push(Pair.create("", root))

    while (!stack.empty()) {
        val pairedStack: Pair<String, View>? = stack.pop()
        val v = pairedStack!!.second

        val isLastOnLevel = stack.empty() || pairedStack.first != stack.peek().first
        val graphics = "" + pairedStack.first + if (isLastOnLevel) "└── " else "├── "

        val className = v.javaClass.simpleName
        val line = graphics + className + " id=" + v.id + resolveIdToName(r, v)

        output.append(line).append("\n")

        if (v is ViewGroup) {
            for (i in v.childCount - 1 downTo 0) {
                stack.push(Pair.create(pairedStack.first + if (isLastOnLevel) "    " else "│   ", v.getChildAt(i)))
            }
        }
    }
    println(output)
}

/** @see [Lookup resource name](https://stackoverflow.com/questions/10137692/how-to-get-resource-name-from-resource-id)
 */
@NonNull
internal fun resolveIdToName(@Nullable r: Resources?, @NonNull v: View): String {
    if (null == r) return ""

    return try {
        " / " + r.getResourceEntryName(v.id)
    } catch (ignored: Throwable) {
        ""
    }
}

}

@kiber-io
Copy link

Hi! Thanks for the code, it works great! I ported it to run through frida: https://github.com/kiber-io/frida-view-dump

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment