隨着最近幾年移動市場蓬勃發展,引來大批人員投入到Android、iOS的開發前線,與此同時全國各大培訓機構每個月都培養出成千上萬名號稱擁有2到3年工做經驗的開發者。固然,這都已經不是什麼祕密了,從目前來看,中國IT行業的主力軍基本上都走過培訓的道路。java
但問題是,這號稱2~3年工做經驗者,使招聘單位錯誤的認爲:2~3年開發經驗和剛剛結束的培訓經歷,基本上劃等號。這就致使了企業大幅度提升用人標準,形成了爲什麼現在移動開發市場依舊火熱,可是工做卻很差找的現狀。react
最悲慘的例子恐怕就是前幾年IOS如日中天,可時間就過了一年開發人員就出現了井噴的狀況,大量IOS開發者找不到工做。android
總的來講:工做機會的確是不少,可是企業把用人要求都大大提升了。如何在萬千人羣中脫穎而出,走上人生巔峯,迎娶白富美,沒有亮點,是萬萬不行滴。。。ios
接下來我就一塊兒學習Android UI優化吧編程
你們在開發應用的時候或多或少都遇到過可感知的界面卡頓現象,尤爲是在佈局層次嵌套太多,存在沒必要要的繪製,或者onDraw方法中執行了過多耗時操做、動畫執行的次數過多等狀況下,很容易形成此類狀況。現在APP設計都要求界面美觀、擁有更多的動畫、圖片等時尚元素從而打造良好的用戶體驗。可是大量複雜的渲染工做極可能形成Android系統壓力過大,沒法及時完成渲染工做。那麼多久執行一次渲染,才能讓界面流暢運行呢?性能優化
如上圖所示,Android系統每隔16ms就會發送一個VSYNC信號(VSYNC:vertical synchronization 垂直同步,幀同步),觸發對UI進行渲染,若是每次渲染都成功,這樣就可以達到流暢的畫面所須要的正常幀率:60fps。一旦這時候系統正在作大於16ms的耗時操做,系統就會沒法響應VSYNC信號,執行渲染工做,致使發生丟幀現象。微信
你們在察覺到APP卡頓的時候,能夠看看logcat控制檯,會有
drop frames
相似的警告
本引用來自: [Android UI性能優化實戰 識別繪製中的性能問題](http://blog.csdn.net/lmj623565791/article/details/45556391)網絡
例如上圖所示:若是你的某個操做花費時間是24ms,系統在獲得VSYNC信號的時候就沒法進行正常渲染,只能等待下一個VSYNC信號(第二個16ms)才能執行渲染工做。那麼用戶在32ms內看到的會是同一幀畫面。(我就是感受google給的圖給錯了,明明是 32ms,怎麼給標了一個34ms,難道是有其餘寓意我沒有理解上去???)微信開發
用戶容易在UI執行動畫、ListView、RecyclerView滑動的時候感知到界面的卡頓與不流暢現象。因此開發者必定要注意在設計佈局時不要嵌套太多層,多使用 include
方法引入佈局。同時不要讓動畫執行次數太多,致使CPU或者GPU負載太重。app
看到這裏同窗可能會疑問:爲何是16ms渲染一次,和60fps有什麼關係呢?下面讓咱們看一下原理:
16ms意味着着1000/60hz,至關於60fps。
那麼只要解釋爲何是60fps,這個問題就迎刃而解:
這是由於人眼和大腦之間的寫做沒法感知超過60fps的畫面更新,12fps大概相似手動快速翻動書籍的幀率,這是明顯能夠感知到不夠順滑的。
24fps使得人眼感知的是連續的線性運動,這實際上是歸功於運動模糊效果,24fps是電影膠圈一般使用的幀率,由於這個幀率已經足夠支撐大部分電影畫面須要表達的內容,同時可以最大的減小費用支出。
可是低於30fps是
沒法順暢表現絢麗的畫面內容的,此時就須要用到60fps來達到想要的效果,固然超過60fps是沒有必要的
本引用來源:Google 發佈 Android 性能優化典範 - 開源中國社區
過渡繪製是指屏幕上某個像素在同一幀的時間內繪製了屢次。在多層次的UI結構裏面,若是不可見的UI也在作繪製操做,這就會致使某些像素區域被繪製了屢次,這就是很大程度上浪費了CPU和GPU資源。最最多見的過分繪製,就是設置了無用的背景顏色!!!
對於Overdraw這個問題仍是很容易發現的,咱們能夠經過如下步驟打開顯示GPU過分繪製(Show GPU Overrdraw)選項
設置 -> 開發者選項 -> 調試GPU過分繪製 -> 顯示GPU過分繪製
打開之後以後,你會發現屏幕上有各類顏色,此時你能夠切換到須要檢測的程序與界面,對於各個色塊的含義,請看下圖:
藍色,淡綠,淡紅,深紅表明了4種不一樣程度的Overdraw狀況,
藍色: 意味着overdraw 1倍。像素繪製了兩次。大片的藍色仍是能夠接受的(若整個窗口是藍色的,能夠擺脫一層)。
綠色: 意味着overdraw 2倍。像素繪製了三次。中等大小的綠色區域是能夠接受的但你應該嘗試優化、減小它們。
淡紅: 意味着overdraw 3倍。像素繪製了四次,小範圍能夠接受。
深紅: 意味着overdraw 4倍。像素繪製了五次或者更多。這是錯誤的,要修復它們。
咱們的目標就是儘可能減小紅色Overdraw,看到更多的藍色區域。
經過Hierarchy Viewer去檢測渲染效率,去除沒必要要的嵌套
經過Show GPU Overdraw去檢測Overdraw,最終能夠經過移除沒必要要的背景。
(因爲公司項目還處於保密階段,因此摘取了Android UI性能優化實戰 識別繪製中的性能問題的部分示例)
下面看一個簡單的展現ListView的例子:
?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:background="@android:color/white" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="@dimen/narrow_space" android:textSize="@dimen/large_text_size" android:layout_marginBottom="@dimen/wide_space" android:text="@string/header_text"/> <ListView android:id="@+id/id_listview_chats" android:layout_width="match_parent" android:background="@android:color/white" android:layout_height="wrap_content" android:divider="@android:color/transparent" android:dividerHeight="@dimen/divider_height"/> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:paddingBottom="@dimen/chat_padding_bottom"> <ImageView android:id="@+id/id_chat_icon" android:layout_width="@dimen/avatar_dimen" android:layout_height="@dimen/avatar_dimen" android:src="@drawable/joanna" android:layout_margin="@dimen/avatar_layout_margin" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/darker_gray" android:orientation="vertical"> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/white" android:textColor="#78A" android:orientation="horizontal"> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:padding="@dimen/narrow_space" android:text="@string/hello_world" android:gravity="bottom" android:id="@+id/id_chat_name" /> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:textStyle="italic" android:text="@string/hello_world" android:padding="@dimen/narrow_space" android:id="@+id/id_chat_date" /> </RelativeLayout> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="@dimen/narrow_space" android:background="@android:color/white" android:text="@string/hello_world" android:id="@+id/id_chat_msg" /> </LinearLayout> </LinearLayout>
package com.zhy.performance_01_render; import android.os.Bundle; import android.os.PersistableBundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; /** * Created by zhy on 15/4/29. */ public class OverDrawActivity01 extends AppCompatActivity { private ListView mListView; private LayoutInflater mInflater ; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_overdraw_01); mInflater = LayoutInflater.from(this); mListView = (ListView) findViewById(R.id.id_listview_chats); mListView.setAdapter(new ArrayAdapter<Droid>(this, -1, Droid.generateDatas()) { @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null ; if(convertView == null) { convertView = mInflater.inflate(R.layout.chat_item,parent,false); holder = new ViewHolder(); holder.icon = (ImageView) convertView.findViewById(R.id.id_chat_icon); holder.name = (TextView) convertView.findViewById(R.id.id_chat_name); holder.date = (TextView) convertView.findViewById(R.id.id_chat_date); holder.msg = (TextView) convertView.findViewById(R.id.id_chat_msg); convertView.setTag(holder); }else { holder = (ViewHolder) convertView.getTag(); } Droid droid = getItem(position); holder.icon.setBackgroundColor(0x44ff0000); holder.icon.setImageResource(droid.imageId); holder.date.setText(droid.date); holder.msg.setText(droid.msg); holder.name.setText(droid.name); return convertView; } class ViewHolder { ImageView icon; TextView name; TextView date; TextView msg; } }); } }
實體的代碼
package com.zhy.performance_01_render; import java.util.ArrayList; import java.util.List; public class Droid { public String name; public int imageId; public String date; public String msg; public Droid(String msg, String date, int imageId, String name) { this.msg = msg; this.date = date; this.imageId = imageId; this.name = name; } public static List<Droid> generateDatas() { List<Droid> datas = new ArrayList<Droid>(); datas.add(new Droid("Lorem ipsum dolor sit amet, orci nullam cra", "3分鐘前", -1, "alex")); datas.add(new Droid("Omnis aptent magnis suspendisse ipsum, semper egestas", "12分鐘前", R.drawable.joanna, "john")); datas.add(new Droid("eu nibh, rhoncus wisi posuere lacus, ad erat egestas", "17分鐘前", -1, "7heaven")); datas.add(new Droid("eu nibh, rhoncus wisi posuere lacus, ad erat egestas", "33分鐘前", R.drawable.shailen, "Lseven")); return datas; } }
如今的效果是:
注意,咱們的需求是總體是Activity是個白色的背景。
開啓Show GPU Overdraw之後:
對比上面的參照圖,能夠發現一個簡單的ListView展現Item,居然不少地方被過分繪製了4X 。 那麼,其實主要緣由是因爲該佈局文件中存在不少沒必要要的背景,仔細看上述的佈局文件,那麼開始移除吧。
* 沒必要要的Background 1
咱們主佈局的文件已是background爲white了,那麼能夠移除ListView的白色背景
沒必要要的Background 2
Item佈局中的LinearLayout的android:background=」@android:color/darker_gray」
沒必要要的Background 3
Item佈局中的RelativeLayout的android:background=」@android:color/white」
沒必要要的Background 4
Item佈局中id爲id_msg的TextView的android:background=」@android:color/white」
這四個沒必要要的背景也比較好找,那麼移除後的效果是:
對比以前的是否是好多了~~~接下來還存在一些沒必要要的背景,你還能找到嗎?
* 沒必要要的Background 5
這個背景比較難發現,主要須要看Adapter的getView的代碼,上述代碼你會發現,首先爲每一個icon設置了背景色(主要是當沒有icon圖的時候去顯示),而後又設置了一個頭像。那麼就形成了overdraw,有頭像的徹底不必去繪製背景,全部修改代碼:
Droid droid = getItem(position); if(droid.imageId ==-1) { holder.icon.setBackgroundColor(0x4400ff00); holder.icon.setImageResource(android.R.color.transparent); }else { holder.icon.setImageResource(droid.imageId); holder.icon.setBackgroundResource(android.R.color.transparent); }
ok,還有最後一個,這個也是很是容易被忽略的。
* 沒必要要的Background 6
記得咱們以前說,咱們的這個Activity要求背景色是白色,咱們的確在layout中去設置了背景色白色,那麼這裏注意下,咱們的Activity的佈局最終會添加在DecorView
中,這個View會中的背景是否是就沒有必要了,因此咱們但願調用mDecor.setWindowBackground(drawable)
;,那麼能夠在Activity
調用getWindow().setBackgroundDrawable(null);
setContentView(R.layout.activity_overdraw_01); getWindow().setBackgroundDrawable(null);
ok,一個簡單的listview顯示item,咱們一共找出了6個沒必要要的背景,如今再看最後的Show GPU Overdraw 與最初的比較。
ok,對比參照圖,基本已經達到了最優的狀態。
相信你們使用的最多的佈局標籤就是 <include>
了。 <include>
的用途就是將佈局中的公共部分提取出來以供其餘Layout使用,從而實現佈局的優化。這種佈局的編寫方式大大便利了開發,我的感受這種思想和React Native中的面向組件編程思想有着殊途同歸之妙,都是將特定功能抽取成爲一個獨立的組件,只要控制其中傳入的參數就能夠滿局不一樣的需求。例如:咱們在編輯Android界面的時候經常須要添加標題欄,若是在不使用<include>
的狀況下,只能在每個須要顯示標題欄的xml文件中編寫重複的代碼,費時費力。可是隻要咱們將這個須要屢次被使用的標題欄佈局抽取成一個獨立的xml文件,而後在須要的地方使用<include>
標籤引入便可。
下面以在一個佈局main.xml中用include引入另外一個佈局foot.xml爲例。main.mxl代碼以下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/simple_list_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="80dp" /> <include android:id="@+id/my_foot_ly" layout="@layout/foot" /> </RelativeLayout>
其中include引入的foot.xml爲公用的頁面底部,代碼以下:
?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/my_foot_parent_id"> <Button android:id="@+id/button" android:layout_width="match_parent" android:layout_height="@dimen/dp_40" android:layout_above="@+id/title_tv"/> <TextView android:id="@+id/title_tv" android:layout_width="match_parent" android:layout_height="@dimen/dp_40" android:layout_alignParentBottom="true" android:text="@string/app_name" /> </RelativeLayout>
<include>
使用起來很簡單,只須要指定一個layout屬性爲須要包含的佈局文件便可。固然還能夠根據需求指定 android:id
、 android:height
、android:width
屬性來覆蓋被引入根節點屬性。
注意
在使用<include>
標籤最多見的問題就是 findViewById
查找不到<include>
進來地控件的跟佈局,而這個問題出現的前提就是在include的時候設置了id
。當設置id後,原有的foot.xml跟佈局Id已經被替換爲在 <include>
中指定的id
了,因此在 findViewById
查找原有id的時候就會報空指針異常。
View titleView = findViewById(R.id.my_foot_parent_id) ; // 此時id已經被覆蓋 titleView 爲空,找不到。此時空指針
View titleView = findViewById(R.id.my_foot_ly) ; //重寫指定id便可
<include>
標籤簡單的說就是至關與將layout
指定的佈局總體引入到main.xml中。因此咱們就和操做直接在main.xml中的佈局是同樣的只不過有一個上面提到的更佈局id
被覆蓋的問題。
ViewStub
標籤同include
同樣能夠用來引入一個外部佈局。不一樣的是,ViewStub
引入的佈局默認是不會顯示也不會佔用位置的,從而在解析的layout
的時候能夠節省cpu、內存等硬件資源。
ViewStub經常用來引入那些默認不顯示,只在特定狀況下才出現的佈局,例如:進度條,網絡鏈接失敗顯示的提示佈局等。
下面以在一個佈局main.xml中加入網絡錯誤時的提示頁面network_error.xml爲例。main.mxl代碼以下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > …… <ViewStub android:id="@+id/network_error_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:layout="@layout/network_error" /> </RelativeLayout>
其中network_error.xml爲只有在網絡錯誤時才須要顯示的佈局,默認不會被解析,示例代碼以下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/network_setting" android:layout_width="@dimen/dp_160" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:text="@string/network_setting" /> <Button android:id="@+id/network_refresh" android:layout_width="@dimen/dp_160" android:layout_height="wrap_content" android:layout_below="@+id/network_setting" android:layout_centerHorizontal="true" android:layout_marginTop="@dimen/dp_10" android:text="@string/network_refresh" /> </RelativeLayout>
在代碼中經過(ViewStub)findViewById(id)找到ViewStub,經過stub.inflate()展開ViewStub,而後獲得子View,以下:
private View networkErrorView; private void showNetError() { if (networkErrorView != null) { networkErrorView.setVisibility(View.VISIBLE); }else{ ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout); if(stub !=null){ networkErrorView = stub.inflate(); // 效果和上面是同樣的 // stub.setVisibility(View.VISIBLE); // ViewStub被展開後的佈局所替換 // networkErrorView = findViewById(R.id.network_error_layout); // 獲取展開後的佈局 } } } private void showNormal() { if (networkErrorView != null) { networkErrorView.setVisibility(View.GONE); } }
在上面showNetError()中展開了ViewStub,同時咱們對networkErrorView進行了保存,這樣下次不用繼續inflate。
注意這裏我對ViewStub的實例進行了一個非空判斷,這是由於ViewStub在XML中定義的id只在一開始有效,一旦ViewStub中指定的佈局加載以後,這個id也就失敗了,那麼此時findViewById()獲得的值也會是空
viewstub標籤大部分屬性同include標籤相似。
注意:
根據需求咱們有時候須要將View的可講性設置爲GONE,在inflate時,這個View以及他的字View仍是會被解析的。因此使用<ViewStub>
就能避免解析其中的指定的佈局文件。從而加快佈局的解析時間,節省cpu內存等硬件資源。同時ViewStub所加載的佈局是不可使用<merge>
標籤的
在使用了include後可能會致使佈局嵌套太多,致使視圖節點太多,減慢了解析速度。
merge標籤可用於兩種典型狀況:
1. 佈局頂接點是FrameLayout
而且不須要設置background
或者padding
等屬性,可以使用merge
代替,由於Activity
內容視圖的parent view
就是一個FrameLayout
,因此能夠用merge
消除只能一個。
2. 某佈局做爲子佈局被其餘佈局include時,使用merge看成該佈局的頂節點,這樣在被引入時,頂結點會自動被忽略,而其本身點所有合併到主佈局中。
以【4.2.1 標籤 】中的代碼示例爲例,使用用hierarchy viewer查看main.xml佈局以下圖:
能夠發現多了一層不必的RelativeLayout,將foot.xml中RelativeLayout改成merge,以下:
<?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" > <Button android:id="@+id/button" android:layout_width="match_parent" android:layout_height="@dimen/dp_40" android:layout_above="@+id/text"/> <TextView android:id="@+id/text" android:layout_width="match_parent" android:layout_height="@dimen/dp_40" android:layout_alignParentBottom="true" android:text="@string/app_name" /> </merge>
運行後再次用hierarchy viewer查看main.xml佈局以下圖:
這樣就不會有多餘的RelativeLayout節點了。