Skip to content

模块 10:老项目实战指南与 Kotlin 迁移

目标:获得接手 Java Android 老项目的系统性指南,学会识别技术栈,理解常见"历史包袱",以及规划渐进式 Kotlin 迁移。

学习目标

读完这一章,你将能够:

  • [ ] 拿到一个陌生的 Java Android 老项目,知道第一周该按什么顺序做"体检"
  • [ ] 通过依赖包名和代码风格判断项目的技术栈年代(Support Library 还是 AndroidX、用了哪些过时库)
  • [ ] 说出 Support Library 到 AndroidX 的变迁原因,并用 Android Studio 工具完成迁移
  • [ ] 为一个老项目制定渐进式现代化计划,知道哪些改动优先级高、哪些必须谨慎
  • [ ] 让 Java 和 Kotlin 在同一个项目里共存,并能在两边互相调用代码
  • [ ] 规划一条渐进式的 Kotlin 迁移路线,而不是一次性重写整个项目

学习建议:这一章更像"经验指南"而不是语法教程,建议结合一个真实的老项目对照阅读——每讲到一个"历史包袱"(ButterKnife、AsyncTask、EventBus……),就到自己代码里搜一搜有没有同类写法。学完即用,印象最深。

概念入门:接手老项目和写新项目有什么不同?

前面九章你学的都是"怎么写"——怎么搭环境、怎么读 Java、怎么画 UI、怎么联网、怎么存数据。这一章换一个角度:怎么接手别人已经写好的老项目。这两件事看起来都是"写代码",能力要求却完全不同。写新项目像在空地上盖房子,你说了算;接手老项目像接手一栋住了二十年的老楼,里面有别人的装修、别人的暗线、别人的"将就"——你的第一反应不该是"推倒重写",而是先搞清楚这栋楼的结构和历史。

为什么要先识别技术栈年代? 因为 Android 这十年变化太快,每隔两三年就换一套主流做法。同样是"显示一个列表",2015 年的项目可能用 ListView + 手写 ViewHolder,2018 年用 RecyclerView,2023 年用 Jetpack Compose;同样是"网络请求",可能是 AsyncTask + HttpURLConnection,也可能是 RxJava + Retrofit,也可能是 Coroutines + Retrofit。你只有先看清这个项目停在哪个年代,才能看懂它为什么这么写、哪些是过时的"历史包袱"、哪些能改哪些动不得。这一章给了一张技术栈时代判断表,照着对就行。

Support Library 到 AndroidX 是怎么回事? 这是 2018 年 Google 做的一次大重构。早年 Android 提供一套叫 Support Library 的兼容库(包名 android.support.*),用来让新特性也能跑在老系统上。但版本越加越多、命名越来越乱,Google 干脆把它们重构成 AndroidX(包名 androidx.*),统一编号、统一管理。这事看着只是改包名,实际影响很大——后来几乎所有第三方库都只支持 AndroidX 了。所以接手 2018 年前的老项目,第一个常遇到的坎就是"要不要迁到 AndroidX"。Android Studio 提供了一键迁移工具,但因为改动面广,迁完一定要回归测试。

为什么 Kotlin 迁移要渐进式,不能一次性重写? 很多人接手 Java 老项目的第一反应是"全部用 Kotlin 重写一遍",这几乎总是灾难。原因有三:一是重写期间项目还得继续发版、修 Bug,两套代码难以并行;二是重写等于把所有"已知能跑的代码"换成"新写的、没测过的代码",风险全压上来;三是重写往往没完没了,最后变成"半新半旧永远合不拢"。正确做法是渐进式:新写的文件用 Kotlin,旧的 Java 文件按优先级一个个慢慢迁,借助 Java/Kotlin 可以互调的特性让两边和平共处。这一章会给出具体的迁移顺序。

本章末尾有一张「前端开发者备忘」对照表,把接手前端项目和接手 Android 项目的工作流做了映射,有前端经验的读者读到结尾时可以对照看看;零基础读者直接进入 第 1 节:接手老项目的第一周 即可。

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 空安全 迁移都需要渐进式进行

本章小结

回顾一下这一章的要点:

  • 接手老项目和写新项目是两种能力:第一周先做项目体检——能编译运行吗、基本信息是什么、技术栈是哪个年代
  • 通过依赖包名和代码风格判断技术栈年代android.support.* 是 2018 前、androidx.* 是 2018 后,ButterKnife/RxJava/EventBus 各对应特定时期
  • Support Library → AndroidX 是接手老项目几乎绕不开的一步,Android Studio 提供 Refactor → Migrate to AndroidX 自动迁移,但要配合回归测试
  • 处理历史包袱要按难度和风险排优先级:先保证能编译运行和修崩溃,再升 targetSdk,最后才动 Kotlin 迁移——别一上来就大改
  • 过时库各有替代方案:ButterKnife/findViewById → ViewBinding,AsyncTask → Executor/Coroutines,EventBus → LiveData
  • 升级构建工具链时每次只升一级,顺序是 JDK → Gradle → AGP → 第三方依赖,跑通再继续
  • Java 和 Kotlin 可以共存并互调,但要注意平台类型(String!)、SAM 转换、泛型不变性等边界陷阱
  • Kotlin 迁移要渐进式:新文件先用 Kotlin,再迁工具类、数据模型,最后才动 Activity/Fragment——绝不一次性重写

这是整个教程的最后一章。到这里,你已经从搭环境、学语法,一路走到了调试测试和接手老项目——具备了独立维护一个 Java Android 项目的完整能力。

恭喜你!

完成全部 10 个模块的学习后,你已经具备了:

  • ✅ 搭建 Android Java 开发环境的能力
  • ✅ 阅读和理解 Java Android 代码的能力
  • ✅ 修改和维护老项目 UI、网络、数据存储等功能的能力
  • ✅ 识别项目技术栈和架构模式的能力
  • ✅ 使用调试工具排查问题的能力
  • ✅ 规划渐进式现代化和 Kotlin 迁移的能力

接下来的建议

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

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

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