问题描述
在项目中遇到了一个代码时序引起UI控件显示异常的bug,代码如下:
@SuppressLint("NewApi")
public class TestFragment extends DialogFragment {
private Button mButton;
//默认初始值为true
private boolean isSucess = true;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//NetworkUtil.requestNet是开启了一个异步线程做请求任务
//执行网络请求,根据返回成功与否(true or false) 来设定 button上的文字
//如下为 伪代码
isSucess = NetworkUtil.requestNet();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View viewLayout = inflater.inflate(R.layout.fragment_test, container);
mButton = viewLayout.findViewById(R.id.button2);
return viewLayout;
}
@Override
public void onResume() {
super.onResume();
//存在bug的代码
/* if (isSucess) {
mButton.setText("返回结果成功");
} else {
mButton.setText("返回结果失败");
}*/
//可以用 View.postDelayed(Runnable action, long delayMillis)方法来解决此问题
mButton.postDelayed(new Runnable() {
@Override
public void run() {
if (isSucess) {
mButton.setText("返回结果成功");
} else {
mButton.setText("返回结果失败");
}
}
}, 1000); //这里延时时间根据网络环境的好坏设置
}
}
原因分析:
一般写代码的逻辑思维,是利用Fragment的生命周期时序方法,在onCreate方法中调用网络请求,然后在onResume方法中设置文字更新UI
这里没有考虑到:请求网络的任务在异步线程中做的,什么时候返回结果是未知的,但是Fragment的各生命周期的方法肯定是顺序执行的
在onResume方法中isSucess作为判断显示文字的条件,它的状态值是不准确的,所以存在显示不准确的bug。
解决方案:
【第一种方案】 在onResume方法中,利用 View.postDelayed(Runnable action, long
delayMillis) 方法做Button上文字的延迟显示,在保证请求网络任务返回正确的结果后,再更新UI.
【第二种方案】 当然第一种方案也存在缺陷,就是在弱网的环境下,可能2秒都无法返回正确的请求结果,这时就需要用在网络请求的回调接口中去更新UI, 代码如下:
//网络请求伪代码片段
public class NetworkUtils {
//回调接口
public interface HttpCallbackListener {
void onFinish(boolean status);
}
public static void getStatus(final HttpCallbackListener listener) {
//网络中的请求任务,当正常返回结果
if (null != listener) {
listener.onFinish(true);
}
//网络请求任务,当出现异常时
if (null != listener) {
listener.onFinish(false);
}
}
}
//DialogFragment中的伪代码片段
@Override
public void onResume() {
NetworkUtils.getStatus(new HttpCallbackListener() {
@Override
public void onFinish(boolean status)
if (status == true)
mButton.setText("返回结果成功");
} else {
mButton.setText("返回结果失败");
}
} );
}
延伸思考:
在Android某个组件中,做延迟处理一般有如下几种方法:
1. 使用线程休眠延时: Thread.sleep(long time) 方法
2. Timer定时器: Timer.schedule(TimerTask task, long delay)方法
3. 最常用的Handler: Handler.postDelayed(Runnable r, long delayMillis)方法
4. AlarmManager : 这个一般用于精准定时/延时的任务 如: setExact( )等方法
之前我是想用Handler去实现的,后来仔细查了下API, 对于某个UI控件延时显示有直接的方法
View.postDelayed可以实现,当后来去查看了下源码,发现最终还是调用的主线程的
Handler.postDelayed()方法实现的。
/**
* <p>Causes the Runnable to be added to the message queue, to be run
* after the specified amount of time elapses.
* The runnable will be run on the user interface thread.</p>
*
* @param action The Runnable that will be executed.
* @param delayMillis The delay (in milliseconds) until the Runnable
* will be executed.
*
* @return true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the Runnable will be processed --
* if the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*
* @see #post
* @see #removeCallbacks
*/
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.postDelayed(action, delayMillis);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().postDelayed(action, delayMillis);
return true;
}
具体源码追踪和分析,可以参考大神的这篇博客文章:
·