Android 架构演变史:从混乱到优雅的 Java 实战之旅
Android 架构演变史:从混乱到优雅的 Java 实战之旅
用一个用户登录功能的演进故事,带读者体验 Android 架构的完整演变过程。(使用 Java 代码展示每个阶段的特点和问题)。
📅 2010年:混沌时代 - 无架构时代
特点:所有代码都在 Activity 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| public class LoginActivity extends Activity { private EditText etUsername; private EditText etPassword; private Button btnLogin; private ProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); etUsername = findViewById(R.id.et_username); etPassword = findViewById(R.id.et_password); btnLogin = findViewById(R.id.btn_login); progressBar = findViewById(R.id.progress_bar); btnLogin.setOnClickListener(v -> { login(); }); } private void login() { String username = etUsername.getText().toString(); String password = etPassword.getText().toString(); if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) { Toast.makeText(this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show(); return; } progressBar.setVisibility(View.VISIBLE); new Thread(() -> { try { URL url = new URL("http://api.example.com/login"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); String response = getResponseFromConnection(conn); JSONObject json = new JSONObject(response); ContentValues values = new ContentValues(); values.put("username", json.getString("username")); values.put("token", json.getString("token")); getContentResolver().insert(USER_URI, values); runOnUiThread(() -> { progressBar.setVisibility(View.GONE); Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show(); startActivity(new Intent(this, MainActivity.class)); finish(); }); } catch (Exception e) { runOnUiThread(() -> { progressBar.setVisibility(View.GONE); Toast.makeText(this, "登录失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); }); } }).start(); } }
|
😱 问题:
- 一个 Activity 几千行代码
- 网络请求、数据库操作、UI 逻辑全混在一起
- 线程管理混乱
- 无法单元测试
- 配置变化(旋转屏幕)丢失数据
📅 2012年:第一次革命 - MVC 出现
思路:把业务逻辑抽到 Model 层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| public class LoginActivity extends Activity { private LoginController controller; private EditText etUsername, etPassword; private Button btnLogin; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); controller = new LoginController(this); etUsername = findViewById(R.id.et_username); etPassword = findViewById(R.id.et_password); btnLogin = findViewById(R.id.btn_login); btnLogin.setOnClickListener(v -> { controller.login( etUsername.getText().toString(), etPassword.getText().toString() ); }); } public void showLoading() { } public void hideLoading() { } public void onLoginSuccess(User user) { } public void onLoginError(String error) { } }
public class LoginController { private LoginActivity view; private UserModel model; public LoginController(LoginActivity view) { this.view = view; this.model = new UserModel(); } public void login(String username, String password) { if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) { view.onLoginError("用户名或密码不能为空"); return; } view.showLoading(); new Thread(() -> { try { User user = model.login(username, password); runOnUiThread(() -> { view.hideLoading(); view.onLoginSuccess(user); }); } catch (Exception e) { runOnUiThread(() -> { view.hideLoading(); view.onLoginError(e.getMessage()); }); } }).start(); } }
public class UserModel { public User login(String username, String password) throws Exception { return null; } }
|
✅ 进步:
😤 新问题:
- Controller 还是太臃肿
- Activity 和 Controller 互相持有,内存泄漏!
- 测试仍然困难
- 回传结果时,Activity 可能已经被销毁
📅 2015年:MVP 时代
核心:用接口解耦,Presenter 不直接持有 View 引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
| public interface LoginContract { interface View { void showLoading(); void hideLoading(); void onLoginSuccess(User user); void onLoginError(String error); void showValidationError(String message); } interface Presenter { void login(String username, String password); void detachView(); } }
public class LoginActivity extends AppCompatActivity implements LoginContract.View { private LoginPresenter presenter; private EditText etUsername, etPassword; private Button btnLogin; private ProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); presenter = new LoginPresenter(); presenter.attachView(this); etUsername = findViewById(R.id.et_username); etPassword = findViewById(R.id.et_password); btnLogin = findViewById(R.id.btn_login); progressBar = findViewById(R.id.progress_bar); btnLogin.setOnClickListener(v -> { presenter.login( etUsername.getText().toString(), etPassword.getText().toString() ); }); } @Override protected void onDestroy() { super.onDestroy(); presenter.detachView(); } @Override public void showLoading() { progressBar.setVisibility(View.VISIBLE); } @Override public void hideLoading() { progressBar.setVisibility(View.GONE); } @Override public void onLoginSuccess(User user) { Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show(); startActivity(new Intent(this, MainActivity.class)); finish(); } @Override public void onLoginError(String error) { Toast.makeText(this, "登录失败: " + error, Toast.LENGTH_SHORT).show(); } }
public class LoginPresenter implements LoginContract.Presenter { private LoginContract.View view; private LoginInteractor interactor; private boolean isViewAttached = false; public LoginPresenter() { this.interactor = new LoginInteractor(); } public void attachView(LoginContract.View view) { this.view = view; this.isViewAttached = true; } @Override public void detachView() { this.view = null; this.isViewAttached = false; } @Override public void login(String username, String password) { if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) { if (isViewAttached) { view.showValidationError("用户名或密码不能为空"); } return; } if (isViewAttached) { view.showLoading(); } interactor.login(username, password, new LoginCallback() { @Override public void onSuccess(User user) { if (isViewAttached) { runOnUiThread(() -> { view.hideLoading(); view.onLoginSuccess(user); }); } } @Override public void onError(String error) { if (isViewAttached) { runOnUiThread(() -> { view.hideLoading(); view.onLoginError(error); }); } } }); } }
public class LoginInteractor { public void login(String username, String password, LoginCallback callback) { } }
|
✅ 巨大进步:
- View 和 Presenter 解耦
- Presenter 可测试
- 通过接口,可以 Mock 测试
- 解决了部分内存泄漏
😫 仍然痛苦:
- 大量接口定义,代码冗余
- 每个 Presenter 都要手动管理生命周期
- View 回调时还是要检查
isViewAttached
- 还是有很多样板代码
📅 2017年:MVVM 来临,Android Architecture Components 发布
革命性变化:LiveData + ViewModel + Data Binding
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| public class LoginViewModel extends ViewModel { private MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false); private MutableLiveData<User> user = new MutableLiveData<>(); private MutableLiveData<String> error = new MutableLiveData<>(); private UserRepository userRepository = new UserRepository(); public LiveData<Boolean> getIsLoading() { return isLoading; } public LiveData<User> getUser() { return user; } public LiveData<String> getError() { return error; } public void login(String username, String password) { if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) { error.setValue("用户名或密码不能为空"); return; } isLoading.setValue(true); userRepository.login(username, password, new Callback<User>() { @Override public void onSuccess(User result) { isLoading.postValue(false); user.postValue(result); } @Override public void onError(Exception e) { isLoading.postValue(false); error.postValue("登录失败: " + e.getMessage()); } }); } }
public class LoginActivity extends AppCompatActivity { private ActivityLoginBinding binding; private LoginViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_login); viewModel = ViewModelProviders.of(this).get(LoginViewModel.class); binding.setViewModel(viewModel); binding.setLifecycleOwner(this); viewModel.getIsLoading().observe(this, isLoading -> { binding.progressBar.setVisibility(isLoading ? View.VISIBLE : View.GONE); }); viewModel.getUser().observe(this, user -> { if (user != null) { Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show(); startActivity(new Intent(this, MainActivity.class)); finish(); } }); viewModel.getError().observe(this, error -> { if (error != null) { Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); } }); } }
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.example.LoginViewModel" /> </data> <LinearLayout> <EditText android:id="@+id/et_username" ... /> <EditText android:id="@+id/et_password" ... /> <Button android:text="登录" android:onClick="@{() -> viewModel.login(et_username.text.toString(), et_password.text.toString())}" ... /> <ProgressBar android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" ... /> </LinearLayout> </layout>
|
🚀 革命性优势:
- ViewModel 生命周期感知:屏幕旋转不丢失数据
- LiveData 自动观察:不用手动管理观察者
- Data Binding 简化代码:XML 中直接绑定
- 真正的关注点分离:Activity 只处理 UI
📅 2020年:MVVM 现代化(Java 版本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public class LoginViewModel extends ViewModel { private CompositeDisposable disposables = new CompositeDisposable(); private LoginRepository loginRepository = new LoginRepository(); private BehaviorSubject<Boolean> isLoading = BehaviorSubject.createDefault(false); private BehaviorSubject<User> user = BehaviorSubject.create(); private BehaviorSubject<String> error = BehaviorSubject.create(); public Observable<Boolean> getIsLoading() { return isLoading.hide(); } public Observable<User> getUser() { return user.hide(); } public Observable<String> getError() { return error.hide(); } public void login(String username, String password) { if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) { error.onNext("用户名或密码不能为空"); return; } disposables.add( loginRepository.login(username, password) .doOnSubscribe(disposable -> isLoading.onNext(true)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( userResult -> { isLoading.onNext(false); user.onNext(userResult); }, throwable -> { isLoading.onNext(false); error.onNext("登录失败: " + throwable.getMessage()); } ) ); } @Override protected void onCleared() { super.onCleared(); disposables.dispose(); } }
|
📅 2023年:现代化架构(Java 版本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| public class User { private String id; private String name; private String email; }
public class Result<T> { private T data; private String error; private boolean isLoading; public static <T> Result<T> success(T data) { return new Result<>(data, null, false); } public static <T> Result<T> error(String error) { return new Result<>(null, error, false); } public static <T> Result<T> loading() { return new Result<>(null, null, true); } }
public class LoginState { public String username = ""; public String password = ""; public String usernameError = null; public String passwordError = null; public boolean isLoginEnabled = false; public Result<User> loginResult = null; }
public class LoginViewModel extends ViewModel { private LoginUseCase loginUseCase = new LoginUseCase(); private MutableLiveData<LoginState> uiState = new MutableLiveData<>(new LoginState()); public LiveData<LoginState> getUiState() { return uiState; } private SingleLiveEvent<String> snackbarMessage = new SingleLiveEvent<>(); public LiveData<String> getSnackbarMessage() { return snackbarMessage; } public void onUsernameChanged(String username) { LoginState current = uiState.getValue(); if (current != null) { current.username = username; validateForm(current); uiState.setValue(current); } } public void onPasswordChanged(String password) { LoginState current = uiState.getValue(); if (current != null) { current.password = password; validateForm(current); uiState.setValue(current); } } private void validateForm(LoginState state) { boolean isValid = !TextUtils.isEmpty(state.username) && state.username.length() >= 3 && !TextUtils.isEmpty(state.password) && state.password.length() >= 6; state.isLoginEnabled = isValid; } public void login() { LoginState current = uiState.getValue(); if (current == null || !current.isLoginEnabled) return; current.loginResult = Result.loading(); uiState.setValue(current); loginUseCase.execute(current.username, current.password, new Callback<Result<User>>() { @Override public void onResult(Result<User> result) { LoginState updated = uiState.getValue(); if (updated != null) { updated.loginResult = result; uiState.postValue(updated); if (result.getError() != null) { snackbarMessage.postValue(result.getError()); } } } }); } }
|
🎯 架构演变总结
演变历程:
1 2 3 4 5 6 7 8 9 10
| 混沌时代 (2010) ↓ Activity 上帝对象 MVC (2012) ↓ 业务逻辑分离 MVP (2015) ↓ 接口解耦 MVVM (2017) ↓ LiveData + ViewModel 现代化 MVVM (2023) ↓ 状态管理 + 单向数据流
|
核心进步点:
生命周期管理:
- 过去:手动处理,容易泄漏
- 现在:ViewModel 自动管理
线程安全:
- 过去:
runOnUiThread() 到处飞
- 现在:LiveData/RxJava 自动线程切换
测试能力:
- 过去:几乎无法测试
- 现在:纯 Java 逻辑,轻松测试
代码组织:
数据驱动 UI:
- 过去:命令式更新 UI (
setText(), setVisibility())
- 现在:声明式响应数据变化
给 Java 开发者的建议:
架构选择:ViewModel + LiveData + Repository
学习重点:
工具升级:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| dependencies { def lifecycle_version = "2.6.0" implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" implementation "io.reactivex.rxjava3:rxjava:3.1.6" implementation "io.reactivex.rxjava3:rxandroid:3.0.2" }
|
ps:架构的演进都是为了解决代码维护性、测试性、团队协作的问题。选择适合你项目规模和团队经验的架构,不要过度设计,但也不要没有设计。