大家都知道,如果在线程中要更新UI的话,有两种方法,一种是handler,还一种就是runOnUiThread。
我这里要说的是runOnUiThread。
大家在网上看例子的时候,基本无一例外都是在线程中这么写的
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
myTextView.setText("更新");//UI更新
}
});
那么问题来了,这样做是有巨大风险的。为什么这么说呢,原因很简单,如果用户在线程还没结束的时候退出了Activity,Activity虽然退出了,但在Activity里开的
线程还在执行,当执行到getActivity().runOnUiThread的时候,由于Activity已经退出了,这时候,getActivity().runOnUiThread显然是要报空指针错误的。
于是,细心些的人说,那么就先判断getActivity()是不是null吧,如果不是null,再做getActivity().runOnUiThread。
听起来挺合理,但有没有人想过,有可能判断getActivity()的时候,Activity没退出,所以还不是空,但恰巧判断完后,Activity退出了(虽然这种概率很低,但也存在),那判断语句后的getActivity().runOnUiThread还是报空指针啊。。。。。
请问大神,这问题该如何解决呢?谢谢!
兄弟,你还是看看runOnUiThread方法的源码吧,runOnUiThread方法主要是封装handler的操作而已。你所说的上面巨大风险,不管用handler机制还是RunOnUi方法都会一样的。一般情况你用handler机制时都会加一个判断 if ( mHandler != null ){ } ; 如果你用RunOnUi方法同样需要加上一个条件判断 getActivity()是不是null。还有你所说的:有可能判断getActivity()的时候,Activity没退出,所以还不是空,但恰巧判断完后,Activity退出了的问题。
if ( getActivity() !=null ) getActivity().runOnUiThread(.......); 这两行代码执行的时间间隔几乎为0ms。不可能发生你所说的问题出现。如果你硬要说有可能,那么请问一下:你在线程中执行任何代码时是不是都会崩溃。
Activity中runOnUiThread方法的使用
安卓handler机制和RunOnUi方法都能修改主线程,两者的区别
runOnUiThread和在子线程运行
在线程中执行if ( getActivity() !=null ) getActivity().runOnUiThread(.......);存在执行巨大风险。难道if ( mHandler != null ) mHandler.post(.......); 就不存在巨大风险风险吗? getActivity()和mHandler是Activity对象方法和常量。
ygauf78y 兄弟,您说的对,我理解这种概率是很低的。但是咱们编程序的时候,非常低的概率也要考虑啊(不然的话,这种程序看着总觉得不舒服)。不光是我,我看了一个老帖子,您看最后那条,
也有人提到这个问题。
https://bbs.csdn.net/topics/390984829/
还有,您说“你在线程中执行任何代码时是不是都会崩溃”。我觉得只要避免了各种问题,为什么执行任何代码时是不是都会崩溃呢?请赐教。
另外,ygauf78y 兄弟,您看run()里面还有myTextView.setText("更新");这条语句对吧。这里只有一条,可能大家都觉得很快就执行完了。但夸张点说,比如要
更新1000个控件呢,那么是不是比较耗时,这时候,因为Activity已经没了,控件的更新也存在空指针问题吧。
A线程里做
while{true){
a = 0;
b = 0;
}
B线程里做
while{true){
c = 0;
d = 0;
}
A线程这个循环执行的时候,a = 0;和b = 0;之间的时间也是极短(无限接近但不等于0ms)。这时候,我估计大家都承认,a = 0;执行完后,有可能在b = 0;执行前被
B线程打断。那么同理,为什么if ( getActivity() !=null ) 和 if ( mHandler != null ) 执行后就不可能被别的线程(比如Activity退出处理)打断呢?
再极端点,从汇编上讲,a = 0;这条语句本身都未必是原子性的语句,a = 0;本身都可能被别的线程打断而分开执行。
zhongruichun 兄弟,此言差矣,如果安卓类库和我们的程序中到处都有这种存在隐患的代码,可能一天中不会出问题,但是您觉得,每个人的程序都这么编,
能保证长时间不出问题吗?
难道没有避免方法吗?比如一块代码执行期间,不被别的线程打断。这样就保证判断是否为空和下面的执行语句在数据上的一致性了。这样岂不是更严谨呢?
兄弟, getActivity().runOnUiThread(.......);不是线程啊,我叫你还是看看Activity的代码。。。。。。
兄弟,你对线程中断和activity生命周期的理解不够到位,你根本不去看android源码。你对android代码运行逻辑不是很懂。
你到现在都不知道runOnUiThread方法是如何更新ui界面的。
ygauf78y 兄弟,runOnUiThread是调用UI线程去更新UI 啊。大概懂一点,runOnUiThread里面其实还是调用handler对吧。
但这并不是我想讨论的问题。我想说的是if ( getActivity() !=null )即使判断不是空,然后突然判断完后,Activity被退出了。这时,到了getActivity().runOnUiThread这一步,由于getActivity()已经是空,所以getActivity().runOnUiThrea会出现空指针。
我现在就是觉得很多程序都有这种写法,感觉总有一天程序会突然崩溃的(当然,你可以反驳说,概率很低)。
ygauf78y 兄弟,我再举个例子,为了避免Handler内存泄露,现在网上的普遍写法都是自己定义个静态的Handler。但是handleMessage需要用Activity里面的控件怎么办呢,于是来个弱引用,但是使用弱引用时候又担心Activity已经被销毁了,于是,保险起见,先判断是不是空(如下)。
但同理,判断的时候,Activity还没销毁,刚判断完,Activity就被销毁了。那么下面的UI更新处理能不崩溃吗。
这就是我担心和想讨论的问题。
private static class MyHandler extends Handler {
private WeakReference<Context> reference; //
public MyHandler(Context context) {
reference = new WeakReference<>(context);//这里传入activity的上下文
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
runOnUiThread的这个大问题,难道没人注意到吗?
挖坟,我觉得原因是这样的。
匿名内部类持有外部类的对象,在内部类没有被回收之前,外部类不会被回收,因为和内部类关联。
所以就算activity被关闭了,生命周期全部走完,也不会被回收,所以不会出现空指针。
但是,如果内部类一直存活的话,会产生内存泄漏。
以前处理过类似问题,一个对象启动定时器,定时器超时会调用对象的处理函数,然而有一种可能就是timeout时候,对象已经被删除了,引起程序crash。 解决方法是处理定时器消息时候,要判断对象是否已经删除了,如果删除了,则不调用对象的处理函数。 但是这个方法有点问题,有时候很难判断一个对象是否已经被删除,因为对象存在再分配,即使你有对象指针或者index,也是不靠谱的。 最终的方法是如果删除一个对象,就去定时器队列scan一下,把这个对象触发的定时器全部干掉。想必android底层也做了类似工作。