Android性能優化(三)-繪製優化

1、Android UI渲染簡述

一、屏幕刷新機制

在一個典型的顯示系統中,通常包括CPU、GPU、display三個部分,其中CPU負責計算數據,把計算好的數據交給GPU,GPU對圖形數據進行渲染,渲染好後放到buffer中存起來,而後display負責把buffer的數據呈現到屏幕上。java

在Android中也是如此,由CPU/GPU準備好數據,存入buffer,display每隔一段時間去buffer裏取數據,而後顯示出來android

display在Android中,讀取的頻率是固定的,爲16ms。之因此是16ms是由於人眼與大腦之間的協做沒法感知超過60fps的畫面更新。canvas

vsync.jpg

咱們在使用APP的時候,當界面出現卡頓不流暢的狀況,是由於當前界面UI的處理超過了16ms,會佔用下一個16ms,這就致使16ms*2都是同一幀,也就是「卡」了。緩存

drop_frame.jpg

瞭解了Android渲染機制後,咱們來分析app爲何會超過16ms的重繪時間,一般有如下緣由:性能優化

一、佈局層級不合理markdown

二、佈局存在過分繪製app

針對上述狀況,咱們接下來說述一些常見的監控手段和佈局和優化手段異步

2、監控手段

一、使用Layout Inspater檢查佈局層級

Layout Inspater是AndroidStudio自帶的工具,用於分析佈局層級ide

一、在鏈接的設備或模擬上運行你的應用工具

二、點擊Tools > Layout Inspector

三、在出現的Choose Process對話框中,選擇你想要檢查的應用進程,而後點擊OK。

四、默認狀況下,Choose Process 對話框僅會爲 Android Studio 中當前打開的項目列出進程,而且該項目必須在設備上運行。 若是想要檢查設備上的其餘應用,請點擊 Show all processes。 若是正在使用已取得 root 權限的設備或者沒有安裝 Google Play 商店的模擬器,那麼您會看到全部正在運行的應用。 不然,您只能看到能夠調試的運行中應用。

layout_inspector.jpg

View Tree:視圖在佈局中的層次結構

Screenshot:帶每一個視圖可視邊界的設備屏幕截圖。

Properties Table:選定視圖的佈局屬性

其中,咱們最須要使用的是View Tree,像本案例中,設置頁的每個欄目,由SettingItem包着RelativeLayout,其中SettingItem這個自定義View自己又繼承了RelativeLayout,其實就多餘了,能夠用merge標籤進行優化,下降層級

固然,最好使用約束佈局,能夠大大下降層級

二、使用調試GPU過分繪製功能檢查過分繪製

從開發者模式中找到調試GPU過分繪製功能開關,打開

過分繪製顯示.jpg

  • 藍色、綠色、淺紅、深紅
  • 分爲四個等級,其中藍色爲可接受的,當出現紅色就應該要優化了

這種過分繪製常見於background的設置

三、使用Choreographer監控幀率

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);
    }
}

複製代碼

四、使用setFactory2統計某個View建立耗時

@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);
    }
複製代碼

3、常見的佈局優化手段

一、使用merge標籤下降層級

下面介紹一個最經常使用的場景:

//假定自定義View爲RelativeLayout
public class LoginButton extends RelativeLayout {
 	。。。
}
//在xml標籤中使用merge做爲根標籤,而不要再次使用RelativeLayout做爲根標籤,能夠省去一個層級
複製代碼

二、使用ViewStub

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

clipRect的功能能夠理解爲在一個大的畫布中,用一些大小可變的矩形一個一個來裁切,在某一個矩形內,繪製想要繪製的圖形,超出的不進行繪製,當咱們的app這樣進行寫自定義View的時候,能夠避免view與view之間的疊加,從而產生同一個像素點被繪製屢次的狀況,原理就是這樣。舉一個案例:

clipRect.jpg

上述的三張圖,相互重疊的狀況,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();

}
複製代碼

四、使用約束佈局下降層級

五、使用AsyncLayoutInflator異步加載佈局文件

@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個則會致使主線程的等待;

4、小結

開發Android應用程序時也不可能無限制的使用CPU和內存,若是對CPU和內存使用不當也會形成應用的卡頓和內存溢出等問題。所以,性能優化是每一個Android開發人員都應該去注意的,本文介紹了繪製相關的一些監控手段和常見的優化手段,但願對你們有幫助~

相關文章
相關標籤/搜索