編輯:關於Android編程
趁最近兩周不忙,自己支配的時間比較多,正好查漏補缺,這兩天看了些Thread的基礎知識,正好工作有個需求就是要記時。就把想到的記錄一下。
在Android中實現計時,有好幾種方式,我用過的是單獨開啟一個Thread和利用Handler。單獨開一個線程的話,沒有辦法直接進行UI更新,想到的依然是借助Handler。感覺還是直接利用Handler比較方便容易一下。效果圖,簡單還丑。
通過最近的學習,對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的知識。本打算明天就寫的,但明天周末打算回家一趟。既然回家了,就不敲代碼了。
package com.gc.textswitcherdemo; /* * 文本切換器(TextSwitcher): * 1、TextSwitcher繼承了ViewS
今天了解了一下android客戶端與服務端是怎樣交互的,發現其實跟web有點類似吧,然後網上找了大神的登陸示例,是基於IntentService的 1.後台
前幾天做的一個仿To圈個人資料界面的實現效果下面是To圈的效果Gif圖:做這個東西其實也花了一下午的時間,一開始思路一直沒理清楚,就開始盲目的去做,結果反而事倍功半。以後
1、ctrl + shift + z 我想就連沒編過程序的人都知道 ctrl + z 是回退鍵,但是很少人知道 這個“前進鍵”吧,事實上這個快捷鍵