Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中計時的兩種方法

Android中計時的兩種方法

編輯:關於Android編程

1.Android中計時

趁最近兩周不忙,自己支配的時間比較多,正好查漏補缺,這兩天看了些Thread的基礎知識,正好工作有個需求就是要記時。就把想到的記錄一下。

在Android中實現計時,有好幾種方式,我用過的是單獨開啟一個Thread和利用Handler。單獨開一個線程的話,沒有辦法直接進行UI更新,想到的依然是借助Handler。感覺還是直接利用Handler比較方便容易一下。效果圖,簡單還丑。
這裡寫圖片描述


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()。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.最後

上篇博客說實現了兩種計時後,就嘗試自己來寫一下多線程下載一個大文件。這正好可以用來學習Thread和I/O的知識。本打算明天就寫的,但明天周末打算回家一趟。既然回家了,就不敲代碼了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved