是否有Android工具可以查找正在运行的应用的布局名称?

时间:2015-09-30 16:13:08

标签: android debugging layout android-studio hierarchyviewer

背景

我最近被雇用来维护非常大型程序(只有两个活动,大约一百个碎片和几百个布局)。此外,布局(图像和文本)的大部分内容以及布局的显示顺序是通过公司的Web API动态确定的。

不幸的是没有文件。没有地图,纳达。在他们拥有Android程序员之前,该公司聘请了第三方来制作这个应用程序。而且代码的质量最好(甚至变量名称也令人困惑和矛盾)。

因此,我花了大约70% - 90%的时间只是为了改变Button的背景而只是搜索布局和代码。

问题

是否有可以运行的工具(可能在Android Studio的调试器中?),它可以以某种方式吐出当前显示的布局文件的名称?

我的上司经常说“将背景纹理从黑色变为浅灰色”。我在想:改变是微不足道的,但找到 xml文件可能需要一个小时。

- 更新 -

产生这个问题的项目不再受我的控制,所以这个问题已经没有实际意义了。但它似乎确实是一个常见问题,所以我将这个问题保持开放。也许将来会出现某种工具/解决方案。我希望这个帖子在那种情况下对其他程序员有用。

7 个答案:

答案 0 :(得分:6)

层次结构查看器工具

在Android设备监视器中,您可以单击一个名为“用于UI Automator的转储视图层次结构”的按钮。

这将在设备监视器中打开UI层次结构查看器,您可以使用它查看每个视图的资源ID。

虽然这实际上并没有为您提供膨胀的XML文件的名称,但它可能非常有用。如果幸运的话,您甚至可以使用资源ID来缩小搜索XML文件的范围。

如果将鼠标悬停在查看器窗口的标题上,它将显示它创建的实际XML文件的路径。这是一种在一个地方查找所有资源ID的方法。

有关层次结构查看器的更多信息,以防它有用:

  

Hierarchy Viewer允许您调试和优化用户   接口。它提供了布局视图的直观表示   层次结构(布局视图)和显示器的放大检查器   (Pixel Perfect View)。

在代码

中创建自定义布局Inflater

这是一篇包含有趣内容的博客文章 - 一个拦截所有观点通胀的自定义布局:

可以使用该示例代码拦截膨胀调用,并接收XML文件的资源ID

此时,您需要将资源ID 转换为有用的名称。引用这个问题(How to get Resource Name from Resource id),您可以使用:

getResources().getResourceEntryName(int resid);

添加一些日志记录,这可能会为每个XML文件提供充值。

这可能是记录整个项目的一种很好的方式。

答案 1 :(得分:5)

还有一个适用于Android的Developer Assistant应用程序,可以在运行时检查视图层次结构,然后在屏幕上显示最可能的布局名称。涉及到启发式算法,因此它不能100%准确地工作,但仍然可以提供帮助,并且可以完全脱机工作(披露:我制作了此应用)。

一个例子:

enter image description here

答案 2 :(得分:3)

可能的“Android / sdk / tools / uiautomatorviewer”可以帮到你。它可以从设备转储当前屏幕,并显示视图/布局及其详细信息 - 也包括id

答案 3 :(得分:3)

您可以尝试我创建的这个工具: https://github.com/nekocode/ResourceInspector

它可以检查当前活动的所有使用的布局文件。

答案 4 :(得分:2)

受@nekocode ResourceInspector的启发,我想出了如何在AS的布局检查器捕获的屏幕截图中显示布局资源名称。 show the layout resource name in the screenshot captured by AS's layout inspector

首先创建一个类LayoutIndicatorInflater

public class LayoutIndicatorInflater extends LayoutInflater {

    private LayoutInflater mOriginalInflater;
    private String mAppPackageName;

    protected LayoutIndicatorInflater(LayoutInflater original, Context newContext) {
        super(original, newContext);
        mOriginalInflater = original;
        mAppPackageName = getContext().getPackageName();
    }

    @Override
    public LayoutInflater cloneInContext(Context newContext) {
        return new LayoutIndicatorInflater(mOriginalInflater.cloneInContext(newContext), newContext);
    }

    @Override
    public void setFactory(Factory factory) {
        super.setFactory(factory);
        mOriginalInflater.setFactory(factory);
    }

    @Override
    public void setFactory2(Factory2 factory) {
        super.setFactory2(factory);
        mOriginalInflater.setFactory2(factory);
    }

    @Override
    public View inflate(int resourceId, ViewGroup root, boolean attachToRoot) {
        Resources res = getContext().getResources();

        String packageName = "";
        try {
            packageName = res.getResourcePackageName(resourceId);
        } catch (Exception e) {}


        String resName = "";
        try {
            resName = res.getResourceEntryName(resourceId);
        } catch (Exception e) {}

        View view = mOriginalInflater.inflate(resourceId, root, attachToRoot);

        if (!mAppPackageName.equals(packageName)) {
            return view;
        }

        View targetView = view;
        if (root != null && attachToRoot) {
            targetView = root.getChildAt(root.getChildCount() - 1);
        }

        targetView.setContentDescription("layout res:" + resName);

        if (targetView instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) targetView;
            for (int i = 0; i < viewGroup.getChildCount(); i++) {
                View child = viewGroup.getChildAt(i);
                if (TextUtils.isEmpty(child.getContentDescription())) {
                    child.setContentDescription("layout res:" + resName);
                }
            }
        }

        return view;
    }
}

然后创建一个帮助类

    public class LayoutIndicatorHelper {

    public static void init(Application application) {
        if (BuildConfig.DEBUG) {
            application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
                @Override
                public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                    try {
                        Field inflaterField = ContextThemeWrapper.class.getDeclaredField("mInflater");
                        inflaterField.setAccessible(true);
                        LayoutInflater inflater = (LayoutInflater) inflaterField.get(activity);
                        LayoutInflater proxyInflater = null;
                        if (inflater != null) {
                            proxyInflater = new LayoutIndicatorInflater(inflater, activity);
                            inflaterField.set(activity, proxyInflater);
                        }

                        Class phoneWindowClass = Class.forName("com.android.internal.policy.PhoneWindow");
                        Field phoneWindowInflater = phoneWindowClass.getDeclaredField("mLayoutInflater");
                        phoneWindowInflater.setAccessible(true);
                        inflater = (LayoutInflater) phoneWindowInflater.get(activity.getWindow());
                        if (inflater != null && proxyInflater != null) {
                            phoneWindowInflater.set(activity.getWindow(), proxyInflater);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onActivityStarted(Activity activity) {

                }

                @Override
                public void onActivityResumed(Activity activity) {

                }

                @Override
                public void onActivityPaused(Activity activity) {

                }

                @Override
                public void onActivityStopped(Activity activity) {

                }

                @Override
                public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

                }

                @Override
                public void onActivityDestroyed(Activity activity) {

                }
            });
        }
    }

}

最后在LayoutIndicatorHelper.init Application

中致电onCreate
    public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        LayoutIndicatorHelper.init(this);
    }
}

答案 5 :(得分:2)

在android 3.0及更高版本中,不推荐使用 Hierarchy Viewer工具。有一个独立的LayoutInspector。您可以通过点击 Android Studio 顶部栏中的工具来访问它。

答案 6 :(得分:1)

也许这是一个太晚的答案,但不同的程序员可以利用我的答案。

Facebook的Stetho库是查看运行应用程序检查的绝佳工具。 Stetho库的工作方式类似于Chrome扩展,对许多程序员来说都是类似的。

使用Stetho也很容易看到HTTP请求/响应。 我绝对建议每个Android程序员尝试。

http://facebook.github.io/stetho/