Android 计时的两种思路

1.Android中计时

趁最近两周不忙,自己支配的时间比较多,正好查漏补缺,这两天看了些Thread的基础知识,正好工作有个需求就是要记时。就把想到的记录一下。

在Android中实现计时,有好几种方式,我用过的是单独开启一个Thread和利用Handler。单独开一个线程的话,没有办法直接进行UI更新,想到的依然是借助Handler。感觉还是直接利用Handler比较方便容易一下。效果图丑,简单。

Android 计时的两种思路_第1张图片
计时

2.本Demo中的MVP模式

通过最近的学习,对Android中使用MVP有一些了解,但并没在完整的实际项目开发中使用过。本demo中,也是通过网上的资料以及看Google官方的Demo中的MVP模式后加上自己个人的习惯所写。

我个人的习惯是:

  1. 创建 BasePresenter、BaseView接口
  2. 创建一个代理contract接口,里面写好Model、View、Presenter所要实现的接口
  3. 创建Model
  4. 创建Presenter
  5. 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,就把本篇的代码放了上去。
代码

你可能感兴趣的:(Android 计时的两种思路)