Android高级UI面试题汇总(含详细解析 五)

Android并发编程高级面试题汇总最全最细面试题讲解持续更新中👊👊
👀你想要的面试题这里都有👀
👇👇👇

如何更新UI,为什么子线程不能更新UI?

这道题想考察什么?

  1. 是否了解为什么子线程不能更新UI的概念与真实场景使用,是否熟悉为什么子线程不能更新UI的本质区别?

考察的知识点

  1. 为什么子线程不能更新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)的异常路径可以看到抛出异常的最终在ViewRootImlcheckThread方法里,ViewRootImlView的根类,其控制着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(); 
    省略...
} 

会发现在内部调用了ActivityperformResume方法,可以肯定应该是要回调生命周期的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()");
        }
        ···
    }

接着然后又调用了InstrumentationcallActivityOnResume方法:

	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();
                }

发现在内部调用了ActivitymakeVisible方法:

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

源码内部调用了WindowManageraddView方法,而WindowManager方法的实现类是WindowManagerImp类,直接找WindowManagerImpaddView方法:

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

最终然后又执行了WindowManagerGlobaladdView方法,在该方法中,终于看到了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机器人小福利哦!!大家不要错过)

目录

img

第一章 Java方面

  • Java基础部分
  • Java集合
  • Java多线程
  • Java虚拟机

img

第二章 Android方面

  • Android四大组件相关
  • Android异步任务和消息机制
  • Android UI绘制相关
  • Android性能调优相关
  • Android中的IPC
  • Android系统SDK相关
  • 第三方框架分析
  • 综合技术
  • 数据结构方面
  • 设计模式
  • 计算机网络方面
  • Kotlin方面

img

第三章 音视频开发高频面试题

  • 为什么巨大的原始视频可以编码成很小的视频呢?这其中的技术是什么呢?
  • 怎么做到直播秒开优化?
  • 直方图在图像处理里面最重要的作用是什么?
  • 数字图像滤波有哪些方法?
  • 图像可以提取的特征有哪些?
  • 衡量图像重建好坏的标准有哪些?怎样计算?

img

第四章 Flutter高频面试题

  • Dart部分
  • Flutter部分

img

第五章 算法高频面试题

  • 如何高效寻找素数
  • 如何运用二分查找算法
  • 如何高效解决雨水问题
  • 如何去除有序数组的重复元素
  • 如何高效进行模幂运算
  • 如何寻找最长回文子串

img

第六章 Andrio Framework方面

  • 系统启动流程面试题解析
  • Binder面试题解析
  • Handler面试题解析
  • AMS面试题解析

img

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值