轉載請標明出處:
http://blog.csdn.net/lmj623565791/article/details/45556391;
本文出自:【張鴻洋的博客】java
2015年初google發佈了Android性能優化典範,發了16個小視頻供你們欣賞,當時我也將其下載,經過微信公衆號給你們推送了百度雲的下載地址(地址在文末,ps:歡迎你們訂閱公衆號),那麼近期google又在udacity上開了系列類的相關課程。有了上述的參考,那麼本性能優化實戰教程就有了堅實的基礎,本系列將結合實例爲你們展現如何去識別
、診斷
、解決
Android應用開發中存在的性能問題。那麼首先帶來的就是你們最關注的渲染的性能優化(~~渲染就是把東西繪製到屏幕上)。android
ps:本博客全部案例可能並不會徹底按照Google給出的例子,由於範例代碼比較多且很差在博客中展現,因此基本代碼都會通過調整,但表達的意思不會變。git
你們本身編寫App的時候,有時會感受界面卡頓,尤爲是自定義View的時候,大多數是由於佈局的層次過多,存在沒必要要的繪製,或者onDraw等方法中過於耗時。那麼究竟須要多快,才能給用戶一個流暢的體驗呢?那麼就須要簡單瞭解下Android的渲染機制,一圖勝千言:github
Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,那麼整個過程若是保證在16ms之內就能達到一個流暢的畫面。那麼若是操做超過了16ms就會發生下面的狀況:canvas
若是系統發生的VSYNC信號,而此時沒法進行渲染,還在作別的操做,那麼就會致使丟幀的現象,(你們在察覺到APP卡頓的時候,能夠看看logcat控制太,會有drop frames相似的警告)。這樣的話,繪製就會在下一個16ms的時候才進行繪製,即便只丟一幀,用戶也會發現卡頓的~~(ps:上面標識不該該是32ms麼,咋是34ms,難道我錯過了什麼)。安全
好了,不少朋友會不會奇怪爲何是16ms,16ms意味着着1000/60hz,至關於60fps,那麼只要解釋爲何是60fps,好在這個問題,網上有解答:性能優化
這是由於人眼與大腦之間的協做沒法感知超過60fps的畫面更新。12fps大概相似手動快速翻動書籍的幀率,這明顯是能夠感知到不夠順滑的。24fps使得人眼感知的是連續線性的運動,這實際上是歸功於運動模糊的
效果。24fps是電影膠圈一般使用的幀率,由於這個幀率已經足夠支撐大部分電影畫面須要表達的內容,同時可以最大的減小費用支出。可是低於30fps是
沒法順暢表現絢麗的畫面內容的,此時就須要用到60fps來達到想要的效果,固然超過60fps是沒有必要的(聽說Dart可以帶來120fps的體驗)。本引用來源:Google 發佈 Android 性能優化典範 - 開源中國社區微信
好了,有了對Android渲染機制基本的認識之後,那麼咱們的卡頓的緣由就在於沒有辦法在16ms內完成該完成的操做,而主要因素是在於沒有必要的layouts、invalidations、Overdraw。渲染的過程是由CPU與GPU協做完成,下面一張圖很好的展現出了CPU和GPU的工做,以及潛在的問題,檢測的工具和解決方案。markdown
若是你對上圖感到不理解,不要緊,你只要知道問題:app
若是你還以爲不能理解,不要緊,文本畢竟是枯燥的,那麼結合實例來展現優化的過程。
對於性能優化,那麼首先確定是去發現問題,那麼對麼overdraw這個問題,仍是比較容易發現的。
按照如下步驟打開Show GPU Overrdraw的選項:
設置 -> 開發者選項 -> 調試GPU過分繪製 -> 顯示GPU過分繪製
好了,打開之後呢,你會發現屏幕上有各類顏色,此時你能夠切換到須要檢測的程序,對於各個色塊,對比一張Overdraw的參考圖:
那麼若是你發現你的app上深紅色的色塊比較多,那麼可能就要注意了,接下來就開始說若是遇到overdraw的狀況比較嚴重咱們該則麼處理。
下面看一個簡單的展現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"
這四個沒必要要的背景也比較好找,那麼移除後的效果是:
對比以前的是否是好多了~~~接下來還存在一些沒必要要的背景,你還能找到嗎?
這個背景比較難發現,主要須要看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,還有最後一個,這個也是很是容易被忽略的。
記得咱們以前說,咱們的這個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 與最初的比較。
咱們在自定義View的時候,常常會因爲疏忽形成不少沒必要要的繪製,好比你們看下面這樣的圖:
多張卡片疊加,那麼若是你是一張一張卡片從左到右的繪製,效果確定沒問題,可是疊加的區域確定是過分繪製了。
而且material design對於界面設計的新的風格更容易形成上述的問題。那麼有什麼好的方法去改善呢?
答案是有的,android的Canvas對象給咱們提供了很便利的方法clipRect就能夠很好的去解決這類問題。
下面經過一個實例來展現,那麼首先看一個效果圖:
package com.zhy.performance_01_render;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.View;
/** * Created by zhy on 15/4/30. */
public class CardView extends View {
private Bitmap[] mCards = new Bitmap[3];
private int[] mImgId = new int[]{R.drawable.alex, R.drawable.chris, R.drawable.claire};
public CardView(Context context)
{
super(context);
Bitmap bm = null;
for (int i = 0; i < mCards.length; i++)
{
bm = BitmapFactory.decodeResource(getResources(), mImgId[i]);
mCards[i] = Bitmap.createScaledBitmap(bm, 400, 600, false);
}
setBackgroundColor(0xff00ff00);
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.save();
canvas.translate(20, 120);
for (Bitmap bitmap : mCards)
{
canvas.translate(120, 0);
canvas.drawBitmap(bitmap, 0, 0, null);
}
canvas.restore();
}
}
/** * Created by zhy on 15/4/30. */
public class OverDrawActivity02 extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(new CardView(this));
}
}
那麼你們能夠考慮下如何去優化,其實很明顯哈,咱們上面已經說了使用cliprect方法,那麼咱們目標直指
自定義View的onDraw方法。
修改後的代碼:
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.save();
canvas.translate(20, 120);
for (int i = 0; i < mCards.length; i++)
{
canvas.translate(120, 0);
canvas.save();
if (i < mCards.length - 1)
{
canvas.clipRect(0, 0, 120, mCards[i].getHeight());
}
canvas.drawBitmap(mCards[i], 0, 0, null);
canvas.restore();
}
canvas.restore();
}
分析得出,除了最後一張須要完整的繪製,其餘的都只須要繪製部分;因此咱們在循環的時候,給i到n-1都添加了clipRect的代碼。
最後的效果圖:
能夠看到,全部卡片變爲了淡紫色,對比參照圖,都是1X過分繪製,那麼是由於個人View添加了一個
ff00ff00
的背景,能夠說明已是最優了。
若是你按照上面的修改,會發現最終效果圖不是淡紫色,而是青色(2X),那是爲何呢?由於你還忽略了
一個優化的地方,本View已經有了不透明的背景,徹底能夠移除Window的背景了,即在Activity中,添加getWindow().setBackgroundDrawable(null);
代碼。
好了,說完了Overdraw的檢測與處理,那麼還剩下一個layouts、invalidations過慢的問題,那麼這類問題主要多是你的XML層級過多致使的,固然也有很好的工具能夠用來檢測,那麼就是Hierarchy Viewer
。
Android SDK中包含這個工具,不過你們確定也不陌生了~~~
那麼就簡單說一下它在哪,如何使用,以及真機沒法使用該工具該怎麼解決。
本博客使用IDE爲Android Studio,那麼只須要按照下圖步驟便可找到:
其餘IDE的兄弟,找到這個確定沒問題,不過仍是建議慢慢的轉向AS。
一圖勝千言:
關注下,全部框住的區域~~
若是你不存在這樣的問題,直接跳過本節。
Android的官方文檔中,有這麼一句話:
出於安全考慮,Hierarchy Viewer只能鏈接Android開發版手機或是模擬器
看來的確是存在這樣的問題了,而且網上也有一些解決方案,修改源碼神馬的,有興趣去試試。
這裏推薦一種解決方案:romainguy在github上有個項目ViewServer,能夠下載下來導入到IDE中,裏面有個ViewServer的類,類註釋上也標註了用法,在你但願調試的Activity如下該三個方法中,添加幾行代碼:
* <pre>
* public class MyActivity extends Activity {
* public void onCreate(Bundle savedInstanceState) {
* super.onCreate(savedInstanceState);
* // Set content view, etc.
* ViewServer.get(this).addWindow(this);
* }
*
* public void onDestroy() {
* super.onDestroy();
* ViewServer.get(this).removeWindow(this);
* }
*
* public void onResume() {
* super.onResume();
* ViewServer.get(this).setFocusedWindow(this);
* }
* }
* </pre>
記得先添加依賴,別問我怎麼找不到ViewServer這個類,添加以上3行之後,手機運行至該Activity,重啓下Android Device Moniter,而後就ok了,我就是這種方法調試的,哈~~
好了,上面介紹完成了如何使用Hierarchy Viewer,下面使用一個案例來講明問題。
主要就是個佈局文件:
<?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="wrap_content">
<!-- Version 1. Uses nested LinearLayouts -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/activity_vertical_margin">
<ImageView android:id="@+id/chat_author_avatar1" android:layout_width="@dimen/avatar_dimen" android:layout_height="@dimen/avatar_dimen" android:layout_margin="@dimen/avatar_layout_margin" android:src="@drawable/joanna"/>
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/line1_text" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/line2_text"/>
</LinearLayout>
</LinearLayout>
<!-- Version 2: uses a single RelativeLayout -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/activity_vertical_margin">
<ImageView android:id="@+id/chat_author_avatar2" android:layout_width="@dimen/avatar_dimen" android:layout_height="@dimen/avatar_dimen" android:layout_margin="@dimen/avatar_layout_margin" android:src="@drawable/joanna"/>
<TextView android:id="@+id/rl_line1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toRightOf="@id/chat_author_avatar2" android:text="@string/line1_text" />
<TextView android:id="@+id/rl_line2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/rl_line1" android:layout_toRightOf="@id/chat_author_avatar2" android:text="@string/line2_text" />
</RelativeLayout>
</LinearLayout>
package com.zhy.performance_01_render;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import com.android.debug.hv.ViewServer;
public class CompareLayoutActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_compare_layouts);
ViewServer.get(this).addWindow(this);
}
@Override
protected void onResume()
{
super.onResume();
ViewServer.get(this).setFocusedWindow(this);
}
@Override
protected void onDestroy()
{
super.onDestroy();
ViewServer.get(this).removeWindow(this);
}
}
能夠看到咱們的Activity裏面添加了ViewServer相關的幾行代碼。
而後手機打開此Activity,打開Android Device Moniter,切換到Hierarchy Viewer視圖,能夠看到:
點擊LinearLayout,而後點擊Profile Node,你會發現全部的子View上面都有了3個圈圈,
(取色範圍爲紅、黃、綠色),這三個圈圈分別表明measure 、layout、draw的速度,而且你也能夠看到實際的運行的速度,若是你發現某個View上的圈是紅色,那麼說明這個View相對其餘的View,該操做運行最慢,注意只是相對
別的View,並非說就必定
很慢。
紅色的指示能給你一個判斷的依據,具體慢不慢仍是須要你本身去判斷的。
好了,上面的佈局文件展現了ListView的Item的編寫的兩個版本,一個是Linearlayout嵌套的,一個是RelativeLayout的。上圖也能夠看出Linearlayout的版本相對RelativeLayout的版本要慢不少(請屢次點擊Profile Node取樣)。便可說明RelativeLayout的版本更優於RelativeLayout的寫法,而且Hierarchy Viewer能夠幫助咱們發現相似的問題。
恩,對了,第一個例子裏面的ListView的Item的寫法就是Liearlayout嵌套的,你們有興趣能夠修改成RelativeLayout的寫法的~~~
到此咱們就介紹完成了如何去對Android渲染進行優化,若是你的app有卡頓的狀況,能夠經過使用上述的工具首先去檢測收集數據,而後按照上面提供的方法進行優化~~have a nice day ~~
羣號:264950424,歡迎入羣
下載地址
微信公衆號:hongyangAndroid
(歡迎關注,第一時間推送博文信息)