1.Android中计时
趁最近两周不忙,自己支配的时间比较多,正好查漏补缺,这两天看了些Thread的基础知识,正好工作有个需求就是要记时。就把想到的记录一下。
在Android中实现计时,有好几种方式,我用过的是单独开启一个Thread和利用Handler。单独开一个线程的话,没有办法直接进行UI更新,想到的依然是借助Handler。感觉还是直接利用Handler比较方便容易一下。效果图丑,简单。
2.本Demo中的MVP模式
通过最近的学习,对Android中使用MVP有一些了解,但并没在完整的实际项目开发中使用过。本demo中,也是通过网上的资料以及看Google官方的Demo中的MVP模式后加上自己个人的习惯所写。
我个人的习惯是:
- 创建
BasePresenter、BaseView
接口 - 创建一个代理
contract
接口,里面写好Model、View、Presenter所要实现的接口 - 创建Model
- 创建Presenter
- View层实现代理
contract
接口中的View的接口
2.1BasePresenter和BaseView接口
public interface BasePresenter {
void atFirst();
}
public interface BaseView {
void bindPresenter(T presenter);
}
这两个接口都很简单。BasePresenter中这个方法在本demo中并没有用到,是个空的方法,只做了声明并没有实现回调。这个方法可以用来初始化一些资源或者控件。BaseView使用了泛型T,目前对于泛型也只是会一些简单使用,深入的讲解不了。bindPresenter()
这个方法顾名思义,就是将Presenter和View层关联起来。
2.2代理contract接口
这个代理接口的作用就是方便管理m,v,p各层的接口。还有个好处就是,一旦定好了方法,接下来的逻辑就会很清晰了,只需要实现这些方法就可以了。这里并不需要在意接口里我声明了哪些方法。看一下形式就可以。稍微需要注意的就是interface MainView extends BaseView
这里的泛型
public interface MainContract {
interface MainBiz{
void onStartByHandler(onStartListener onStartListener);
void onStartByThread(onStartListener onStartListener);
void onStop(onStopListener onStopListener);
interface onStartListener{
void start(String time);
}
interface onStopListener{
void stop(String info,boolean b);
}
}
interface MainView extends BaseView{
void initView();
void onStop(String info,boolean b);
}
interface Presenter extends BasePresenter{
void startByHandler();
void startByThread();
void stop();
void initView(TextView tv);
void onRecycler();
}
}
2.3创建桥梁Presenter
我并没有按照前面的顺序来,因为Model层里是逻辑关键,先把简单的层给介绍了。
public class MainPresenter implements MainContract.Presenter {
private MainContract.MainView view;
private MainModel model;
private TextView tv;
public static MainPresenter newInstance(MainContract.MainView view) {
return new MainPresenter(view);
}
public MainPresenter(MainContract.MainView view) {
this.view = view;
this.view.bindPresenter(this);
model = new MainModel();
}
@Override
public void initView(TextView tv) {
this.tv = tv;
}
@Override
public void startByHandler() {
model.onStartByHandler(new MainContract.MainBiz.onStartListener() {
@Override
public void start(String time) {
if (view != null) {
view.initView();
if (tv != null) {
tv.setText(time);
}
}
}
});
}
@Override
public void startByThread() {
model.onStartByThread(new MainContract.MainBiz.onStartListener() {
@Override
public void start(String time) {
if (view != null) {
view.initView();
if (tv != null) {
tv.setText(time);
}
}
}
});
}
@Override
public void stop() {
model.onStop(new MainContract.MainBiz.onStopListener() {
@Override
public void stop(String info, boolean b) {
if (view != null)
view.onStop(info, b);
}
});
}
@Override
public void onRecycler() {
if (model != null) model = null;
if (view != null) view = null;
}
@Override
public void atFirst() { }
}
这里想说的也就两点,一个是构造方法,一个是onRecycler()方法。
Presnter是Model层和View层的桥梁。构造方法将3者给联系起来。
关于onRecycler()这个方法,我的目的是将创建的Model层、View层和Presenter层的对象置null,这样能被回收,不然这三个对象在内存并不会被回收。这个方法我会在Activity后者Frgment的onDestroy()调用。如果要用到了RecyclerView,可以再加上recyclerView.setAdapter(null)
然而,onRecycler()只是我的个人想法,我目前还没验证清楚,这个方法到底能不能起到些防止内存泄露的作用。若看博客的哪位对于有好的想法,请留言。
2.5View层实现MainContract.MainView接口
public class MainActivity extends AppCompatActivity implements MainContract.MainView {
private MainContract.Presenter presenter;
private Unbinder unbinder;
@BindView(R.id.tv_time_main_activity)
TextView tv;
@BindView(R.id.bt_handler_main_activity)
Button bt_handler;
@BindView(R.id.bt_thread_main_activity)
Button bt_thread;
@BindView(R.id.bt_stop_main_activity)
Button bt_stop;
private boolean isHasClicked = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
unbinder = ButterKnife.bind(this);
//初始化MainPresenter
MainPresenter.newInstance(this);
}
/**
* 点击Handler 按钮
*/
@OnClick(R.id.bt_handler_main_activity)
void onClickBtHandler() {
if (presenter != null && !isHasClicked) {
isHasClicked = true;
presenter.startByHandler();
}
}
/**
* 点击 Thread 按钮
*/
@OnClick(R.id.bt_thread_main_activity)
void onClickBtThread() {
if (presenter != null && !isHasClicked) {
isHasClicked = true;
presenter.startByThread();
}
}
/**
* 点击停止按钮
*/
@OnClick(R.id.bt_stop_main_activity)
void onClickBtStop() {
if (presenter != null) {
presenter.stop();
}
}
/**
* 点击按钮时 拿到显示时间的TextView
*/
@Override
public void initView() {
if (tv != null && presenter != null)
presenter.initView(tv);
}
/**
* 结束计时给出提示
*
* @param info
*/
@Override
public void onStop(String info, boolean b) {
isHasClicked = b;
ToastUtils.show(MainActivity.this, info);
}
@Override
public void bindPresenter(MainContract.Presenter presenter) {
this.presenter = presenter;
}
@Override
protected void onDestroy() {
super.onDestroy();
if (presenter != null) presenter.onRecycler();
if (unbinder != null) unbinder.unbind();
}
}
代码很简单,就是接口方法的回调。使用了ButterKnife省了findViewById(),setListener()
记得在合适的时机初始化MainPresenter就可以。
2.6Model层,实现计时逻辑
public class MainModel implements MainContract.MainBiz {
private onStartListener onStartListener;
private final int MSG_WHAT_HANDLER = 101;
private final int MSG_WHAT_THREAD = 102;
private int currentTime;
private int type = 0;
private final int TYPE_DEFAULT = 0;
private final int TYPE_HANDLER = 1;
private final int TYPE_THREAD = 2;
private final String THREAD_NAME = "thread_time";
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == MSG_WHAT_HANDLER && onStartListener != null) {
onStartListener.start(TimeUitls.milliSecondToMinute(currentTime));
currentTime += 1000;
onStartByHandler(onStartListener);
} else if (msg.what == MSG_WHAT_THREAD && onStartListener != null) {
onStartListener.start(TimeUitls.milliSecondToMinute(currentTime));
currentTime += 1000;
}
}
};
/**
* 使用Handler方式
* @param onStartListener
*/
@Override
public void onStartByHandler(onStartListener onStartListener) {
if (this.onStartListener == null) {
this.onStartListener = onStartListener;
type = TYPE_HANDLER;
}
long now = SystemClock.uptimeMillis();
long next = now + (1000 - now % 1000);
handler.sendEmptyMessageAtTime(MSG_WHAT_HANDLER, next);
}
/**
* 使用单独开启线程
* @param onStartListener
*/
@Override
public void onStartByThread(onStartListener onStartListener) {
if (this.onStartListener == null) {
this.onStartListener = onStartListener;
type = TYPE_THREAD;
}
ThreadUtils.newThread(THREAD_NAME, new Runnable() {
@Override
public void run() {
while (ThreadUtils.isAlive(THREAD_NAME)) {
handler.sendEmptyMessage(MSG_WHAT_THREAD);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
public void onStop(onStopListener onStopListener) {
if (type == TYPE_DEFAULT) {
onStopListener.stop("计时还没有开始",false);
return;
}
if (type == TYPE_HANDLER) {
handler.removeMessages(MSG_WHAT_HANDLER);
} else if (type == TYPE_THREAD) {
ThreadUtils.killThread(THREAD_NAME);
}
onStopListener.stop("计时结束",false);
currentTime = 0;
onStartListener = null;
type = TYPE_DEFAULT;
}
}
在onStartByHandler()
方法中,Handler发送一个Message的方法用的是handler.sendEmptyMessageAtTime()
,并没有使用handler.sendEmptyMessageDelayed()
。这里是看了徐医生大神的博客强迫症的研究——MediaPlayer播放进度条的优化中学习到的一个知识点。long next = now + (1000 - now % 1000);
这短短的一行代码可以有误差补偿的作用,算法这玩意果然好神奇。我也直接在Handler的handleMessage()
方法中,在onStartByHandler(onStartListener)
这句前,直接用Thread.sleep(500)
验证了下,拿另外一个手机打开系统带的计时器,同时看两个手机,感觉两次计时的间隔还是1s,也可能是感觉不出来。但设置900后,就明显感觉到两次计时间隔不是1s了。这里还有待继续了解。但目前直接来用,是没有问题的。我把手机放在那不管,一直50分钟也没有问题。
在onStartByThread()
方法中,就是开启一个子线程,每隔1s利用Handler发一个空消息。结束一个Thread,就是让run()
方法结束就可以了,只需要将while()
循环的条件改为false
就可以。这里我简单实现了一个工具类,可以直接方便的更改循环条件。
3.0补充
在2.6中,private Handler handler = new Handler() {...}
,Handler没有使用静态,这时Android Studio会给一个警告This Handler class should be static or leaks might occur (anonymous android.os.Handler)
。这是因为,如果Handler等待在执行一个延时操作,而此时想要把Activity销毁,点击Back键,虽然Activity执行了onDestroy()
方法,就会因为此时Handler还持有Activity的对象而不会被立刻回收,就会造成内存泄漏。所以,应该把Handler改为静态内部类。
public class MainModel implements MainContract.MainBiz {
private onStartListener onStartListener;
private final int MSG_WHAT_THREAD = 102;
private int currentTime;
private int type = 0;
private final int TYPE_DEFAULT = 0;
private final int TYPE_HANDLER = 1;
private final int TYPE_THREAD = 2;
private final String THREAD_NAME = "thread_time";
private StaticHandler handler = null;
public static class StaticHandler extends Handler {
private final WeakReference mActivity;
public StaticHandler(MainActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity mainActivity = mActivity.get();
MainContract.MainBiz.onStartListener onStartListener = (MainContract.MainBiz.onStartListener) msg.obj;
if (mainActivity != null && onStartListener != null) {
if (msg.what == 102) {
onStartListener.start(TimeUitls.milliSecondToMinute(msg.arg1));
}
}
}
}
private Runnable runnable = new Runnable() {
@Override
public void run() {
onStartListener.start(TimeUitls.milliSecondToMinute(currentTime));
long now = SystemClock.uptimeMillis();
long next = now + (1000 - now % 1000);
currentTime += 1000;
handler.postAtTime(runnable, next);
}
};
/**
* 使用Handler方式
*/
@Override
public void onStartByHandler(MainActivity mainActivity, onStartListener onStartListener) {
if (this.onStartListener == null) {
this.onStartListener = onStartListener;
type = TYPE_HANDLER;
handler = new StaticHandler(mainActivity);
handler.post(runnable);
}
}
/**
* 使用单独开启线程
*/
@Override
public void onStartByThread(MainActivity activity, final onStartListener onStartListener) {
if (this.onStartListener == null) {
handler = new StaticHandler(activity);
this.onStartListener = onStartListener;
type = TYPE_THREAD;
ThreadUtils.newThread(THREAD_NAME, new Runnable() {
@Override
public void run() {
while (ThreadUtils.isAlive(THREAD_NAME)) {
Message message = handler.obtainMessage();
message.what = MSG_WHAT_THREAD;
message.obj = onStartListener;
message.arg1 = currentTime;
handler.sendMessage(message);
currentTime += 1000;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
@Override
public void onStop(onStopListener onStopListener) {
if (type == TYPE_DEFAULT) {
onStopListener.stop("计时还没有开始", false);
return;
}
if (type == TYPE_HANDLER) {
handler.removeCallbacks(runnable);
} else if (type == TYPE_THREAD) {
ThreadUtils.killThread(THREAD_NAME);
}
onStopListener.stop("计时结束", false);
currentTime = 0;
onStartListener = null;
type = TYPE_DEFAULT;
}
}
主要是利用WeakReference,来进行Activity对象的弱引用。
主要就是onStartByHandler()
方法进行修改。
代码已更新。
3.1最后
上篇博客说实现了两种计时后,就尝试自己来写一下多线程下载一个大文件。这正好可以用来学习Thread和I/O的知识。本打算明天就写的,但明天周末打算回家一趟。既然回家了,就不敲代码了。最后,刚刚学会了一点Android Studio使用Git,就把本篇的代码放了上去。
代码