一、什麼是SurfaceView
普通的View是通過Android系統發出的VSYNC信息來進行屏幕的繪製,但是在一些需要頻繁刷新的場景,如直播、遊戲等,如果執行了很多操作,就會導致繪製間隔超時,也就是用戶感知的卡頓,SurfaceView就是爲了應對這樣的場景而誕生的。SurfaceView繼承自View,擁有獨立的繪製表面,即它不與其父View共享同一個繪製表面,可以單獨在一個線程進行繪製,並不會佔用主線程資源。
二、SurfaceView與View的對比
View適用於主動更新的場景,而SurfaceView適用於被動更新的場景,例如頻繁的刷新。
View在主線程中對畫面進行刷新,而SurfaceView通常會通過一個子線程來進行頁面的刷新。
View在繪製時沒有使用雙緩衝機制,而SurfaceView在底層實現機制中就已經實現了雙緩衝機制。
三、SurfaceView的應用
基礎模版,根據具體需求優化拓展。
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable{
private SurfaceHolder surfaceHolder;
private Canvas canvas;
//子線程繪製標記
private volatile boolean isDrawing;
public MySurfaceView(Context context) {
super(context);
init();
}
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
setFocusable(true);
// setFocusableInTouchMode(true);
// setKeepScreenOn(true);
}
//當SurfaceView被創建的時候被調用
@Override
public void surfaceCreated(SurfaceHolder holder) {
isDrawing = true;
new Thread(this).start();
}
//當SurfaceView的視圖發生改變,比如橫豎屏切換時,這個方法被調用
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
//當SurfaceView被銷燬的時候,比如不可見了,會被調用
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isDrawing = false;
surfaceHolder.removeCallback(this);
}
@Override
public void run() {
while (isDrawing) {
draw();
}
}
private void draw() {
try {
canvas = surfaceHolder.lockCanvas();
//執行具體的繪製操作
} catch (Exception e) {
e.printStackTrace();
}finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
1)SurfaceView必須實現SurfaceHolder的Callback接口,主要是三個方法,用來監聽SurfaceView的狀態
surfaceCreated:SurfaceView被創建時調用,一般執行初始化操作,例如設置繪製線程的標記位,創建用於繪製的子線程等。
2)surfaceChanged:當SurfaceView狀態發生改變時,例如尺寸、格式、屏幕旋轉等。
3)surfaceDestroyed:surfaceDestroyed被調用後,就不能再對Surface對象進行任何操作,所以我們需要在surfaceDestroyed方法中將繪製的子線程停掉。
這三個回調方法有點像Activity的生命週期回調。對於較爲複雜的繪製場景,開啓子線程避免阻塞UI線程,通過SurfaceHolder的loackCanvas方法獲取Canvas對象來進行具體的繪製操作,此時Canvas對象被當前線程鎖定,繪製完成後通過SurfaceHolder的unlockCanvasAndPost方法提交繪製結果並釋放Canvas對象,這也就是雙緩衝機制的體現,同時在使用過程中要注意線程安全的問題。
四、雙緩衝機制
因爲CPU訪問內存的速度要遠快於訪問屏幕的速度,如果繪製大量複雜圖像時,每次都是在內存中讀取圖形數據然後繪製到屏幕,就會造成多次的屏幕訪問,從而導致低效繪製及可能的卡頓,這就和CPU和內存之間還需要有三級緩存一樣,需要提高效率。
1.第一層緩衝
在繪製時不用向上面講的那樣一個一個的繪製,而採用先在內存中將所有的圖形都繪製到一個Bitmap對象上,然後一次性將內存中的Bitmap繪製到屏幕,從而提高繪製效率。Android中View的onDraw方法已經實現了這一層緩衝,onDraw方法不是繪製一點顯示一點,而是繪製完成之後一次性顯示到屏幕。
2.第二層緩衝
onDraw方法的Canvas對象和屏幕是關聯的,而onDraw方法是運行在UI線程中,如果要繪製的圖像過於複雜,則有可能導致應用卡頓,甚至ANR。因此我們可以先創建一個臨時的Canvas對象,將圖像先繪製到這個臨時的Canvas對象中,然後在將這個臨時的Canvas對象中的內容,其實也是一個Bitmap,通過drawBitmap方法繪製到onDraw方法中的Canvas對象,這樣也是一個Bitmap的拷貝過程,比直接繪製效率要高,可以減少對UI線程的阻塞。
五、SurfaceView源碼分析
與SurfaceView緊密相關的還有兩個類,分別是SurfaceHolder和Surface,它們三個其實就是典型的MVC模式,SurfaceView對應的View層,SurfaceHolder對應controller接口,Surface對應Model層,裏面持有Canvas,保存着繪製數據。
1.SurfaceHolder
public interface SurfaceHolder {
...
public interface Callback {
public void surfaceCreated(SurfaceHolder holder);
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height);
public void surfaceDestroyed(SurfaceHolder holder);
}
public interface Callback2 extends Callback {
public void surfaceRedrawNeeded(SurfaceHolder holder);
}
public void addCallback(Callback callback);
public void removeCallback(Callback callback);
public Canvas lockCanvas();
public Canvas lockCanvas(Rect dirty);
public void unlockCanvasAndPost(Canvas canvas);
public Surface getSurface();
...
}
SurfaceHolder的接口可以分爲兩類
一類是Callback接口,也就是上面提到的三個回調方法,用於監聽SurfaceView的狀態。
另一類主要用於和Surface和SurfaceView交互,比如lockCanvas和unlockCanvasAndPost方法用於獲取Canvas以及提交繪製結果等。
2.SurfaceView
public class SurfaceView extends View {
...
final Surface mSurface = new Surface();
final ReentrantLock mSurfaceLock = new ReentrantLock();
private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
private static final String LOG_TAG = "SurfaceHolder";
...
@Override
public void addCallback(Callback callback) {
synchronized (mCallbacks) {
// This is a linear search, but in practice we'll
// have only a couple callbacks, so it doesn't matter.
if (mCallbacks.contains(callback) == false) {
mCallbacks.add(callback);
}
}
}
@Override
public void removeCallback(Callback callback) {
synchronized (mCallbacks) {
mCallbacks.remove(callback);
}
}
@Override
public Canvas lockCanvas() {
return internalLockCanvas(null);
}
@Override
public Canvas lockCanvas(Rect inOutDirty) {
return internalLockCanvas(inOutDirty);
}
private final Canvas internalLockCanvas(Rect dirty) {
mSurfaceLock.lock();
Canvas c = null;
if (!mDrawingStopped && mWindow != null) {
try {
c = mSurface.lockCanvas(dirty);
} catch (Exception e) {
Log.e(LOG_TAG, "Exception locking surface", e);
}
}
if (c != null) {
mLastLockTime = SystemClock.uptimeMillis();
return c;
}
long now = SystemClock.uptimeMillis();
long nextTime = mLastLockTime + 100;
if (nextTime > now) {
try {
Thread.sleep(nextTime-now);
} catch (InterruptedException e) {
}
now = SystemClock.uptimeMillis();
}
mLastLockTime = now;
mSurfaceLock.unlock();
return null;
}
@Override
public void unlockCanvasAndPost(Canvas canvas) {
mSurface.unlockCanvasAndPost(canvas);
mSurfaceLock.unlock();
}
@Override
public Surface getSurface() {
return mSurface;
}
@Override
public Rect getSurfaceFrame() {
return mSurfaceFrame;
}
};
...
}
SurfaceView雖然繼承自View,但是其實和View有很大的不同,除了上面的幾點特性外,在底層也有很大的不同,包括擁有自己獨立的繪圖表面等。從上面的源碼可以看出,SurfaceHolder的lockCanvas方法實際上調用的是Surface的lockCanvas方法,返回的是Surface中的Canvas,並且加入了一個可重入鎖,所以繪製過程中只能繪製完一貞內容並提交更改以後纔會釋放Canvas,也就是才能繼續下一貞的操作。
3.Surface
public class Surface implements Parcelable {
final Object mLock = new Object(); // protects the native state
private final Canvas mCanvas = new CompatibleCanvas();
...
public Canvas lockCanvas(Rect inOutDirty)
throws Surface.OutOfResourcesException, IllegalArgumentException {
synchronized (mLock) {
checkNotReleasedLocked();
if (mLockedObject != 0) {
throw new IllegalArgumentException("Surface was already locked");
}
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
}
public void unlockCanvasAndPost(Canvas canvas) {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext != null) {
mHwuiContext.unlockAndPost(canvas);
} else {
unlockSwCanvasAndPost(canvas);
}
}
}
private void unlockSwCanvasAndPost(Canvas canvas) {
if (canvas != mCanvas) {
throw new IllegalArgumentException("canvas object must be the same instance that "
+ "was previously returned by lockCanvas");
}
if (mNativeObject != mLockedObject) {
Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
Long.toHexString(mLockedObject) +")");
}
if (mLockedObject == 0) {
throw new IllegalStateException("Surface was not locked");
}
try {
nativeUnlockCanvasAndPost(mLockedObject, canvas);
} finally {
nativeRelease(mLockedObject);
mLockedObject = 0;
}
}
...
}
Surface實現了Parcelable接口,因爲它需要在進程間以及本地方法間傳輸。Surface中創建了Canvas對象,用於執行具體的繪製操作
六、簡單應用
public class CircleMoveView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder holder;
private Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (flag) {
draw();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
private boolean flag;
private Canvas canvas;
private Paint paint;
private int x = 100, y = 100, r = 50;
public CircleMoveView(Context context) {
this(context, null);
}
public CircleMoveView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleMoveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
holder = getHolder();
holder.addCallback(this);
paint = new Paint();
paint.setColor(Color.RED);
setFocusable(true);
}
private void draw() {
canvas = holder.lockCanvas(); //獲取canvas對象 這裏的canvas對象其實是surface裏的canvas對象
canvas.drawRGB(0, 0, 0);
canvas.drawCircle(x, y, r, paint);
holder.unlockCanvasAndPost(canvas);//完成繪製,把畫布顯示在屏幕上 這裏是第一層緩衝 把surface裏的canvas直接拷貝到當前canvas
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d("TAG", "surfaceCreated");
flag = true;
thread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d("TAG", "surfaceChanged");
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d("TAG", "surfaceDestroyed");
flag = false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
x = (int) event.getX();
y = (int) event.getY();
return true;
}
}