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