Android 架构演变史:从混乱到优雅的 Java 实战之旅

Lior

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
// LoginActivity.java
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);

// 1. 网络请求
new Thread(() -> {
try {
URL url = new URL("http://api.example.com/login");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// ... 网络请求代码

// 2. 解析 JSON
String response = getResponseFromConnection(conn);
JSONObject json = new JSONObject(response);

// 3. 保存到数据库
ContentValues values = new ContentValues();
values.put("username", json.getString("username"));
values.put("token", json.getString("token"));
getContentResolver().insert(USER_URI, values);

// 4. 更新 UI
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();
}

// 还有 1000+ 行其他功能的代码...
}

😱 问题

  • 一个 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
// LoginActivity.java (View)
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) {
// 登录失败
}
}

// LoginController.java (Controller)
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();
}
}

// UserModel.java (Model)
public class UserModel {
public User login(String username, String password) throws Exception {
// 网络请求、数据库操作
return null;
}
}

✅ 进步

  • 职责分离了
  • Model 可以复用
  • 代码稍微清晰了

😤 新问题

  • 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
// LoginContract.java - 定义契约
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();
}
}

// LoginActivity.java
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();
}

// 实现 View 接口的方法
@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();
}
}

// LoginPresenter.java
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);
});
}
}
});
}
}

// LoginInteractor.java - 业务逻辑
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
// LoginViewModel.java
public class LoginViewModel extends ViewModel {
// LiveData 自动处理生命周期
private MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
private MutableLiveData<User> user = new MutableLiveData<>();
private MutableLiveData<String> error = new MutableLiveData<>();

// Repository 模式
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());
}
});
}
}

// LoginActivity.java - 变得非常简单!
public class LoginActivity extends AppCompatActivity {
private ActivityLoginBinding binding; // Data Binding
private LoginViewModel viewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Data Binding
binding = DataBindingUtil.setContentView(this, R.layout.activity_login);

// ViewModel
viewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
binding.setViewModel(viewModel);
binding.setLifecycleOwner(this);

// 观察 LiveData
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 中绑定
// <Button
// android:onClick="@{() -> viewModel.login(username.getText().toString(), password.getText().toString())}"
// ... />
}
}

// activity_login.xml - 使用 Data Binding
<?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>

🚀 革命性优势

  1. ViewModel 生命周期感知:屏幕旋转不丢失数据
  2. LiveData 自动观察:不用手动管理观察者
  3. Data Binding 简化代码:XML 中直接绑定
  4. 真正的关注点分离: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
// LoginViewModel.java - 使用 RxJava
public class LoginViewModel extends ViewModel {

private CompositeDisposable disposables = new CompositeDisposable();
private LoginRepository loginRepository = new LoginRepository();

// 使用 BehaviorSubject 替代 LiveData
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
// User.java - 实体类
public class User {
private String id;
private String name;
private String email;
// getters, setters, constructor
}

// Result.java - 包装结果
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);
}
}

// LoginState.java - UI 状态
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;
}

// LoginViewModel.java - 使用 StateFlow 概念
public class LoginViewModel extends ViewModel {

private LoginUseCase loginUseCase = new LoginUseCase();

// UI 状态
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)
↓ 状态管理 + 单向数据流

核心进步点:

  1. 生命周期管理

    • 过去:手动处理,容易泄漏
    • 现在:ViewModel 自动管理
  2. 线程安全

    • 过去:runOnUiThread() 到处飞
    • 现在:LiveData/RxJava 自动线程切换
  3. 测试能力

    • 过去:几乎无法测试
    • 现在:纯 Java 逻辑,轻松测试
  4. 代码组织

    • 过去:一个文件几千行
    • 现在:职责单一,文件短小
  5. 数据驱动 UI

    • 过去:命令式更新 UI (setText(), setVisibility())
    • 现在:声明式响应数据变化

给 Java 开发者的建议:

  1. 架构选择:ViewModel + LiveData + Repository

  2. 学习重点

    • 观察者模式
    • 响应式编程思想
    • 单向数据流概念
  3. 工具升级

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 在 build.gradle 中添加
    dependencies {
    def lifecycle_version = "2.6.0"

    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"

    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"

    // 可选:RxJava
    implementation "io.reactivex.rxjava3:rxjava:3.1.6"
    implementation "io.reactivex.rxjava3:rxandroid:3.0.2"
    }

ps:架构的演进都是为了解决代码维护性、测试性、团队协作的问题。选择适合你项目规模和团队经验的架构,不要过度设计,但也不要没有设计。

  • Title: Android 架构演变史:从混乱到优雅的 Java 实战之旅
  • Author: Lior
  • Created at : 2026-01-19 15:15:00
  • Updated at : 2026-01-29 09:02:47
  • Link: https://newblog.buleng.xyz/26011901/
  • License: This work is licensed under CC BY-NC-SA 4.0.