2024年Android最全Android UI 线程更新UI也会崩溃???(1),数据结构与算法面试题答案

本文分享了如何快速进阶为Android高级工程师,包括面试真题集锦、系统学习资料推荐,强调了知识体系的重要性,以及在处理UI操作时必须保证在主线程进行,避免常见错误。
摘要由CSDN通过智能技术生成

最后

跳槽季整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

}.start();

}

private void showQuestionInDialog(String title) {

}

}

很简单吧,点击按钮,新启动一个线程去模拟网络请求,结果拿到后,把问题展示在Dialog。

下面开始写Dialog的代码:

public class QuestionDialog extends Dialog {

private TextView mTvTitle;

private Button mBtnYes;

private Button mBtnNo;

public QuestionDialog(@NonNull Context context) {

super(context);

setContentView(R.layout.dialog_question);

mTvTitle = findViewById(R.id.tv_title);

mBtnYes = findViewById(R.id.btn_yes);

mBtnNo = findViewById(R.id.btn_no);

}

public void show(String title) {

mTvTitle.setText(title);

show();

}

}

很简答,就一个标题,两个按钮。

<RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”

xmlns:tools=“http://schemas.android.com/tools”

android:layout_width=“match_parent”

android:layout_height=“match_parent”>

<TextView

android:id=“@+id/tv_title”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:textSize=“24dp”

android:textStyle=“bold”

tools:text=“鸿洋丑的一匹?鸿洋丑的一匹?鸿洋丑的一匹?鸿洋丑的一匹?” />

<Button

android:id=“@+id/btn_yes”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_below=“@id/tv_title”

android:layout_marginTop=“10dp”

android:text=“是的”>

<Button

android:id=“@+id/btn_no”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_alignTop=“@id/btn_yes”

android:layout_alignParentRight=“true”

android:layout_marginLeft=“20dp”

android:layout_toRightOf=“@id/btn_yes”

android:text=“不是”>

然后我们在showQuestionInDialog让它show出来。

private void showQuestionInDialog(String title) {

QuestionDialog questionDialog = new QuestionDialog(this);

questionDialog.show(title);

}

你们猜结果怎么着…

崩溃了…

第一次崩溃


应届生小齐迎来了第一次工作中的崩溃…

我们先停下来。

上面的代码很简单吧,那么我想问各位为什么会崩溃呢?凭各位多年的经验。

猜想:

new Thread(){

puublic void run(){

show(“…”);

}

}

public void show(String title) {

mTvTitle.setText(title);

show();

}

上面new Thread模拟数据,没有切到UI线程就show Dialog了,而且执行了TextView#setText,肯定是在非UI线程更新UI导致的。

很有道理,绝不是一个人会这么猜测吧。

下面我们看真正报错的原因:

Process: com.example.testviewrootimpl, PID: 10544

java.lang.RuntimeException: Can’t create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()

at android.os.Handler.(Handler.java:207)

at android.os.Handler.(Handler.java:119)

at android.app.Dialog.(Dialog.java:133)

at android.app.Dialog.(Dialog.java:162)

at com.example.testviewrootimpl.QuestionDialog.(QuestionDialog.java:17)

at com.example.testviewrootimpl.MainActivity.showQuestionInDialog(MainActivity.java:46)

at com.example.testviewrootimpl.MainActivity.access$100(MainActivity.java:10)

at com.example.testviewrootimpl.MainActivity$2.run(MainActivity.java:40)

Can’t create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()

虽然猜错了,但是依旧有点熟悉的感觉,以前大家在子线程弹toast的时候是不是见过类似的错误。

作为一个老鸟,遇到这个问题,肯定是不在UI线程弹Dialog,但是应届小哥就不同了。

瞎猫遇到死耗子


小哥,直接把报错信息扔进Google,不,百度:

点开第一篇CSDN的博客:

然后迅速举一反三,在刚才show Dialog的方法中增加:

private void showQuestionInDialog(String title) {

Looper.prepare(); // 增加部分

QuestionDialog questionDialog = new QuestionDialog(this);

questionDialog.show(title);

Looper.loop(); // 增加部分

}

解决问题就是这么简单,嘴角露出一丝对自己满意的笑容。

再次运行App…

这里大家再停一下。

凭各位多年的经验,我想再问一句,这次还会崩溃吗?

会吗?

猜想:

这代码治标不治本,还是没有在UI线程执行相关代码,还是会崩,而却刚才的show里面还有TextView#setText操作

有点道理。

看一下运行效果:

没有崩溃…

是不是有一丝的郁闷?

没关系,作为拥有多年经验的老鸟,总能立马想到解释的理由:

大家都知道在Activity#onCreate的时候,我们开个线程去执行Text#setText也不会崩溃,原因是ViewRootImpl那时候还没初始化,所以这次没崩溃也是一个原因。

对应源码解释是这样的:

Dialog源码

public void show() {

// 省略一堆代码

mWindowManager.addView(mDecor, l);

}

我们首次创建的Dialog,第一次调用show方法,内部确实会执行mWindowManager.addView,这个代码会执行到:

WindowManagerImpl

@Override

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

applyDefaultToken(params);

mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);

}

这个mGlobal对象是WindowManagerGlobal,我们看它的addView方法:

WindowManagerGlobal

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

// 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.

if (index >= 0) {

removeViewLocked(index, true);

}

throw e;

}

}

果然立马有new ViewRootImpl的代码,你看ViewRootImpl没有创建,所以这和Activity那个是一个情况。

好像有那么点道理哈…

我们继续往下看。

应届小哥要继续做需求了。

一个隐藏的问题


接下来的需求很奇怪,就是当询问"鸿洋帅气吗?"的时候,如果你点击不是,那么Dialog不消失,在问题的末尾再加一个?号,如此循环,永不关闭。

这难不倒我们的小哥:

mBtnNo.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

String s = mTvTitle.getText().toString();

mTvTitle.setText(s+“?”);

}

});

运行效果:

很完美。

如果我问,你觉得这个代码有问题吗?

你往上看了几眼,就这两行代码有个鸡儿问题,可能有空指针?

当然不是。

我稍微修改一下代码:

mBtnNo.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

String s = mTvTitle.getText().toString();

mTvTitle.setText(s+“?”);

boolean uiThread = Looper.myLooper() == Looper.getMainLooper();

Toast.makeText(getContext(),"Ui thread = " + uiThread , Toast.LENGTH_LONG).show();

}

});

每次点击的时候,我弹了个Toast,输出当前线程是不是UI线程。

看下效果:

发现问题了吗?

出乎自己的意料吗?

我们在非UI线程一直在更新TextView的text。

这个时候,你不能跟我扯什么ViewRootImpl还没有创建了吧?

别急…

还有更刺激的。

更刺激的事情


我再改一下代码:

private Handler sUiHandler = new Handler(Looper.getMainLooper());

public QuestionDialog(@NonNull Context context) {

super(context);

setContentView(R.layout.dialog_question);

mBtnNo.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

sUiHandler.post(new Runnable() {

@Override

public void run() {

String s = mTvTitle.getText().toString();

mTvTitle.setText(s+“?”);

}

});

}

});

}

我搞了个UI线程的handler,然后post一下Runnable,确保我们的TextView#setText在UI线程执行,严谨而又优雅。

再停一下,以各位多年经验,这次会崩溃吗?

按照我写博客的套路,这次肯定是演示崩溃呀,不然博客怎么往下写。

好像是这个道理…

我们跑一下效果:

[图片上传中…(image-bfd5c6-1588405433234-1)]

点击了几下,没崩…

// 配图:小朋友,你是不是有很多问号。

作为拥有多年经验的老鸟,总能立马想到解释的理由:

UI线程更新当然不会崩溃呀(言语中有一丝不自信)。

是吗?

我们多点击几次:

崩溃了…

但是刚才在没有添加UiHandler.post之前可没有崩溃哟。

这个结果,我都得把代码露出来了,怕你们说我演你们…

好了,再停一停。

我又要问大家一个问题了,这次你猜是什么崩溃?

是不是求我别搞你们了,直接揭秘吧。

com.example.testviewrootimpl E/AndroidRuntime: FATAL EXCEPTION: main

Process: com.example.testviewrootimpl, PID: 18323

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8188)

at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1421)

at android.view.View.requestLayout(View.java:24434)

at android.view.View.requestLayout(View.java:24434)

at android.view.View.requestLayout(View.java:24434)

at android.view.View.requestLayout(View.java:24434)

at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:380)

at android.view.View.requestLayout(View.java:24434)

at android.widget.TextView.checkForRelayout(TextView.java:9667)

at android.widget.TextView.setText(TextView.java:6261)

at android.widget.TextView.setText(TextView.java:6089)

at android.widget.TextView.setText(TextView.java:6041)

at com.example.testviewrootimpl.QuestionDialog$1$1.run(QuestionDialog.java:38)

at android.os.Handler.handleCallback(Handler.java:883)

at android.os.Handler.dispatchMessage(Handler.java:100)

at android.os.Looper.loop(Looper.java:214)

at android.app.ActivityThread.main(ActivityThread.java:7319)

at java.lang.reflect.Method.invoke(Native Method)

at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)

at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:934)

那个熟悉的身影回来了:

Only the original thread that created a view hierarchy can touch its views.

但是!

但是!

这次可是在切换到UI线程抛出来的。

对应我开头的灵魂拷问:

结尾

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

odAndArgsCaller.run(RuntimeInit.java:492)

at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:934)

那个熟悉的身影回来了:

Only the original thread that created a view hierarchy can touch its views.

但是!

但是!

这次可是在切换到UI线程抛出来的。

对应我开头的灵魂拷问:

结尾

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~

[外链图片转存中…(img-YmvUr2HZ-1714823341300)]

[外链图片转存中…(img-0aClmE7J-1714823341301)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值