多线程学习之--真的不能在子线程里更新UI吗?
来源:互联网 发布:手机防沉溺软件 编辑:程序博客网 时间:2024/06/10 09:01
在我们学习多线程的路上,都会听到这样一句话:
不能在子线程里更新UI,UI更新必须在UI线程中
why?为什么不能在子线程中更新UI?如果在子线程中更新UI会怎样?
为了模拟在子线程中更新UI的场景,简单地写了几行代码:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); saButton = (Button) findViewById(R.id.text); final Thread thread = new Thread(new Runnable() { @Override public void run() { ((TextView)findViewById(R.id.sv_view)).setText("子线程"); } }); saButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { thread.start(); } }); }
运行,“理所当然”地崩溃了。打印错误日志如下:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357) at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)
崩溃的原因是:“Only the original thread that created a view hierarchy can touch its views.”意思是只有创建这个View布局层次的原始线程才可以改变这个View,看起来好像也并没有解释为什么子线程中不能更新UI。
而我们能看到产生异常崩溃的代码在ViewRootImpl这个类的checkThread方法,所以我们找到这个类:
ViewRootImpl.java#checkThread
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
异常抛出的条件是mThread != Thread.currentThread()那么这个mThread在哪里初始化的呢?接着看。
public ViewRootImpl(Context context, Display display) { mContext = context; mWindowSession = WindowManagerGlobal.getWindowSession(); mDisplay = display; mBasePackageName = context.getBasePackageName(); mDisplayAdjustments = display.getDisplayAdjustments(); mThread = Thread.currentThread();//此处初始化 ... }
在ViewRootImpl的构造方法里可以看到mThread指向当前线程的引用,意思是只要在子线程中创建ViewRootImpl的实例我们就可以避免抛异常了吗?于是楼主尝试在子线程中创建ViewRootImpl的实例可是发现并不能找到ViewRootImpl这个类。换个角度,如果不能在子线程更新UI,那主线程刷新UI是不是也要实例化这个类呢?而我们启动Activity绘制UI的方法在onResume方法里,所以我们找到Activity的线程ActivityThread类。
ActivityThread.java#handleResumeActivity
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { ...if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; wm.addView(decor, l); } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. } else if (!willBeVisible) { if (localLOGV) Slog.v( TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; } ...}
wm.addView(decor, l);
是他进行的View的加载,我们去看看他的实现方法,在WindowManager的实现类WindowManagerImpl里:
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); }
发现他是调用WindowManagerGlobal的方法实现的,最后我们找到了最终实现addView的方法:
WindowManagerGlobal.java#addView
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. ... 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; } }
果然在这里,View的加载最后就是在这里实现的,而ViewRootImpl的实例化也在这里。所以如果我们在子线程中调用WindowManager的addView方法,是不是就可以成功更新UI呢?所以我修改了代码:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); saButton = (Button) findViewById(R.id.text); final Thread thread = new Thread(new Runnable() { @Override public void run() { TextView tx = new TextView(MainActivity.this); tx.setText("子线程"); tx.setBackgroundColor(Color.WHITE); ViewManager viewManager = MainActivity.this.getWindowManager(); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( 200, 200, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW, WindowManager.LayoutParams.TYPE_TOAST, PixelFormat.OPAQUE); viewManager.addView(view,layoutParams); } }); ... }
运行,程序崩溃了,来看看错误日志:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
错误原因是没有启动Looper。原来是因为在ViewRootImpl类里新建了ViewRootHandler的实例mHandler,而mHandler要启动Looper才能处理相关信息。所以我们在代码里加入两行:
...public void run() { Looper.prepare(); TextView tx = new TextView(MainActivity.this); tx.setText("子线程"); ... windowManager.addView(tx, params); Looper.loop(); } ...
再次运行,成功了!
所以其实是可以在子线程中更新UI的,只要实例化ViewRootImpl。而为什么Android设计只能在UI线程中更新UI呢?大概是因为如果子线程更新UI可能导致线程之间抢夺资源和死锁等线程安全问题而不允许在子线程中更新UI。
- 多线程学习之--真的不能在子线程里更新UI吗?
- Android 真的不能在子线程更新 UI 吗
- android 子线程真的不能更新ui吗
- 子线程真的不能更新UI吗?
- 子线程真的不能更新UI吗?
- Android子线程真的不能更新UI么
- Android子线程真的不能更新UI么
- Android子线程真的不能更新UI么
- 子线程中真的不能更新UI?
- 关于子线程里不能更新UI操作的解决方法
- 非UI线程真的不能更新UI吗?
- Android 中非UI线程真的不能更新UI吗?
- Android中子线程真的不能更新UI吗?
- Android中子线程真的不能更新UI吗?
- Android中子线程真的不能更新UI吗?
- Android中子线程真的不能更新UI吗?
- Android中子线程真的不能更新UI吗?
- Android中子线程真的不能更新UI吗?
- LeetCode - 217. Contains Duplicate
- java.lang包
- asp.net mvc中应用Grid++ Report (基本应用示例)
- JAVA之旅【第二天】 常量 进制 数据类型 变量 运算符
- ImageView+ViewPager+PhotoView实现朋友圈图片点击全屏查看支持放大缩小
- 多线程学习之--真的不能在子线程里更新UI吗?
- 细说addEventListener与事件捕获、事件冒泡
- struct stat各成员含义
- OpenCV入门学习1
- Java数组拼接字符串几个写法性能比较
- bzoj4565【HAOI2016】字符合并
- 概率论里的 随机变量相关的严格定义
- JDK基础类之GZIPInputStream/GZIPOutputStream
- thinkphp整合系列之融云即时通讯在线聊天