在一個典型的顯示系統中,通常包括CPU、GPU、display三個部分,其中CPU負責計算數據,把計算好的數據交給GPU,GPU對圖形數據進行渲染,渲染好後放到buffer中存起來,而後display負責把buffer的數據呈現到屏幕上。java
在Android中也是如此,由CPU/GPU準備好數據,存入buffer,display每隔一段時間去buffer裏取數據,而後顯示出來android
display在Android中,讀取的頻率是固定的,爲16ms。之因此是16ms是由於人眼與大腦之間的協做沒法感知超過60fps的畫面更新。
canvas
咱們在使用APP的時候,當界面出現卡頓不流暢的狀況,是由於當前界面UI的處理超過了16ms,會佔用下一個16ms,這就致使16ms*2都是同一幀,也就是「卡」了。緩存
瞭解了Android渲染機制後,咱們來分析app爲何會超過16ms的重繪時間,一般有如下緣由:性能優化
一、佈局層級不合理markdown
二、佈局存在過分繪製app
針對上述狀況,咱們接下來說述一些常見的監控手段和佈局和優化手段異步
Layout Inspater是AndroidStudio自帶的工具,用於分析佈局層級ide
一、在鏈接的設備或模擬上運行你的應用工具
二、點擊Tools > Layout Inspector
三、在出現的Choose Process對話框中,選擇你想要檢查的應用進程,而後點擊OK。
四、默認狀況下,Choose Process 對話框僅會爲 Android Studio 中當前打開的項目列出進程,而且該項目必須在設備上運行。 若是想要檢查設備上的其餘應用,請點擊 Show all processes。 若是正在使用已取得 root 權限的設備或者沒有安裝 Google Play 商店的模擬器,那麼您會看到全部正在運行的應用。 不然,您只能看到能夠調試的運行中應用。
View Tree:視圖在佈局中的層次結構
Screenshot:帶每一個視圖可視邊界的設備屏幕截圖。
Properties Table:選定視圖的佈局屬性
其中,咱們最須要使用的是View Tree,像本案例中,設置頁的每個欄目,由SettingItem包着RelativeLayout,其中SettingItem這個自定義View自己又繼承了RelativeLayout,其實就多餘了,能夠用merge標籤進行優化,下降層級
固然,最好使用約束佈局,能夠大大下降層級
從開發者模式中找到調試GPU過分繪製
功能開關,打開
這種過分繪製常見於background的設置
public class FPSMonitor implements Choreographer.FrameCallback, Runnable{
private HandlerThread mHandlerThread;
private long startTime = -1;
private long endTime = -1;
private final int MONITOR_TIME = 1000;
private Handler mWorkHandler;
private int mFpsCount;
@Override
public void doFrame(long frameTimeNanos) {
if (startTime == -1) {
startTime = frameTimeNanos;
}
mFpsCount++;
//超過一秒了,發消息到工做線程,計算幀率
long duration = (frameTimeNanos - startTime) / 1000000L;
if (duration >= MONITOR_TIME) {
endTime = frameTimeNanos;
mWorkHandler.post(this);
} else {
//沒到一秒,設定下一幀監聽
Choreographer.getInstance().postFrameCallback(this);
}
}
@Override
public void run() {
//計算幀率
long duration = (endTime - startTime) / 1000000L;
float frame = 1000.0f * mFpsCount / duration;
Log.i("Restart", "當前幀率: " + frame);
//開啓下一秒的計算
start();
}
public void start() {
if (mHandlerThread == null) {
mHandlerThread = new HandlerThread("FPS Monitor Thread");
mHandlerThread.start();
mWorkHandler = new Handler(mHandlerThread.getLooper());
}
//重置計算值
startTime = -1;
endTime = -1;
mFpsCount = 0;
//設置幀繪製監聽器
Choreographer.getInstance().postFrameCallback(this);
}
}
複製代碼
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//注意:要在super.onCreate(savedInstanceState);以前調用纔不會報錯
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
long start = System.currentTimeMillis();
AppCompatDelegate delegate = getDelegate();
View view = delegate.createView(parent, name, context, attrs);
long duration = System.currentTimeMillis() - start;
Log.i("Restart", name + "的繪製耗時: " + duration);
return view;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
}
複製代碼
下面介紹一個最經常使用的場景:
//假定自定義View爲RelativeLayout
public class LoginButton extends RelativeLayout {
。。。
}
//在xml標籤中使用merge做爲根標籤,而不要再次使用RelativeLayout做爲根標籤,能夠省去一個層級
複製代碼
ViewStub的使用場景爲當一個佈局可能須要加載,也可能不須要的狀況。假如佈局delayInflateLayout可能不須要也可能須要。則能夠用ViewStub標籤,以下代碼所示,在佈局文件中使用。
<ViewStub android:id="@+id/contentPanel" android:inflatedId="@+id/inflatedStart" android:layout="@layout/delayInflateLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" />
複製代碼
再在代碼中控制是否真正加載它
//調用inflate則會真正的加載它,可是隻能調用一次
viewStub.inflate();
複製代碼
clipRect的功能能夠理解爲在一個大的畫布中,用一些大小可變的矩形一個一個來裁切,在某一個矩形內,繪製想要繪製的圖形,超出的不進行繪製,當咱們的app這樣進行寫自定義View的時候,能夠避免view與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();
}
複製代碼
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new AsyncLayoutInflater(this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
setContentView(R.layout.activity_main);
}
});
}
複製代碼
使用AsyncLayoutInflater 的侷限性:
全部構建的View中必須不能直接使用 Handler 或者是調用 Looper.myLooper(),由於異步線程默認沒有調用 Looper.prepare ();
異步轉換出來的 View 並無被加到 parent view中,必須手動添加;
AsyncLayoutInflater 不支持設置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;
同時緩存隊列默認 10 的大小限制若是超過了10個則會致使主線程的等待;
開發Android應用程序時也不可能無限制的使用CPU和內存,若是對CPU和內存使用不當也會形成應用的卡頓和內存溢出等問題。所以,性能優化是每一個Android開發人員都應該去注意的,本文介紹了繪製相關的一些監控手段和常見的優化手段,但願對你們有幫助~