每日一题,书接上文每日一题(Android相关)(答案次日更新)
2018.09.13
Q:在Java中,请说说finally与return的执行顺序?
A:
- 首先,finally是不一定被执行的,因为当你的程序没有进入try或者在try中直接强行终止(执行System.out.exit(0))时,finally是不会执行的;
- 然后当finally中有return时,此时函数的返回值就是finally中的return,这时无论try或者catch中的return都是无效的,而且函数的return也是无效的(finally中有return的话函数如果还有return编译器会报错)
- 然后当finally中没有return语句,finally中的语句是可以改变函数返回值的,这里涉及到一个值传递和引用传递的问题了,详细可以看看下面这两个函数:
public class Test {
public static void main(String[] args) {
System.out.println(test1());
System.out.println(test2());
}
public static int test1() {
int a = 10;
try {
a += 5;
return a;
} catch (Exception e) {
a += 10;
return a;
} finally {
a += 20;
}
}
public static A test2() {
A aClass = new A(10);
try {
aClass.a += 5;
return aClass;
} catch (Exception e) {
aClass.a++;
return aClass;
} finally {
aClass.b=10;
aClass.a += 20;
}
}
private static class A {
int a;
int b;
public A(int a) {
this.a = a;
}
@Override
public String toString() {
return "a=" + a + "\nb=" + b;
}
}
}
return:
15
a=35
b=10
复制代码
看到这里相信有部分童鞋可能不理解了,为什么会这样呢?在Java中有着值传递,而非引用传递这一说,简单来说,就是基本类型作为参数传递时,是传递值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的,test1中先执行了一个return语句,返回栈中有了一个
a
,当执行到finally中时,做了a = a +· 20
的操作,此时的a
已经不是返回栈中的a
了,a
的引用发生了改变,所以不回传给返回栈中的a
了,所以test1返回值是15
;而对象作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数,在test2中,我们把A直接返回了,而在finally中操作的是A对象的属性,所以这样是可以成功的。总结:finally块的语句在try或catch中的return语句执行之后,在函数return语句之前执行的
2018.09.25
Q:请从源码的角度谈谈Activity,View,Window之间的关系?
A:详解参考
2018.09.26
Q:Android子线程可以更新UI吗?为什么?
A:相信我们大多数人在刚开始学习Android的时候已经知道Android UI操作不是线程安全的,更新UI的操作一定要在主线程中进行,但事实上真的是这样吗?我们不妨来看看下面这段代码:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
new Thread(() -> {
textView.setText("你好啊");
}
}
复制代码
不知道大家有没有试过,这样是可以的,程序不会报错,相信很多人会有疑问了,为什么会这样呢?是不是感觉幼小的心灵受到了欺骗 >_< 别急,我们再来改造一下,请看下面这段代码:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
new Thread(() -> {
try {
Thread.sleep(1000);
textView.setText("你好啊");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
咦~怎么报错了
Only the original thread that created a view hierarchy can touch its views.
为什么会这样呢?不延迟就不报错,延迟了就报错。下面我们来分析一下,先找到这个错是在哪里报的,这个应该不难找吧ctrl + shift + f
全部搜索一下这个报错信息,我们发现是在ViewRootImpl
这个类中的checkMain
方法中,刚好我们昨天分析从源码的角度浅谈Activity、Window、View之间的关系的时候有介绍过:
在
handleResumeActivity
中调用了addView
方法,在这之前,Activity
的onCreate
方法已经被调用了,而ViewRootImpl
的初始化是在WindowManagerGlobal
中的addView
方法中:
WindowManagerGlobal类:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
...
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) {
}
...
}
复制代码
初始化完
ViewRootImpl
之后,会调用它的setView
方法:
ViewRootImpl类:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();
...
}
@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.");
}
}
复制代码
看到这里相信大家应该就明白了,因为
onCreate
方法在ViewRootImpl
初始化之前就已经被调用了;还有一个问题就是,我在下面那个方法中更新UI了,ViewRootImpl
中的requestLayout
是怎么被调用的呢?因为这里调用了setText
改变了UI
,这个方法会触发ViewRootImpl
中的requestLayout
,有疑问的同学可以参考我的另外一篇文章带着问题去看源码——TextView篇总结:在UI线程中能更新UI吗?答案是能的,只不过我们在日常开发中很少这样干,大多都是请求后台之后然后再去更新UI,这个时候我们就不能在子线程中更新UI了。