模块 10:老项目实战指南与 Kotlin 迁移
目标:获得接手 Java Android 老项目的系统性指南,学会识别技术栈,理解常见"历史包袱",以及规划渐进式 Kotlin 迁移。
1. 接手老项目的第一周
1.1 项目体检清单
拿到项目后,按以下顺序检查:
Step 1:能编译运行吗?
□ 克隆代码
□ 检查 JDK 版本要求(gradle-wrapper.properties → Gradle 版本 → JDK 版本)
□ 配置 local.properties(SDK 路径)
□ 运行 Gradle Sync
□ 尝试编译 Debug 版本:./gradlew assembleDebug
□ 在模拟器或真机上运行
□ 记录所有编译错误和警告Step 2:项目基本信息
| 检查项 | 文件/位置 | 关注点 |
|---|---|---|
| 包名 | AndroidManifest.xml | 应用唯一标识 |
| 最低 SDK | build.gradle → minSdk | 支持的设备范围 |
| 目标 SDK | build.gradle → targetSdk | 适配行为 |
| 版本号 | build.gradle → versionCode/Name | 当前版本 |
| 构建类型 | build.gradle → buildTypes | debug/release 配置 |
| 依赖列表 | build.gradle → dependencies | 第三方库 |
| 权限列表 | AndroidManifest.xml | 申请了哪些权限 |
| 混淆规则 | proguard-rules.pro | 哪些类被 keep |
| 签名配置 | build.gradle → signingConfigs | release 签名位置 |
| 构建变体 | build.gradle → productFlavors | 渠道包/品牌包 |
Step 3:技术栈识别
运行以下命令快速了解依赖:
./gradlew app:dependencies --configuration releaseRuntimeClasspath1.2 技术栈时代判断
通过依赖和代码风格判断项目年代和技术栈:
| 特征 | 年代 | 说明 |
|---|---|---|
android.support.* 包名 | 2018 前 | 使用 Support Library(已过时) |
androidx.* 包名 | 2018+ | 已迁移到 AndroidX |
com.jakewharton:butterknife | 2015-2019 | ButterKnife 视图绑定 |
io.reactivex / rxjava | 2016-2020 | RxJava 响应式编程 |
dagger / com.google.dagger | 2016+ | Dagger 依赖注入 |
com.google.dagger:hilt | 2020+ | Hilt 依赖注入 |
| MVP 模式(Contract + Presenter) | 2016-2019 | 常见架构 |
| MVVM + LiveData + ViewModel | 2019+ | 现代架构 |
com.squareup.okhttp | 2014-2016 | OkHttp 2.x(已过时) |
com.squareup.okhttp3 | 2016+ | OkHttp 3.x/4.x(现代) |
retrofit | 2016+ | Retrofit(主流) |
com.loopj.android:android-async-http | 2012-2015 | 已停维护 |
com.android.volley | 2013+ | Volley |
greenrobot:eventbus | 2015-2019 | EventBus 事件总线 |
com.j256.ormlite | 2013-2017 | OrmLite(已过时) |
android.arch.persistence.room | 2017-2018 | Room 早期版本 |
androidx.room | 2018+ | Room(AndroidX 版本) |
1.3 Support Library vs AndroidX
这是接手老项目遇到的第一个大问题。2018 年 Google 将 Support Library 重构为 AndroidX:
// 旧版 Support Library(2018年前)
import android.support.v7.app.AppCompatActivity;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.support.design.widget.Snackbar;
// 新版 AndroidX(2018年后)
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;包名映射表(常见):
| Support Library | AndroidX |
|---|---|
android.support.v7.app.AppCompatActivity | androidx.appcompat.app.AppCompatActivity |
android.support.v4.app.Fragment | androidx.fragment.app.Fragment |
android.support.v7.widget.RecyclerView | androidx.recyclerview.widget.RecyclerView |
android.support.v7.widget.LinearLayoutManager | androidx.recyclerview.widget.LinearLayoutManager |
android.support.design.widget.* | com.google.android.material.* |
android.support.constraint.ConstraintLayout | androidx.constraintlayout.widget.ConstraintLayout |
android.support.v4.widget.SwipeRefreshLayout | androidx.swiperefreshlayout.widget.SwipeRefreshLayout |
迁移方式:Android Studio 提供自动迁移:Refactor → Migrate to AndroidX。
建议:如果项目还在使用 Support Library,优先考虑迁移到 AndroidX。但要做好回归测试的准备。
2. 常见"历史包袱"与处理策略
2.1 过时库的处理
| 过时库 | 替代方案 | 迁移难度 |
|---|---|---|
| ButterKnife | ViewBinding | 低(逐个替换) |
| EventBus | LiveData / SharedFlow | 中(需要重构通信) |
| RxJava 1.x | RxJava 3.x 或 Kotlin Coroutines | 高 |
| OrmLite | Room | 高(数据模型不同) |
| Picasso | Glide 或 Coil | 低(API 相似) |
| Volley | OkHttp + Retrofit | 中 |
| Android Annotations | 标准注解 + Hilt | 高 |
android-percent-support | ConstraintLayout / FlexboxLayout | 低 |
com.android.support:multidex | androidx.multidex:multidex | 低 |
2.2 ButterKnife 替换为 ViewBinding
// ❌ 旧代码:ButterKnife
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_title) TextView titleView;
@BindView(R.id.btn_submit) Button submitBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
titleView.setText("Hello");
submitBtn.setOnClickListener(v -> { /* ... */ });
}
@OnClick(R.id.btn_submit)
void onSubmit() {
// ...
}
}// ✅ 新代码:ViewBinding
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.tvTitle.setText("Hello");
binding.btnSubmit.setOnClickListener(v -> { /* ... */ });
}
}2.3 AsyncTask 替换为 Executor / Kotlin Coroutines
AsyncTask 已在 API 30 弃用并在高版本移除,老项目常见此类代码:
// ❌ 旧代码:AsyncTask
new AsyncTask<String, Void, String>() {
@Override protected String doInBackground(String... params) {
return networkRequest(params[0]);
}
@Override protected void onPostExecute(String result) {
textView.setText(result);
}
}.execute(url);// ✅ 新代码:ExecutorService + Handler(Java)
ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());
executor.execute(() -> {
final String result = networkRequest(url);
handler.post(() -> textView.setText(result));
});// ✅ 新代码:Kotlin Coroutines
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkRequest(url) }
textView.text = result
}2.4 EventBus 替换为 LiveData
// ❌ 旧代码:EventBus
// 发送事件
EventBus.getDefault().post(new UserLoginEvent(user));
// 接收事件
@Subscribe(threadMode = ThreadMode.MAIN)
public void onUserLogin(UserLoginEvent event) {
updateUI(event.getUser());
}
// 注册/注销
EventBus.getDefault().register(this);
EventBus.getDefault().unregister(this);// ✅ 新代码:LiveData(通过共享 ViewModel)
// 共享 ViewModel
public class AppViewModel extends ViewModel {
private final MutableLiveData<User> currentUser = new MutableLiveData<>();
public void login(User user) {
currentUser.setValue(user);
}
public LiveData<User> getCurrentUser() {
return currentUser;
}
}
// 观察(在任何 Fragment/Activity 中)
appViewModel.getCurrentUser().observe(this, user -> {
updateUI(user);
});2.5 findViewById 替换为 ViewBinding
老项目里大量 findViewById 是空指针和类型转换错误的温床:
// ❌ 旧代码
TextView tvTitle = findViewById(R.id.tv_title);
Button btnSubmit = findViewById(R.id.btn_submit);// ✅ 新代码
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.tvTitle.setText("Hello");
binding.btnSubmit.setOnClickListener(v -> { /* ... */ });迁移技巧:Android Studio 提供
Refactor → Migrate to View Binding,可批量替换部分简单场景,但复杂自定义 View 仍需手动处理。
3. 渐进式现代化策略
3.1 优先级矩阵
高影响 ┃ 升级 targetSdk 修复崩溃
┃ 安全漏洞修复 性能优化
┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━
低影响 ┃ 替换过时 UI 组件 代码风格统一
┃ 添加注释 升级依赖版本
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━
低风险 高风险建议顺序:
- 第一优先:确保能编译运行,修复崩溃
- 第二优先:升级 targetSdk(Google Play 有要求)
- 第三优先:安全漏洞修复、关键依赖更新
- 第四优先:架构改进、代码质量提升
- 第五优先:Kotlin 迁移(如果确定要迁移)
3.2 targetSdk 升级注意事项
每次升级 targetSdk,都需要检查行为变更:
| 升级到 | 关键变更 |
|---|---|
| API 26 (Android 8) | 后台服务限制、通知渠道必须创建 |
| API 28 (Android 9) | 非 SDK 接口限制、前台服务需要权限 |
| API 29 (Android 10) | 分区存储(Scoped Storage) |
| API 30 (Android 11) | 强制分区存储、包可见性限制 |
| API 31 (Android 12) | 必须声明 android:exported、精确闹钟限制 |
| API 33 (Android 13) | 细粒度媒体权限、通知运行时权限 |
| API 34 (Android 14) | 前台服务类型声明、精确闹钟默认禁止 |
| API 35 (Android 15) | 16 KB 页大小支持(部分新设备) |
3.3 升级 Gradle / AGP / JDK 的兼容矩阵
AGP 版本 所需 Gradle 版本 兼容 JDK
8.1.x 8.0+ 17
8.2.x 8.2+ 17
8.3.x 8.4+ 17
8.4.x 8.6+ 17
8.5.x 8.7+ 17-21升级顺序建议:JDK → Gradle → AGP → 第三方依赖。每次只升一级,构建通过后再继续。
4. Java 与 Kotlin 共存
4.1 在 Java 项目中使用 Kotlin
Gradle 原生支持 Java 和 Kotlin 混编:
// app/build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android' version '1.9.22' // 添加 Kotlin 插件
}
android {
// ...
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
}混编时 Java 和 Kotlin 文件放在同一目录下:
src/main/java/com/example/app/
├── MainActivity.java # Java 文件
├── UserDetailActivity.kt # Kotlin 文件(共存)
├── model/
│ ├── User.java # Java 数据类
│ └── Product.kt # Kotlin data class
└── util/
├── DateUtils.java # Java 工具类
└── StringUtils.kt # Kotlin 扩展函数4.2 Java 调用 Kotlin 代码
// Kotlin 文件
class UserHelper {
fun getUserName(): String = "张三"
@JvmStatic
fun getDefaultUser(): User = User("默认用户")
}
object AppConfig {
const val API_URL = "https://api.example.com"
}// Java 中调用
UserHelper helper = new UserHelper();
String name = helper.getUserName(); // 普通方法
User defaultUser = UserHelper.getDefaultUser(); // @JvmStatic 方法
String apiUrl = AppConfig.API_URL; // object 中的 const4.3 Kotlin 调用 Java 代码
// Java 文件
public class UserManager {
private String currentName;
public String getCurrentName() { return currentName; }
public void setCurrentName(String name) { this.currentName = name; }
public static UserManager getInstance() {
return instance;
}
}// Kotlin 中调用
val manager = UserManager.getInstance()
manager.currentName = "张三" // 自动识别 getter/setter 为属性
val name = manager.currentName4.4 Java 与 Kotlin 互操作常见陷阱
// 1. 平台类型(来自 Java 的 String!)
// Java 方法返回 String,Kotlin 视为 String!,可能空指针
val name: String = javaManager.name // 运行时可能 NPE
// 修复:在 Kotlin 端显式声明可空性,或在 Java 添加 @Nullable/@NotNull
// 2. SAM 转换差异
// Java 接口只有一个抽象方法时可自动 SAM 转换
button.setOnClickListener { doSomething() }
// 3. Java 的泛型在 Kotlin 中的不变性
val list: MutableList<String> = javaObject.strings
list.add("x") // 如果 Java 返回 List<String> 可能编译/运行异常前端对照:Java/Kotlin 互操作类似 TypeScript 调用 JavaScript 库时的类型边界问题:TS 会引入
any或可选类型,需要额外注意运行时行为和空值。
5. Kotlin 迁移指南
5.1 何时迁移?
适合迁移的情况:
- 项目计划长期维护
- 团队有 Kotlin 经验或愿意学习
- 需要引入 Jetpack Compose 等新组件
- Google 官方文档和社区已全面转向 Kotlin
不适合迁移的情况:
- 项目即将下线
- 团队完全不懂 Kotlin
- 只修复 Bug 不加新功能
5.2 迁移顺序建议
第 1 步:新建的 Kotlin 文件
↓ (新功能用 Kotlin 写)
第 2 步:工具类和常量
↓ (简单的、独立的文件先迁移)
第 3 步:数据模型(Model / Entity)
↓ (data class 替代 POJO,代码量大幅减少)
第 4 步:Adapter 和 ViewHolder
↓ (模板代码多,Kotlin 简化明显)
第 5 步:Fragment 和 Activity
↓ (逐页面迁移)
第 6 步:ViewModel 和 Repository
↓ (业务逻辑层)
第 7 步:核心框架类(Application、BaseActivity 等)5.3 Kotlin 对照速查表
// Java 的 POJO → Kotlin 的 data class
// Java: 50+ 行代码
public class User {
private String name;
private int age;
// getter/setter/toString/equals/hashCode...
}
// Kotlin: 1 行
data class User(val name: String, val age: Int)
// Java 的 findViewById → Kotlin 的属性访问
// Java
TextView tv = findViewById(R.id.tv_title);
tv.setText("Hello");
// Kotlin(配合 ViewBinding)
binding.tvTitle.text = "Hello"
// Java 的匿名内部类 → Kotlin 的 Lambda
// Java
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSomething();
}
});
// Kotlin
button.setOnClickListener { doSomething() }
// Java 的 null 检查 → Kotlin 的安全调用
// Java
if (user != null && user.getAddress() != null) {
String city = user.getAddress().getCity();
}
// Kotlin
val city = user?.address?.city
// Java 的 switch → Kotlin 的 when
// Java
switch (status) {
case 0: return "pending";
case 1: return "active";
case 2: return "completed";
default: return "unknown";
}
// Kotlin
return when (status) {
0 -> "pending"
1 -> "active"
2 -> "completed"
else -> "unknown"
}
// Java 的 for 循环 → Kotlin 的集合操作
// Java
List<String> names = new ArrayList<>();
for (User user : users) {
if (user.getAge() > 18) {
names.add(user.getName());
}
}
// Kotlin
val names = users.filter { it.age > 18 }.map { it.name }
// Java 的 try-catch → Kotlin 的 runCatching
// Java
String result;
try {
result = riskyOperation();
} catch (Exception e) {
result = "default";
}
// Kotlin
val result = runCatching { riskyOperation() }.getOrDefault("default")5.4 Android Studio 自动转换
Android Studio 内置 Java → Kotlin 转换工具:
- 打开 Java 文件
Code → Convert Java File to Kotlin File(Ctrl+Alt+Shift+K)- 检查转换结果(不完全准确,需要手动调整)
注意:自动转换的代码可能不是最佳 Kotlin 风格,但作为起点很有用。
5.5 Kotlin 迁移后的 R8 / ProGuard 注意事项
# Kotlin 反射、协程常见 keep 规则
-keep class kotlin.** { *; }
-keep class kotlinx.coroutines.** { *; }
-keepattributes *Annotation*
-keepattributes Signature
-keepattributes Exceptions
-keepclassmembers class * implements android.os.Parcelable { *; }常见 Release 崩溃:
java.lang.NoSuchMethodError: No virtual method ...
# 原因:R8 过度混淆了 Kotlin 标准库或反射调用
# 解决:添加 -keep 规则,并在构建时启用 R8 完整模式测试6. 老项目中的依赖注入
6.1 识别 DI 框架
// Dagger 2(最常见)
@Component(modules = {AppModule.class, NetworkModule.class})
public interface AppComponent {
void inject(MainActivity activity);
}
@Module
public class NetworkModule {
@Provides
@Singleton
OkHttpClient provideOkHttpClient() {
return new OkHttpClient.Builder().build();
}
@Provides
Retrofit provideRetrofit(OkHttpClient client) {
return new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
// 使用
@Inject Retrofit retrofit;
// Hilt(Dagger 的简化版,2020+)
@HiltAndroidApp
public class MyApplication extends Application { }
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
@Inject UserRepository repository;
}
@Module
@InstallIn(SingletonComponent.class)
public class NetworkModule {
@Provides
@Singleton
static OkHttpClient provideOkHttpClient() {
return new OkHttpClient.Builder().build();
}
}7. 完整接手流程总结
Day 1: 环境搭建 → 编译运行 → 基本信息收集
↓
Day 2: 技术栈识别 → 架构理解 → 依赖清单
↓
Day 3: 核心流程走读 → 页面结构梳理 → 数据流向
↓
Week 1: 修复编译问题 → 运行测试 → 记录技术债
↓
Week 2: 制定改进计划 → 确定优先级 → 开始渐进改进8. 实用工具清单
| 工具 | 用途 |
|---|---|
| Android Studio Layout Inspector | 实时检查 UI 层级 |
| Android Studio Database Inspector | 实时查看数据库 |
| Stetho / Flipper | 网络请求和数据库调试 |
| LeakCanary | 内存泄漏检测 |
| Charles / mitmproxy | 网络抓包 |
| jadx | APK 反编译查看源码 |
| APK Analyzer | 分析 APK 大小组成 |
前端开发者备忘
| 前端项目接手 | Android 项目接手 | 备注 |
|---|---|---|
阅读 package.json | 阅读 build.gradle | 了解依赖和配置 |
检查 node_modules | ./gradlew dependencies | 依赖树 |
npm audit | 手动检查过时依赖 | 安全审计 |
| 检查 ESLint 配置 | 检查 Lint 配置 | 代码质量 |
| 阅读路由文件 | 阅读 AndroidManifest.xml | 页面结构 |
| 技术债务盘点 | 技术栈时代判断 | 评估现代化程度 |
| jQuery → React 迁移 | Java → Kotlin 迁移 | 渐进式迁移策略 |
Webpack -> Vite 升级 | Gradle 版本升级 | 构建工具升级 |
TypeScript strict 迁移 | Kotlin 空安全 迁移 | 都需要渐进式进行 |
恭喜你!
完成全部 10 个模块的学习后,你已经具备了:
- ✅ 搭建 Android Java 开发环境的能力
- ✅ 阅读和理解 Java Android 代码的能力
- ✅ 修改和维护老项目 UI、网络、数据存储等功能的能力
- ✅ 识别项目技术栈和架构模式的能力
- ✅ 使用调试工具排查问题的能力
- ✅ 规划渐进式现代化和 Kotlin 迁移的能力
接下来的建议:
- 拿一个实际的老项目练手
- 尝试从头编译运行,记录遇到的问题
- 尝试修复一个小 Bug 或添加一个小功能
- 加入 Android 开发者社区(如 Stack Overflow、Reddit r/androiddev)
祝你在 Android 维护之路上顺利!