性能優化包含的部分不少,包括佈局、內存、耗電、流量等等,其中佈局優化是最容易掌握,也最容易被你們所忽視的一個方面,今天,就來介紹一下有關佈局優化的一些技巧。android
當咱們的佈局中有多個相同的佈局時,可使用include
標籤來進行佈局的複用,這樣,當視覺須要修改單個Item
的間距,文字大小時,只須要修改一個佈局就能夠了,例如像下面這種狀況,咱們就可使用include
標籤來實現: canvas
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include android:id="@+id/include_1" layout="@layout/layout_is_merge" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<include android:id="@+id/include_2" layout="@layout/layout_is_merge" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<include android:id="@+id/include_3" layout="@layout/layout_is_merge" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</LinearLayout>
複製代碼
單個Item
的佈局爲:性能優化
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_content_1"
android:text="tv_content_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_content_2"
android:text="tv_content_2"
android:layout_marginLeft="40dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</merge>
複製代碼
要點:bash
<include>
所指定的layout
中包含的控件,那麼就須要給<include>
指定id
,再經過它來尋找子容器中的控件。<include>
標籤中,能夠指定layout_xxx
屬性,它將會覆蓋子佈局中的根標籤中的屬性。當出現下面這種狀況:一個xml
佈局文件的根節點是一個FrameLayout
,而且它沒有一個有用的背景,那麼當該xml
佈局文件渲染出的ViewGroup
被添加到父佈局中時,鏈接處就會出現一個多餘的節點,而採用<merge>
標籤能夠去掉這一無用節點,從而下降佈局的層級。異步
例如,在上面的例子當中,咱們使用了<merge>
標籤的情形爲: async
<merge>
標籤,那麼情形爲:
LayoutInflater
的inflate
方法渲染出以<merge>
做爲根節點標籤的xml
文件時,必須傳入不爲null
的root
參數,且attachToRoot
參數必須爲true
。<merge>
只可做爲xml
的根節點。<merge>
既不是View
也不是ViewGroup
,它只是表示一組等待被添加的視圖,所以,對它設定的任何屬性都是無用的。當咱們的佈局中,存在一些須要按序加載的控件,那麼就可使用ViewStub
標籤預先聲明,當狀況知足時再去實例化ViewStub
中所聲明的佈局,其用法以下:ide
ViewStub
,而且經過layout
標籤指定對應的佈局layout_stub
:<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub
android:id="@+id/view_stub"
android:inflatedId="@+id/view_inflated"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/layout_stub"/>
</LinearLayout>
複製代碼
ViewStub
,再調用它的inflate
或者setVisibility(View.VISIBLE)
方法,其返回的佈局就是layout=
所指定的佈局的根節點:private void inflateIfNeed() {
//1.獲取到佈局中的ViewStub。
mViewStub = (ViewStub) findViewById(R.id.view_stub);
//2.調用其inflate方法實例化它所指定的layout。
mStubView = mViewStub.inflate();
}
複製代碼
要點:工具
ViewStub
只能調用一次inflate
或者setVisibility(View.VISIBLE)
方法,而且調用完以後它將再也不可用,ViewStub
原先所在位置將被替換成爲layout
參數所指定的佈局的根節點,而且其根節點的id
值將變成android:inflatedId
所指定的值:
當咱們須要經過父容器來容納多個子控件時,如何選擇父容器,將會影響到佈局的效率,而對於父容器的選擇,有如下幾點原則:佈局
FrameLayout
、不帶layout_weight
參數的LinearLayout
、RelativeLayout
,這裏選取的標準爲帶有layout_weight
的LinearLayout
或者RelativeLayout
會測量兩次。LinearLayout
時,應當儘可能避免使用layout_weight
參數。RelativeLayout
嵌套RelativeLayout
。Google
新推出的ConstraintLayout
佈局。當咱們存在多種不一樣大小、顏色或者圖文混排鬚要顯示時,咱們每每會利用多個TextView
來進行組合,可是某些效果經過一個TextView
就能夠實現,通常來講,利用SpannableStringBuilder
能夠經過單個TextView
實現多種不一樣的佈局,更多Span
的用法能夠參考這篇文章:Android 中各類 Span 的用法,下面以不一樣大小的TextView
爲例:性能
private void useSpan() {
TextView textView = (TextView) findViewById(R.id.tv_span);
SpannableStringBuilder ssb = new SpannableStringBuilder("300 RMB");
//設置文字大小。
ssb.setSpan(new RelativeSizeSpan(6.0f), 0, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
//設置文字顏色。
ssb.setSpan(new ForegroundColorSpan(0xff303F9F), 0, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(ssb);
}
複製代碼
最終能夠實現以下的效果:
例以下面的佈局:
LinearLayout
自帶的
divider
屬性來實現分割線:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:showDividers="beginning|end|middle"
android:divider="@android:drawable/divider_horizontal_bright"
android:dividerPadding="5dp"
android:paddingTop="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Line 1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Line 2"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Line 3"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Line 4"/>
</LinearLayout>
複製代碼
與分割線相關的屬性包括如下幾個:
divider
:傳入分割線的drawable
,能夠是一個圖片,也能夠是本身經過xml
實現的drawable
。showDividers
:分割線顯示的位置,beginning/middle/end
,分割對應頭部、中間、尾部。dividerPadding
:分割線距離兩邊的間距。Space
控件位於android.support.v4.widget
包中,與通常控件不一樣,它的draw
方法是一個空實現,所以它只佔位置,而不去渲染,使用它來進行佔位填充比其它控件更加高效,例以下面,咱們須要將一行均等地分紅五份,有顏色部分位於2,4
當中:
Space
控件,加上
layout_weight
屬性來實現:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.Space
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"/>
<View
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:background="@color/colorAccent"/>
<android.support.v4.widget.Space
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"/>
<View
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:background="@color/colorAccent"/>
<android.support.v4.widget.Space
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"/>
</LinearLayout>
複製代碼
當出現圖片在文案的四周時,咱們應當首先考慮可以經過單個TextView
來實現,而不是經過LinearLayout
包裹TextView+ImageView
的方式來實現,例以下面的效果:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 方式一:使用 ImageView + TextView -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<ImageView
android:src="@android:drawable/ic_btn_speak_now"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="ImageView + TextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- 方式二:使用單個 TextView -->
<TextView
android:drawableLeft="@android:drawable/ic_btn_speak_now"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="單個 TextView"/>
</LinearLayout>
複製代碼
能夠看到,雖然都是實現了圖片加上文字的顯示效果,可是第二種經過單個TextView
來實現其佈局層級更少,而且控件的個數更少,所以效率更高,而且圖片不只能夠顯示在左邊,還能夠顯示在TextView
的四周,圖片和TextView
之間的間隔能夠經過drawablePadding
來實現。
當兩個控件在佈局上有重疊的部分,可是它們具備背景時,就會出現過分繪製的狀況,形成無用的性能損耗。而且肉眼沒法發現,須要經過設置當中的」調試GPU過分繪製"選項進行檢查,詳細使用以下:性能優化工具知識梳理(3) - 調試GPU過分繪製 & GPU呈現模式分析。例以下面佈局當中,根佈局和子控件有100dp
部分重疊,而且它們都有背景:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF">
<LinearLayout
android:background="#FFFFFF"
android:layout_width="match_parent"
android:layout_height="100dp"/>
</LinearLayout>
複製代碼
那麼最終,打開過分繪製檢測時,就會出現下面的效果:
WindowBackgroud
當咱們使用某些主題時,系統有可能在DecorView
中給咱們加上一個背景,可是有時候它是無用的,例如上面的例子中,咱們根佈局爲紫色,這其實就是因爲默認主題中的背景所致使的,咱們能夠經過下面的方式去除掉該背景。@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_overdraw);
getWindow().setBackgroundDrawable(null);
}
複製代碼
此時的檢測結果以下,能夠看到,根佈局就不存在過分繪製的狀況了:
當咱們在自定義控件,並重寫onDraw
方法來完成相應的需求時,一些錯誤的操做每每會致使佈局效率的下降,通常來講,有兩點須要注意:
Canvas
的ClipRect
方法避免過分繪製這裏用一個簡單的例子來講明一下第二點的實現,當咱們須要實現下面這個多張圖片重疊的自定義控件時:
public class ClipRectView extends View {
private static final int[] ID = new int[]{R.drawable.pic_1, R.drawable.pic_2, R.drawable.pic_3};
private Bitmap[] mBitmaps;
public ClipRectView(Context context) {
super(context);
prepareBitmap();
}
public ClipRectView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
prepareBitmap();
}
public ClipRectView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
prepareBitmap();
}
private void prepareBitmap() {
mBitmaps = new Bitmap[ID.length];
int i = 0;
for (int id : ID) {
mBitmaps[i++] = BitmapFactory.decodeResource(getResources(), id);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Bitmap bitmap : mBitmaps) {
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.translate(bitmap.getWidth() / 2, 0);
}
}
}
複製代碼
可是,若是咱們打開調試GPU
過分繪製的開關,那麼能夠獲得下面的檢測結果,能夠發如今兩張圖片重疊的地方,會出現明顯的過分繪製:
ClipRect
對
onDraw
方法進行優化:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
int bits = mBitmaps.length;
for (int i = 0; i < bits; i++) {
Bitmap bitmap = mBitmaps[i];
int bitW = bitmap.getWidth();
int bitH = bitmap.getHeight();
if (i != 0) {
canvas.translate(bitW / 2, 0);
}
canvas.save();
if (i != bits - 1) {
canvas.clipRect(0, 0, bitW / 2, bitH);
}
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.restore();
}
canvas.restore();
}
複製代碼
此時,檢測的結果以下,和上圖相比,咱們很好地解決了過分繪製的問題:
在Android Support Library 24
中,提供了一個AsyncLayoutInflater
工具類用於實現xml
佈局的異步inflate
,它的用法和普通的LayoutInflater
相似,只不過它inflate
的執行是在子線程當中,當這一過程完成以後,再經過OnInflateFinishedListener
接口,回調到主線程當中。
首先是整個Activity
的根佈局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_root"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_async"
android:text="開始異步 Inflate 佈局"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="40dp"/>
</LinearLayout>
複製代碼
接下來是須要異步inflate
的子佈局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="異步 Inflate 的佈局"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"/>
</LinearLayout>
複製代碼
使用方式以下:
private void asyncInflated() {
TextView textView = (TextView) findViewById(R.id.tv_async);
final ViewGroup root = (ViewGroup) findViewById(R.id.ll_root);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AsyncLayoutInflater asyncLayoutInflater = new AsyncLayoutInflater(OptActivity.this);
asyncLayoutInflater.inflate(R.layout.layout_async, root, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(View view, int resId, ViewGroup parent) {
parent.addView(view);
}
});
}
});
}
複製代碼
其inflate
方法接收三個參數:
inflate
的佈局id
。inflate
完成的回調,該回調是在主線程當中執行。須要注意,在該回調執行時,異步inflate
出來的佈局並無添加到父佈局當中,所以,咱們須要經過addView
的方法將其添加到View
樹當中。最終的運行結果爲:
在分析佈局有可能致使的性能問題時,咱們通常會用到如下幾種工具,這些工具咱們在以前學習性能優化工具的時候都有接觸過:
HierecyViewer
性能優化工具知識梳理(4) - Hierarchy ViewerGPU
過分繪製 性能優化工具知識梳理(3) - 調試GPU過分繪製 & GPU呈現模式分析Lint
檢查 性能優化工具知識梳理(8) - Lint