/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tools.lint.checks; import com.android.annotations.NonNull; import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Context; import com.android.tools.lint.detector.api.Implementation; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.LayoutDetector; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; import java.util.Collection; /** * Checks whether a view hierarchy has too many views or has a suspiciously deep hierarchy */ public class TooManyViewsDetector extends LayoutDetector { private static final Implementation IMPLEMENTATION = new Implementation( TooManyViewsDetector.class, Scope.RESOURCE_FILE_SCOPE); /** Issue of having too many views in a single layout */ public static final Issue TOO_MANY = Issue.create( "TooManyViews", //$NON-NLS-1$ "Layout has too many views", "Using too many views in a single layout is bad for " + "performance. Consider using compound drawables or other tricks for " + "reducing the number of views in this layout.\n" + "\n" + "The maximum view count defaults to 80 but can be configured with the " + "environment variable `ANDROID_LINT_MAX_VIEW_COUNT`.", Category.PERFORMANCE, 1, Severity.WARNING, IMPLEMENTATION); /** Issue of having too deep hierarchies in layouts */ public static final Issue TOO_DEEP = Issue.create( "TooDeepLayout", //$NON-NLS-1$ "Layout hierarchy is too deep", "Layouts with too much nesting is bad for performance. " + "Consider using a flatter layout (such as `RelativeLayout` or `GridLayout`)." + "The default maximum depth is 10 but can be configured with the environment " + "variable `ANDROID_LINT_MAX_DEPTH`.", Category.PERFORMANCE, 1, Severity.WARNING, IMPLEMENTATION); private static final int MAX_VIEW_COUNT; private static final int MAX_DEPTH; static { int maxViewCount = 0; int maxDepth = 0; String countValue = System.getenv("ANDROID_LINT_MAX_VIEW_COUNT"); //$NON-NLS-1$ if (countValue != null) { try { maxViewCount = Integer.parseInt(countValue); } catch (NumberFormatException e) { // pass: set to default below } } String depthValue = System.getenv("ANDROID_LINT_MAX_DEPTH"); //$NON-NLS-1$ if (depthValue != null) { try { maxDepth = Integer.parseInt(depthValue); } catch (NumberFormatException e) { // pass: set to default below } } if (maxViewCount == 0) { maxViewCount = 80; } if (maxDepth == 0) { maxDepth = 10; } MAX_VIEW_COUNT = maxViewCount; MAX_DEPTH = maxDepth; } private int mViewCount; private int mDepth; private boolean mWarnedAboutDepth; /** Constructs a new {@link TooManyViewsDetector} */ public TooManyViewsDetector() { } @NonNull @Override public Speed getSpeed() { return Speed.FAST; } @Override public void beforeCheckFile(@NonNull Context context) { mViewCount = mDepth = 0; mWarnedAboutDepth = false; } @Override public Collection<String> getApplicableElements() { return ALL; } @Override public void visitElement(@NonNull XmlContext context, @NonNull Element element) { mViewCount++; mDepth++; if (mDepth == MAX_DEPTH && !mWarnedAboutDepth) { // Have to record whether or not we've warned since we could have many siblings // at the max level and we'd warn for each one. No need to do the same thing // for the view count error since we'll only have view count exactly equal the // max just once. mWarnedAboutDepth = true; String msg = String.format("`%1$s` has more than %2$d levels, bad for performance", context.file.getName(), MAX_DEPTH); context.report(TOO_DEEP, element, context.getLocation(element), msg); } if (mViewCount == MAX_VIEW_COUNT) { String msg = String.format("`%1$s` has more than %2$d views, bad for performance", context.file.getName(), MAX_VIEW_COUNT); context.report(TOO_MANY, element, context.getLocation(element), msg); } } @Override public void visitElementAfter(@NonNull XmlContext context, @NonNull Element element) { mDepth--; } }