• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.lint.checks;
18 
19 import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI;
20 import static com.android.tools.lint.detector.api.LintConstants.ATTR_BASELINE_ALIGNED;
21 import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_HEIGHT;
22 import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_WEIGHT;
23 import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT_WIDTH;
24 import static com.android.tools.lint.detector.api.LintConstants.ATTR_ORIENTATION;
25 import static com.android.tools.lint.detector.api.LintConstants.LINEAR_LAYOUT;
26 import static com.android.tools.lint.detector.api.LintConstants.VALUE_VERTICAL;
27 
28 import com.android.tools.lint.detector.api.Category;
29 import com.android.tools.lint.detector.api.Issue;
30 import com.android.tools.lint.detector.api.LayoutDetector;
31 import com.android.tools.lint.detector.api.LintUtils;
32 import com.android.tools.lint.detector.api.Scope;
33 import com.android.tools.lint.detector.api.Severity;
34 import com.android.tools.lint.detector.api.Speed;
35 import com.android.tools.lint.detector.api.XmlContext;
36 
37 import org.w3c.dom.Attr;
38 import org.w3c.dom.Element;
39 import org.w3c.dom.Node;
40 
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.Map;
46 
47 /**
48  * Checks whether a layout_weight is declared inefficiently.
49  */
50 public class InefficientWeightDetector extends LayoutDetector {
51 
52     /** Can a weight be replaced with 0dp instead for better performance? */
53     public static final Issue INEFFICIENT_WEIGHT = Issue.create(
54             "InefficientWeight", //$NON-NLS-1$
55             "Looks for inefficient weight declarations in LinearLayouts",
56             "When only a single widget in a LinearLayout defines a weight, it is more " +
57             "efficient to assign a width/height of 0dp to it since it will absorb all " +
58             "the remaining space anyway. With a declared width/height of 0dp it " +
59             "does not have to measure its own size first.",
60             Category.PERFORMANCE,
61             3,
62             Severity.WARNING,
63             InefficientWeightDetector.class,
64             Scope.RESOURCE_FILE_SCOPE);
65 
66     /** Are weights nested? */
67     public static final Issue NESTED_WEIGHTS = Issue.create(
68             "NestedWeights", //$NON-NLS-1$
69             "Looks for nested layout weights, which are costly",
70             "Layout weights require a widget to be measured twice. When a LinearLayout with " +
71             "non-zero weights is nested inside another LinearLayout with non-zero weights, " +
72             "then the number of measurements increase exponentially.",
73             Category.PERFORMANCE,
74             3,
75             Severity.WARNING,
76             InefficientWeightDetector.class,
77             Scope.RESOURCE_FILE_SCOPE);
78 
79     /** Should a LinearLayout set android:baselineAligned? */
80     public static final Issue BASELINE_WEIGHTS = Issue.create(
81             "DisableBaselineAlignment", //$NON-NLS-1$
82             "Looks for LinearLayouts which should set android:baselineAligned=false",
83             "When a LinearLayout is used to distribute the space proportionally between " +
84             "nested layouts, the baseline alignment property should be turned off to " +
85             "make the layout computation faster.",
86             Category.PERFORMANCE,
87             3,
88             Severity.WARNING,
89             InefficientWeightDetector.class,
90             Scope.RESOURCE_FILE_SCOPE);
91 
92     /**
93      * Map from element to whether that element has a non-zero linear layout
94      * weight or has an ancestor which does
95      */
96     private Map<Node, Boolean> mInsideWeight = new HashMap<Node, Boolean>();
97 
98     /** Constructs a new {@link InefficientWeightDetector} */
InefficientWeightDetector()99     public InefficientWeightDetector() {
100     }
101 
102     @Override
getSpeed()103     public Speed getSpeed() {
104         return Speed.FAST;
105     }
106 
107     @Override
getApplicableElements()108     public Collection<String> getApplicableElements() {
109         return Collections.singletonList(LINEAR_LAYOUT);
110     }
111 
112     @Override
visitElement(XmlContext context, Element element)113     public void visitElement(XmlContext context, Element element) {
114         List<Element> children = LintUtils.getChildren(element);
115         // See if there is exactly one child with a weight
116         boolean multipleWeights = false;
117         Element weightChild = null;
118         boolean checkNesting = context.isEnabled(NESTED_WEIGHTS);
119         Node parent = element.getParentNode();
120         for (Element child : children) {
121             if (child.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)) {
122                 if (weightChild != null) {
123                     // More than one child defining a weight!
124                     multipleWeights = true;
125                 } else if (!multipleWeights) {
126                     weightChild = child;
127                 }
128 
129                 if (checkNesting) {
130                     mInsideWeight.put(element, Boolean.TRUE);
131 
132                     Boolean inside = mInsideWeight.get(parent);
133                     if (inside == null) {
134                         mInsideWeight.put(parent, Boolean.FALSE);
135                     } else if (inside) {
136                         Attr sizeNode = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT);
137                         context.report(NESTED_WEIGHTS, sizeNode,
138                                 context.getLocation(sizeNode),
139                                 "Nested weights are bad for performance", null);
140                         // Don't warn again
141                         checkNesting = false;
142                     }
143                 }
144             }
145         }
146 
147         if (context.isEnabled(BASELINE_WEIGHTS) && weightChild != null
148                 && !VALUE_VERTICAL.equals(element.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION))
149                 && !element.hasAttributeNS(ANDROID_URI, ATTR_BASELINE_ALIGNED)) {
150             // See if all the children are layouts
151             boolean allChildrenAreLayouts = children.size() > 0;
152             for (Element child : children) {
153                 // TODO: Make better check
154                 if (!child.getTagName().endsWith("Layout")) { //$NON-NLS-1$
155                     allChildrenAreLayouts = false;
156                 }
157             }
158             if (allChildrenAreLayouts) {
159                 context.report(BASELINE_WEIGHTS,
160                         element,
161                         context.getLocation(element),
162                         "Set android:baselineAligned=\"false\" on this element for better performance",
163                         null);
164             }
165         }
166 
167         if (context.isEnabled(INEFFICIENT_WEIGHT)
168                 && weightChild != null && !multipleWeights) {
169             String dimension;
170             if (VALUE_VERTICAL.equals(element.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION))) {
171                 dimension = ATTR_LAYOUT_HEIGHT;
172             } else {
173                 dimension = ATTR_LAYOUT_WIDTH;
174             }
175             Attr sizeNode = weightChild.getAttributeNodeNS(ANDROID_URI, dimension);
176             String size = sizeNode != null ? sizeNode.getValue() : "(undefined)";
177             if (!size.startsWith("0")) { //$NON-NLS-1$
178                 String msg = String.format(
179                         "Use a %1$s of 0dip instead of %2$s for better performance",
180                         dimension, size);
181                 context.report(INEFFICIENT_WEIGHT,
182                         weightChild,
183                         context.getLocation(sizeNode != null ? sizeNode : weightChild), msg, null);
184 
185             }
186         }
187     }
188 }
189