Android 应用由 四大核心组件 构成,它们是 Android 独有的设计思想,每个组件都有独立的生命周期和系统调度机制:
💡 重要认知:每个组件都有自己的生命周期(系统会在特定时机创建或销毁它们),理解生命周期是写出稳定应用的关键。
Android 架构图
Android 系统分为五个主要层次,从下到上依次为。作为 App 开发者,你主要在最顶层(Applications Layer)工作,通过 Application Framework 调用系统能力。
- Applications Layer:你写的代码就在这里,直接面向用户
- Application Framework:Android SDK API 所在,
startActivity()、Toast.show()等都属于这一层,你每天都在调用 - Android Runtime (ART):把你写的 Java/Kotlin 代码编译成机器码运行,自动管理内存
- HAL / Linux Kernel:负责与摄像头、GPS、WiFi 等硬件通信,通常不需要直接接触
Java vs Kotlin 对比
| 特性 | Java | Kotlin |
|---|---|---|
| Null 安全 | 需手动 null 检查,易 NPE | 内置 Null Safety,? 和 !! 运算符 |
| 扩展函数 | 不支持 | 支持,无需继承即可扩展 |
| 协程 | 不支持(需 RxJava) | 原生支持,简化异步编程 |
| 数据类 | 需手写 getter/setter/equals/hashCode | data class 自动生成 |
| Lambda | Java 8+ 支持,Android API 24+ | 一级公民,语法更简洁 |
| 互操作性 | 100% 与 Kotlin 互操作 | 100% 与 Java 互操作 |
| 性能 | 相同(都编译为字节码) | 相同,inline 函数零开销 |
| Google 推荐 | 稳定,大量历史代码 | 2019 年起为首选语言 |
Android 项目结构
用 Android Studio 创建新项目后,你会看到下面这棵目录树。最需要关注的三个位置:① java/ 目录下写业务代码;② res/layout/ 下写 XML 布局;③ AndroidManifest.xml 注册组件。其他文件初期不用深究。
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 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>
Activity 生命周期
Activity 生命周期是 Android 开发中最核心的概念,正确理解并处理各状态回调是构建稳定应用的基础。
各回调方法详解与最佳实践
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 状态(输入框内容、滚动位置、计数器) | 保存大量数据或用户数据(用数据库) | 只在系统主动销毁时调用(旋转屏幕、内存不足),用户按返回键不触发 |
当用户按下 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。适合来电界面、锁屏。
<!-- singleTask: 主页 Activity,确保全局唯一 -->
<activity
android:name=".HomeActivity"
android:launchMode="singleTask"
android:taskAffinity="com.example.myapp" />
<!-- singleTop: 推送通知目标 Activity -->
<activity
android:name=".NotificationActivity"
android:launchMode="singleTop" />
// 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:明确指定目标组件(类名)。用于启动自己应用内的 Activity,如
new Intent(this, DetailActivity.class) - 隐式 Intent:不指定目标,只描述动作(Action)和数据(Data)。系统会查找所有声明了匹配
intent-filter的组件。例如打开浏览器、拨打电话、分享内容
Intent 还可以携带数据:通过 putExtra(key, value) 传递基本类型或 Serializable/Parcelable 对象,目标 Activity 通过 getIntent().getStringExtra(key) 取出。
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);
}
}
从 Service、BroadcastReceiver、Application 等非 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。这意味着你在内存中保存的所有变量、已加载的数据,都会丢失,界面从头开始初始化。
解决方案有两种:
- 方案一(不推荐):在 Manifest 中声明
android:configChanges,告诉系统"这个 Activity 自己处理旋转,不要帮我重建"——但这会让你错过屏幕旋转时的布局自动切换 - 方案二(推荐):使用
ViewModel——ViewModel 的生命周期比 Activity 长,配置变更时 Activity 被销毁,但 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();
}
}
想象你正在用手机听音乐,这时候切换到微信回消息——音乐并没有停,对吗?这就是 Service 在发挥作用。Service 是一种在后台运行、没有用户界面的组件,专门处理那些不需要用户直接操作、但需要持续运行的任务。
Service 解决了一个核心问题:Activity 一旦不可见就可能被系统回收,但很多任务(播放音乐、下载文件、保持网络连接)必须在用户切换界面后依然运行。Service 就是为这类场景而生的。
Started Service vs Bound Service
Started Service(启动服务)
通过 startService() 启动,独立运行,调用者销毁后仍继续执行,需调用 stopSelf() 或 stopService() 停止。适合下载文件、播放音乐。
Bound Service(绑定服务)
通过 bindService() 绑定,提供客户端-服务器接口,所有绑定者解绑后自动销毁。适合 Activity 与 Service 通信。
Service 生命周期对比
完整音乐播放器 Service 示例
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);
}
}
}
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 唯一推荐的后台任务调度方案:
- 保证执行:即使应用退出、设备重启,任务仍会在满足条件后执行(区别于普通协程——进程被杀就丢失了)
- 约束条件:可以设置"只在 WiFi 下""只在充电时""只在有网时"等前置条件
- 适合的场景:数据同步、日志上传、图片压缩处理等"可以延迟但必须完成"的任务
| 方案 | 状态 | 使用场景 | 特点 |
|---|---|---|---|
| IntentService | 已弃用 | - | 串行执行,自动停止 |
| JobIntentService | 已弃用 | - | 兼容旧版,支持 JobScheduler |
| WorkManager | 推荐 | 延迟可靠后台任务 | 保证执行,支持约束条件 |
| Foreground Service | 推荐 | 长时间用户感知任务 | 必须显示通知 |
| Coroutines/RxJava | 推荐 | 即时后台操作 | 与 UI 生命周期绑定 |
使用 WorkManager 的核心步骤:① 创建 Worker 类(继承 Worker,在 doWork() 中写你的任务逻辑)→ ② 构建 WorkRequest(配置约束条件、执行策略)→ ③ 提交给 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 里直接做网络请求:Service 运行在主线程!网络请求会触发 NetworkOnMainThreadException 崩溃,必须在 Service 内部手动开子线程(或用 WorkManager)。
- 忘记调用 stopSelf() / stopService():Started Service 不会自动停止,一直运行会消耗电量和内存。确保任务完成后调用停止方法。
- Bound Service 解绑后没有 stop:如果同时用 startService() 启动又 bindService() 绑定,解绑后 Service 不会停止,必须额外调用 stopService()。
- 没注册前台服务通知(Android 8.0+):从 Android 8.0 起,后台 Service 有严格的存活限制。长期运行的任务必须用前台服务(
startForeground())并显示通知,否则系统几分钟后就会强制停止。
你有没有注意到,当手机电量低时,所有应用都会收到提醒;或者插上耳机,音乐播放器就自动知道了?这就是广播机制(Broadcast)的体现。
BroadcastReceiver 实现了一种发布-订阅模式:系统或其他应用发送广播(Publisher),而注册了对应 IntentFilter 的 BroadcastReceiver 会自动收到通知(Subscriber)。这让不同组件和应用之间可以相互通信,而无需直接持有对方的引用。
常见使用场景:
- 监听系统事件:网络变化、充电状态、屏幕开关、开机启动
- 应用内通信:一个模块通知其他模块某件事情发生了
- 跨应用通信:向其他应用发送通知(需权限)
静态注册 vs 动态注册
静态注册(Manifest 声明)
在 AndroidManifest.xml 中注册,应用未运行时也能接收广播。Android 8.0+ 对大多数隐式广播有限制。
动态注册(代码注册)
在代码中 registerReceiver(),随组件生命周期注册/注销。更灵活,适合与 Activity/Service 生命周期绑定。
下面的示例展示了完整的广播使用流程:① 定义 BroadcastReceiver 类(重写 onReceive 处理事件)→ ② 在 Activity 中动态注册(指定要监听的 action)→ ③ 在 onDestroy 中注销(防止内存泄漏)。
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
);
}
}
<!-- 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
// 推荐方案: 使用 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 | 时间被手动修改 | 是 |
假设你开发了一款日历应用,想让其他应用(比如 Siri、第三方小工具)能够读取或写入你的日历数据;或者你想在自己的应用中访问手机联系人、相册、短信——这就是 ContentProvider 发挥作用的场景。
ContentProvider 是 Android 四大组件中负责"数据共享"的那一个,它为跨应用的数据访问提供了统一的接口,基于 URI(统一资源标识符) 来定位数据,类似于 REST API 用 URL 定位资源的方式。
什么时候需要用 ContentProvider?
- 访问系统数据:读取联系人(ContactsContract)、相册(MediaStore)、短信等,系统已内置 ContentProvider
- 对外共享数据:你希望自己应用的数据库让其他应用也能访问
- 配合 FileProvider:在不同应用之间安全共享文件(Android 7.0+ 必须)
URI 结构解析
myapp.provider
自定义 ContentProvider 完整实现
自定义 ContentProvider 需要继承 ContentProvider 并实现 6 个核心方法,这些方法直接对应 CRUD 操作:
onCreate():初始化数据库等资源query():查询数据,返回 Cursorinsert():插入数据,返回新记录 URIupdate():更新数据,返回受影响行数delete():删除数据,返回受影响行数getType():返回 URI 对应的 MIME 类型
UriMatcher 是关键辅助类,它负责解析 URI 路径,判断客户端请求的是"整张表"还是"某一条记录",让你在一个 ContentProvider 中优雅地处理多种 URI 格式。
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 响应式数据绑定的底层机制之一。
// ===== 客户端访问 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 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>
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);
你有没有遇到过这种情况:在应用 A 点击了一个链接,跳转到浏览器;再在浏览器点击某个按钮,跳到了另一个应用 B;然后你按返回键,却回到了浏览器而不是你期望的地方——这背后的机制就是 Task 与返回栈。
理解 Task 和返回栈,能帮助你解决以下实际问题:
- 为什么用户从通知点进来,按返回键回到了不该回去的地方?
- 如何让通知打开的 Activity 有完整的返回栈(而不是直接回到桌面)?
- 如何防止同一个 Activity 被重复创建(比如登录页面只应该有一个)?
- 如何实现"清空所有页面,回到首页"的效果?
Task 概念与可视化
Task 是一组 Activity 的集合,以返回栈(Back Stack)的形式组织。用户按下返回键时,栈顶 Activity 出栈。
A2 重新显示
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 切换 |
// 场景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 中配置
}
开始学习这部分之前,先理解一个关键规则: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 三者分工明确,共同构成一个消息驱动的事件循环:
- MessageQueue(消息队列):一个按时间排序的消息链表。所有待处理的消息都在这里排队,等候执行。
- Looper(循环器):每个线程最多只有一个 Looper。它在
loop()方法中不停循环,从 MessageQueue 取出消息,交给对应 Handler 处理。主线程的 Looper 在 App 启动时由系统自动创建并运行。 - Handler(处理器):你创建 Handler 时它会绑定到当前线程的 Looper。子线程调用
handler.sendMessage()/handler.post(runnable)把消息塞入 MessageQueue,消息会在 Handler 绑定的那个线程里被处理。在主线程创建的 Handler,消息就在主线程处理——这就是切回主线程的原理。 - Message(消息):消息包,携带数据(what、arg1、arg2、obj)和目标 Handler 引用。
完整数据流:子线程 → handler.post(runnable) → 消息进入 MessageQueue → Looper 取出 → 在主线程执行 runnable → 更新 UI
Android 消息机制由四个核心组件构成,共同实现线程间安全通信:
sendMessage()post(Runnable)
消息链表
取出并分发消息
handleMessage()Handler 基本用法
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
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()
handler.obtainMessage() 或 Message.obtain() 从消息池获取复用对象,减少 GC 压力。
// 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);
内存泄漏防范
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);
}
}
每一个 Android 应用启动时,系统会创建一个主线程(Main Thread),也称为 UI 线程。主线程负责:处理用户输入事件、执行 UI 渲染、调用 Activity / Fragment 的生命周期方法。
如果你在主线程做了耗时操作(网络请求、读写大文件、复杂计算),主线程就会被阻塞,无法响应用户操作。Android 系统监测到主线程超过 5 秒无响应时,会弹出"应用无响应"对话框,这就是 ANR(Application Not Responding)。
解决方案:把耗时操作放到子线程,完成后再切回主线程更新 UI。这正是多线程编程的核心目标。
把主线程想象成前台服务员:负责与顾客互动(响应触摸)、传递菜单(更新 UI)。如果服务员要去厨房炒菜(耗时操作),前台就没人接待了——这就是 ANR。正确做法是:服务员把菜单给后厨(子线程),自己继续接待顾客,菜做好了厨房通知服务员端菜(切回主线程更新 UI)。
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(),复用线程、控制并发数量、统一管理任务队列。
// 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 个 | 定时/周期性任务 |
并发工具类
// ===== 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
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();
}
}
}
Java 在 Lambda 出现之前,传递一段"行为"(比如"点击按钮时做什么")需要创建匿名内部类,代码繁琐:需要写完整的类声明、方法签名,就为了一两行实际逻辑。Lambda 表达式(Java 8 引入)解决了这个问题——它让你可以用极简的语法传递一段代码作为参数。
- 在
build.gradle中设置compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 } - 或者
minSdkVersion 24以上(原生支持所有 Java 8 特性) - 低于 API 24 时,部分 Stream API 功能不可用(但基本 Lambda 通过脱糖机制可用)
Lambda 的核心语法是 (参数) -> { 函数体 }。下面对比旧写法与 Lambda 的差异,你会感受到代码量的明显减少:
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 中常见用途 |
|---|---|---|---|
Runnable | void 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) | 条件判断 | 过滤、条件检查 |
方法引用
// 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
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
// 创建
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("未知城市");
View 体系
Android UI 由 View 树构成。每个 View 经历 测量 (Measure) → 布局 (Layout) → 绘制 (Draw) 三个阶段才能显示到屏幕。
onMeasure()
MeasureSpec 传递约束
onLayout()
setFrame() 设置边界
onDraw(Canvas)
硬件加速/软件渲染
@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 的三个核心方法:
onCreateViewHolder():创建新的 ViewHolder(只在没有可复用的 View 时调用)onBindViewHolder():将数据绑定到 ViewHolder(每次滚动到该条目时调用)getItemCount():返回列表总条数
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);
}
}
}
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 常见错误与性能优化
当列表数据变化(插入/删除)时,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() 会触发整个列表的重新绑定,动画也会丢失。应优先使用精确通知方法:
notifyItemInserted(pos) / notifyItemRemoved(pos) / notifyItemChanged(pos)。
如果数据集整体替换,使用 DiffUtil(示例代码中已展示),让系统自动计算最小变更集。
如果 RecyclerView 的大小不随 Adapter 内容变化而改变,设置 recyclerView.setHasFixedSize(true),系统可以跳过对父容器的重新测量,提升滚动流畅度。
onBindViewHolder 每次滚动到该条目时都会调用,如果在里面 findViewById() 会非常耗性能。ViewHolder 模式的核心就是在 onCreateViewHolder 时一次性找好所有子 View,存入 ViewHolder 字段,之后只做数据填充。
自定义 View
当 Android 系统提供的内置控件(TextView、Button、ImageView...)无法满足你的设计需求时,就需要自定义 View。什么时候需要自定义 View?
- 独特的视觉效果(如圆形进度条、波纹背景、自定义图表)
- 特殊的交互手势(需要重写触摸事件处理)
- 将复杂的组合控件封装成一个独立组件复用
自定义 View 的三种方式(按复杂度递增):
- 继承现有控件(如继承 TextView):在已有基础上扩展,最简单
- 组合控件(继承 ViewGroup):将多个已有控件组合成新控件
- 完全自绘(继承 View):通过
onDraw(Canvas)用画笔完全自己画,灵活度最高
下面以"圆形进度条"为例,展示完全自绘方式的实现步骤。自定义 View 的开发遵循固定流程:
- 构造函数 — 解析自定义 XML 属性(
AttributeSet),初始化画笔(Paint)等绘制工具 - onMeasure() — 决定 View 的宽高。如果你用
wrap_content,必须在这里提供默认大小,否则大小为 0 - onSizeChanged() — 当大小确定后调用,在这里创建依赖尺寸的对象(如 RectF 边界)
- onDraw(Canvas) — 真正的绘制逻辑,用 Canvas 的各种
drawXxx()方法绘制图形、文字 - invalidate() — 当数据变化需要重绘时调用,会触发系统重新调用
onDraw()
onDraw() 每一帧都可能被调用(60fps 时每秒 60 次),在里面 new Paint() 或 new RectF() 会造成大量 GC 压力,导致卡顿。所有对象必须在构造函数或 onSizeChanged() 中创建好。
下面以"圆形进度条"为例:
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 动画分为三大类,理解它们的区别可以避免踩坑:
属性动画三剑客:ObjectAnimator(动画 View 的某个属性)、ValueAnimator(动画任意数值,需自己处理更新逻辑)、ViewPropertyAnimator(链式调用,最简洁的写法,用于常见动画场景)。
// ===== 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); // 取消时恢复
}
});
// 链式调用,自动批量优化,推荐用于简单动画
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();
如果你是初学者,跳过本章也完全没问题——日常 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 层在运转。
学习这部分内容适合以下场景:
- 系统级开发(ROM 定制、OEM 设备厂商)
- 开发需要跨进程通信的复杂应用(如输入法、无障碍服务)
- 深度理解 Android 工作原理,提升系统级问题排查能力
- 高级 Android 开发岗位面试
Binder 机制
IPC(Inter-Process Communication,进程间通信) 是指不同进程(独立内存空间)之间交换数据的机制。在 Android 中,每个 App 运行在独立进程里,想调用系统的 ActivityManagerService(管理所有 Activity)、WindowManagerService(管理窗口)等系统服务,就必须跨进程通信。
Binder 是 Android 最核心的 IPC(进程间通信)机制,所有系统服务调用的底层都依赖它。
为什么 Android 不用传统的 Linux IPC(管道、Socket、共享内存)? 因为 Binder 针对 Android 的使用场景做了优化:只需要一次内存拷贝(传统 IPC 需要两次),性能更好;内核驱动天然支持 UID/PID 鉴权,更安全;对象引用计数自动管理,不会出现僵尸服务。
AIDL — Android 接口定义语言
AIDL(Android Interface Definition Language) 是 Android 提供的一种接口描述语言,专门用来定义跨进程通信的接口。
为什么需要 AIDL? 手写 Binder 通信代码非常繁琐(需要处理序列化/反序列化、线程管理、代理/存根对象)。AIDL 就像一个代码生成器:你只需用类似 Java 接口的语法定义"我要提供什么方法",编译器自动帮你生成完整的 Binder IPC 骨架代码(Stub 类和 Proxy 类)。
AIDL 让编译器自动生成 Binder IPC 的模板代码,开发者只需关注接口定义和业务逻辑。
// 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);
}
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 实例
}
}
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)
// IMyCustomService.aidl
package android.os;
interface IMyCustomService {
// 获取自定义系统属性
String getCustomProperty(String key);
void setCustomProperty(String key, String value);
// 权限保护方法
boolean isFeatureEnabled(String featureName);
}
// 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);
}
}
// 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);
}
}
// 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 可以通过
MyCustomManager mgr = (MyCustomManager) context.getSystemService(Context.MY_CUSTOM_SERVICE); 来调用你的系统服务。