1
概述 (Overview)
Android 架构、项目结构与 Java vs Kotlin 对比
📌 在开始之前:Android 四大组件是什么?

Android 应用由 四大核心组件 构成,它们是 Android 独有的设计思想,每个组件都有独立的生命周期和系统调度机制:

① Activity
代表一个屏幕界面,用户能看到、能操作。比如登录页、主页、设置页都是独立的 Activity。
② Service
后台运行的任务,没有界面。音乐播放、文件下载、保持心跳连接都由 Service 完成。
③ BroadcastReceiver
监听系统或应用广播事件的"监听器"。当网络断开、电量不足、收到短信时会触发。
④ ContentProvider
跨应用数据共享提供统一接口。手机通讯录、相册就是通过 ContentProvider 对外开放数据的。

💡 重要认知:每个组件都有自己的生命周期(系统会在特定时机创建或销毁它们),理解生命周期是写出稳定应用的关键。

Android 架构图

Android 系统分为五个主要层次,从下到上依次为。作为 App 开发者,你主要在最顶层(Applications Layer)工作,通过 Application Framework 调用系统能力。

📱
Applications Layer
系统应用 + 第三方 App(Maps, Gmail, Chrome…)
▲ ▼
🔧
Application Framework
Activity Manager · Window Manager · Content Providers · View System · Package Manager · Telephony · Resource Manager · Notification Manager
▲ ▼
⚙️
Android Runtime (ART)
AOT / JIT 编译 · 垃圾回收 (GC)
Core Java Libraries
java.util · java.io · java.net · java.nio…
▲ ▼
🔌
Hardware Abstraction Layer (HAL)
Audio · Bluetooth · Camera · Sensors · GPS · NFC…
▲ ▼
🐧
Linux Kernel
Binder IPC · Display Driver · Camera Driver · WiFi Driver · Power Management
作为 App 开发者,你需要关心的层次
  • Applications Layer:你写的代码就在这里,直接面向用户
  • Application Framework:Android SDK API 所在,startActivity()Toast.show() 等都属于这一层,你每天都在调用
  • Android Runtime (ART):把你写的 Java/Kotlin 代码编译成机器码运行,自动管理内存
  • HAL / Linux Kernel:负责与摄像头、GPS、WiFi 等硬件通信,通常不需要直接接触

Java vs Kotlin 对比

特性JavaKotlin
Null 安全需手动 null 检查,易 NPE内置 Null Safety,? 和 !! 运算符
扩展函数不支持支持,无需继承即可扩展
协程不支持(需 RxJava)原生支持,简化异步编程
数据类需手写 getter/setter/equals/hashCodedata class 自动生成
LambdaJava 8+ 支持,Android API 24+一级公民,语法更简洁
互操作性100% 与 Kotlin 互操作100% 与 Java 互操作
性能相同(都编译为字节码)相同,inline 函数零开销
Google 推荐稳定,大量历史代码2019 年起为首选语言

Android 项目结构

用 Android Studio 创建新项目后,你会看到下面这棵目录树。最需要关注的三个位置:① java/ 目录下写业务代码;② res/layout/ 下写 XML 布局;③ AndroidManifest.xml 注册组件。其他文件初期不用深究。

Project Structure
MyApp/
├── app/
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/com/example/myapp/   # Java 源码目录
│   │   │   │   ├── MainActivity.java
│   │   │   │   ├── model/               # 数据模型
│   │   │   │   ├── adapter/             # RecyclerView 适配器
│   │   │   │   ├── service/             # Service 类
│   │   │   │   └── util/                # 工具类
│   │   │   ├── res/
│   │   │   │   ├── layout/              # XML 布局文件
│   │   │   │   ├── drawable/            # 图片/矢量图
│   │   │   │   ├── values/              # strings.xml, colors.xml, styles.xml
│   │   │   │   ├── menu/                # 菜单资源
│   │   │   │   └── anim/                # 动画资源
│   │   │   └── AndroidManifest.xml      # 应用清单(核心配置文件)
│   │   ├── test/                        # 单元测试
│   │   └── androidTest/                 # 集成测试
│   └── build.gradle                     # 模块级构建脚本
├── build.gradle                         # 项目级构建脚本
├── gradle.properties
└── settings.gradle

AndroidManifest.xml 详解

AndroidManifest.xml 是每个 Android 项目必须有的配置文件,它向系统"申报"这个应用的基本信息:应用包名、权限声明、四大组件注册。重要的是:所有的 Activity、Service、BroadcastReceiver、ContentProvider 都必须在 Manifest 中注册,否则系统根本不知道它们存在。

XML
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">

    <!-- 权限声明 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">

        <!-- Activity 注册 -->
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Service 注册 -->
        <service android:name=".MusicService"
            android:exported="false" />

        <!-- BroadcastReceiver 静态注册 -->
        <receiver android:name=".BootReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <!-- ContentProvider 注册 -->
        <provider
            android:name=".MyContentProvider"
            android:authorities="com.example.myapp.provider"
            android:exported="false" />
    </application>
</manifest>
2
Activity
四大组件之一 — 用户交互界面的基本单元

Activity 生命周期

Activity 生命周期是 Android 开发中最核心的概念,正确理解并处理各状态回调是构建稳定应用的基础。

Activity 启动
onCreate()
onStart()
onResume() ← 可见且可交互
↓ 用户按 Home / 其他 Activity 覆盖
onPause() ← 失去焦点,仍可见
↓ 完全不可见
onStop()
↓ 系统回收 / 用户按返回
onDestroy()
Activity 销毁
⟳ 短暂失焦恢复
onPause()
onResume()
短暂失焦后恢复
↺ 重新可见
onStop()
onRestart()
onStart()
onResume()
重新回到前台

各回调方法详解与最佳实践

Java
package com.example.myapp;

import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static final String KEY_COUNT = "key_count";
    private int clickCount = 0;

    /**
     * onCreate: Activity 首次创建时调用
     * - 初始化 UI 组件
     * - 恢复保存的状态
     * - 绑定 ViewModel
     * savedInstanceState 非空时说明是从销毁状态恢复
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);  // 设置布局
        Log.d(TAG, "onCreate");

        // 恢复状态
        if (savedInstanceState != null) {
            clickCount = savedInstanceState.getInt(KEY_COUNT, 0);
        }

        // 初始化视图
        initViews();
    }

    /**
     * onStart: Activity 变为可见时调用(用户可以看到但不能交互)
     * - 开始监听广播(如网络变化)
     * - 刷新 UI 数据
     */
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart");
    }

    /**
     * onResume: Activity 进入前台,用户可以交互
     * - 开始动画
     * - 打开相机/传感器
     * - 恢复视频播放
     */
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
        // 开始传感器监听、恢复播放等
    }

    /**
     * onPause: Activity 失去焦点(另一个 Activity 覆盖在上面)
     * 重要: 此方法必须快速执行,不能做耗时操作
     * - 暂停动画
     * - 释放相机
     * - 保存未提交的数据
     */
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause");
        // 暂停传感器、相机等资源密集型操作
    }

    /**
     * onStop: Activity 完全不可见
     * - 停止位置更新
     * - 保存数据到数据库
     * - 注销不必要的广播接收器
     */
    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop");
        // 保存数据,释放资源
    }

    /**
     * onRestart: 从停止状态重新变为可见(onStop -> onRestart -> onStart)
     */
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart");
    }

    /**
     * onDestroy: Activity 即将销毁
     * - 释放所有资源
     * - 取消异步任务
     * - isFinishing() 判断是主动销毁还是系统回收
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy, isFinishing=" + isFinishing());
        // 释放所有资源,避免内存泄漏
    }

    /**
     * onSaveInstanceState: 系统即将销毁 Activity 前调用(旋转、内存不足)
     * 注意: 用户主动按返回键不会触发此方法
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(KEY_COUNT, clickCount);  // 保存计数
        outState.putString("user_input", "some text");
    }

    /**
     * onRestoreInstanceState: 从已保存状态恢复时调用
     * 也可以在 onCreate 的 savedInstanceState 中恢复
     */
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        clickCount = savedInstanceState.getInt(KEY_COUNT, 0);
    }

    private void initViews() {
        // 初始化视图绑定
    }
}

生命周期速查表:每个回调应该做什么

回调方法✅ 适合做的事❌ 不应该做的事注意要点
onCreate() 初始化 UI 控件、绑定 ViewModel、恢复 savedInstanceState、设置点击监听 长时间网络请求或数据库操作(会延迟启动) 整个生命周期只调用一次,是入口
onStart() 开始位置更新、注册广播、刷新界面数据 独占资源(相机)——用户不一定能交互 Activity 可见但未获得焦点
onResume() 开启动画、打开相机、恢复视频播放、打开传感器 耗时操作(会阻塞 UI) 用户能看到且能交互,是"运行中"状态
onPause() 暂停动画、释放相机、保存未提交的草稿 耗时操作!onPause() 必须快速返回,否则下一个 Activity 无法显示 失去焦点但可能仍可见(如对话框覆盖时)
onStop() 保存数据到数据库、停止动画、释放大资源 更新 UI(Activity 不可见) Activity 完全不可见,可能被系统销毁
onDestroy() 释放所有资源、取消异步任务 重新初始化数据(应在 onCreate 中做) isFinishing() 区分主动关闭 vs 系统回收
onSaveInstanceState() 保存 UI 状态(输入框内容、滚动位置、计数器) 保存大量数据或用户数据(用数据库) 只在系统主动销毁时调用(旋转屏幕、内存不足),用户按返回键触发
初学者最容易犯的错误:在 onPause() 中做耗时操作

当用户按下 Home 键或其他 Activity 弹出时,系统等待你的 onPause() 执行完毕才能显示下一个界面。如果 onPause 里有网络请求,用户会感觉"卡顿"。在 onPause 里只做毫秒级操作,数据保存放 onStop。

启动模式 (Launch Mode)

启动模式决定了 Activity 实例的创建方式及其在返回栈中的行为,在 AndroidManifest.xml 中通过 android:launchMode 配置。

默认

standard

每次启动都创建新实例,压入当前 Task 栈顶。适合大多数 Activity。栈可以有多个相同 Activity 实例。

常用

singleTop

若目标 Activity 已在栈顶,则不创建新实例,调用 onNewIntent();否则创建新实例。适合通知跳转、搜索页。

推荐

singleTask

整个系统中只有一个实例。若已存在,将其之上的所有 Activity 出栈,调用 onNewIntent()。适合主界面。

特殊

singleInstance

独占一个 Task,整个系统唯一实例。其他 Activity 不能进入此 Task。适合来电界面、锁屏。

XML — AndroidManifest.xml
<!-- singleTask: 主页 Activity,确保全局唯一 -->
<activity
    android:name=".HomeActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.example.myapp" />

<!-- singleTop: 推送通知目标 Activity -->
<activity
    android:name=".NotificationActivity"
    android:launchMode="singleTop" />
Java — onNewIntent 处理
// singleTop/singleTask/singleInstance 模式下,已存在实例时调用 onNewIntent
@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    // 更新当前 Activity 的 Intent
    setIntent(intent);
    // 处理新的 Intent 数据
    String action = intent.getStringExtra("action");
    if ("refresh".equals(action)) {
        refreshData();
    }
}

private void refreshData() { /* 刷新数据 */ }

Intent 显式/隐式启动

Intent(意图)是 Android 组件间通信的核心机制,可以理解为一个"消息包"——它描述了"你想做什么",系统负责找到合适的组件来完成这件事。

Intent 分为两种:

Intent 还可以携带数据:通过 putExtra(key, value) 传递基本类型或 Serializable/Parcelable 对象,目标 Activity 通过 getIntent().getStringExtra(key) 取出。

Java — Intent 使用
package com.example.myapp;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;

public class IntentDemoActivity extends AppCompatActivity {

    // ========== 新 API: ActivityResultLauncher (推荐) ==========
    // 替代已废弃的 startActivityForResult()
    private ActivityResultLauncher resultLauncher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intent_demo);

        // 注册 ActivityResultLauncher(必须在 onCreate 中注册)
        resultLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            (ActivityResult result) -> {
                // 处理返回结果
                if (result.getResultCode() == RESULT_OK && result.getData() != null) {
                    String returnedData = result.getData().getStringExtra("result_key");
                    // 使用返回数据
                }
            }
        );
    }

    // --------- 显式 Intent:明确指定目标 Activity ---------
    private void startExplicitActivity() {
        Intent intent = new Intent(this, DetailActivity.class);

        // 传递基本数据
        intent.putExtra("user_id", 123);
        intent.putExtra("user_name", "张三");

        // 传递 Bundle
        Bundle bundle = new Bundle();
        bundle.putString("token", "abc123");
        intent.putExtras(bundle);

        startActivity(intent);
    }

    // --------- 带返回结果的启动 ---------
    private void startForResult() {
        Intent intent = new Intent(this, EditActivity.class);
        intent.putExtra("edit_mode", true);
        resultLauncher.launch(intent);
    }

    // --------- 隐式 Intent:通过 Action 匹配 ---------
    private void startImplicitActivities() {
        // 打开网页
        Intent webIntent = new Intent(Intent.ACTION_VIEW,
            Uri.parse("https://www.google.com"));
        startActivity(webIntent);

        // 拨打电话(需要 CALL_PHONE 权限)
        Intent callIntent = new Intent(Intent.ACTION_DIAL,
            Uri.parse("tel:+86138xxxxxx"));
        startActivity(callIntent);

        // 发送邮件
        Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
        emailIntent.setData(Uri.parse("mailto:"));
        emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"test@example.com"});
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, "主题");
        emailIntent.putExtra(Intent.EXTRA_TEXT, "邮件内容");
        startActivity(Intent.createChooser(emailIntent, "选择邮件应用"));

        // 分享文本
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.setType("text/plain");
        shareIntent.putExtra(Intent.EXTRA_TEXT, "分享内容");
        startActivity(Intent.createChooser(shareIntent, "分享到"));
    }

    // --------- Intent Flags ---------
    private void startWithFlags() {
        Intent intent = new Intent(this, HomeActivity.class);

        // FLAG_ACTIVITY_CLEAR_TOP: 清除 HomeActivity 之上的所有 Activity
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

        // FLAG_ACTIVITY_SINGLE_TOP: 若已在栈顶则不创建新实例
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

        // FLAG_ACTIVITY_NEW_TASK: 在新 Task 中启动(从非 Activity 上下文启动时必须加)
        // intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        // FLAG_ACTIVITY_NO_HISTORY: 不保留历史记录(Activity 离开后立即从栈中移除)
        // intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);

        startActivity(intent);
    }
}
❌ 初学者常见崩溃:从非 Activity 的 Context 启动 Activity 时忘记加 FLAG_ACTIVITY_NEW_TASK

ServiceBroadcastReceiverApplication 等非 Activity Context 启动 Activity,必须加上 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),否则程序会崩溃并报 Calling startActivity() from outside of an Activity context

// 在 Service 中启动 Activity — 必须加 NEW_TASK
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  // ← 不能少
startActivity(intent);

处理配置变更(屏幕旋转)

这是 Android 初学者最常遇到的"灵异问题"之一:用户转动屏幕,界面数据全部丢失了!

原因是 Android 的设计:当设备发生"配置变更"(屏幕旋转、语言切换、字体大小改变、折叠屏展开等),系统会销毁并重新创建当前的 Activity。这意味着你在内存中保存的所有变量、已加载的数据,都会丢失,界面从头开始初始化。

解决方案有两种:

Java — ViewModel + 配置变更
// 方案一: 在 AndroidManifest 中声明自己处理配置变更
// android:configChanges="orientation|screenSize|keyboardHidden"

// 方案二(推荐): 使用 ViewModel 保留数据
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;

// ViewModel: 在配置变更时自动保留
public class MainViewModel extends ViewModel {
    private MutableLiveData userData = new MutableLiveData<>();
    private List itemList = new ArrayList<>();

    public LiveData getUserData() { return userData; }

    public void loadData() {
        // 异步加载数据
        userData.setValue("loaded data");
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        // 清理资源(如取消网络请求)
    }
}

// 在 Activity 中使用 ViewModel
public class ConfigChangeActivity extends AppCompatActivity {
    private MainViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // ViewModelProvider 确保旋转后返回同一 ViewModel 实例
        viewModel = new ViewModelProvider(this).get(MainViewModel.class);

        // 观察数据变化
        viewModel.getUserData().observe(this, data -> {
            // 更新 UI
        });

        viewModel.loadData();
    }
}
最佳实践 使用 ViewModel + LiveData 处理配置变更,ViewModel 的生命周期与 Activity 的配置变更无关,只有 Activity 真正销毁时才会被清除。
3
Service
四大组件之一 — 后台运行,无用户界面

想象你正在用手机听音乐,这时候切换到微信回消息——音乐并没有停,对吗?这就是 Service 在发挥作用。Service 是一种在后台运行、没有用户界面的组件,专门处理那些不需要用户直接操作、但需要持续运行的任务。

Service 解决了一个核心问题:Activity 一旦不可见就可能被系统回收,但很多任务(播放音乐、下载文件、保持网络连接)必须在用户切换界面后依然运行。Service 就是为这类场景而生的。

重要认知: Service 默认在主线程运行,并不是一个独立线程!执行耗时操作(网络请求、大量计算)时,你仍然需要在 Service 内部手动开启子线程,否则会导致 ANR(应用无响应)。

Started Service vs Bound Service

Started Service(启动服务)

通过 startService() 启动,独立运行,调用者销毁后仍继续执行,需调用 stopSelf() 或 stopService() 停止。适合下载文件、播放音乐。

Bound Service(绑定服务)

通过 bindService() 绑定,提供客户端-服务器接口,所有绑定者解绑后自动销毁。适合 Activity 与 Service 通信。

Service 生命周期对比

Started Service
startService()
onCreate()
onStartCommand()
↓ 运行中
stopSelf() / stopService()
onDestroy()
Bound Service
bindService()
onCreate()
onBind()
↓ 所有客户端解绑
onUnbind()
onDestroy()

完整音乐播放器 Service 示例

Java — MusicService.java
package com.example.myapp.service;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import androidx.core.app.NotificationCompat;

public class MusicService extends Service {

    private static final String TAG = "MusicService";
    private static final String CHANNEL_ID = "music_channel";
    private static final int NOTIFICATION_ID = 1;

    // Binder 对象,供客户端调用本地方法
    private final IBinder musicBinder = new MusicBinder();
    private MediaPlayer mediaPlayer;
    private String currentSong = "";

    /**
     * MusicBinder: 返回给客户端,用于直接调用 Service 方法
     * 适用于同进程通信
     */
    public class MusicBinder extends Binder {
        public MusicService getService() {
            return MusicService.this;  // 返回 Service 实例
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
        mediaPlayer = new MediaPlayer();
        // 创建通知渠道(Android 8.0+)
        createNotificationChannel();
    }

    /**
     * onStartCommand: 每次 startService() 都会调用
     * 返回值决定系统杀死后的重启行为:
     * START_STICKY: 重启,Intent 为 null
     * START_NOT_STICKY: 不重启
     * START_REDELIVER_INTENT: 重启,重新传递最后一个 Intent
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand, startId=" + startId);

        if (intent != null) {
            String action = intent.getAction();
            if ("PLAY".equals(action)) {
                String songPath = intent.getStringExtra("song_path");
                playSong(songPath);
            } else if ("PAUSE".equals(action)) {
                pauseSong();
            } else if ("STOP".equals(action)) {
                stopSong();
                stopSelf();  // 停止服务
            }
        }

        return START_STICKY;  // 被杀死后系统会重启 Service
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        return musicBinder;  // 返回 Binder 给客户端
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }

    // ---- 公开给 Bound 客户端调用的方法 ----
    public void playSong(String path) {
        try {
            currentSong = path;
            mediaPlayer.reset();
            mediaPlayer.setDataSource(path);
            mediaPlayer.prepareAsync();
            mediaPlayer.setOnPreparedListener(mp -> {
                mp.start();
                // 启动前台服务,显示通知栏
                startForeground(NOTIFICATION_ID, buildNotification());
            });
        } catch (Exception e) {
            Log.e(TAG, "playSong error", e);
        }
    }

    public void pauseSong() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }
    }

    public void stopSong() {
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            stopForeground(true);  // 移除通知
        }
    }

    public boolean isPlaying() {
        return mediaPlayer != null && mediaPlayer.isPlaying();
    }

    public int getCurrentPosition() {
        return mediaPlayer != null ? mediaPlayer.getCurrentPosition() : 0;
    }

    // ---- 前台服务通知 ----
    private Notification buildNotification() {
        Intent stopIntent = new Intent(this, MusicService.class);
        stopIntent.setAction("STOP");
        PendingIntent stopPendingIntent = PendingIntent.getService(
            this, 0, stopIntent,
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );

        return new NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("正在播放")
            .setContentText(currentSong)
            .setSmallIcon(android.R.drawable.ic_media_play)
            .addAction(android.R.drawable.ic_media_pause, "停止", stopPendingIntent)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .setOngoing(true)  // 不可滑动消除
            .build();
    }

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID, "音乐播放",
                NotificationManager.IMPORTANCE_LOW
            );
            channel.setDescription("音乐播放控制");
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
    }
}
Java — Activity 绑定 Service
public class PlayerActivity extends AppCompatActivity {

    private MusicService musicService;
    private boolean isBound = false;

    // ServiceConnection: 监听 Service 连接状态
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 获取 Binder,进而获取 Service 实例
            MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
            musicService = binder.getService();
            isBound = true;
            // 此时可以调用 musicService 的方法
            updateUI();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBound = false;
            musicService = null;
        }
    };

    @Override
    protected void onStart() {
        super.onStart();
        // 绑定 Service
        Intent intent = new Intent(this, MusicService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 解绑 Service(避免内存泄漏)
        if (isBound) {
            unbindService(serviceConnection);
            isBound = false;
        }
    }

    private void onPlayButtonClick() {
        if (isBound) {
            musicService.playSong("/sdcard/music/song.mp3");
        }
    }

    private void updateUI() {
        if (isBound && musicService.isPlaying()) {
            // 更新播放按钮状态
        }
    }
}

WorkManager vs JobIntentService

Android 对后台执行有严格限制(从 Android 8.0 开始越来越严),这是为了省电和保护用户体验。简单来说:应用在后台时,不能随意执行代码、启动 Service、接收广播。这导致 Android 后台任务方案经历了多次演化,IntentService → JobScheduler → WorkManager,旧 API 相继被弃用。

WorkManager 是目前(2024+)Google 唯一推荐的后台任务调度方案:

方案状态使用场景特点
IntentService已弃用-串行执行,自动停止
JobIntentService已弃用-兼容旧版,支持 JobScheduler
WorkManager推荐延迟可靠后台任务保证执行,支持约束条件
Foreground Service推荐长时间用户感知任务必须显示通知
Coroutines/RxJava推荐即时后台操作与 UI 生命周期绑定

使用 WorkManager 的核心步骤:① 创建 Worker 类(继承 Worker,在 doWork() 中写你的任务逻辑)→ ② 构建 WorkRequest(配置约束条件、执行策略)→ ③ 提交给 WorkManager(调度执行)。

Java — WorkManager 示例
import androidx.work.*;
import java.util.concurrent.TimeUnit;

// 定义 Worker
public class SyncWorker extends Worker {
    public SyncWorker(Context context, WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Result doWork() {
        // 在后台线程执行同步任务
        String userId = getInputData().getString("user_id");
        try {
            syncDataToServer(userId);
            return Result.success();
        } catch (Exception e) {
            // 失败重试(最多 3 次)
            if (getRunAttemptCount() < 3) {
                return Result.retry();
            }
            return Result.failure();
        }
    }

    private void syncDataToServer(String userId) { /* 网络请求 */ }
}

// 在 Activity/Fragment 中调度任务
public void scheduleSync() {
    // 定义约束条件
    Constraints constraints = new Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)  // 需要网络
        .setRequiresBatteryNotLow(true)                 // 电量不低
        .build();

    // 创建工作请求
    Data inputData = new Data.Builder()
        .putString("user_id", "user_123")
        .build();

    OneTimeWorkRequest syncRequest = new OneTimeWorkRequest.Builder(SyncWorker.class)
        .setConstraints(constraints)
        .setInputData(inputData)
        .setInitialDelay(10, TimeUnit.MINUTES)  // 延迟 10 分钟
        .addTag("sync_task")
        .build();

    // 加入队列
    WorkManager.getInstance(this)
        .enqueueUniqueWork(
            "sync_work",
            ExistingWorkPolicy.REPLACE,  // 已有相同任务则替换
            syncRequest
        );

    // 观察状态
    WorkManager.getInstance(this)
        .getWorkInfoByIdLiveData(syncRequest.getId())
        .observe(this, info -> {
            if (info != null && info.getState().isFinished()) {
                // 任务完成
            }
        });
}
❌ Service 四大常见错误,初学者必看
  1. 在 Service 里直接做网络请求:Service 运行在主线程!网络请求会触发 NetworkOnMainThreadException 崩溃,必须在 Service 内部手动开子线程(或用 WorkManager)。
  2. 忘记调用 stopSelf() / stopService():Started Service 不会自动停止,一直运行会消耗电量和内存。确保任务完成后调用停止方法。
  3. Bound Service 解绑后没有 stop:如果同时用 startService() 启动又 bindService() 绑定,解绑后 Service 不会停止,必须额外调用 stopService()。
  4. 没注册前台服务通知(Android 8.0+):从 Android 8.0 起,后台 Service 有严格的存活限制。长期运行的任务必须用前台服务(startForeground())并显示通知,否则系统几分钟后就会强制停止。
4
BroadcastReceiver
四大组件之一 — 系统/应用级消息广播

你有没有注意到,当手机电量低时,所有应用都会收到提醒;或者插上耳机,音乐播放器就自动知道了?这就是广播机制(Broadcast)的体现。

BroadcastReceiver 实现了一种发布-订阅模式:系统或其他应用发送广播(Publisher),而注册了对应 IntentFilter 的 BroadcastReceiver 会自动收到通知(Subscriber)。这让不同组件和应用之间可以相互通信,而无需直接持有对方的引用。

常见使用场景:

BroadcastReceiver 的执行限制: onReceive() 方法在主线程执行,且最多只有 10 秒的执行时间。超时会触发 ANR。因此不能在 onReceive() 中做任何耗时操作,如需执行后台任务,应在其中启动 Service 或使用 WorkManager。

静态注册 vs 动态注册

静态注册(Manifest 声明)

在 AndroidManifest.xml 中注册,应用未运行时也能接收广播。Android 8.0+ 对大多数隐式广播有限制。

动态注册(代码注册)

在代码中 registerReceiver(),随组件生命周期注册/注销。更灵活,适合与 Activity/Service 生命周期绑定。

下面的示例展示了完整的广播使用流程:① 定义 BroadcastReceiver 类(重写 onReceive 处理事件)→ ② 在 Activity 中动态注册(指定要监听的 action)→ ③ 在 onDestroy 中注销(防止内存泄漏)。

Java — BroadcastReceiver 完整示例
package com.example.myapp;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;

// ===== 定义 BroadcastReceiver =====
public class NetworkChangeReceiver extends BroadcastReceiver {

    private static final String TAG = "NetworkReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG, "接收到广播: " + action);

        if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
            // 检查网络状态
            ConnectivityManager cm = (ConnectivityManager)
                context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo info = cm.getActiveNetworkInfo();
            boolean isConnected = info != null && info.isConnected();
            Log.d(TAG, "网络状态: " + (isConnected ? "已连接" : "断开"));
        }
    }
}

// ===== 在 Activity 中动态注册/注销 =====
public class MainActivity extends AppCompatActivity {

    private NetworkChangeReceiver networkReceiver;

    @Override
    protected void onStart() {
        super.onStart();
        // 动态注册广播接收器
        networkReceiver = new NetworkChangeReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        // 可以添加多个 Action
        filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
        registerReceiver(networkReceiver, filter);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 必须注销,否则内存泄漏
        if (networkReceiver != null) {
            unregisterReceiver(networkReceiver);
            networkReceiver = null;
        }
    }

    // 发送自定义广播
    private void sendCustomBroadcast() {
        String action = "com.example.myapp.MY_ACTION";

        // 普通广播(异步,无序)
        Intent intent = new Intent(action);
        intent.putExtra("data", "some data");
        sendBroadcast(intent);

        // 有序广播(同步,可被拦截,有优先级)
        sendOrderedBroadcast(intent, null,
            new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    // 最终结果接收者(无论中间是否被拦截都会收到)
                    String result = getResultData();
                    Log.d("Final", "最终广播结果: " + result);
                }
            },
            null, Activity.RESULT_OK, null, null
        );
    }
}
XML — 静态注册
<!-- AndroidManifest.xml 静态注册(Android 8.0+ 仅支持特定系统广播)-->
<receiver
    android:name=".BootReceiver"
    android:exported="true"
    android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
    <intent-filter android:priority="100">
        <!-- 开机广播,仍然支持静态注册 -->
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

<!-- 自定义广播静态注册,设置权限保护 -->
<receiver
    android:name=".SecureReceiver"
    android:exported="true"
    android:permission="com.example.myapp.SEND_BROADCAST">
    <intent-filter>
        <action android:name="com.example.myapp.SECURE_ACTION" />
    </intent-filter>
</receiver>

本地广播 LocalBroadcastManager

注意 LocalBroadcastManager 已被 弃用,推荐使用 LiveData、Flow 或 EventBus 代替应用内通信。
Java — 应用内事件通信(推荐替代方案)
// 推荐方案: 使用 ViewModel + LiveData 替代本地广播
// 在 ViewModel 中定义可观察的事件
public class SharedViewModel extends ViewModel {
    private MutableLiveData eventLiveData = new MutableLiveData<>();

    public void postEvent(String event) {
        eventLiveData.setValue(event);
    }

    public LiveData getEvent() {
        return eventLiveData;
    }
}

// Fragment A 发送事件
SharedViewModel vm = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
vm.postEvent("data_updated");

// Fragment B 接收事件
vm.getEvent().observe(getViewLifecycleOwner(), event -> {
    if ("data_updated".equals(event)) {
        refreshList();
    }
});

常用系统广播

Action触发时机是否支持静态注册
BOOT_COMPLETED开机完成
ACTION_POWER_CONNECTED连接充电器
ACTION_BATTERY_LOW电量不足
ACTION_SCREEN_ON/OFF屏幕开/关否(8.0+)
CONNECTIVITY_ACTION网络状态变化否(8.0+)
ACTION_LOCALE_CHANGED语言/地区变化
ACTION_PACKAGE_ADDED安装新应用
ACTION_TIME_CHANGED时间被手动修改
5
ContentProvider
四大组件之一 — 跨应用数据共享

假设你开发了一款日历应用,想让其他应用(比如 Siri、第三方小工具)能够读取或写入你的日历数据;或者你想在自己的应用中访问手机联系人、相册、短信——这就是 ContentProvider 发挥作用的场景。

ContentProvider 是 Android 四大组件中负责"数据共享"的那一个,它为跨应用的数据访问提供了统一的接口,基于 URI(统一资源标识符) 来定位数据,类似于 REST API 用 URL 定位资源的方式。

什么时候需要用 ContentProvider?

普通开发者需要自己实现 ContentProvider 吗? 大多数情况下不需要——你主要是作为"客户端"通过 ContentResolver 来访问系统的 ContentProvider。只有当你的应用需要向其他应用提供结构化数据时,才需要自定义 ContentProvider。

URI 结构解析

content://com.example.myapp.provider/users/42
scheme
content://
固定前缀,标识 ContentProvider
authority
com.example.
myapp.provider
唯一标识符,对应包名
path
/users
数据表名或资源路径
id
/42
具体记录的行 ID

自定义 ContentProvider 完整实现

自定义 ContentProvider 需要继承 ContentProvider 并实现 6 个核心方法,这些方法直接对应 CRUD 操作:

UriMatcher 是关键辅助类,它负责解析 URI 路径,判断客户端请求的是"整张表"还是"某一条记录",让你在一个 ContentProvider 中优雅地处理多种 URI 格式。

Java — UserContentProvider.java
package com.example.myapp;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class UserContentProvider extends ContentProvider {

    private static final String TAG = "UserProvider";

    // Authority 必须与 Manifest 中声明的一致
    public static final String AUTHORITY = "com.example.myapp.provider";
    public static final Uri CONTENT_URI =
        Uri.parse("content://" + AUTHORITY + "/users");

    // URI 匹配码
    private static final int USERS = 100;       // content://authority/users
    private static final int USER_ID = 101;     // content://authority/users/42

    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        uriMatcher.addURI(AUTHORITY, "users", USERS);
        uriMatcher.addURI(AUTHORITY, "users/#", USER_ID);  // # 匹配数字
    }

    private SQLiteOpenHelper dbHelper;

    @Override
    public boolean onCreate() {
        // 初始化数据库(不要做耗时操作)
        dbHelper = new UserDatabaseHelper(getContext());
        Log.d(TAG, "ContentProvider onCreate");
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
                        @Nullable String selection, @Nullable String[] selectionArgs,
                        @Nullable String sortOrder) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor;

        switch (uriMatcher.match(uri)) {
            case USERS:
                // 查询所有用户
                cursor = db.query("users", projection, selection,
                    selectionArgs, null, null, sortOrder);
                break;
            case USER_ID:
                // 查询指定 ID 用户
                long id = ContentUris.parseId(uri);
                cursor = db.query("users", projection,
                    "_id = ?", new String[]{String.valueOf(id)},
                    null, null, sortOrder);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }

        // 注册内容观察者,数据变化时通知
        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        if (uriMatcher.match(uri) != USERS) {
            throw new IllegalArgumentException("Invalid URI for insert: " + uri);
        }

        SQLiteDatabase db = dbHelper.getWritableDatabase();
        long rowId = db.insert("users", null, values);

        if (rowId > 0) {
            Uri newUri = ContentUris.withAppendedId(CONTENT_URI, rowId);
            // 通知所有观察者数据已变化
            getContext().getContentResolver().notifyChange(newUri, null);
            return newUri;
        }
        return null;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values,
                      @Nullable String selection, @Nullable String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count;

        switch (uriMatcher.match(uri)) {
            case USERS:
                count = db.update("users", values, selection, selectionArgs);
                break;
            case USER_ID:
                long id = ContentUris.parseId(uri);
                count = db.update("users", values,
                    "_id = ?", new String[]{String.valueOf(id)});
                break;
            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }

        if (count > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return count;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection,
                      @Nullable String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count;

        switch (uriMatcher.match(uri)) {
            case USERS:
                count = db.delete("users", selection, selectionArgs);
                break;
            case USER_ID:
                long id = ContentUris.parseId(uri);
                count = db.delete("users",
                    "_id = ?", new String[]{String.valueOf(id)});
                break;
            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }

        if (count > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return count;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        // 返回 MIME 类型
        switch (uriMatcher.match(uri)) {
            case USERS:
                return "vnd.android.cursor.dir/vnd.com.example.users";
            case USER_ID:
                return "vnd.android.cursor.item/vnd.com.example.users";
            default:
                return null;
        }
    }

    // ===== 内部 SQLite 帮助类 =====
    private static class UserDatabaseHelper extends SQLiteOpenHelper {
        UserDatabaseHelper(android.content.Context context) {
            super(context, "users.db", null, 1);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE users (" +
                "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                "name TEXT NOT NULL," +
                "email TEXT," +
                "age INTEGER)");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL("DROP TABLE IF EXISTS users");
            onCreate(db);
        }
    }
}

访问 ContentProvider 时,客户端(使用方)不直接操作 ContentProvider,而是通过 ContentResolver(内容解析器)来发起请求——ContentResolver 负责找到对应 URI 的 Provider 并转发调用。

ContentObserver(内容观察者)实现了观察者模式:你可以注册监听某个 URI,当该 URI 下的数据发生变化时(Provider 调用了 notifyChange()),ContentObserver 的 onChange() 方法会被自动回调,让你实时更新 UI。实现了数据与 UI 的自动同步,是 Android 响应式数据绑定的底层机制之一。

Java — 访问 ContentProvider + ContentObserver
// ===== 客户端访问 ContentProvider =====
public class ContentProviderClient extends AppCompatActivity {

    private ContentObserver userObserver;

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

        // 注册内容观察者:数据变化时自动回调
        userObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
            @Override
            public void onChange(boolean selfChange, Uri uri) {
                super.onChange(selfChange, uri);
                // 数据已变化,刷新列表
                loadUsers();
            }
        };
        getContentResolver().registerContentObserver(
            UserContentProvider.CONTENT_URI, true, userObserver);

        loadUsers();
    }

    // 查询所有用户
    private void loadUsers() {
        Cursor cursor = getContentResolver().query(
            UserContentProvider.CONTENT_URI,
            new String[]{"_id", "name", "email", "age"},  // projection
            null, null,  // selection / selectionArgs
            "name ASC"  // sortOrder
        );

        if (cursor != null) {
            while (cursor.moveToNext()) {
                long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
                String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
                String email = cursor.getString(cursor.getColumnIndexOrThrow("email"));
                Log.d("User", id + ": " + name + " (" + email + ")");
            }
            cursor.close();
        }
    }

    // 插入用户
    private void insertUser() {
        ContentValues values = new ContentValues();
        values.put("name", "李四");
        values.put("email", "lisi@example.com");
        values.put("age", 28);

        Uri newUri = getContentResolver().insert(
            UserContentProvider.CONTENT_URI, values);
        Log.d("Insert", "新记录 URI: " + newUri);
    }

    // 更新用户
    private void updateUser(long userId) {
        Uri uri = ContentUris.withAppendedId(UserContentProvider.CONTENT_URI, userId);
        ContentValues values = new ContentValues();
        values.put("email", "newemail@example.com");

        int rows = getContentResolver().update(uri, values, null, null);
        Log.d("Update", "更新了 " + rows + " 行");
    }

    // 删除用户
    private void deleteUser(long userId) {
        Uri uri = ContentUris.withAppendedId(UserContentProvider.CONTENT_URI, userId);
        int rows = getContentResolver().delete(uri, null, null);
        Log.d("Delete", "删除了 " + rows + " 行");
    }

    // 访问系统联系人(需要 READ_CONTACTS 权限)
    private void readContacts() {
        Cursor cursor = getContentResolver().query(
            android.provider.ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            null, null, null, null);

        if (cursor != null) {
            while (cursor.moveToNext()) {
                String name = cursor.getString(cursor.getColumnIndexOrThrow(
                    android.provider.ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                String phone = cursor.getString(cursor.getColumnIndexOrThrow(
                    android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER));
                Log.d("Contact", name + ": " + phone);
            }
            cursor.close();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 注销内容观察者
        getContentResolver().unregisterContentObserver(userObserver);
    }
}

FileProvider 共享文件

Android 7.0(API 24)之前,应用间共享文件可以直接用 file:// URI,类似文件系统的绝对路径。但这意味着接收方应用可以直接访问你应用私有目录的文件,存在安全风险。

从 Android 7.0 开始,通过 Intent 传递 file:// URI 会直接抛出 FileUriExposedException 导致崩溃。必须使用 FileProvider 将文件路径转换为 content:// URI,并通过临时权限授权让目标应用访问。常见的使用场景是:调用相机拍照后保存到应用私有目录,或将文件分享给其他应用打开。

XML — res/xml/file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 内部存储的 files 目录 -->
    <files-path name="my_files" path="." />
    <!-- 外部存储的 files 目录 -->
    <external-files-path name="external_files" path="." />
    <!-- 缓存目录 -->
    <cache-path name="my_cache" path="." />
</paths>
Java — FileProvider 使用
import androidx.core.content.FileProvider;

// 获取可以安全共享的 Uri(Android 7.0+ 必须用 FileProvider)
File imageFile = new File(getFilesDir(), "photo.jpg");
Uri photoUri = FileProvider.getUriForFile(
    this,
    getPackageName() + ".fileprovider",  // 与 Manifest 中 authorities 一致
    imageFile
);

// 拍照并保存到 FileProvider Uri
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
// 授予临时读取权限
cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(cameraIntent, REQUEST_CAMERA);
6
Task 与返回栈
Activity 的组织方式与导航模型

你有没有遇到过这种情况:在应用 A 点击了一个链接,跳转到浏览器;再在浏览器点击某个按钮,跳到了另一个应用 B;然后你按返回键,却回到了浏览器而不是你期望的地方——这背后的机制就是 Task 与返回栈

理解 Task 和返回栈,能帮助你解决以下实际问题:

Task 概念与可视化

Task 是一组 Activity 的集合,以返回栈(Back Stack)的形式组织。用户按下返回键时,栈顶 Activity 出栈。

Back Stack
Task A (前台)
Activity A1 (底部)
Activity A2
Activity A3 栈顶
↑ 入栈 (Push)
↓ 出栈 (Pop)
按返回键:A3 出栈
A2 重新显示
Back Stack
Task B (后台)
Activity B1 (底部)
Activity B2

taskAffinity 属性

taskAffinity 定义 Activity 倾向于归属的 Task。默认值为应用包名。通过设置不同的 taskAffinity,可以让同一应用的 Activity 运行在不同 Task 中。
XML — taskAffinity 配置
<!-- 默认 affinity = 包名 com.example.myapp -->
<activity android:name=".MainActivity" />

<!-- 独立 Task 的 Activity -->
<activity
    android:name=".SubActivity"
    android:taskAffinity="com.example.myapp.sub"
    android:launchMode="singleTask" />

<!-- 无 affinity(永远不会被移入其他 Task)-->
<activity
    android:name=".IsolatedActivity"
    android:taskAffinity=""
    android:launchMode="singleTask" />

Intent Flags 详解

Flag效果常见使用场景
FLAG_ACTIVITY_NEW_TASK在新 Task 中启动(若 affinity 相同的 Task 存在则复用)从 Service/Notification 启动 Activity 时必加
FLAG_ACTIVITY_CLEAR_TOP若目标 Activity 在栈中存在,清除其上所有 Activity从通知跳转到已有页面,配合 singleTop 使用
FLAG_ACTIVITY_SINGLE_TOP若目标已在栈顶,不新建实例(调用 onNewIntent)防止重复创建相同 Activity
FLAG_ACTIVITY_CLEAR_TASK清空目标 Task 所有 Activity,再启动(需配合 NEW_TASK)登录成功后跳转主页,清除登录栈
FLAG_ACTIVITY_NO_HISTORY该 Activity 不会保留在历史栈中引导页、过渡页
FLAG_ACTIVITY_REORDER_TO_FRONT若已存在则移到栈顶,不新建Tab 切换
Java — 常见 Flag 使用场景
// 场景1: 登录成功后跳转主页,清空登录流程的所有 Activity
public void onLoginSuccess() {
    Intent intent = new Intent(this, MainActivity.class);
    // FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK
    // 清空当前 Task 并以 MainActivity 为根重新开始
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    startActivity(intent);
    finish();  // 不需要,因为 CLEAR_TASK 会清掉当前 Activity
}

// 场景2: 通知跳转 — 已有 DetailActivity 则复用,否则新建
public static void startFromNotification(Context context, int itemId) {
    Intent intent = new Intent(context, DetailActivity.class);
    intent.putExtra("item_id", itemId);
    // CLEAR_TOP: 清除 DetailActivity 之上的所有 Activity
    // SINGLE_TOP: 若 DetailActivity 恰好在栈顶,触发 onNewIntent
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    // 从非 Activity Context 启动必须加 NEW_TASK
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

// 场景3: 从 Service 启动 Activity(非 Activity Context)
public class MyService extends Service {
    private void launchActivity() {
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  // 必须!
        startActivity(intent);
    }
}

// 场景4: 避免引导页被回退访问
public void startHomeFromSplash() {
    Intent intent = new Intent(this, HomeActivity.class);
    // 不保留引导页在返回栈中
    startActivity(intent);
    // 直接 finish() 自己即可
    finish();
    // 或者用 FLAG_ACTIVITY_NO_HISTORY 在 Manifest 中配置
}
7
Handler / Looper / MessageQueue
Android 线程间通信的核心消息机制

开始学习这部分之前,先理解一个关键规则:Android 不允许在子线程直接更新 UI。如果你在网络请求的回调线程里调用 textView.setText(),程序会立刻崩溃,报错 "Only the original thread that created a view hierarchy can touch its views"

为什么有这个限制?因为 Android 的 UI 渲染是单线程模型——所有 UI 操作必须在主线程(Main Thread / UI Thread)上串行执行,这样才能保证界面状态的一致性,避免多线程并发导致的渲染问题。

那么问题来了:网络请求、数据库读写这些耗时操作必须在子线程完成,但结果又要更新到 UI,怎么做到跨线程通信?Handler / Looper / MessageQueue 就是 Android 给出的答案——它们构成了一套完整的线程间消息传递机制。

工作原理

Handler / Looper / MessageQueue 三者分工明确,共同构成一个消息驱动的事件循环

  1. MessageQueue(消息队列):一个按时间排序的消息链表。所有待处理的消息都在这里排队,等候执行。
  2. Looper(循环器):每个线程最多只有一个 Looper。它在 loop() 方法中不停循环,从 MessageQueue 取出消息,交给对应 Handler 处理。主线程的 Looper 在 App 启动时由系统自动创建并运行。
  3. Handler(处理器):你创建 Handler 时它会绑定到当前线程的 Looper。子线程调用 handler.sendMessage() / handler.post(runnable) 把消息塞入 MessageQueue,消息会在 Handler 绑定的那个线程里被处理。在主线程创建的 Handler,消息就在主线程处理——这就是切回主线程的原理。
  4. Message(消息):消息包,携带数据(what、arg1、arg2、obj)和目标 Handler 引用。

完整数据流:子线程 → handler.post(runnable) → 消息进入 MessageQueue → Looper 取出 → 在主线程执行 runnable → 更新 UI

Android 消息机制由四个核心组件构成,共同实现线程间安全通信:

🔧 Worker Thread
sendMessage()
post(Runnable)
📬 MessageQueue
按时间排序
消息链表
🔄 Looper.loop()
无限循环
取出并分发消息
🖼️ 主线程 Handler
handleMessage()
UI 更新
子线程
主线程

Handler 基本用法

Java — Handler 在主线程更新 UI
public class MainActivity extends AppCompatActivity {

    // 在主线程创建 Handler,自动绑定主线程的 Looper
    private final Handler mainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case MSG_UPDATE_PROGRESS:
                    progressBar.setProgress(msg.arg1);
                    break;
                case MSG_UPDATE_TEXT:
                    textView.setText((String) msg.obj);
                    break;
                case MSG_HIDE_LOADING:
                    loadingView.setVisibility(View.GONE);
                    break;
            }
        }
    };

    private static final int MSG_UPDATE_PROGRESS = 1;
    private static final int MSG_UPDATE_TEXT     = 2;
    private static final int MSG_HIDE_LOADING    = 3;

    private void startDownload() {
        new Thread(() -> {
            for (int i = 0; i <= 100; i += 10) {
                // 模拟下载耗时
                try { Thread.sleep(200); } catch (InterruptedException e) { break; }

                // 方式1: 发送带数据的 Message
                Message msg = mainHandler.obtainMessage(MSG_UPDATE_PROGRESS);
                msg.arg1 = i;
                mainHandler.sendMessage(msg);
            }
            // 方式2: post Runnable(更简洁)
            mainHandler.post(() -> {
                textView.setText("下载完成");
                loadingView.setVisibility(View.GONE);
            });
        }).start();
    }

    // 延迟执行
    private void showToastLater() {
        mainHandler.postDelayed(() -> {
            Toast.makeText(this, "3秒后显示", Toast.LENGTH_SHORT).show();
        }, 3000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 清理未执行的消息,防止内存泄漏
        mainHandler.removeCallbacksAndMessages(null);
    }
}

HandlerThread — 子线程中使用 Handler

Java — HandlerThread
public class MainActivity extends AppCompatActivity {

    private HandlerThread handlerThread;
    private Handler backgroundHandler;
    private Handler mainHandler;

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

        mainHandler = new Handler(Looper.getMainLooper());

        // 创建后台 HandlerThread(内部自带 Looper)
        handlerThread = new HandlerThread("BackgroundWorker");
        handlerThread.start();

        // 绑定到后台线程的 Looper
        backgroundHandler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(@NonNull Message msg) {
                // 这里在后台线程执行
                String result = doHeavyWork(msg.arg1);
                // 结果回调到主线程
                mainHandler.post(() -> updateUI(result));
            }
        };
    }

    private void doWork(int param) {
        // 将任务派发给后台线程串行执行
        Message msg = backgroundHandler.obtainMessage();
        msg.arg1 = param;
        backgroundHandler.sendMessage(msg);
    }

    private String doHeavyWork(int param) {
        // 耗时操作...
        return "result_" + param;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 安全退出 HandlerThread 的 Looper
        handlerThread.quitSafely();
    }
}

Message 复用 — obtainMessage()

避免 new Message():始终使用 handler.obtainMessage()Message.obtain() 从消息池获取复用对象,减少 GC 压力。
Java — Message 字段与复用
// Message 主要字段
// msg.what   — 消息标识符 (int)
// msg.arg1   — 轻量整数参数1 (int)
// msg.arg2   — 轻量整数参数2 (int)
// msg.obj    — 任意对象参数 (Object)
// msg.data   — Bundle 数据 (用于多参数)
// msg.target — 处理该消息的 Handler

// ✅ 推荐: 从消息池获取(复用)
Message msg = handler.obtainMessage(WHAT, arg1, arg2, obj);

// 或者链式写法
handler.obtainMessage(MSG_LOAD_IMAGE)
       .sendToTarget();

// ❌ 避免直接 new
// Message msg = new Message();

// 携带 Bundle 数据(多个参数)
Message msg2 = handler.obtainMessage(MSG_BATCH_DATA);
Bundle data = msg2.getData();
data.putString("key", "value");
data.putInt("count", 42);
handler.sendMessage(msg2);

内存泄漏防范

Handler 内存泄漏:匿名内部类 Handler 持有外部 Activity 引用,若 Activity 销毁后 MessageQueue 中仍有未处理消息,将导致 Activity 无法被 GC 回收。
Java — 静态内部类 + 弱引用(安全写法)
public class SafeActivity extends AppCompatActivity {

    // ✅ 静态内部类 + 弱引用,不持有 Activity 强引用
    private static class SafeHandler extends Handler {
        private final WeakReference<SafeActivity> activityRef;

        SafeHandler(SafeActivity activity) {
            super(Looper.getMainLooper());
            this.activityRef = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            SafeActivity activity = activityRef.get();
            if (activity == null || activity.isFinishing()) return;

            switch (msg.what) {
                case 1:
                    activity.textView.setText((String) msg.obj);
                    break;
            }
        }
    }

    private SafeHandler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handler = new SafeHandler(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
    }
}
8
多线程
Thread、ThreadPool、Future、并发工具类

每一个 Android 应用启动时,系统会创建一个主线程(Main Thread),也称为 UI 线程。主线程负责:处理用户输入事件、执行 UI 渲染、调用 Activity / Fragment 的生命周期方法。

如果你在主线程做了耗时操作(网络请求、读写大文件、复杂计算),主线程就会被阻塞,无法响应用户操作。Android 系统监测到主线程超过 5 秒无响应时,会弹出"应用无响应"对话框,这就是 ANR(Application Not Responding)

解决方案:把耗时操作放到子线程,完成后再切回主线程更新 UI。这正是多线程编程的核心目标。

类比理解:餐厅服务员模型

把主线程想象成前台服务员:负责与顾客互动(响应触摸)、传递菜单(更新 UI)。如果服务员要去厨房炒菜(耗时操作),前台就没人接待了——这就是 ANR。正确做法是:服务员把菜单给后厨(子线程),自己继续接待顾客,菜做好了厨房通知服务员端菜(切回主线程更新 UI)。

Java 提供了多种方式创建线程,以下是三种基本方式及其适用场景:

Thread 基础

Java — Thread 创建方式
// 方式1: 继承 Thread
class DownloadThread extends Thread {
    private final String url;
    DownloadThread(String url) { this.url = url; }

    @Override
    public void run() {
        // 下载逻辑
        Log.d("Thread", "当前线程: " + Thread.currentThread().getName());
    }
}
new DownloadThread("https://example.com/file").start();

// 方式2: 实现 Runnable(推荐,避免单继承限制)
Runnable task = () -> {
    // 任务逻辑
};
new Thread(task, "worker-1").start();

// 方式3: 实现 Callable(可获得返回值)
Callable<String> callable = () -> {
    Thread.sleep(1000);
    return "result";
};
FutureTask<String> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
try {
    String result = futureTask.get(); // 阻塞等待结果
} catch (ExecutionException | InterruptedException e) {
    e.printStackTrace();
}

ThreadPoolExecutor

直接使用线程池代替手动 new Thread(),复用线程、控制并发数量、统一管理任务队列。

Java — ThreadPoolExecutor 详解
// ThreadPoolExecutor 构造参数说明
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2,                          // corePoolSize: 核心线程数(常驻)
    4,                          // maximumPoolSize: 最大线程数
    60L,                        // keepAliveTime: 空闲线程存活时间
    TimeUnit.SECONDS,           // 时间单位
    new LinkedBlockingQueue<>(100), // 任务队列(容量100)
    new ThreadFactory() {       // 自定义线程工厂(可命名线程)
        private final AtomicInteger count = new AtomicInteger(1);
        @Override
        public Thread newThread(@NonNull Runnable r) {
            return new Thread(r, "pool-worker-" + count.getAndIncrement());
        }
    },
    new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略:调用者线程执行
    // 其他拒绝策略:
    // AbortPolicy(默认): 抛出 RejectedExecutionException
    // DiscardPolicy: 静默丢弃新任务
    // DiscardOldestPolicy: 丢弃最老的任务,重试提交
);

// 提交任务
executor.execute(() -> doWork());      // 无返回值
Future<String> future = executor.submit(() -> fetchData());  // 有返回值

// 获取线程池状态
Log.d("Pool", "活跃线程: " + executor.getActiveCount());
Log.d("Pool", "已完成任务: " + executor.getCompletedTaskCount());

// 有序关闭(等待已提交任务完成)
executor.shutdown();
// 立即关闭(尝试中断正在执行的任务)
executor.shutdownNow();

Executors 工厂方法

工厂方法线程数适用场景
newFixedThreadPool(n)固定 n 个CPU 密集型任务,控制并发度
newCachedThreadPool()0 ~ Integer.MAX大量短期异步任务
newSingleThreadExecutor()1串行执行,保证顺序
newScheduledThreadPool(n)固定 n 个定时/周期性任务
阿里巴巴 Java 规范:不推荐使用 Executors 工厂方法创建线程池,应直接使用 ThreadPoolExecutor,明确每个参数,避免 OOM。

并发工具类

Java — CountDownLatch / CyclicBarrier / Semaphore
// ===== CountDownLatch: 等待多个线程完成 =====
CountDownLatch latch = new CountDownLatch(3); // 等待3个任务

for (int i = 0; i < 3; i++) {
    final int taskId = i;
    executor.execute(() -> {
        try {
            doTask(taskId);
        } finally {
            latch.countDown(); // 任务完成,计数-1
        }
    });
}
latch.await(); // 阻塞直到计数为0
Log.d("TAG", "所有任务完成");

// ===== CyclicBarrier: 多线程相互等待,到达屏障后同时继续 =====
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    Log.d("TAG", "所有线程到达屏障,开始下一阶段");
});

for (int i = 0; i < 3; i++) {
    executor.execute(() -> {
        prepareData();
        try {
            barrier.await(); // 等待其他线程
        } catch (Exception e) { e.printStackTrace(); }
        processData(); // 所有线程同时开始
    });
}

// ===== Semaphore: 控制并发访问数量(限流)=====
Semaphore semaphore = new Semaphore(5); // 最多5个线程同时访问

executor.execute(() -> {
    try {
        semaphore.acquire(); // 获取许可
        accessResource();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        semaphore.release(); // 释放许可
    }
});

volatile 与 synchronized

Java — 可见性与原子性
public class Counter {

    // volatile: 保证可见性(所有线程读到最新值),但不保证原子性
    private volatile boolean running = true;

    // AtomicInteger: 保证原子性操作(底层 CAS)
    private final AtomicInteger count = new AtomicInteger(0);

    // synchronized: 保证可见性 + 原子性(同一时刻只有一个线程执行)
    private int syncCount = 0;

    public synchronized void increment() {
        syncCount++;
    }

    public synchronized int getCount() {
        return syncCount;
    }

    // 单例模式中的双重检查锁(正确写法)
    private static volatile Counter instance;

    public static Counter getInstance() {
        if (instance == null) {              // 第一次检查(无锁,提升性能)
            synchronized (Counter.class) {
                if (instance == null) {      // 第二次检查(防止重复创建)
                    instance = new Counter();
                }
            }
        }
        return instance;
    }

    public void stopLoop() {
        running = false; // volatile 写,立刻对所有线程可见
    }

    public void runLoop() {
        while (running) { // volatile 读
            doWork();
        }
    }
}
9
Lambda 表达式
Java 8 函数式编程 — 让 Android 代码更简洁

Java 在 Lambda 出现之前,传递一段"行为"(比如"点击按钮时做什么")需要创建匿名内部类,代码繁琐:需要写完整的类声明、方法签名,就为了一两行实际逻辑。Lambda 表达式(Java 8 引入)解决了这个问题——它让你可以用极简的语法传递一段代码作为参数

Android 中使用 Lambda 的前提:
  • build.gradle 中设置 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 }
  • 或者 minSdkVersion 24 以上(原生支持所有 Java 8 特性)
  • 低于 API 24 时,部分 Stream API 功能不可用(但基本 Lambda 通过脱糖机制可用)

Lambda 的核心语法是 (参数) -> { 函数体 }。下面对比旧写法与 Lambda 的差异,你会感受到代码量的明显减少:

Lambda 语法

Java — Lambda 基本语法对比
// ===== 无参数 =====
// 旧写法
new Thread(new Runnable() {
    @Override
    public void run() { doWork(); }
}).start();
// Lambda
new Thread(() -> doWork()).start();
// 方法引用
new Thread(this::doWork).start();

// ===== 单参数 =====
// 旧写法
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) { v.setAlpha(0.5f); }
});
// Lambda(单参数可省略括号)
button.setOnClickListener(v -> v.setAlpha(0.5f));

// ===== 多参数 =====
Comparator<String> comp = (a, b) -> a.length() - b.length();
List<String> list = Arrays.asList("banana", "apple", "kiwi");
list.sort(comp);

// ===== 多行语句 =====
executor.execute(() -> {
    String data = fetchFromNetwork();
    saveToDatabase(data);
    mainHandler.post(() -> updateUI(data));
});

常用函数式接口

接口方法签名描述Android 中常见用途
Runnablevoid run()无参无返回值Thread、Handler.post()
Callable<T>T call()无参有返回值ExecutorService.submit()
Consumer<T>void accept(T)接受一个参数forEach、回调处理
Supplier<T>T get()无参返回值懒加载、工厂方法
Function<T,R>R apply(T)输入转换输出数据映射转换
Predicate<T>boolean test(T)条件判断过滤、条件检查

方法引用

Java — 四种方法引用
// 1. 静态方法引用: ClassName::staticMethod
List<String> list = Arrays.asList("1", "2", "3");
list.stream().map(Integer::parseInt).collect(Collectors.toList());

// 2. 实例方法引用(特定对象): instance::method
String prefix = "Hello ";
list.stream().map(prefix::concat).forEach(System.out::println);

// 3. 实例方法引用(任意对象): ClassName::instanceMethod
list.stream().map(String::toUpperCase).forEach(System.out::println);
list.sort(String::compareTo);

// 4. 构造方法引用: ClassName::new
List<User> users = nameList.stream()
    .map(User::new)          // 调用 User(String name) 构造器
    .collect(Collectors.toList());

// Android 中常见方法引用
button.setOnClickListener(this::onButtonClicked); // 实例方法引用
Log.d("TAG", list.stream().collect(Collectors.joining(", ")));

Stream API

Java — Stream 流式操作
List<User> users = getUsers();

// 过滤 + 映射 + 收集
List<String> activeNames = users.stream()
    .filter(u -> u.isActive())           // 中间操作: 过滤
    .filter(u -> u.getAge() >= 18)       // 中间操作: 再次过滤
    .map(User::getName)                  // 中间操作: 提取姓名
    .sorted()                            // 中间操作: 排序
    .distinct()                          // 中间操作: 去重
    .collect(Collectors.toList());       // 终止操作: 收集为 List

// 统计
long count = users.stream().filter(User::isActive).count();
OptionalDouble avgAge = users.stream()
    .mapToInt(User::getAge)
    .average();

// 分组
Map<String, List<User>> groupByCityMap = users.stream()
    .collect(Collectors.groupingBy(User::getCity));

// anyMatch / allMatch / noneMatch
boolean hasAdmin = users.stream().anyMatch(u -> "admin".equals(u.getRole()));

// findFirst
Optional<User> firstAdmin = users.stream()
    .filter(u -> "admin".equals(u.getRole()))
    .findFirst();
firstAdmin.ifPresent(u -> Log.d("TAG", "找到管理员: " + u.getName()));

// reduce 归约
int totalAge = users.stream()
    .mapToInt(User::getAge)
    .reduce(0, Integer::sum);

Optional — 避免 NullPointerException

Java — Optional 用法
// 创建
Optional<String> opt1 = Optional.of("value");       // 非空值
Optional<String> opt2 = Optional.empty();            // 空值
Optional<String> opt3 = Optional.ofNullable(maybeNull); // 可能为 null

// 使用
String value = opt3.orElse("默认值");
String value2 = opt3.orElseGet(() -> computeDefault());
opt3.ifPresent(v -> Log.d("TAG", v));
String upper = opt3.map(String::toUpperCase).orElse("");

// 链式防 NPE(替代多层 if null 判断)
String city = Optional.ofNullable(user)
    .map(User::getAddress)
    .map(Address::getCity)
    .orElse("未知城市");
10
UI 开发
View 体系、RecyclerView、自定义 View、动画

View 体系

Android UI 由 View 树构成。每个 View 经历 测量 (Measure) → 布局 (Layout) → 绘制 (Draw) 三个阶段才能显示到屏幕。

① Measure
onMeasure()
确定 View 的宽高
MeasureSpec 传递约束
② Layout
onLayout()
确定 View 的位置
setFrame() 设置边界
③ Draw
onDraw(Canvas)
绘制背景/内容/子View
硬件加速/软件渲染
Java — MeasureSpec 解析
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // MeasureSpec 由 mode + size 打包成一个 int
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    int measuredWidth;
    switch (widthMode) {
        case MeasureSpec.EXACTLY:   // match_parent 或具体 dp 值
            measuredWidth = widthSize;
            break;
        case MeasureSpec.AT_MOST:   // wrap_content
            measuredWidth = Math.min(getDesiredWidth(), widthSize);
            break;
        default:                    // UNSPECIFIED(ScrollView 中)
            measuredWidth = getDesiredWidth();
            break;
    }
    setMeasuredDimension(measuredWidth, getDesiredHeight());
}

RecyclerView

RecyclerView 是 Android 中最常用、最重要的控件之一,几乎每一个有列表的应用都会用到它。它的前身是 ListView,但 RecyclerView 在性能和灵活性上全面超越了它。

为什么不直接把所有 View 都创建出来? 假设列表有 1000 条数据,如果每条都创建一个 View,内存会爆炸。RecyclerView 的核心思想是回收复用(Recycle):屏幕上只有 10 条可见,它就只创建约 12 个 View(多几个作为缓冲),当你向下滑动时,滑出屏幕的 View 不会被销毁,而是被放入"回收池",新进入屏幕的条目直接复用这个 View,只替换数据内容。

理解 RecyclerView 需要掌握三个核心角色:

Adapter
连接数据和视图的桥梁。告诉 RecyclerView 有多少条目、每条长什么样、显示什么数据
ViewHolder
持有每个列表项的所有子 View 引用,避免重复调用 findViewById(),是回收复用的基本单元
LayoutManager
决定列表的排列方式:LinearLayoutManager(线性)/ GridLayoutManager(网格)/ StaggeredGridLayoutManager(瀑布流)

Adapter 的三个核心方法:

Java — RecyclerView 完整 Adapter
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {

    public interface OnItemClickListener {
        void onItemClick(User user, int position);
        void onItemLongClick(User user, int position);
    }

    private final List<User> items;
    private OnItemClickListener listener;

    public UserAdapter(List<User> items) {
        this.items = new ArrayList<>(items);
    }

    public void setOnItemClickListener(OnItemClickListener l) {
        this.listener = l;
    }

    // ① 创建 ViewHolder(inflate 布局)
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.item_user, parent, false);
        return new ViewHolder(view);
    }

    // ② 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        User user = items.get(position);
        holder.nameText.setText(user.getName());
        holder.emailText.setText(user.getEmail());
        // 使用 Glide 加载头像
        Glide.with(holder.itemView)
            .load(user.getAvatarUrl())
            .circleCrop()
            .placeholder(R.drawable.ic_avatar_default)
            .into(holder.avatarImage);

        holder.itemView.setOnClickListener(v -> {
            if (listener != null) listener.onItemClick(user, holder.getAdapterPosition());
        });
        holder.itemView.setOnLongClickListener(v -> {
            if (listener != null) listener.onItemLongClick(user, holder.getAdapterPosition());
            return true;
        });
    }

    @Override
    public int getItemCount() { return items.size(); }

    // DiffUtil 局部刷新(性能优化)
    public void updateData(List<User> newData) {
        DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
            @Override public int getOldListSize() { return items.size(); }
            @Override public int getNewListSize() { return newData.size(); }
            @Override public boolean areItemsTheSame(int o, int n) {
                return items.get(o).getId() == newData.get(n).getId();
            }
            @Override public boolean areContentsTheSame(int o, int n) {
                return items.get(o).equals(newData.get(n));
            }
        });
        items.clear();
        items.addAll(newData);
        result.dispatchUpdatesTo(this); // 自动计算最小更新操作
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView avatarImage;
        TextView nameText;
        TextView emailText;

        ViewHolder(View v) {
            super(v);
            avatarImage = v.findViewById(R.id.iv_avatar);
            nameText    = v.findViewById(R.id.tv_name);
            emailText   = v.findViewById(R.id.tv_email);
        }
    }
}
Java — RecyclerView 配置
RecyclerView recyclerView = findViewById(R.id.recycler_view);

// 布局管理器
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
// recyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, VERTICAL));

// 固定大小优化(每个 item 尺寸相同时开启)
recyclerView.setHasFixedSize(true);

UserAdapter adapter = new UserAdapter(userList);
recyclerView.setAdapter(adapter);

// 分割线
recyclerView.addItemDecoration(
    new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
);

// Item 动画(默认已有,可自定义)
recyclerView.setItemAnimator(new DefaultItemAnimator());

// 监听
adapter.setOnItemClickListener((user, pos) -> {
    Intent intent = new Intent(this, DetailActivity.class);
    intent.putExtra("user_id", user.getId());
    startActivity(intent);
});

RecyclerView 常见错误与性能优化

❌ 错误:在 onBindViewHolder 中设置点击监听时直接用 position 参数

当列表数据变化(插入/删除)时,position 参数可能已经过时。应该改用 holder.getAdapterPosition()holder.getBindingAdapterPosition()

// ❌ 错误写法
holder.itemView.setOnClickListener(v -> openDetail(items.get(position)));
// ✅ 正确写法
holder.itemView.setOnClickListener(v -> {
    int pos = holder.getAdapterPosition();
    if (pos != RecyclerView.NO_ID) openDetail(items.get(pos));
});
⚠️ 警告:不要频繁调用 notifyDataSetChanged()

notifyDataSetChanged() 会触发整个列表的重新绑定,动画也会丢失。应优先使用精确通知方法: notifyItemInserted(pos) / notifyItemRemoved(pos) / notifyItemChanged(pos)。 如果数据集整体替换,使用 DiffUtil(示例代码中已展示),让系统自动计算最小变更集。

✅ 性能技巧:setHasFixedSize(true)

如果 RecyclerView 的大小不随 Adapter 内容变化而改变,设置 recyclerView.setHasFixedSize(true),系统可以跳过对父容器的重新测量,提升滚动流畅度。

✅ 初始化:在 onCreateViewHolder 而非 onBindViewHolder 里 findViewById

onBindViewHolder 每次滚动到该条目时都会调用,如果在里面 findViewById() 会非常耗性能。ViewHolder 模式的核心就是在 onCreateViewHolder 时一次性找好所有子 View,存入 ViewHolder 字段,之后只做数据填充。

自定义 View

当 Android 系统提供的内置控件(TextView、Button、ImageView...)无法满足你的设计需求时,就需要自定义 View。什么时候需要自定义 View?

自定义 View 的三种方式(按复杂度递增):

下面以"圆形进度条"为例,展示完全自绘方式的实现步骤。自定义 View 的开发遵循固定流程:

  1. 构造函数 — 解析自定义 XML 属性(AttributeSet),初始化画笔(Paint)等绘制工具
  2. onMeasure() — 决定 View 的宽高。如果你用 wrap_content,必须在这里提供默认大小,否则大小为 0
  3. onSizeChanged() — 当大小确定后调用,在这里创建依赖尺寸的对象(如 RectF 边界)
  4. onDraw(Canvas) — 真正的绘制逻辑,用 Canvas 的各种 drawXxx() 方法绘制图形、文字
  5. invalidate() — 当数据变化需要重绘时调用,会触发系统重新调用 onDraw()
⚠️ 不要在 onDraw() 里创建对象!

onDraw() 每一帧都可能被调用(60fps 时每秒 60 次),在里面 new Paint()new RectF() 会造成大量 GC 压力,导致卡顿。所有对象必须在构造函数或 onSizeChanged() 中创建好。

下面以"圆形进度条"为例:

Java — 圆形进度条自定义 View
public class CircleProgressView extends View {

    private Paint bgPaint;
    private Paint progressPaint;
    private Paint textPaint;
    private RectF ovalRect;

    private int progress = 0;    // 0~100
    private int maxProgress = 100;

    // 自定义属性(在 res/values/attrs.xml 中定义后这里读取)
    private int progressColor = Color.parseColor("#58a6ff");
    private int bgColor       = Color.parseColor("#30363d");
    private float strokeWidth = 12f;

    public CircleProgressView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView);
            progressColor = a.getColor(R.styleable.CircleProgressView_progressColor, progressColor);
            strokeWidth   = a.getDimension(R.styleable.CircleProgressView_strokeWidth, strokeWidth);
            a.recycle();
        }
        initPaints();
    }

    private void initPaints() {
        bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        bgPaint.setStyle(Paint.Style.STROKE);
        bgPaint.setColor(bgColor);
        bgPaint.setStrokeWidth(strokeWidth);

        progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        progressPaint.setStyle(Paint.Style.STROKE);
        progressPaint.setColor(progressColor);
        progressPaint.setStrokeWidth(strokeWidth);
        progressPaint.setStrokeCap(Paint.Cap.ROUND);

        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(Color.WHITE);
        textPaint.setTextSize(sp2px(18));
        textPaint.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldW, int oldH) {
        super.onSizeChanged(w, h, oldW, oldH);
        float padding = strokeWidth / 2f + getPaddingLeft();
        ovalRect = new RectF(padding, padding, w - padding, h - padding);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制背景圆弧
        canvas.drawArc(ovalRect, -90, 360, false, bgPaint);
        // 绘制进度圆弧(从 -90° 开始,顺时针扫过)
        float sweepAngle = 360f * progress / maxProgress;
        canvas.drawArc(ovalRect, -90, sweepAngle, false, progressPaint);
        // 绘制中心文字
        String text = progress + "%";
        float textY = getHeight() / 2f - (textPaint.descent() + textPaint.ascent()) / 2f;
        canvas.drawText(text, getWidth() / 2f, textY, textPaint);
    }

    public void setProgress(int progress) {
        this.progress = Math.min(Math.max(progress, 0), maxProgress);
        invalidate(); // 触发重绘
    }

    // 属性动画驱动进度(需要 getter 配合)
    public int getProgress() { return progress; }

    private float sp2px(float sp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
            getResources().getDisplayMetrics());
    }
}

动画

Android 动画分为三大类,理解它们的区别可以避免踩坑:

视图动画(View Animation)
XML 定义的传统动画(alpha/scale/translate/rotate)。只是视觉效果移动,View 的实际位置不变。基本已被淘汰。
属性动画(Property Animation)
ObjectAnimator / ValueAnimator。真实改变对象属性值,包括点击区域也会跟着移动。现代开发的主流选择。
转场动画(Transition)
Activity/Fragment 切换时的动画,以及 Scene 之间的布局变化动画(LayoutTransition)。

属性动画三剑客:ObjectAnimator(动画 View 的某个属性)、ValueAnimator(动画任意数值,需自己处理更新逻辑)、ViewPropertyAnimator(链式调用,最简洁的写法,用于常见动画场景)。

Java — 属性动画
// ===== ObjectAnimator: 动画单个属性 =====
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
fadeOut.setDuration(400);
fadeOut.setInterpolator(new AccelerateDecelerateInterpolator());
fadeOut.start();

// 平移
ObjectAnimator slideIn = ObjectAnimator.ofFloat(view, "translationX", -500f, 0f);
slideIn.setDuration(300);

// 自定义 View 的属性(需要 getter+setter)
ObjectAnimator progressAnim = ObjectAnimator.ofInt(circleView, "progress", 0, 80);
progressAnim.setDuration(1200);
progressAnim.setInterpolator(new DecelerateInterpolator());
progressAnim.start();

// ===== AnimatorSet: 组合多个动画 =====
AnimatorSet set = new AnimatorSet();
set.playTogether(                       // 同时播放
    ObjectAnimator.ofFloat(view, "scaleX", 0f, 1f),
    ObjectAnimator.ofFloat(view, "scaleY", 0f, 1f),
    ObjectAnimator.ofFloat(view, "alpha", 0f, 1f)
);
set.setDuration(500);
set.setInterpolator(new OvershootInterpolator());
set.start();

// 顺序播放
AnimatorSet seq = new AnimatorSet();
seq.playSequentially(fadeOut, slideIn);
seq.start();

// ===== ValueAnimator: 纯数值动画(配合自定义绘制)=====
ValueAnimator colorAnim = ValueAnimator.ofArgb(
    Color.parseColor("#58a6ff"),
    Color.parseColor("#bc8cff")
);
colorAnim.setDuration(1000);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.addUpdateListener(animator -> {
    int color = (int) animator.getAnimatedValue();
    view.setBackgroundColor(color);
});
colorAnim.start();

// ===== 监听动画状态 =====
fadeOut.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setVisibility(View.GONE);
    }
    @Override
    public void onAnimationCancel(Animator animation) {
        view.setAlpha(1f); // 取消时恢复
    }
});
Java — ViewPropertyAnimator(简洁 API)
// 链式调用,自动批量优化,推荐用于简单动画
view.animate()
    .alpha(0f)
    .translationY(-200f)
    .scaleX(0.8f)
    .setDuration(400)
    .setInterpolator(new AccelerateInterpolator())
    .withEndAction(() -> view.setVisibility(View.GONE))
    .start();

// 出现动画
view.setVisibility(View.VISIBLE);
view.setAlpha(0f);
view.animate().alpha(1f).translationY(0f).setDuration(300).start();
11
Framework 开发
Binder IPC、AIDL、系统服务开发
📖 本章适合谁阅读?

如果你是初学者,跳过本章也完全没问题——日常 App 开发中你几乎不会直接写 Binder 代码。Binder 和 AIDL 属于 Android 进阶知识,适合以下人群:

  • 需要在两个不同进程之间通信的复杂 App(如音乐 App 的播放控制 Service 运行在独立进程)
  • 系统级开发工程师(ROM 定制、车机系统、OEM 厂商)
  • 准备 Android 高级岗位面试

大多数 Android 应用开发不会直接接触 Framework 层,但理解它能帮你看穿很多"为什么 Android 这样设计"的问题,也是面试高级岗位的必备知识。

什么是 Framework 层? 在 Android 架构中,Framework 层(Application Framework)是位于应用层和 Native 层之间的中间层,它提供了所有上层应用使用的 API:ActivityManager、WindowManager、ContentProvider、View System... 你每次调用 startActivity(),背后都是 Framework 层在运转。

学习这部分内容适合以下场景:

Binder 机制

IPC(Inter-Process Communication,进程间通信) 是指不同进程(独立内存空间)之间交换数据的机制。在 Android 中,每个 App 运行在独立进程里,想调用系统的 ActivityManagerService(管理所有 Activity)、WindowManagerService(管理窗口)等系统服务,就必须跨进程通信。

Binder 是 Android 最核心的 IPC(进程间通信)机制,所有系统服务调用的底层都依赖它。

为什么 Android 不用传统的 Linux IPC(管道、Socket、共享内存)? 因为 Binder 针对 Android 的使用场景做了优化:只需要一次内存拷贝(传统 IPC 需要两次),性能更好;内核驱动天然支持 UID/PID 鉴权,更安全;对象引用计数自动管理,不会出现僵尸服务。

📱 客户端进程
App 调用代理对象
BinderProxy (Java)
BpBinder (Native)
Binder
Kernel
/dev/binder
mmap
🖥️ 服务端进程
Service 实现
Binder (Java)
BBinder (Native)
⚡ 一次拷贝:Client 用户空间 → Kernel Binder 驱动 → Server 用户空间(mmap 共享内存)
Binder 四要素: Client(调用方)、Server(实现方)、ServiceManager(名字服务/注册中心)、Binder Driver(内核驱动,唯一可跨进程传递数据的入口)。

AIDL — Android 接口定义语言

AIDL(Android Interface Definition Language) 是 Android 提供的一种接口描述语言,专门用来定义跨进程通信的接口。

为什么需要 AIDL? 手写 Binder 通信代码非常繁琐(需要处理序列化/反序列化、线程管理、代理/存根对象)。AIDL 就像一个代码生成器:你只需用类似 Java 接口的语法定义"我要提供什么方法",编译器自动帮你生成完整的 Binder IPC 骨架代码(Stub 类和 Proxy 类)。

AIDL 让编译器自动生成 Binder IPC 的模板代码,开发者只需关注接口定义和业务逻辑。

AIDL — IRemoteService.aidl
// src/main/aidl/com/example/IRemoteService.aidl
package com.example;

// 自定义 Parcelable 需要声明
import com.example.UserInfo;

interface IRemoteService {
    // 基本类型直接使用
    int getPid();
    String getVersion();

    // 复杂对象需实现 Parcelable
    // in: 输入(Client→Server),out: 输出(Server→Client),inout: 双向
    void registerCallback(in UserInfo user);
    List<UserInfo> getActiveUsers();

    // 单向(oneway)调用:不阻塞客户端,不等服务端返回
    oneway void notifyEvent(int eventType, String data);
}
Java — 服务端实现(Service)
public class RemoteService extends Service {

    // Stub 是 AIDL 编译器生成的抽象类,继承 Binder
    private final IRemoteService.Stub binder = new IRemoteService.Stub() {

        @Override
        public int getPid() throws RemoteException {
            return android.os.Process.myPid();
        }

        @Override
        public String getVersion() throws RemoteException {
            return BuildConfig.VERSION_NAME;
        }

        @Override
        public void registerCallback(UserInfo user) throws RemoteException {
            Log.d("RemoteService", "注册用户: " + user.getName());
        }

        @Override
        public List<UserInfo> getActiveUsers() throws RemoteException {
            return userRepository.getActiveUsers();
        }

        @Override
        public void notifyEvent(int eventType, String data) throws RemoteException {
            // oneway 调用,不需要返回值
            eventBus.post(new RemoteEvent(eventType, data));
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return binder; // 返回 Stub 实例
    }
}
Java — 客户端绑定与调用
public class ClientActivity extends AppCompatActivity {

    private IRemoteService remoteService;
    private boolean isBound = false;

    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 将 IBinder 转为 AIDL 接口
            // 同进程: asInterface 返回原 Stub 对象
            // 跨进程: asInterface 返回 Proxy 代理对象
            remoteService = IRemoteService.Stub.asInterface(service);
            isBound = true;

            try {
                int pid = remoteService.getPid();
                Log.d("Client", "远程服务 PID: " + pid);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            remoteService = null;
            isBound = false;
        }
    };

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, RemoteService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (isBound) {
            unbindService(connection);
            isBound = false;
        }
    }

    private void callRemote() {
        if (!isBound || remoteService == null) return;
        try {
            // 注意:跨进程调用在调用线程阻塞,避免在主线程调用耗时接口
            String version = remoteService.getVersion();
            Log.d("Client", "版本: " + version);
        } catch (RemoteException e) {
            // 服务端崩溃或被杀死时抛出
            Log.e("Client", "远程调用失败", e);
        }
    }
}

添加自定义 SystemService(AOSP)

以下流程适用于在 AOSP 源码中添加自定义系统服务,需要修改系统代码并编译系统镜像,适合 Framework 工程师。
Java — 1. 定义 AIDL 接口(frameworks/base/core/java/)
// IMyCustomService.aidl
package android.os;

interface IMyCustomService {
    // 获取自定义系统属性
    String getCustomProperty(String key);
    void setCustomProperty(String key, String value);
    // 权限保护方法
    boolean isFeatureEnabled(String featureName);
}
Java — 2. 实现 Service(frameworks/base/services/core/)
// MyCustomService.java
public class MyCustomService extends IMyCustomService.Stub {

    private static final String TAG = "MyCustomService";
    private static final String PERMISSION = "android.permission.MY_CUSTOM_PERMISSION";

    private final Context mContext;
    private final Map<String, String> mProperties = new HashMap<>();

    public MyCustomService(Context context) {
        mContext = context;
        // 在系统启动时初始化
        Slog.i(TAG, "MyCustomService 初始化完成");
    }

    @Override
    public String getCustomProperty(String key) throws RemoteException {
        // 检查调用者权限
        mContext.enforceCallingOrSelfPermission(PERMISSION, TAG);
        synchronized (mProperties) {
            return mProperties.get(key);
        }
    }

    @Override
    public void setCustomProperty(String key, String value) throws RemoteException {
        // 只允许系统进程调用
        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
            throw new SecurityException("只有系统进程可以调用 setCustomProperty");
        }
        synchronized (mProperties) {
            mProperties.put(key, value);
        }
        Slog.d(TAG, "设置属性: " + key + " = " + value);
    }

    @Override
    public boolean isFeatureEnabled(String featureName) throws RemoteException {
        return SystemProperties.getBoolean("persist.sys.feature." + featureName, false);
    }
}
Java — 3. 在 SystemServer 中注册服务
// frameworks/base/services/java/com/android/server/SystemServer.java
// 在 startOtherServices() 方法中添加:

private void startOtherServices() {
    // ... 现有代码 ...

    // 注册自定义服务到 ServiceManager
    try {
        Slog.i(TAG, "启动 MyCustomService");
        ServiceManager.addService(
            "my_custom_service",              // 服务名称(Context.MY_CUSTOM_SERVICE)
            new MyCustomService(mSystemContext)
        );
    } catch (Throwable e) {
        reportWtf("启动 MyCustomService 失败", e);
    }
}
Java — 4. 添加 Manager 类供 App 调用
// MyCustomManager.java(frameworks/base/core/java/android/app/)
public class MyCustomManager {

    public static final String SERVICE_NAME = "my_custom_service";

    private final IMyCustomService mService;

    /** @hide */
    public MyCustomManager(IMyCustomService service) {
        mService = service;
    }

    public String getCustomProperty(String key) {
        try {
            return mService.getCustomProperty(key);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    public boolean isFeatureEnabled(String featureName) {
        try {
            return mService.isFeatureEnabled(featureName);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}

// 在 ContextImpl 中注册(frameworks/base/core/java/android/app/ContextImpl.java)
// registerService(Context.MY_CUSTOM_SERVICE, MyCustomManager.class,
//     new CachedServiceFetcher<MyCustomManager>() {
//         @Override
//         public MyCustomManager createService(ContextImpl ctx) {
//             IBinder b = ServiceManager.getService(MyCustomManager.SERVICE_NAME);
//             return new MyCustomManager(IMyCustomService.Stub.asInterface(b));
//         }
//     });
调用自定义系统服务(App 端):
完成以上步骤并编译系统后,App 可以通过 MyCustomManager mgr = (MyCustomManager) context.getSystemService(Context.MY_CUSTOM_SERVICE); 来调用你的系统服务。