21

背景

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

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

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

问题

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

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

- 更新 -

产生这个问题的项目不再在我的控制之下,所以这个问题变得没有实际意义。但这似乎是一个普遍的问题,所以我将这个问题悬而未决。也许将来会出现某种工具/解决方案。我希望这个线程对这种情况下的其他程序员有用。

4

7 回答 7

13

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

一个例子:

在此处输入图像描述

于 2018-07-05T20:06:10.073 回答
8

层次结构查看器工具

在 Android 设备监视器中,您可以单击一个名为“Dump View Hierarchy for UI Automator”的按钮。

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

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

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


如果有帮助,请提供有关 Hierarchy Viewer 的更多信息:

Hierarchy Viewer 允许您调试和优化您的用户界面。它提供了布局视图层次结构(布局视图)的可视化表示和显示的放大检查器(像素完美视图)。


在代码中创建自定义 Layout Inflater

这是一篇博客文章,其中包含一些有趣的东西 - 一个拦截所有视图膨胀的自定义布局充气器:

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

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

getResources().getResourceEntryName(int resid);

添加一些日志记录,这可能会在膨胀时为您提供每个 XML 文件。

这可能是记录整个项目的好方法。

于 2015-09-30T16:21:55.537 回答
4

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

于 2015-09-30T16:23:50.903 回答
3

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

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

于 2017-10-27T07:14:49.877 回答
2

在 android 3.0 及以后的版本中,层级查看器工具现已弃用。有一个独立的LayoutInspector. 您可以通过单击Android Studio顶部栏中的工具来访问它。

于 2018-08-03T10:37:28.117 回答
2

受@nekocode ResourceInspector 的启发,我弄清楚了如何在AS 的布局检查器捕获的屏幕截图中显示布局资源名称。 在 AS 的布局检查器捕获的屏幕截图中显示布局资源名称

首先创建一个类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.initApplicationonCreate

    public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        LayoutIndicatorHelper.init(this);
    }
}
于 2018-06-04T03:45:39.140 回答
1

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

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

还可以使用 Stetho 轻松查看 HTTP 请求/响应。我绝对建议每个 Android 程序员尝试。

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

于 2017-02-19T08:05:16.783 回答