Skip to content

模块 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应用唯一标识
最低 SDKbuild.gradleminSdk支持的设备范围
目标 SDKbuild.gradletargetSdk适配行为
版本号build.gradleversionCode/Name当前版本
构建类型build.gradlebuildTypesdebug/release 配置
依赖列表build.gradledependencies第三方库
权限列表AndroidManifest.xml申请了哪些权限
混淆规则proguard-rules.pro哪些类被 keep
签名配置build.gradlesigningConfigsrelease 签名位置
构建变体build.gradleproductFlavors渠道包/品牌包

Step 3:技术栈识别

运行以下命令快速了解依赖:

bash
./gradlew app:dependencies --configuration releaseRuntimeClasspath

1.2 技术栈时代判断

通过依赖和代码风格判断项目年代和技术栈:

特征年代说明
android.support.* 包名2018 前使用 Support Library(已过时)
androidx.* 包名2018+已迁移到 AndroidX
com.jakewharton:butterknife2015-2019ButterKnife 视图绑定
io.reactivex / rxjava2016-2020RxJava 响应式编程
dagger / com.google.dagger2016+Dagger 依赖注入
com.google.dagger:hilt2020+Hilt 依赖注入
MVP 模式(Contract + Presenter)2016-2019常见架构
MVVM + LiveData + ViewModel2019+现代架构
com.squareup.okhttp2014-2016OkHttp 2.x(已过时)
com.squareup.okhttp32016+OkHttp 3.x/4.x(现代)
retrofit2016+Retrofit(主流)
com.loopj.android:android-async-http2012-2015已停维护
com.android.volley2013+Volley
greenrobot:eventbus2015-2019EventBus 事件总线
com.j256.ormlite2013-2017OrmLite(已过时)
android.arch.persistence.room2017-2018Room 早期版本
androidx.room2018+Room(AndroidX 版本)

1.3 Support Library vs AndroidX

这是接手老项目遇到的第一个大问题。2018 年 Google 将 Support Library 重构为 AndroidX:

java
// 旧版 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 LibraryAndroidX
android.support.v7.app.AppCompatActivityandroidx.appcompat.app.AppCompatActivity
android.support.v4.app.Fragmentandroidx.fragment.app.Fragment
android.support.v7.widget.RecyclerViewandroidx.recyclerview.widget.RecyclerView
android.support.v7.widget.LinearLayoutManagerandroidx.recyclerview.widget.LinearLayoutManager
android.support.design.widget.*com.google.android.material.*
android.support.constraint.ConstraintLayoutandroidx.constraintlayout.widget.ConstraintLayout
android.support.v4.widget.SwipeRefreshLayoutandroidx.swiperefreshlayout.widget.SwipeRefreshLayout

迁移方式:Android Studio 提供自动迁移:Refactor → Migrate to AndroidX

建议:如果项目还在使用 Support Library,优先考虑迁移到 AndroidX。但要做好回归测试的准备。

2. 常见"历史包袱"与处理策略

2.1 过时库的处理

过时库替代方案迁移难度
ButterKnifeViewBinding低(逐个替换)
EventBusLiveData / SharedFlow中(需要重构通信)
RxJava 1.xRxJava 3.x 或 Kotlin Coroutines
OrmLiteRoom高(数据模型不同)
PicassoGlide 或 Coil低(API 相似)
VolleyOkHttp + Retrofit
Android Annotations标准注解 + Hilt
android-percent-supportConstraintLayout / FlexboxLayout
com.android.support:multidexandroidx.multidex:multidex

2.2 ButterKnife 替换为 ViewBinding

java
// ❌ 旧代码: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() {
        // ...
    }
}
java
// ✅ 新代码: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 弃用并在高版本移除,老项目常见此类代码:

java
// ❌ 旧代码: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);
java
// ✅ 新代码: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
// ✅ 新代码:Kotlin Coroutines
lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) { networkRequest(url) }
    textView.text = result
}

2.4 EventBus 替换为 LiveData

java
// ❌ 旧代码: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);
java
// ✅ 新代码: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 是空指针和类型转换错误的温床:

java
// ❌ 旧代码
TextView tvTitle = findViewById(R.id.tv_title);
Button btnSubmit = findViewById(R.id.btn_submit);
java
// ✅ 新代码
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 组件    代码风格统一
       ┃ 添加注释            升级依赖版本
       ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━
         低风险                高风险

建议顺序

  1. 第一优先:确保能编译运行,修复崩溃
  2. 第二优先:升级 targetSdk(Google Play 有要求)
  3. 第三优先:安全漏洞修复、关键依赖更新
  4. 第四优先:架构改进、代码质量提升
  5. 第五优先: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 混编:

groovy
// 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
// Kotlin 文件
class UserHelper {
    fun getUserName(): String = "张三"

    @JvmStatic
    fun getDefaultUser(): User = User("默认用户")
}

object AppConfig {
    const val API_URL = "https://api.example.com"
}
java
// Java 中调用
UserHelper helper = new UserHelper();
String name = helper.getUserName();  // 普通方法

User defaultUser = UserHelper.getDefaultUser();  // @JvmStatic 方法

String apiUrl = AppConfig.API_URL;  // object 中的 const

4.3 Kotlin 调用 Java 代码

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
// Kotlin 中调用
val manager = UserManager.getInstance()
manager.currentName = "张三"  // 自动识别 getter/setter 为属性
val name = manager.currentName

4.4 Java 与 Kotlin 互操作常见陷阱

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 对照速查表

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 转换工具:

  1. 打开 Java 文件
  2. Code → Convert Java File to Kotlin FileCtrl+Alt+Shift+K
  3. 检查转换结果(不完全准确,需要手动调整)

注意:自动转换的代码可能不是最佳 Kotlin 风格,但作为起点很有用。

5.5 Kotlin 迁移后的 R8 / ProGuard 注意事项

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 框架

java
// 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网络抓包
jadxAPK 反编译查看源码
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 迁移的能力

接下来的建议

  1. 拿一个实际的老项目练手
  2. 尝试从头编译运行,记录遇到的问题
  3. 尝试修复一个小 Bug 或添加一个小功能
  4. 加入 Android 开发者社区(如 Stack Overflow、Reddit r/androiddev)

祝你在 Android 维护之路上顺利!

面向前端开发者的 Java Android 开发教程