學習資料:
- Android 開發群英傳
搜索學習資料時,搜到了羅升陽老師的Android視圖SurfaceView的實現原理分析,老羅老師寫的一系列博客,一年前開始學習Android
時看不懂,現在依然看不懂,感覺涉及到的知識面太廣並且非常深入,還得需要積累很多知識后才能看得懂
在包建強老師的博客中看到,說老羅的博客不是給開發App的人寫的,是給開發Rom的人寫的。哈哈,為看不懂找個心安理得的理由,推薦包老師的系列博客
寫給Android App開發人員看的Android底層知識
1. SurfaceView
View通過刷新來重繪視圖,Android系統通過發出VSYNC信號來進行屏幕的重繪,刷新的時間間隔為16ms
在一些需要頻繁刷新,執行很多邏輯操作的時候,超過了16ms
,就會導致卡頓
SurfaceView
繼承之View
,但擁有獨立的繪制表面,即它不與其宿主窗口共享同一個繪圖表面,可以單獨在一個線程進行繪制,並不會占用主線程的資源。這樣,繪制就會比較高效,游戲,視頻播放,還有最近熱門的直播,都可以用SurfaceView
SurfaceView
有兩個子類GLSurfaceView
和VideoView
SurfaceView
和View
的區別:
View
主要適用於主動更新的情況下,而SurfaceView
主要適用於被動更新,例如頻繁地刷新View
在主線程中對畫面進行刷新,而SurfaceView
通常會通過一個子線程來進行頁面的刷新- View在繪圖時沒有使用雙緩沖機制,而
SufaceView
在底層實現機制中就已經實現了雙緩沖機制
如果自定義View需要頻繁刷新,或者刷新時數據處理量比較大,就 可以考慮使用SurfaceView來取代View了
2. SurfaceView的使用模板
SurfaceView
使用過程有一套模板代碼,大部分的SurfaceView
都可以套用
3步走套路:
- 創建SurfaceView
- 初始化SurfaceView
- 使用SurfaceView
2.1 創建SurfaceView
創建一個自定義的SurfaceViewL
,繼承之SurfaceView
,並實現兩個接口SurfaceHolder.CallBack
和Runnable
代碼:
public class SurfaceViewL extends SurfaceView implements SurfaceHolder.Callback,Runnable{ public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void surfaceCreated(SurfaceHolder holder) {//創建 } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//改變 } @Override public void surfaceDestroyed(SurfaceHolder holder) {//銷毀 } @Override public void run() { } }
SurfaceHolder.CallBack
有3個方法,分別在SurfaceView
創建,改變,銷毀時進行回調
SurfaceHolder.CallBack
還有一個子Callback2
接口,里面添加了一個surfaceRedrawNeeded (SurfaceHolder holder)
方法
當需要重繪SurfaceView
中的內容時,可以使用這個接口。目前還不了解具體的使用場景
2.2 初始化SurfaceView
在自定義的SurfaceView
中,通常需要3個成員變量
- SurfaceHolder mSurfaceHolder 可以控制
SurfaceView
的大小,格式,可以監控或者改變SurfaceView
- Canvas mCanvas 畫布
- boolean isDrawing 子線程標志位,用來控制子線程
在構造方法中,對SurfaceHolder mSurfaceHolder
進行初始化
public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mSurfaceHolder = getHolder();//得到SurfaceHolder對象 mSurfaceHolder.addCallback(this);//注冊SurfaceHolder setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true);//保持屏幕長亮 }
setFocusable(true)
能否獲得焦點setFocusableInTouchMode(true)
能否通過觸摸獲得焦點
這兩個方法都是View
類的方法,可以看看setFocusable與setFocusableInTouchMode差異以及clickable
2.3 使用SurfaceView
利用在2.2拿到的mSurfaceHolder
對象,通過lockCanvas()
方法獲得當前的Canvas
注意:lockCanvas()
獲取到的Canvas
對象還是上次的Canvas
對象,並不是一個新的對象。之前的繪圖都將被保留,如果需要擦除,可以在繪制之前通過drawColor()
方法來進行清屏
繪制要充分利用SurfaceView
的三個回調方法,在surfaceCreate()
方法中開啟子線程進行繪制。在子線程中,使用一個while(isDrawing)
循環來不停地繪制。具體的繪制過程,由lockCanvas()
方法進行繪制,並通過unlockCanvasAndPost(mCanvas)
進行畫布內容的提交
2.4 完整的模板代碼
public class SurfaceViewL extends SurfaceView implements SurfaceHolder.Callback, Runnable { // SurfaceHolder private SurfaceHolder mSurfaceHolder; // 畫布 private Canvas mCanvas; // 子線程標志位 private boolean isDrawing; public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); } @Override public void surfaceCreated(SurfaceHolder holder) {//創建 isDrawing = true; new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//改變 } @Override public void surfaceDestroyed(SurfaceHolder holder) {//銷毀 isDrawing = false; } @Override public void run() { while (isDrawing){ drawing(); } } private void drawing() { try { mCanvas = mSurfaceHolder.lockCanvas(); //這里進行內容的繪制 ... }finally { if (mCanvas != null){ mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } } }
mSurfaceHolder.unlockCanvasAndPost(mCanvas)
將這行代碼放入finally
代碼塊中,目的是為了確保內容都能夠被提交
3. 簡單使用
效果還是一個簡易的畫圖板

public class SurfaceViewL extends SurfaceView implements SurfaceHolder.Callback, Runnable { // SurfaceHolder private SurfaceHolder mSurfaceHolder; // 畫布 private Canvas mCanvas; // 子線程標志位 private boolean isDrawing; // 畫筆 Paint mPaint; // 路徑 Path mPath; private float mLastX, mLastY;//上次的坐標 public SurfaceViewL(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * 初始化 */ private void init() { //初始化 SurfaceHolder mSurfaceHolder mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); //畫筆 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPaint.setStrokeWidth(10f); mPaint.setColor(Color.parseColor("#FF4081")); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeCap(Paint.Cap.ROUND); //路徑 mPath = new Path(); } @Override public void surfaceCreated(SurfaceHolder holder) {//創建 isDrawing = true; Log.e("surfaceCreated","--"+isDrawing); //繪制線程 new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {//改變 } @Override public void surfaceDestroyed(SurfaceHolder holder) {//銷毀 isDrawing = false; Log.e("surfaceDestroyed","--"+isDrawing); } @Override public void run() { while (isDrawing){ drawing(); } } /** * 繪制 */ private void drawing() { try { mCanvas = mSurfaceHolder.lockCanvas(); mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath,mPaint); } finally { if (mCanvas != null) { mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastX = x; mLastY = y; mPath.moveTo(mLastX, mLastY); break; case MotionEvent.ACTION_MOVE: float dx = Math.abs(x - mLastX); float dy = Math.abs(y - mLastY); if (dx >= 3 || dy >= 3) { mPath.quadTo(mLastX, mLastY, (mLastX + x) / 2, (mLastY + y) / 2); } mLastX = x; mLastY = y; break; case MotionEvent.ACTION_UP: break; } return true; } /** * 測量 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int wSpecMode = MeasureSpec.getMode(widthMeasureSpec); int wSpecSize = MeasureSpec.getSize(widthMeasureSpec); int hSpecMode = MeasureSpec.getMode(heightMeasureSpec); int hSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(300, 300); } else if (wSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(300, hSpecSize); } else if (hSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(wSpecSize, 300); } } }
代碼主要是從徐醫生的Android群英傳
中學到的。
由於while (isDrawing)
是個死循環,drawing
方法一直在執行,就導致一直在繪制,徐醫生給了一個優化的方案,對run()
方法進行了修改
@Override public void run() { Log.e("drawing","--"+111111); long start = System.currentTimeMillis(); while (isDrawing) { drawing(); } long end = System.currentTimeMillis(); if (end- start < 100){ try { Log.e("drawing","--"+22222); Thread.sleep(100-(end-start)); } catch (InterruptedException e) { e.printStackTrace(); } } }
但對於優化后run()
方法有些疑問,Thread.sleep(100-(end-start))
感覺這行代碼並不執行到,因為isDrawing = true
,是個死循環,只有surfaceDestroyed(SurfaceHolder holder)
方法執行時,isDrawing = false
,之后才可以執行到sleep()
方法
根據個人的理解,修改了代碼:
public void run() { while (isDrawing) { Log.e("drawing","--"+111111); long start = System.currentTimeMillis(); drawing(); long end = System.currentTimeMillis(); if (end- start < 100){ try { Log.e("drawing","--"+22222); Thread.sleep(100-(end-start)); } catch (InterruptedException e) { e.printStackTrace(); } } } }
這樣修改后,雖然sleep()
方法能執行到,但繪制過程有種不跟手的感覺,個人感覺並不用優化,在run()
方法中
4. 自己想的優化
既然不想時時刻在繪制,那就只有在手指在屏幕滑動時,進行繪制
修改代碼:
// 修改 1 @Override public void surfaceCreated(SurfaceHolder holder) {//創建 drawing(); } //修改onTouchEvent(MotionEvent event)方法 //修改 2 case MotionEvent.ACTION_DOWN: isDrawing = true ;//每次開始將標記設置為ture new Thread(this).start();//開啟線程 mLastX = x; mLastY = y; mPath.moveTo(mLastX, mLastY); break; //修改3 case MotionEvent.ACTION_UP: isDrawing = false;//每次結束將標記設置為false break; //修改4 run()方法 @Override public void run() { while (isDrawing){ drawing(); } }
手指落下,將標記設置為true
,並開啟線程
手指離開,將標記設置為false
,循環結束后,線程也就停止
5. 最后
關於4的優化,哪位同學有好的建議,請留言
又是周末,周末愉快
本人很菜,有錯誤請指出
共勉 : )
作者:英勇青銅5
鏈接:http://www.jianshu.com/p/15060fc9ef18
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。