學習資料:
- 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. 自己想的優化
思路不對,每次在 ACTION_DOWN時, new 一個線程不行,創建再銷毀一個線程開銷太大。當作一個錯誤示例吧
20180503 17:34 補充
既然不想時時刻在繪制,那就只有在手指在屏幕滑動時,進行繪制
修改代碼:
// 修改 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的優化,哪位同學有好的建議,請留言
又是周末,周末愉快
本人很菜,有錯誤請指出
共勉 : )