从源码分析 对 非UI线程不允许访问UI元素的 理解
来源:互联网 发布:透明图片下载软件 编辑:程序博客网 时间:2024/05/22 04:55
做Android开发初期大部分开发者都会遇到下面这个问题.
android.view.viewroot$calledfromwrongthreadexception: only the original thread that created a view hierarchy can touch its views.
原因大半都是 由于在异步线程中视图 更新界面中的元素
百度一搜,可以搜到大量解决办法.
结果提炼一下: 大致就是讲 UI元素/视图元素 必须在 UI线程(主线程)中更新修改.
新手这样理解就够了.
但是仔细看这个Exception说明
only the original thread that created a view hierarchy can touch its views.
里面并没有提到 主线程/UI 线程,只是提到,必须是创建View hierarchy
的线程
!注意区分
创建View的线程和创建View hierarchy的不一定是同一个线程,不要理解错了,下面在源码中具体分析
观察ViewRoot源码中抛出calledfromwrongthreadexception的位置
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); }}
可以注意到 这里只检查了 当前执行线程和 mThread是否为同一个线程
public ViewRoot(Context context) { super(); if (MEASURE_LATENCY && lt == null) { lt = new LatencyTimer(100, 1000); } // For debug only //++sInstanceCount; // Initialize the statics when this class is first instantiated. This is // done here instead of in the static block because Zygote does not // allow the spawning of threads. getWindowSession(context.getMainLooper()); mThread = Thread.currentThread();
由ViewRoot的构造函数可以看到,mThread被绑定在了 创建viewRoot
的线程上.
当然大部分情况下,ViewRoot
都是在主线程中创建的,所以在异步线程中修改view会造成checkThread失败.
mTextView.setText(“123”);为例子.
跟踪一下调用栈
-- android.widget.TextView.setText -- android.widget.TextView.checkForRelayout -- android.view.View.invalidate -- android.view.ViewGroup.invalidateChild -- android.view.ViewRoot.invalidateChildInParent -- android.view.ViewRoot.invalidateChild -- android.view.ViewRoot.checkThread
可以看到 view会不断遍历去获取 ViewParent
一个视图上的View遍历到最后 就是 获取到了 ViewRoot
(ViewRoot本身就是ViewParent的一个子类)
然后就会调用到上面提到的 checkThread()
到这里有一个结论:
UI修改操作确实会验证操作的线程
* * *
回到标题上来说, 因为 ViewRoot是由UI线程创建的,所以所有被添加到ViewRoot下的子View都必须在UI线程中修改,看起来也很合理.
那么,有可能在非UI线程中创建ViewRoot吗?
其实是可以的.
这里就涉及到ViewRoot和WindowManager之间的关系
demo代码如下:
package com.dk.asyncui;import android.app.Activity;import android.graphics.PixelFormat;import android.os.Bundle;import android.os.Looper;import android.view.Menu;import android.view.MenuItem;import android.view.View;import android.view.WindowManager;import android.widget.Button;public class MainActivity extends Activity { private Button mButton ; private WindowManager mWindowManager ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout. activity_main); mWindowManager = (WindowManager) getSystemService("window" ); mButton = new Button(this ); mButton.setText( "HelloWorld"); Thread mthread = new Thread(new Runnable() { @Override public void run() { Looper. prepare(); WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams(); wmParams. type = WindowManager.LayoutParams.TYPE_APPLICATION ; wmParams. format = PixelFormat. RGBA_8888; wmParams. flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL ; wmParams. width = 320; wmParams. height = 140; mWindowManager.addView(mButton , wmParams); Looper. loop(); } }); mthread.start(); mButton.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { runOnUiThread( new Runnable() { public void run() { mButton.setText( "22222222"); } }); } }); }}
可以看到,我在异步线程中将 mButton添加到 WindowManager中
查看 WindowManangerImpl中addView的代码:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (display == null) { throw new IllegalArgumentException("display must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. if (mSystemPropertyUpdater == null) { mSystemPropertyUpdater = new Runnable() { @Override public void run() { synchronized (mLock) { for (int i = mRoots.size() - 1; i >= 0; --i) { mRoots.get(i).loadSystemProperties(); } } } }; SystemProperties.addChangeCallback(mSystemPropertyUpdater); } int index = findViewLocked(view, false); if (index >= 0) { if (mDyingViews.contains(view)) { // Don't wait for MSG_DIE to make it's way through root's queue. mRoots.get(index).doDie(); } else { throw new IllegalStateException("View " + view + " has already been added to the window manager."); } // The previous removeView() had not completed executing. Now it has. } // If this is a panel window, then find the window it is being // attached to for future reference. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { final int count = mViews.size(); for (int i = 0; i < count; i++) { if (mRoots.get(i).mWindow.asBinder() == wparams.token) { panelParentView = mViews.get(i); } } } root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; }}
可以发现在addView的过程中 WindowMananger创建了 ViewRoot
这就是上面提到的在 异步线程中创建 ViewRoot
上面Demo代码中的 runOnUiThread是为了保证下面的
mButton.setText( “22222222” );
是在主线程中执行
可以看到,这段代码即使是在主线程中执行,依然抛出了CalledFromWrongThreadException
在特意构造的这个特殊场景下,主线程/UI线程中 不能更新在异步线程中”创建/添加”的UI元素!!
从源码和设计的角度上分析,我倾向于认为这其实是设计上的一个漏洞,我通过这个漏洞创建了一个非主线程绑定的ViewRoot对象,才会导致上面的结果.
- 从源码分析 对 非UI线程不允许访问UI元素的 理解
- Android非UI线程访问UI线程的方法总结
- android 为什么不允许子线程访问UI
- SWT的线程(UI线程和非UI线程的理解)
- Android系统为什么不允许在线程中访问UI呢?
- Android为什么不允许在子线程中访问UI?
- 基础篇-在非UI线程中更新UI元素
- 非UI线程更新UI!?
- 非UI线程在休眠与不休眠情况下更新UI的原理分析
- SWT的UI线程和非UI线程
- 5.UI线程和非UI线程的交互方式
- C#在非UI线程调用UI线程的控件
- UI线程和非UI线程的交互方式
- 非UI线程的跨线程操作
- Android UI线程和非UI线程
- Android UI线程和非UI线程
- Android UI线程和非UI线程
- Android UI线程和非UI线程
- 微信公众平台开发学习记录(3)————接收消息和发送回复消息
- FTP传输和下载文件
- 开发必会的 Linux 命令
- [Java] 作业1答疑
- div标签
- 从源码分析 对 非UI线程不允许访问UI元素的 理解
- 装SSD记录
- Qt入门学习书籍
- 稀疏矩阵
- Anaconda的升级与卸载
- 关于java.io.Serializable的使用
- mysql分区
- 大型网站架构不得不考虑的 10 个问题
- 使用srvany.exe将程序安装成windows服务的详细教程(附bat程序)