編輯:關於Android編程
當我們談到android應用程序的架構模式時,MVP一直是占主流的地位。就像 Ted Mosby, Nucleus and Mortar 框架都是用Presenters 架構,使我們的app結構變得非常清晰,還幫助我們解決一個湊名昭著的問題:android設備的旋轉和狀態持久。當然這不是直接的MVP概念,但事實上這一架構模型,使我們的代碼解耦。
數據綁定,是在Google I/O 2015提出並且Android M預覽版是的支持庫。聲稱改變一切。根據Wikipedia 是介紹的MVP,Presenter 有以下作用:
Presenter 根據模型和視圖。它從存儲庫(模型)中檢索數據,並將其格式化為在視圖中顯示的數據。
問題是,數據綁定框架將代替Presenter 的主要職責(作用在模型和視圖),而其余的功能則是留給增強模型–ViewModel(檢索數據庫和並格式處理)。視圖模型是ViewModel是一個標准的java類,其唯一的責任是代表在一個View中的數據。它可以合並來自多個源(模型)的數據,並展示數據。我以前有一篇在ViewModel的短文章,它主要講述了Data Model 和Transport Model的不同。
構建我們最終的MVVM–模型視圖ViewModel,MVVM概念最初是由微軟在2005年(不要讓它嚇到你了)。下面我說明MVP到MVVM的變化,引用Hanne Dorfmann介紹他的 Ted Mosby framework中使用的圖片
所有的數據的綁定和更新都是通過數據綁定框架(Data Binding Framework)來完成的。Observablefield類允許視圖對模型的變化做出反應,而XML引用字段改變時也會改變ViewModel 。當作用在視圖上時,你也可以通過程序訂閱變化,比如一個CheckBox點擊時,讓TextView時禁用。如果有一個標准的java類能夠表示View的狀態,這樣的好處顯而易見。並且我們可以很容易的單元測試。
注意在MVP上圖有一個方法調用 Presenter.loadUsers()的方法。在MVVM模式中這所有的方法都定義在 ViewMode。從維基百科文章:
The view model is an abstraction of the view that exposes public properties and commands
在MVP模式很中,你的模型很可能是“簡單”的類,它只保存數據。不要害怕把業務邏輯放在你的模型或視圖模型中。這是面向對象編程的一個核心原則。回到 Presenter.loadUsers()這個方法,我們沒有在ViewModel中調用這個方法,視圖是通過綁定在xml文件中。如果我們不把數據綁定,我們仍然必須使用以前的android語法:onclick語法,或手動添加監聽代碼。
仍然有一系列的工作在View中去做,比如初始化系統的回調,打開dialog或者涉及Context的對象。不要這些代碼放到ViewModel。如果你把像這種Context放入到ViewModel中,這是錯誤的。
我還沒有找到最好的解決辦法,但有一些可以嘗試。一種方式是在View的ViewModel保持一個presenter 一個接口的引用。這樣你就不會降低可測試性。但是沒有一個單獨的presenter 的類在我們程序中,我堅持認為,只要保持它的簡單接口的具體實現。另一種方式就像event bus或者Square Otto初始化方式 ShowToastMessage(“hello world”)(具體參見eventbus 或者otto的使用)。這將產生一個更大的分離View和ViewModel –但那是好事情嗎?
因此,Data Binding框架代替其他的框架嗎?只有部分。我希望看到的是這些框架進化為MVVM風格框架,使我們可以利用最好的數據綁定同時依賴第三方的框架到最小,並保持框架小而簡單。而Presenter 的時代就要結束了,這個Presenter僅僅做一些重要的工作比如生命周期管理和視圖狀態(ViewModel)持久性。不幸的是,這並沒有改變。
我最近了解到Android ViewModel框架,這實際上可能是一個很好的適合MVVM和Android的數據綁定。
當我第一聽說Android M 所有工作是為了改進sdk和關注開發者們。我是多麼的興奮。當接受到數據綁定的時候,我是如此的吃驚,我已經和其他平台上的數據綁定工作多年:WinForms,WPF,Silverlight和Windows手機。我知道這將有助於我們寫更清潔的架構和更少的耦合代碼。這個框架與我們一起工作,而不是反對我們,現在,它終於要來了。
但是,它仍然有一些缺點。事實上你定義的xml文件,它沒有編譯,也就是沒辦法進行單元測試。這樣你會了現許多錯誤在運行期間,而不是在編譯期間。不幸的是,如果你經常忘記綁定view呢?
這就是為什麼我希望谷歌能夠讓Android Studio支持數據綁定到最大的語法和編譯期間檢查的XML,自動完成。支持重命名在xml字段中。從我用的Android Studdio1.3測試版-中思考這樣的問題。有些東西是支持的,很也有許多不支持,但是我看到了很大的希望。
下面分別使用mvp和mvvm的代碼示例
MVP – VIEW – XML
MVP – VIEW – JAVA
package com.nilzor.presenterexample;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
import com.hannesdorfmann.mosby.mvp.MvpFragment;
import com.hannesdorfmann.mosby.mvp.MvpView;
import butterknife.InjectView;
import butterknife.OnClick;
public class MainActivityFragment extends MvpFragment implements MvpView {
@InjectView(R.id.username)
TextView mUsername;
@InjectView(R.id.password)
TextView mPassword;
@InjectView(R.id.newUserRb)
RadioButton mNewUserRb;
@InjectView(R.id.returningUserRb)
RadioButton mReturningUserRb;
@InjectView(R.id.loginOrCreateButton)
Button mLoginOrCreateButton;
@InjectView(R.id.email_block)
ViewGroup mEmailBlock;
@InjectView(R.id.loggedInUserCount)
TextView mLoggedInUserCount;
public MainActivityFragment() {
}
@Override
public MainPresenter createPresenter() {
return new MainPresenter();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
attachEventListeners();
}
private void attachEventListeners() {
mNewUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
updateDependentViews();
}
});
mReturningUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
updateDependentViews();
}
});
}
/** Prepares the initial state of the view upon startup */
public void setInitialState() {
mReturningUserRb.setChecked(true);
updateDependentViews();
}
/** Shows/hides email field and sets correct text of login button depending on state of radio buttons */
public void updateDependentViews() {
if (mReturningUserRb.isChecked()) {
mEmailBlock.setVisibility(View.GONE);
mLoginOrCreateButton.setText(R.string.log_in);
}
else {
mEmailBlock.setVisibility(View.VISIBLE);
mLoginOrCreateButton.setText(R.string.create_user);
}
}
public void setNumberOfLoggedIn(int numberOfLoggedIn) {
mLoggedInUserCount.setText("" + numberOfLoggedIn);
}
@OnClick(R.id.loginOrCreateButton)
public void loginOrCreate() {
if (mNewUserRb.isChecked()) {
Toast.makeText(getActivity(), "Please enter a valid email address", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getActivity(), "Invalid username or password", Toast.LENGTH_SHORT).show();
}
}
}
MVP – PRESENTER
package com.nilzor.presenterexample;
import android.os.Handler;
import android.os.Message;
import com.hannesdorfmann.mosby.mvp.MvpPresenter;
public class MainPresenter implements MvpPresenter {
MainModel mModel;
private MainActivityFragment mView;
public MainPresenter() {
mModel = new MainModel();
}
@Override
public void attachView(MainActivityFragment view) {
mView = view;
view.setInitialState();
updateViewFromModel();
ensureModelDataIsLoaded();
}
@Override
public void detachView(boolean retainInstance) {
mView = null;
}
private void ensureModelDataIsLoaded() {
if (!mModel.isLoaded()) {
mModel.loadAsync(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
updateViewFromModel();
return true;
}
});
}
}
/** Notifies the views of the current value of "numberOfUsersLoggedIn", if any */
private void updateViewFromModel() {
if (mView != null && mModel.isLoaded()) {
mView.setNumberOfLoggedIn(mModel.numberOfUsersLoggedIn);
}
}
}
MVP – MODEL
package com.nilzor.presenterexample;
import android.os.AsyncTask;
import android.os.Handler;
import java.util.Random;
public class MainModel {
public Integer numberOfUsersLoggedIn;
private boolean mIsLoaded;
public boolean isLoaded() {
return mIsLoaded;
}
public void loadAsync(final Handler.Callback onDoneCallback) {
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
// Simulating some asynchronous task fetching data from a remote server
try {Thread.sleep(2000);} catch (Exception ex) {};
numberOfUsersLoggedIn = new Random().nextInt(1000);
mIsLoaded = true;
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
onDoneCallback.handleMessage(null);
}
}.execute((Void) null);
}
}
用mvvm代碼如下
MVVM – VIEW – XML
MVVM – VIEW – JAVA
package com.nilzor.presenterexample;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Toast;
import com.nilzor.presenterexample.databinding.FragmentMainBinding;
public class MainActivityFragment extends Fragment {
private FragmentMainBinding mBinding;
private MainModel mViewModel;
public MainActivityFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
mBinding = FragmentMainBinding.bind(view);
mViewModel = new MainModel(this, getResources());
mBinding.setData(mViewModel);
attachButtonListener();
return view;
}
private void attachButtonListener() {
mBinding.loginOrCreateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mViewModel.logInClicked();
}
});
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
ensureModelDataIsLodaded();
}
private void ensureModelDataIsLodaded() {
if (!mViewModel.isLoaded()) {
mViewModel.loadAsync();
}
}
public void showShortToast(String text) {
Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
}
}
MVVM – VIEWMODEL
package com.nilzor.presenterexample;
import android.content.res.Resources;
import android.databinding.ObservableField;
import android.os.AsyncTask;
import android.view.View;
import java.util.Random;
public class MainModel {
public ObservableField numberOfUsersLoggedIn = new ObservableField();
public ObservableField isExistingUserChecked = new ObservableField();
public ObservableField emailBlockVisibility = new ObservableField();
public ObservableField loginOrCreateButtonText = new ObservableField();
private boolean mIsLoaded;
private MainActivityFragment mView;
private Resources mResources;
public MainModel(MainActivityFragment view, Resources resources) {
mView = view;
mResources = resources; // You might want to abstract this for testability
setInitialState();
updateDependentViews();
hookUpDependencies();
}
public boolean isLoaded() {
return mIsLoaded;
}
private void setInitialState() {
numberOfUsersLoggedIn.set("...");
isExistingUserChecked.set(true);
}
private void hookUpDependencies() {
isExistingUserChecked.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
updateDependentViews();
}
});
}
public void updateDependentViews() {
if (isExistingUserChecked.get()) {
emailBlockVisibility.set(View.GONE);
loginOrCreateButtonText.set(mResources.getString(R.string.log_in));
}
else {
emailBlockVisibility.set(View.VISIBLE);
loginOrCreateButtonText.set(mResources.getString(R.string.create_user));
}
}
public void loadAsync() {
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
// Simulating some asynchronous task fetching data from a remote server
try {Thread.sleep(2000);} catch (Exception ex) {};
numberOfUsersLoggedIn.set("" + new Random().nextInt(1000));
mIsLoaded = true;
return null;
}
}.execute((Void) null);
}
public void logInClicked() {
// Illustrating the need for calling back to the view though testable interfaces.
if (isExistingUserChecked.get()) {
mView.showShortToast("Invalid username or password");
}
else {
mView.showShortToast("Please enter a valid email address");
}
}
}
用android studio創建文件隨便寫了個demo,項目文件就90多M,感覺沒干什麼就導致文件這麼大了,瞬間頭皮發麻。。。為什麼呢?帶著疑惑,打開android s
今天給大家帶來一點干貨,就是橫向循環滾動的廣告條。有點類似淘寶的banner廣告位,可以手勢滑動,也會依據固定時間間隔自動滾動,滑到盡頭時會一直循環。過渡非常
本文章圍繞著Android的包管理機制,著重分析Android的包格式(包括簽名),以及應用程序的安裝,升級以及卸載過程。1. Android APK文件Android的
矢量室內地圖開發因為公司項目的需要,需要開發一套室內地圖,並實現路線的規劃功能。因為之前沒做過這方面的開發,相關的資料也比較少,所以只能一個人去摸索。剛開始我是使用一般的