本文为"[Android] SurfaceView使用实例"的辅助文章。
本文由4部分内容组成,前3部分取自网络,原文贴出,第4部分为Sodino原创码出。请仔细阅读前3部分后再阅读第4部分"4.为何SurfaceView能够在非UI线程中刷新界面?"
-------------------------------------------------------------------------------------------------------------------------
以下内容出自网络。
http://stackoverflow.com/questions/4576909/understanding-canvas-and-surface-concepts
A Surface is an object holding pixels that are being composited to the screen.Every window you see on the screen (a dialog, your full-screen activity, the status bar) has its own surface that it draws in to, and Surface Flinger renders these to the final display in their correct Z-order. A surface typically has more than one buffer (usually two) to do double-buffered rendering: the application can be drawing its next UI state while the surface flinger is compositing the screen using the last buffer, without needing to wait for the application to finish drawing.
A window is basically like you think of a window on the desktop. It has a single Surface in which the contents of the window is rendered. An application interacts with the Window Manager to create windows; the Window Manager creates a Surface for each window and gives it to the application for drawing. The application can draw whatever it wants in the Surface; to the Window Manager it is just an opaque rectangle.
A View is an interactive UI element inside of a window.A window has a single view hierarchy attached to it, which provides all of the behavior of the window. Whenever the window needs to be redrawn (such as because a view has invalidated itself), this is done into the window's Surface. The Surface is locked, which returns a Canvas that can be used to draw into it. A draw traversal is done down the hierarchy, handing the Canvas down for each view to draw its part of the UI. Once done, the Surface is unlocked and posted so that the just drawn buffer is swapped to the foreground to then be composited to the screen by Surface Flinger.
A SurfaceView is a special implementation of View that also creates its own dedicated Surface for the application to directly draw into (outside of the normal view hierarchy, which otherwise must share the single Surface for the window). The way this works is simpler than you may expect -- all SurfaceView does is ask the window manager to create a new window, telling it to Z-order that window either immediately behind or in front of the SurfaceView's window, and positioning it to match where the SurfaceView appears in the containing window. If the surface is being placed behind the main window (in Z order), SurfaceView also fills its part of the main window with transparency so that the surface can be seen.
2.Relationship between Surface and Canvas
http://stackoverflow.com/questions/3370212/relationship-between-surface-and-canvas-android?lq=1
A surface is a buffer. A Canvas holds the drawing.
Views are not attached to the Canvas nor the Surface. The window is tied to a Surface and the ViewRoot asks the Surface for a Canvas that is then used by the Views to draw onto.
3. 7 Questions
http://www.mail-archive.com/android-framework@googlegroups.com/msg01901.html
> 1. what is the relationship between ViewRoot and View class.
The ViewRoot is the root of each view hierarchy. Like you said, there
is one ViewRoot per window. The ViewRoot is responsible to handling
the layout and drawing of the view hierarchy. The view hierarchy is
made of Views and ViewGroups. A ViewGroup is a special View that can
contain other Views.
> 2. Is all view ,its children's view and their viewRoot share the same
> canvas for draw?
Yes, but that Canvas might change on every drawing operation. The
ViewRoot acquires a Canvas from the underlying Surface and hands that
Canvas to the top-level View.
> 3. What is the relationship within View, canvas and Surface. To my
> thinking, every view will be attached a canvas and surface.
No, the Views are not attached to the Canvas nor the Surface. The
window is tied to a Surface and the ViewRoot asks the Surface for a
Canvas that is then used by the Views to draw onto.
> 4. canvas hold the bitmap for view, will the actual view drawn data will
> be in canvas' bitmap.
Yes.
> 5. After View draw its data to canvas, ViewRoot
> will call surface.unlockCanvasAndPost(canvas) to schedule
> surfaceFlinger::composeSurfaces() which do the actually display to
> display panel.
Yes.
> 6. Where is the drawn data in canvas transfer to surface front buffer or
> backbuffer? I cannot find the code to do that.
It's done by SurfaceFlinger.
> 7. Do different views within the same ViewRoot share the same surface?
Yes, except for SurfaceViews.
以上为源文拷贝。
---------------------------------------------------------------------------------------------------------------------------------------4.为何SurfaceView能够在非UI线程中刷新界面?
把这个问题分解为两部分:
4.1 普通的View是如何刷新界面的?
查看ViewRoot.java代码,可以发现界面的刷新操作是在方法draw(boolean fullRedrawNeeded)中完成的。
仔细阅读代码,发现其刷新界面的步骤和SurfaceView的步骤是一致的。如下:
a. canvas = surface.lockCanvas(dirty);
b. do something draw actions...
c. surface.unlockCanvasAndPost(canvas);
由此可见其实普通的View与SurfaceView界面刷新操作最核心的都是:向Surface请求canvas,执行绘画操作后再次提交给Surface完成屏幕显示。
亦可断定,普通线程与UI线程相比,并没有什么先天不足,皆具备刷新的功能。只是Google工程师在代码中人为设置了障碍。
接下来第2个问题就是要找出这个障碍。
附:ViewRoot.java源代码
http://www.netmite.com/android/mydroid/frameworks/base/core/java/android/view/ViewRoot.java
4.2 界面的刷新代码是如何判断当前线程是否为UI线程的?
在上面的ViewRoot.java处,发现View在执行以下操作时都会checkThread()。
requestLayout() invalidateChild() requestTransparentRegion() requestChildFocus() clearChildFocus() focusableViewAvailable() recomputeViewAttributes()
即当界面进行重新布局,背景变化,焦点转移等UI操作时,皆会检查执行线程是否具备UI操作资格。
checkThread()的代码在ViewRootImple.java中找到,如下:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
其中 mThread 为final,且在ViewRootImple的构造函数中已经被赋值。
即:是否具备UI操作资格的条件是要与mThread进行判断。
附:ViewRootImple.java源代码地址
http://www.oschina.net/code/explore/android-4.0.1/core/java/android/view/ViewRootImpl.java
综上所述,SurfaceView具有自己专有的Surface,所以在普通线程中的异步操作不会对UI线程的绘画布局产生冲突;且绕过了checkThread()这个限制,所以可以直接刷新界面。
本文由Sodino所有,转载请注明出处:http://blog.csdn.net/sodino/article/details/7709128
对"4.2 界面的刷新代码是如何判断当前线程是否为UI线程的?"中checkThread()的扩展:
真的不能在非UI线程中更新界面吗?答案是:只要非UI线程运行得比UI线程快,亦可更新界面。利用的是checkThread()的漏洞。
注意:checkThread()的判断中,并没有限制mThread不为null。所以只有界面更新速度足够快,在mThread被赋值之前即可,则checkThread()返回值仍为true,则非UI线程仍可更新界面。
给出利用此漏洞的例子吧。
新建一个标准的Android工程,将默认的main.xml中已存在的TextView设置id为"txtInfo",然后将Activity的onCreated()代码改成如下:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final ProgressDialog dlg = ProgressDialog.show(this, "Title", "Content");
final TextView txtInfo = (TextView) findViewById(R.id.txtInfo);
Log.d("ANDROID_LAB", "thread[" + Thread.currentThread().getName() + "]ID:" + Thread.currentThread().getId());
new Thread() {
public void run() {
// 此处未报错
txtInfo.setText("haha");
txtInfo.setBackgroundColor(0xff123456);
TextView txt = new TextView(ActLab.this);
txt.setText("Sodino");
LinearLayout layout = (LinearLayout) txtInfo.getParent();
layout.addView(txt);
Log.d("ANDROID_LAB", "thread[" + Thread.currentThread().getName() + "]ID:"
+ Thread.currentThread().getId());
try {
Thread.sleep(5000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 此处未报错
dlg.cancel();
Log.d("ANDROID_LAB","after dlg.cancel()");
// 报错了!
txtInfo.setText("654321");
}
}.start();
// runOnUiThread(action);
}
运行之后,发现界面刚显示时,txtInfo文字已经由原来的"Hello world"改为"haha",并且背景色也变了,且新增了一个TextView。而这些操作都是在非UI线程下完成的。
接下来线程sleep 5秒。再执行取消显示ProgressDialog,结果又逆天了,真的顺利取消了,此原因未知,但至少证明了所有的界面刷新并非真的得在UI线程下完成。
然后再次对txtInfo进行更新,结果此次报错了,提示"Only the original thread that created a view hierarchy can touch its views."
Written by Sodino
呵呵,以上的实验也提醒了大家,写代码时一定要考虑周密。另外,对于这种因代码漏洞而引起的特例,知道就好,在正规的代码编程中禁止去使用它。