| /* |
| * 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.XmlContext; |
| import java.util.Collection; |
| import org.w3c.dom.Element; |
| |
| /** 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", |
| "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", |
| "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"); |
| if (countValue != null) { |
| try { |
| maxViewCount = Integer.parseInt(countValue); |
| } catch (NumberFormatException e) { |
| // pass: set to default below |
| } |
| } |
| String depthValue = System.getenv("ANDROID_LINT_MAX_DEPTH"); |
| 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() {} |
| |
| @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.getElementLocation(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.getElementLocation(element), msg); |
| } |
| } |
| |
| @Override |
| public void visitElementAfter(@NonNull XmlContext context, @NonNull Element element) { |
| mDepth--; |
| } |
| } |