View更新UI問(wèn)題
對(duì)于一個(gè)View的onDraw方法,不能這樣做,因?yàn)閺暮笈_(tái)線程修改一個(gè)GUI元素會(huì)被顯式地禁止的。
當(dāng)需要快速地更新View的UI,或者當(dāng)渲染代碼阻塞GUI線程的時(shí)間過(guò)長(zhǎng)的時(shí)候,SurfaceView就是解決上述問(wèn)題的最佳選擇。
SurfaceView和View的區(qū)別
本質(zhì)區(qū)別
surfaceView是在一個(gè)新起的單獨(dú)線程中可以重新繪制畫面,而View必須在UI的主線程中更新畫面。-
為什么普通View只能在主線程中更新UI?
- UI線程是最重要的線程,負(fù)責(zé)繪制界面和分發(fā)窗口事件,它既不能被阻塞,也不是線程安全的。
- 通常界面繪制和分發(fā)事件本該是多線程訪問(wèn)的,但是多線程處理時(shí)為了保證訪問(wèn)資源的正確性,對(duì)于某些操作都會(huì)加上同步鎖,這樣會(huì)顯然會(huì)降低效率,而且還會(huì)涉及到線程的等待與線程上下文切換。
- 為了提高效率,UI線程不在使用這些繁瑣的多線程機(jī)制,為了保證對(duì)UI操作的正確性,只允許在UI線程中操作UI。在非UI線程中可通過(guò)post或者runOnUiThread來(lái)刷新view。
- UI主線程是多線程訪問(wèn)不安全的,因此更新畫面時(shí)可能會(huì)引發(fā)問(wèn)題,比如你更新畫面的時(shí)間過(guò)長(zhǎng),那么你的主UI線程會(huì)被你正在畫的函數(shù)阻塞。那么將無(wú)法響應(yīng)按鍵,觸屏等消息。
SurfaceView子線程更新UI帶來(lái)問(wèn)題
事件同步問(wèn)題。比如你觸屏了一下,你需要surfaceView中 thread處理,一般就需要有一個(gè)event queue的設(shè)計(jì)來(lái)保存touch event,這會(huì)稍稍復(fù)雜一點(diǎn),因?yàn)樯婕暗骄€程同步。-
兩種實(shí)例情況
畫面依賴于onTouch來(lái)更新的,不需要一直更新,且更新效果簡(jiǎn)單,耗費(fèi)資源少,這一次touch和下一次touch時(shí)間間隔比較長(zhǎng),可以直接用invalidate更新即可。
畫面一直在更新,比如一個(gè)人在跑步,為了避免阻塞main UI thread這樣就需要單獨(dú)的thread不停地重繪ui了,這就要用到SurfaceView。
SurfaceView原理
什么是Surface?
簡(jiǎn)單地說(shuō)Surface對(duì)應(yīng)了一塊屏幕緩沖區(qū),每個(gè)window對(duì)應(yīng)一個(gè)Surface,任何View都是畫在Surface上的,傳統(tǒng)的view共享一塊屏幕緩沖區(qū),所有的繪制必須在UI線程中進(jìn)行Surface和SurfaceView的關(guān)系
SurfaceView封裝了一個(gè)Surface對(duì)象,而不是Canvas,因?yàn)镾urface可以使用后臺(tái)線程繪制。
對(duì)于那些資源敏感的操作或者那些要求快速更新或者高速幀率的地方,例如,使用3D圖形,創(chuàng)建游戲,或者實(shí)時(shí)預(yù)覽攝像頭,這一點(diǎn)特別有用。什么是SurfaceHolder.CallBack?
SurfaceHolder.Callback主要是當(dāng)?shù)讓拥腟urface被創(chuàng)建、銷毀或者改變時(shí)提供回調(diào)通知。
由于繪制必須在surface被創(chuàng)建后才能進(jìn)行,因此SurfaceHolder.Callback中的surfaceCreated 和surfaceDestroyed 就成了繪圖處理代碼的邊界。
SurfaceHolder,可以把它當(dāng)成Surface的容器和控制器,用來(lái)操縱Surface,處理它的Canvas上畫的效果和動(dòng)畫,控制表面,大小,像素等。使用代價(jià)
獨(dú)立于GUI線程進(jìn)行繪圖的代價(jià)是額外的內(nèi)存消耗,所以,雖然它是創(chuàng)建定制的View的有效方式–有時(shí)甚至是必須的,但是使用Surface View的時(shí)候仍然要保持謹(jǐn)慎。生命周期
SurfaceView變得可見(jiàn)時(shí),Surface被創(chuàng)建;SurfaceView隱藏前,Surface被銷毀。這樣能節(jié)省資源。應(yīng)該注意
所有SurfaceView和SurfaceHolder.Callback的方法都應(yīng)該在UI線程里調(diào)用,一般來(lái)說(shuō)就是應(yīng)用程序主線程。渲染線程所要訪問(wèn)的各種變量應(yīng)該作同步處理。
創(chuàng)建SurfaceView
要?jiǎng)?chuàng)建一個(gè)新的SurfaceView,需要?jiǎng)?chuàng)建一個(gè)新的擴(kuò)展了SurfaceView的類,并實(shí)現(xiàn)SurfaceHolder.Callback。因?yàn)槔L制工作在子線程中進(jìn)行,模板代碼一般會(huì)直接實(shí)現(xiàn)Runnable接口。
如果是比較復(fù)雜的情況,那可能就需要使用線程池,任務(wù)隊(duì)列進(jìn)行處理了。因?yàn)閮纱问录|發(fā)間隔時(shí)間可能比一次事件處理時(shí)間還短,為保證響應(yīng)正確性,一般用任務(wù)隊(duì)列處理。SurfaceHolder回調(diào)可以在底層的Surface被創(chuàng)建和銷毀的時(shí)候通知View,并傳遞給它對(duì)SurfaceHolder對(duì)象的引用,其中包含了當(dāng)前有效的Surface。
一個(gè)典型的SurfaceView設(shè)計(jì)模型包括一個(gè)由Thread所派生的類,它可以接收對(duì)當(dāng)前的SurfaceHolder的引用,并獨(dú)立地更新它。
使用場(chǎng)景
SurfaceView使用的方式與任何View所派生的類都是完全相同的。可以像其他View那樣應(yīng)用動(dòng)畫,并把它們放到布局中。
SurfaceView封裝的Surface支持使用本章前面所描述的所有標(biāo)準(zhǔn)Canvas方法進(jìn)行繪圖,同時(shí)也支持完全的OpenGL ES庫(kù)。
使用OpenGL,你可以再Surface上繪制任何支持的2D或者3D對(duì)象,與在2D畫布上模擬相同的效果相比,這種方法可以依靠硬件加速(可用的時(shí)候)來(lái)極大地提高性能。
對(duì)于顯示動(dòng)態(tài)的3D圖像來(lái)說(shuō),例如,那些使用Google Earth功能的應(yīng)用程序,或者那些提供沉浸體驗(yàn)的交互式游戲,SurfaceView特別有用。它還是實(shí)時(shí)顯示攝像頭預(yù)覽的最佳選擇。
簡(jiǎn)單畫圖板代碼
package com.hcsys.display.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.EventLog;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* des:
* author: admin
* time: 2016/10/24 11:32.
* e-mail:guohaichang@163.com
*/
public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
// SurfaceHolder
private SurfaceHolder mHolder;
// 用于繪圖的Canvas
private Canvas mCanvas;
// 子線程標(biāo)志位
private volatile boolean mIsDrawing;
//畫筆
private Paint mPaint;
//繪制路徑
private Path mPath;
public CustomSurfaceView(Context context) {
super(context);
initView();
}
public CustomSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public CustomSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
//mHolder.setFormat(PixelFormat.OPAQUE);
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
mPaint.setAntiAlias(true);//抗鋸齒
mPath = new Path();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}
@Override
public void run() {
long start = System.currentTimeMillis();
while (mIsDrawing) {
draw();
}
long end = System.currentTimeMillis();
// 50 - 100
if (end - start < 100) {//保證線程運(yùn)行時(shí)間不少于100ms
try {
Thread.sleep(100 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private synchronized void draw() {
try {
mCanvas = mHolder.lockCanvas();
mCanvas.drawColor(Color.WHITE);
mCanvas.drawPath(mPath,mPaint);
} catch (Exception e) {
} finally {
if (mCanvas != null){
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mPath.moveTo(event.getX(),event.getY());
break;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(event.getX(),event.getY());
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}