问题
在 PlyRP 中需要在界面上实时显示目前媒体的时长/剩余时长,TimeTask 本身是一个子线程,但在 Android 的子线程去更新 UI 的内容,会导致不确定的异常。
在非 UI 线程中刷新界面的时候,UI 线程(或者其他非 UI 线程)也在刷新界面,这样就导致多个界面刷新的操作不能同步,导致线程不安全。
UI 线程及 Android 的单线程模型原则
当应用启动,系统会创建一个主线程(main thread)。
这个主线程负责向 UI 组件分发事件(包括绘制事件),也是在这个主线程里,你的应用和 Android 的 UI 组件(components from the Android UI toolkit (components from the android.widget and android.view packages))发生交互。
所以 main thread 也叫 UI thread 也即 UI 线程。
系统不会为每个组件单独创建线程,在同一个进程里的 UI 组件都会在 UI 线程里实例化,系统对每一个组件的调用都从 UI 线程分发出去。
结果就是,响应系统回调的方法(比如响应用户动作的 onKeyDown() 和各种生命周期回调)永远都是在 UI 线程里运行。
Android 单线程模型的两条原则
当 App 做一些比较重(intensive)的工作的时候,除非你合理地实现,否则单线程模型的 performance 会很 poor。
特别的是,如果所有的工作都在 UI 线程,做一些比较耗时的工作比如访问网络或者数据库查询,都会阻塞 UI 线程,导致事件停止分发(包括绘制事件)。对于用户来说,应用看起来像是卡住了,更坏的情况是,如果 UI 线程 blocked 的时间太长(大约超过 5 秒),用户就会看到 ANR(application not responding)的对话框。
另外,Andoid UI toolkit 并不是线程安全的,所以你不能从非 UI 线程来操纵 UI 组件。你必须把所有的 UI 操作放在 UI 线程里,所以 Android 的单线程模型有两条原则:
- 不要阻塞 UI 线程。
- 不要在 UI 线程之外访问 Android UI toolkit(主要是这两个包中的组件:android.view)。
方案
Android 提供了几种从其他线程访问主线程的方式:
- Activity.runOnUiThread(Runnable)
还没写
- View.post(Runnable)
还没写
- View.postDelayed(Runnable, long)
还没写
- Handler
可以使用 Handler 机制主动进行信息的传递再更新,避免在主 UI 线程外更新界面导致的线程不安全。
Handler 是什么
handler 是 android 线程之间的消息机制,主要的作用是将一个任务切换到指定的线程中去执行(准确的说是切换到构成 handler 的 looper 所在的线程中去出处理),android 系统中的一个例子就是主线程中的所有操作都是通过主线程中的 handler 去处理的。
只有在 UI 线程中的对象才能操作 UI 线程中的对象,为了将非 UI 线程中的数据传送到 UI 线程,可以使用一个 Handler 运行在 UI 线程中。
Handler 是 Android framework 中管理线程的部分,一个 Handler 对象负责接收消息然后处理消息。
你可以为一个新的线程创建一个 Handler,也可以创建一个 Handler 然后将它和已有线程连接。
如果你将一个 Handler 和你的 UI 线程连接,处理消息的代码就将会在 UI 线程中执行。 具体可以看[3]
可以在你创建线程池的类的构造方法中实例化 Handler 的对象,然后用全局变量存储这个对象。
要和 UI 线程连接,实例化 Handler 的时候应该使用
当你用一个特定的 Handler 时,这个 Handler 就运行在这个在 Handler 中,要覆写会在 Handler 管理的相应线程收到新消息时调用这个方法。一个特定线程的所有 Handler 对象都会收到同样的方法。(这是一个“一对多”的关系)。
解决
问题以倒计时为例,构建的倒计时可以完成指定时间按指定时间间隔完成倒数。
必要定义
import java.util.Timer;
import java.util.TimerTask;
private int timecount = 0;
private TextView TimeText;
private Timer timer = null;
private TimerTask task = null;
必要初始化
TimeText = (TextView) findViewById(R.id.TimeText);
主体
若需要启动倒计时,调用 startTime(),同样,若需要停止计时,调用 stopTime()即可。
private Handler mHandler = new Handler(){
public void handleMessage(Message msg) {
TimeText.setText(msg.arg1+"");
startTime();
};
};
public void startTime(){
timer = new Timer();
task = new TimerTask() {
@Override
public void run() {
i--;
Message message = mHandler.obtainMessage();
message.arg1 = i;
mHandler.sendMessage(message);
}
};
timer.schedule(task, 1);
/*
java.util.Timer定时器的常用方法如下:
schedule(TimerTask task, long delay) // 延迟delay毫秒后,执行一次task。
schedule(TimerTask task, long delay, long period) // 延迟delay毫秒后,执行第一次task,然后每隔period毫秒执行一次task。
*/
}
public void stopTime(){
timer.cancel();
}
声明
标签:task,倒计时,Handler,UI,组件,线程,Android From: https://blog.csdn.net/qq_17440007/article/details/142873315LocalWu 编写的文章。
文章内容仅供参考,遵循 CC 4.0 BY-SA 版权协议。