性能優化之一就是layout的優化,
as 常識:
佈局是否合理主要影響的是頁面測量時間的多少,我們知道一個頁面的顯示測量和繪製過程都是通過遞歸來完成的,多叉樹遍歷的時間與樹的高度h有關,其時間複雜度 O(h),如果層級太深,每增加一層則會增加更多的頁面顯示時間,所以佈局的合理性就顯得很重要。
那佈局優化有哪些方法呢,主要通過減少層級、減少測量和繪製時間、提高複用性三個方面入手。總結如下:
- 減少層級。合理使用 RelativeLayout 和 LinerLayout,合理使用Merge。
- 提高顯示速度。使用 ViewStub,它是一個看不見的、不佔佈局位置、佔用資源非常小的視圖對象。
- 佈局複用。可以通過includ標籤來提高複用。
- 儘可能少用wrap_content。wrap_content 會增加布局 measure 時計算成本,在已知寬高爲固定值時,不用wrap_content 。
- 刪除控件中無用的屬性。
這裏主要說第一點的merge。
對比一下吧,使用merge之前,和使用merge之後的效果:第一張圖片是沒有使用merge的,第二章是使用merge的。
對應佈局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.spreadtrumshitaoli.layoutoptimize.MainActivity"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="Not merge" android:textSize="30dp"/> </RelativeLayout>
對應佈局:
<merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.spreadtrumshitaoli.layoutoptimize.MainActivity"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="Not merge" android:textSize="30dp"/> </merge>
可以看到少了RelativeLayout 這一層佈局。
現在認識到merge可以起到優化作用,辣麼,什麼場景下用好呢?爲什麼merge可以優化佈局,但是大多數layout沒有使用merge呢?
- 如果Activity的佈局文件根節點是FrameLayout,可以替換爲merge標籤,這樣,執行setContentView之後,會減少一層FrameLayout節點。
- 自定義View如果繼承LinearLayout,建議讓自定義View的佈局文件根節點設置成merge,這樣能少一層結點。
- 知道當前父佈局的佈局是什麼,可以使用merge並添加相應的layout屬性(正規軍(源碼中)沒有使用),推薦星爲0星,不過也是一個方案。
針對3,舉個例子:
剛纔的例子,relativelayout中,有屬性android:layout_alignParentRight 等,那麼子添加到這個layout中的merge裏,你也可以寫這個屬性。就在圖1對應的layout中,我們
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.spreadtrumshitaoli.layoutoptimize.MainActivity"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="Not merge" android:textSize="30dp"/> <include layout="@layout/merge_tag"></include> </RelativeLayout>
merge_tag如下:其中的android:layout_alignParentBottom="true" 是有效的。
<merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <TextView android:text="@string/app_name" android:layout_alignParentBottom="true" android:textColor="@android:color/holo_green_light" android:layout_height="wrap_content" android:layout_width="match_parent"/> </merge>
這種情況不推薦使用。
主要記住1、2的使用場景即可,其實merge標籤使用並不太多,更多的是include和viewstub這兩個。
下篇文章會介紹一下include及viewstub。
merge爲什麼會這樣?父佈局的屬性,merge裏可以使用,merge到底是view、viewgroup還是什麼呢,它是不是個layout?
使用的時候,有沒有要注意的點呢?
先來看看merge標籤:
在layoutinflater中,有源碼
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
}
其中,rInflate中部分代碼如下:
{
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
可以看到,會把merge解析到成一個view(注意是解析,merge並不是view,只不過吧內容解析了,因爲merge裏面的是view),然後add到父佈局中,帶的param是父佈局的。所以上面的3點也就得到解釋了。
注意的點:
- merge必須放在佈局文件的根節點上。
- merge並不是一個ViewGroup,也不是一個View,它相當於聲明瞭一些視圖,等待被添加。
- merge標籤被添加到A容器下,那麼merge下的所有視圖將被添加到A容器下。
- 因爲merge標籤並不是View,所以在通過LayoutInflate.inflate方法渲染的時候, 第二個參數必須指定一個父容器,且第三個參數必須爲true,也就是必須爲merge下的視圖指定一個父親節點。(其中第三個參數可以省略:源碼:
)public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }
android 源碼例子:
- only in /sprdroid8.1_trunk/frameworks/base/tests/SharedLibrary/lib/res/layout/
1<?xml version="1.0" encoding="utf-8"?> 2 3<merge xmlns:android="http://schemas.android.com/apk/res/android"> 4 <TextView android:id="@+id/name" 5 android:layout_width="wrap_content" 6 android:layout_height="wrap_content"/> 7 <TextView android:id="@+id/street" 8 android:layout_width="wrap_content" 9 android:layout_height="wrap_content"/> 10 <TextView android:id="@+id/cityStateZip" 11 android:layout_width="wrap_content" 12 android:layout_height="wrap_content"/> 13 <TextView android:id="@+id/country" 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content"/> 16</merge>
sprdroid8.1_trunk/frameworks/base/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/AddressView.java
public classAddressView extends LinearLayout
View view = LayoutInflater.from(context).inflate(R.layout.address, this);