Android 在设备上打印视图层次结构

Android 在设备上打印视图层次结构,android,android-layout,Android,Android Layout,在我没有物理访问权限的三星手机上调试我的应用程序,结果很奇怪。我想让用户运行一个插入指令的应用程序来帮助调试。我的应用程序获得了一个视图,其中有一个未知的(对我来说;-)层次结构(视图组等)。是否有办法“漫游”一个视图,并将视图中的所有组件打印成字符串/ddms(etc视图组) 这将类似于HierarchyViewer工具-如果我有adb级别的设备访问权限 更新:我想我可以用这个方法 void dumpViewHierarchyWithProperties(Context context, Vi

在我没有物理访问权限的三星手机上调试我的应用程序,结果很奇怪。我想让用户运行一个插入指令的应用程序来帮助调试。我的应用程序获得了一个
视图
,其中有一个未知的(对我来说;-)层次结构(
视图组
等)。是否有办法“漫游”一个
视图
,并将
视图
中的所有组件打印成字符串/
ddms
(etc
视图组

这将类似于
HierarchyViewer
工具-如果我有
adb
级别的设备访问权限

更新:我想我可以用这个方法

void dumpViewHierarchyWithProperties(Context context, ViewGroup group, BufferedWriter out, int level)
ViewDebug.java
Android操作系统源代码

有人有更好的主意吗

谢谢

这些视图不是我的,它们来自其他应用程序,因此我无法触摸与它们相关的代码(我从RemoteView中放大它们)


如果您正在接收一个
远程视图
,并将其应用于活动中的某些内容,则您可以直接访问生成的
视图
,这与您自己从XML中扩展视图没有什么不同。给定root
视图
,只需遍历孩子(可能是深度优先,但这是您的要求)并记录您想要的任何信息即可。

以下是我刚才为此目的创建的实用函数:

public static void printViewHierarchy(ViewGroup vg, String prefix) {
    for (int i = 0; i < vg.getChildCount(); i++) {
        View v = vg.getChildAt(i);
        String desc = prefix + " | " + "[" + i + "/" + (vg.getChildCount()-1) + "] "+ v.getClass().getSimpleName() + " " + v.getId();
        Log.v("x", desc);

        if (v instanceof ViewGroup) {
            printViewHierarchy((ViewGroup)v, desc);
        }
    }
}
publicstaticvoidprintviewhierarchy(视图组vg,字符串前缀){
对于(int i=0;i
我创建了一个实用方法,它使用人类可读的视图ID以漂亮的打印方式返回层次结构。以下是一个输出示例:

[LinearLayout] no_id
  [CardView] com.example:id/card_view
    [RelativeLayout] no_id
      [LinearLayout] com.example:id/image_container
        [AppCompatImageView] com.example:id/incident_icon
        [CustomTextView] com.example:id/road_number
      [RelativeLayout] no_id
        [CustomTextView] com.example:id/distance_to_user
        [CustomTextView] com.example:id/obstruction_title
        [CustomTextView] com.example:id/road_direction
        [CustomTextView] com.example:id/obstruction_description
        [AppCompatImageView] com.example:id/security_related
以下是实用方法:

public static String getViewHierarchy(@NonNull View v) {
    StringBuilder desc = new StringBuilder();
    getViewHierarchy(v, desc, 0);
    return desc.toString();
}

private static void getViewHierarchy(View v, StringBuilder desc, int margin) {
    desc.append(getViewMessage(v, margin));
    if (v instanceof ViewGroup) {
        margin++;
        ViewGroup vg = (ViewGroup) v;
        for (int i = 0; i < vg.getChildCount(); i++) {
            getViewHierarchy(vg.getChildAt(i), desc, margin);
        }
    }
}

private static String getViewMessage(View v, int marginOffset) {
    String repeated = new String(new char[marginOffset]).replace("\0", "  ");
    try {
        String resourceId = v.getResources() != null ? (v.getId() > 0 ? v.getResources().getResourceName(v.getId()) : "no_id") : "no_resources";
        return repeated + "[" + v.getClass().getSimpleName() + "] " + resourceId + "\n";
    } catch (Resources.NotFoundException e) {
        return repeated + "[" + v.getClass().getSimpleName() + "] name_not_found\n";
    }
}
公共静态字符串getViewHierarchy(@NonNull View v){
StringBuilder desc=新的StringBuilder();
getViewHierarchy(v,desc,0);
返回desc.toString();
}
私有静态void getViewHierarchy(视图v、StringBuilder描述、整型边距){
desc.append(getViewMessage(v,margin));
if(视图组的v实例){
保证金++;
视图组vg=(视图组)v;
对于(int i=0;i0?v.getResources()。getResourceName(v.getId()):“no_id”):“no_resources”;
返回重复的+“[”+v.getClass().getSimpleName()+“]”+resourceId+“\n”;
}catch(Resources.notfounde异常){
返回重复的+“[”+v.getClass().getSimpleName()+“]name\u未找到\n”;
}
}
提示:我们使用此方法将视图层次结构添加到一些崩溃报告中在某些情况下,它确实很有帮助

与Kotlin几乎相同:

fun getViewTree(root: ViewGroup): String{
    fun getViewDesc(v: View): String{
        val res = v.getResources()
        val id = v.getId()
        return "[${v::class.simpleName}]: " + when(true){
            res == null -> "no_resouces"
            id > 0 -> try{
                res.getResourceName(id)
            } catch(e: android.content.res.Resources.NotFoundException){
                "name_not_found"
            }
            else -> "no_id"
        }
    }

    val output = StringBuilder(getViewDesc(root))
    for(i in 0 until root.getChildCount()){
        val v = root.getChildAt(i)
        output.append("\n").append(
            if(v is ViewGroup){
                getViewTree(v).prependIndent("  ")
            } else{
                "  " + getViewDesc(v)
            }
        )
    }
    return output.toString()
}

只需从BottomNavigationView中找到菜单项并注册长时间单击即可

View itemBag = bottomNavigationView.findViewById(R.id.action_bag);
    if (itemBag != null) {
      itemBag.setOnLongClickListener(BottomNavigationActivity.this::onLongClick);
    }

private boolean onLongClick(View v) {
    switch (v.getId()) {
      case R.id.action_bag: {
        showToastMessage(R.string.menu_shopping_bag);
      }
      break;
    }
    return false;
  }

其他输出的格式令我不满意,一些代码的开销很大。因此,我通过以下方法改进了输出:

/**
 * Prints the view hierarchy.
 */
public static void printViewHierarchy(ViewGroup parent, String intent) {
    for (int i = 0, max = parent.getChildCount(), numLenght = (max + "").length(); i < max; i++) {
        View child = parent.getChildAt(i);
        String childString = child.getClass().getSimpleName() + " " + child.getId();
        String format = "|— %0" + numLenght + "d/%0" + numLenght + "d %s";
        Log.d("debug", intent + String.format(format, i + 1, max, childString));

        if (child instanceof ViewGroup)
            printViewHierarchy((ViewGroup) child, intent + "  ");
    }
}

我就是这么做的。我要找的是一个类或库,它提供了“预先封装”的功能。我在Android源代码中为ViewDebug使用了一些代码,基本上做到了你在这里所说的。我只会在变量名中使用$时给出+2 ups,我认为这是不可能的,从未尝试过。我喜欢这个答案,但我会通过使用以下代码使它变得更好,这样你也可以打印id(如果可用):
String desc=$prefix+“|”+“[”+i+“/”+($vg.getChildCount()-1)+“]”+v.getClass().getSimpleName();字符串id=null;尝试{id=v.getResources().getResourceName(v.getId());}catch(异常e){//no op(找不到id)}desc+=“”+(id==null?v.getId():id)这是如何打印的顺便说一句:
println(getViewTree(getWindow().getDecorView().getRootView()作为视图组))
|— 1/4 ScrollView 2131296482
|— 2/4 MaterialTextView 2131296445
|— 3/4 FloatingActionButton 2131296374
|— 4/4 MaterialTextView 2131296363
|— 1/4 ScrollView 2131296482
  |— 1/1 LinearLayout 2129243036
    |— 01/74 RelativeLayout 2131296449
      |— 1/4 MaterialCheckBox 2131296332
      |— 2/4 MaterialTextView 2131296547
      |— 3/4 MaterialTextView 2131296531
      |— 4/4 AppCompatImageButton 2131296462
    |— 02/74 RelativeLayout 2131296449
      |— 1/4 MaterialCheckBox 2131296332
      |— 2/4 MaterialTextView 2131296547
      |— 3/4 MaterialTextView 2131296531
      |— 4/4 AppCompatImageButton 2131296462
    |— ...
    |— 74/74 RelativeLayout 2131296449
      |— 1/4 MaterialCheckBox 2131296332
      |— 2/4 MaterialTextView 2131296547
      |— 3/4 MaterialTextView 2131296531
      |— 4/4 AppCompatImageButton 2131296462
|— 2/4 MaterialTextView 2131296445
|— 3/4 FloatingActionButton 2131296374
|— 4/4 MaterialTextView 2131296363