Android并发编程高级面试题汇总最全最细面试题讲解持续更新中👊👊
👀你想要的面试题这里都有👀
👇👇👇
如何更新UI,为什么子线程不能更新UI?
这道题想考察什么?
- 是否了解为什么子线程不能更新UI的概念与真实场景使用,是否熟悉为什么子线程不能更新UI的本质区别?
考察的知识点
- 为什么子线程不能更新UI的概念在项目中使用与基本知识
考生应该如何回答
1.你工作这么些年,有注意到子线程不能更新UI?
答:
其实Android开发者都在说,子线程不能更新UI,难道一定就不能在子线程更新UI吗,那也未必:
下面代码是可以的,运行结果并无异常,可以正常的在子线程中更新了TextView
控件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
new Thread(new Runnable() {
@Override
public void run() {
tv.setText("Test");
}
}).start();
}
但是假如让线程休眠1000ms
,就会发生错误:
Only the original thread that created a view hierarchy can touch its views.
上面报错的意思是只有创建视图层次结构的原始线程才能更新这个视图,也就是说只有主线程才有权力去更新UI
,其他线程会直接抛异常的;
从at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7905)
的异常路径可以看到抛出异常的最终在ViewRootIml
的checkThread
方法里,ViewRootIml
是View
的根类,其控制着View
的测量、绘制等操作,那么现在我们转到ViewRootImpl.java
源码观察:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
上面的scheduleTraversals()
函数里是对View
进行绘制操作,而在绘制之前都会检查当前线程是否为主线程mThread
,如果不是主线程,就抛出异常;这样做法就限制了开发者在子线程中更新UI的操作;
但是为什么最开始的在onCreate()
里子线程对UI
的操作没有报错呢,可以设想一下是因为ViewRootImp
此时还没有创建,还未进行当前线程的判断;
现在,我们寻找ViewRootImp
在何时创建,从Activity
启动过程中寻找源码,通过分析可以查看ActivityThread.java
源码,并找到handleResumeActivity
方法:
final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume) {
···
// TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide);
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);
}
} else if (!willBeVisible) {
if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
···
}
可以看到内部执行了performResumeActivity
方法:
public final ActivityClientRecord performResumeActivity(IBinder token, boolean clearHide) {
if (r != null && !r.activity.mFinished) {
r.activity.performResume();
省略...
}
会发现在内部调用了
Activity的
performResume方法,可以肯定应该是要回调生命周期的
onResume方法了:
final void performResume() {
···
mCalled = false;
// mResumed is set by the instrumentation
mInstrumentation.callActivityOnResume(this);
if (!mCalled) {
throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
" did not call through to super.onResume()");
}
···
}
接着然后又调用了Instrumentation
的callActivityOnResume
方法:
public void callActivityOnResume(Activity activity) {
activity.mResumed = true;
activity.onResume();
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
am.match(activity, activity, activity.getIntent());
}
}
}
}
然后就可以看到执行了 activity.onResume()
方法,也就是回调了Activity
生命周期的onResume
方法;现在让我们回头看看handleResumeActivity
方法,会执行这段代码:
···
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
发现在内部调用了Activity
的makeVisible
方法:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
源码内部调用了WindowManager
的addView
方法,而WindowManager
方法的实现类是WindowManagerImp
类,直接找WindowManagerImp
的addView
方法:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
最终然后又执行了WindowManagerGlobal
的addView
方法,在该方法中,终于看到了ViewRootImpl
的创建;
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
···
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
···
}
真相大白:
刚刚源码分析可得知,ViewRootImpl
对象是在onResume
方法回调之后才创建,那么就说明了为什么在生命周期的onCreate
方法里,甚至是onResume
方法里都可以实现子线程更新UI,因为此时还没有创建ViewRootImpl
对象,并不会进行是否为主线程的判断;
个人理解
必须要在主线程更新UI
,实际是为了提高界面的效率和安全性,带来更好的流畅性;你反推一下,假如允许多线程更新UI
,但是访问UI
是没有加锁的,一旦多线程抢占了资源,那么界面将会乱套更新了,体验效果就不言而喻了;所以在Android
中规定必须在主线程更新UI
,很合理吧
总结源码分析
第一点:子线程可以在ViewRootImpl还没有创建之前更新UI的
第二点:访问UI是没有加对象锁的,在子线程环境下更新UI,会造成各种未知风险的
第三点:Android开发者一定要在主线程更新UI操作,这个是职业习惯哦
由于面试题内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要2023年Android中高级最全面试真题答案 完整文档的可扫描下方卡片免费获取~
PS:(文末还有使用ChatGPT机器人小福利哦!!大家不要错过)
目录
第一章 Java方面
- Java基础部分
- Java集合
- Java多线程
- Java虚拟机
第二章 Android方面
- Android四大组件相关
- Android异步任务和消息机制
- Android UI绘制相关
- Android性能调优相关
- Android中的IPC
- Android系统SDK相关
- 第三方框架分析
- 综合技术
- 数据结构方面
- 设计模式
- 计算机网络方面
- Kotlin方面
第三章 音视频开发高频面试题
- 为什么巨大的原始视频可以编码成很小的视频呢?这其中的技术是什么呢?
- 怎么做到直播秒开优化?
- 直方图在图像处理里面最重要的作用是什么?
- 数字图像滤波有哪些方法?
- 图像可以提取的特征有哪些?
- 衡量图像重建好坏的标准有哪些?怎样计算?
第四章 Flutter高频面试题
- Dart部分
- Flutter部分
第五章 算法高频面试题
- 如何高效寻找素数
- 如何运用二分查找算法
- 如何高效解决雨水问题
- 如何去除有序数组的重复元素
- 如何高效进行模幂运算
- 如何寻找最长回文子串
第六章 Andrio Framework方面
- 系统启动流程面试题解析
- Binder面试题解析
- Handler面试题解析
- AMS面试题解析