最后是今天给大家分享的一些独家干货:
【Android开发核心知识点笔记】
【Android思维脑图(技能树)】
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
【Android高级架构视频学习资源】
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
“小兄弟,你这个是没有在UI线程执行UI操作导致的错误,你搞个UI线程的handler.post一下就好了”。
但是…
我今天要说,真是是只有UI线程才能更新UI吗?
你作为一只老鸟,肯定立马脑子里闪过:
我知道你这文章写啥了,又要在Activity#onCreate,去搞个线程执行TextView#setText,然后发现更新成功了,是不是?
这多年以前我就看过这样的文章,ViewRootImpl还没创建而已。
看你们这么强,我这个文章没法写下去了…
但是我这个人专治各种不服好吧,我换个问题:
UI线程更新UI就不会出现上面的错误了吗?
好了,开讲。
下面是一个应届小哥小奇写需求的故事。
注意本文代码为应届小哥角度所写,为了引出问题及原理,不要随意参考,另外如果尝试复现相关代码,务必看好每一个字符,甚至xml里面的属性都很关键
小哥的需求
需求很简单,就是:
- 点击一个按钮;
- Server会下发一个问题,客户端Dialog展示;
- 在Dialog交互回答问题;
是不是很简答。
小哥怒写一波代码:
package com.example.testviewrootimpl;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Button mBtnQuestion;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnQuestion = findViewById(R.id.btn_question);
mBtnQuestion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
requestAQuestion();
}
});
}
private void requestAQuestion() {
new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟服务器请求,返回问题
String title = “鸿洋帅气吗?”;
showQuestionInDialog(title);
}
}.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();
}
}
很简答,就一个标题,两个按钮。
然后我们在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+“?”);
}
});
}
});
}
文末
不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊
小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。
其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊**
小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。
[外链图片转存中…(img-FcUgNZ8p-1715356457963)]
其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!