研究这两个的区别好久了,一个定时器用Handler做出来了,但是用timer怎么都做不出来,现在终于明白了,好开心。
这是我学到的一句话:Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。
在我们Android开发过程中,经常需要执行一些短周期的定时任务,这时候有两个选择Timer或者Handler。然而个人认为:Handler在多个方面比Timer更为优秀,更推荐使用。
- 可重复执行
Handler可以重复执行某个任务。
Timer若在某个任务执行/取消之后,再次执行则会抛出一个IllegalStateException异常。为了避免这个异常,需要重新创建一个Timer对象。
- UI界面更新
Handler:在创建的时候可以指定所在的线程,一般在Activity中构建的,即主线程上,所以可以在回调方法中很方便的更新界面。
Timer:异步回调,所以必须借助Handler去更新界面,不方便。
既然都得用Handler去更新界面了,为何不如把定时的功能也交给Handler去做呢?
这是实现音乐播放器的功能,随着音乐的播放,自动更新时间。
- Timer实现
package com.mingrisoft;
import java.io.File;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
public class MainActivity extends Activity implements OnSeekBarChangeListener {
private MediaPlayer player = new MediaPlayer(); // MediaPlayer对象
private boolean isPause = false; // 是否暂停
private File file; // 要播放的音频文件
private TextView hint; // 声明显示提示信息的文本框
private SeekBar sbar;
private Timer timer, timer2;
private TimerTask task, task2;
private int position;
private TextView mTextView_one, mTextView_two;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
sbar = (SeekBar) findViewById(R.id.sbar);
final Button button1 = (Button) findViewById(R.id.button1); // 获取播放按钮
final Button button2 = (Button) findViewById(R.id.button2); // 获取“暂停/继续”按钮
final Button button3 = (Button) findViewById(R.id.button3); // 获取“停止”按钮
mTextView_one = (TextView) findViewById(R.id.main_textview_time_one);
mTextView_two = (TextView) findViewById(R.id.main_textview_time_two);
file = new File("/mnt/sdcard/Gary Allan - One More Time.mp3"); // 获取要播放的文件
sbar.setOnSeekBarChangeListener(this);
// 初始化计时器
timer = new Timer();
task = new TimerTask() {
@Override
public void run() {
if (player != null && player.isPlaying()) {
final int progress = player.getCurrentPosition();
int total = player.getDuration();
sbar.setMax(total);
sbar.setProgress(progress);
//修改界面的相关设置只能在UI线程中执行
runOnUiThread(new Runnable() {
@Override
public void run() {
String time = formatTime(progress);
mTextView_one.setText(time);
}
});
}
}
};
timer.schedule(task, 1000, 1000);
// 为MediaPlayer对象添加完成事件监听器
player.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
play(); // 重新开始播放
}
});
// 为“播放”按钮添加单击事件监听器
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
play();// 开始播放音乐
if (isPause) {
button2.setText("暂停");
isPause = false; // 设置暂停标记变量的值为false
}
button2.setEnabled(true); // “暂停/继续”按钮可用
button3.setEnabled(true); // “停止”按钮可用
button1.setEnabled(false); // “播放”按钮不可用
}
});
// 为“暂停/继续”按钮添加单击事件监听器
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (player.isPlaying() && !isPause) {
player.pause(); // 暂停播放;
isPause = true;
((Button) v).setText("继续");
button1.setEnabled(true); // “播放”按钮可用
} else {
player.start(); // 继续播放
((Button) v).setText("暂停");
isPause = false;
button1.setEnabled(false); // “播放”按钮不可用
}
}
});
// 为“停止”按钮添加单击事件监听器
button3.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
player.stop(); // 停止播放;
button2.setEnabled(false); // “暂停/继续”按钮不可用
button3.setEnabled(false); // “停止”按钮不可用
button1.setEnabled(true); // “播放”按钮可用
}
});
}
// 播放音乐的方法
private void play() {
try {
player.reset();
player.setDataSource(file.getAbsolutePath()); // 重新设置要播放的音频
player.prepare(); // 预加载音频
player.start(); // 开始播放
int position = player.getDuration();
mTextView_two.setText(formatTime(position));
} catch (Exception e) {
e.printStackTrace(); // 输出异常信息
}
}
@Override
protected void onDestroy() {
if (player.isPlaying()) {
player.stop(); // 停止音频的播放
}
player.release(); // 释放资源
timer.cancel();
task.cancel();
timer = null;
task = null;
super.onDestroy();
}
@Override
public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {
// TODO Auto-generated method stub
}
@Override
public void onStartTrackingTouch(SeekBar arg0) {
// TODO Auto-generated method stub
}
@Override
public void onStopTrackingTouch(SeekBar arg0) {
// TODO Auto-generated method stub
int position = sbar.getProgress();
if (player != null && player.isPlaying()) {
player.seekTo(position);
}
}
private static String formatTime(int time) {
if (time / 1000 % 60 < 10) {
return time / 1000 / 60 + ":0" + time / 1000 % 60;
} else {
return time / 1000 / 60 + ":" + time / 1000 % 60;
}
}
}
- Handler实现
package com.example.mymusic;
import java.io.File;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
public class Main extends Activity implements OnClickListener {
MediaPlayer mMediaPlayer;
Button mPlayButton, mPauseButton, mStopButton, mForWard, mBackWard;
SeekBar mSeekBar;
TextView mTextView_one, mTextView_two;
Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (mMediaPlayer != null) {
int currentPosition = mMediaPlayer.getCurrentPosition();
mSeekBar.setProgress(currentPosition);
String time = formatTime(currentPosition);
mTextView_one.setText(time);
mHandler.sendEmptyMessageDelayed(0, 1000);
}
};
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initView();
setPlayListener();
setPause();
setStopListener();
setSeekBarListener();
}
/********************************************************
* 初始化控件
*/
private void initView() {
mPlayButton = (Button) findViewById(R.id.button1);
mPauseButton = (Button) findViewById(R.id.button2);
mStopButton = (Button) findViewById(R.id.button3);
mForWard = (Button) findViewById(R.id.media_forward);
mBackWard = (Button) findViewById(R.id.media_backwrad);
mSeekBar = (SeekBar) findViewById(R.id.seekBar);
mTextView_one = (TextView) findViewById(R.id.main_textview_time_one);
mTextView_two = (TextView) findViewById(R.id.main_textview_time_two);
mForWard.setOnClickListener(this);
mBackWard.setOnClickListener(this);
}
/*******************************************************
* 必须在方法中初始化mMediaPlayer对象 播放音乐的初始化动作 setDataSource(path)设置音频的路径就是mp3的路径
* mMediaPlayer.prepare()此方法就是装载音频文件
* mMediaPlayer.start();此方法必须在上面的方法执行完毕后才可以
*
* @param path
* 传入音频文件的路径参数
*/
public void play(String path) {
if (mMediaPlayer == null) {
try {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setDataSource(path);
mMediaPlayer.prepare();
mMediaPlayer.start();
int position = mMediaPlayer.getDuration();
mTextView_two.setText(formatTime(position));
mSeekBar.setMax(position);
mHandler.sendEmptyMessageDelayed(0, 1000);
} catch (Exception e) {
e.printStackTrace();
}
} else if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
mMediaPlayer.start();
}
}
/**********************************************************
* 设置播放按钮的点击事件 同时执行播放音乐方法 此点击事件采用的是匿名内部类的方式实现的
*/
public void setPlayListener() {
mPlayButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
play("/mnt/sdcard/a.mp3");
}
});
}
/*********************************************************
* 暂停当前播放的音乐
*/
@SuppressLint("SdCardPath")
public void setPause() {
mPauseButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
}
}
});
/*
* if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
* mMediaPlayer.pause(); }
*/
}
/*************************************************
* 设置点击暂停按钮的点击事件 同时执行mMediaPlayer的暂停pause方法
* 此点击事件的方式是采用xml属性中设置onclick这个属性的方法
*/
public void pause(View v) {
setPause();
}
/************************************************
* 停止播放音乐的方法 同时我们要释放内存,节约内存 把mMediaPlayer赋值为空,返回到开始播放的状态
*/
public void stop() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
}
/*************************************************
* 设置停止按钮的监听事件 此按钮的监听事件采用第三种方式
*/
public void setStopListener() {
mStopButton.setOnClickListener(listener);
}
OnClickListener listener = new OnClickListener() {
@Override
public void onClick(View v) {
stop();
}
};
/***************************************************
* 设置快进10秒方法
*/
public void forWard() {
if (mMediaPlayer != null) {
int position = mMediaPlayer.getCurrentPosition();
mMediaPlayer.seekTo(position + 10000);
}
}
/*****************************************************
* 设置后退10秒的方法
*/
public void backWard() {
if (mMediaPlayer != null) {
int position = mMediaPlayer.getCurrentPosition();
if (position > 10000) {
position -= 10000;
} else {
position = 0;
}
mMediaPlayer.seekTo(position);
}
}
/********************************************************
* 设置seekbar的拖动监听事件
*/
public void setSeekBarListener() {
mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
public void onStopTrackingTouch(SeekBar seekBar) {
}
public void onStartTrackingTouch(SeekBar seekBar) {
}
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// TODO Auto-generated method stub
if (mMediaPlayer != null && fromUser) {
mMediaPlayer.seekTo(progress);
}
}
});
}
/*******************************************************
* 关闭当前页面,停止播放
*/
@Override
protected void onDestroy() {
super.onDestroy();
stop();
}
/********************************************************
* 设置快进和后退按钮的监听事件
*
* @param v
*/
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.media_forward:
forWard();
break;
case R.id.media_backwrad:
backWard();
break;
default:
break;
}
}
/********************************************************
* 对时间的格式化
*
* @param time
* @return
*/
public static String formatTime(int time) {
if (time / 1000 % 60 < 10) {
return time / 1000 / 60 + ":0" + time / 1000 % 60;
} else {
return time / 1000 / 60 + ":" + time / 1000 % 60;
}
}
}
总结:
主线程不更新UI,在Timer的例子里面,UI的改变是在主线程里面,所以会有线程安全问题,我想把它单独出来,水平有限,而且代码比较粗糙,不建议使用。
下面是二者安全使用的对比:
http://blog.csdn.net/dany1202/article/details/6129548