概述
Android 应用的四大组件:Activity(界面)、Service(后台服务)、BroadcastReceiver(广播接收器)、ContentProvider(内容提供者)。除 BroadcastReceiver 可以动态注册外,其余组件都必须在 AndroidManifest.xml 中声明后才能被系统识别。
| 组件 | 职责 | 声明方式 |
|---|---|---|
| Activity | 管理用户界面,一个 Activity 通常对应一个屏幕 | <activity> |
| Service | 后台执行长时间任务,无界面 | <service> |
| BroadcastReceiver | 接收并响应系统/应用广播 | <receiver> 或代码动态注册 |
| ContentProvider | 跨进程共享结构化数据 | <provider> |
四大组件之间主要通过 Intent 通信,Intent 是组件间传递消息与数据的载体。
认识Activity
Activity 是Android应用程序的四大组件之一,负责管理应用的用户界面。每个Activity组件负责一个用户界面的展示,通常应用中的某一个Activity被指定为主界面,这是应用用户启动时出现的第一个屏幕。
Activity跳转
1 概述
Activity之间的跳转主要使用
startActivity(Intent intent);
startActivityForResult(Intent intent,int requestCode);这两个函数,传递数据的话利用Intent进行传递,负责数据需要配合Bundle使用。
startActivityForResult()与onActivityResult()从 AndroidX Activity 1.2 起被标记为废弃,官方推荐使用ActivityResultLauncher(registerForActivityResult)替代,但逆向旧应用时仍大量见到旧 API。
2 Activity间跳转
2.1 直接跳转
这里有两个Activity:FirstActivity与SecondActivity,从FirstActivity中跳转到SecondActivity中只需要一个startActivity()即可:
startActivity(new Intent(this, SecondActivity.class));一般需要在SecondActivity中加上finish()函数,表示这个Activity结束,比如在SecondActivity的按钮点击事件中调用:
// SecondActivity.java
findViewById(R.id.btn_back).setOnClickListener(v -> finish());finish() 会把当前 Activity 从任务栈中弹出,用户返回到上一个 Activity。
2.2 回调
很多情况下需要在FirstActivity跳转到SecondActivity后,在SecondActivity进行相应的操作,如更新数据之类,然后返回FirstActivity,通知FirstActivity执行某些操作,这时候就需要使用startActivityForResult()。
startActivityForResult有两个参数,一个是Intent,表示将要跳转的Activity,一个是requestCode,表示请求码,用于调用Activity的onActivityResult()函数。
如FirstActivity中:
startActivityForResult(new Intent(this, SecondActivity.class), 11);这里11就是请求码,然后在SecondActivity中,使用setResult()函数:
setResult(22, new Intent().putExtra("str", "from second activity"));setResult()接受两个参数,第一个参数表示resultCode,从这个SecondActivity返回的结果码(系统预定义了 RESULT_OK = -1、RESULT_CANCELED = 0),另一个Intent,表示要返回给FirstActivity的数据。
最后在FirstActivity中重载onActivityResult():
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 11 && resultCode == 22 && data != null) {
String str = data.getStringExtra("str");
// 处理 SecondActivity 返回的数据
}
}onActivityResult中判断请求码与结果码后进行相应操作。
3 Activity间传递数据
3.1 传递普通数据
传递普通类型的数据直接使用Intent的putExtra()即可,putExtra接受两个参数,第一个是一个String,表示键,第二个是值,类型可以是byte、char、short、long等基本类型与这些基本类型组成的数组,当然String也可以:
public Intent putExtra(String name, int value);
public Intent putExtra(String name, String value);
// ... 各基本类型及数组的重载比如在FirstActivity中,传递数据给SecondActivity:
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("name", "tom");
intent.putExtra("age", 18);
startActivity(intent);然后在SecondActivity中使用getIntent()获取Intent后,再从里面获取数据:
Intent intent = getIntent();
String name = intent.getStringExtra("name");
int age = intent.getIntExtra("age", 0); // 第二个参数是默认值如果想从SecondActivity中返回数据给FirstActivity,也就是从被调用的Activity中返回数据,可以使用前面介绍过的setResult()方法。
其中第二个参数是一个用于存储数据的Intent,把数据put进去即可:
// SecondActivity 返回数据
setResult(RESULT_OK, new Intent().putExtra("result", "some data"));
finish();最后记得在onActivityResult做相应的判断处理:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && data != null) {
String result = data.getStringExtra("result");
}
}3.2 传递一组数据
当数据的种类变多时,比如混合了int、String、byte、char等,可以对逐个类型使用putExtra(),但更好的方法是使用Bundle。
Bundle的使用与Intent类似,也是对于不同的类型采用键值对的添加方法,直接 putXXX:
Bundle bundle = new Bundle();
bundle.putString("name", "tom");
bundle.putInt("age", 18);
bundle.putChar("gender", 'M');数据添加完毕之后,在intent中使用putExtras(bundle):
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtras(bundle);
startActivity(intent);然后就可以在SecondActivity中接收数据了。
首先需要使用getIntent().getExtras()获取Bundle,再获取里面的数据:
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
String name = bundle.getString("name");
int age = bundle.getInt("age");
}注意一下获取到的Bundle有可能为null,取数据前需要判断一下。
3.3 传递对象
如果想要传递的数据是一个对象怎么办?难道对每一个属性都使用getter再放进去吗?
并不是,Bundle提供了一个处理序列化对象的方法:
public void putSerializable(String key, Serializable value);只要对象实现了 Serializable,就可以被Bundle处理,比如这里有一个测试类Student:
public class Student implements Serializable {
private String name;
private int age;
// getter / setter ...
}然后在FirstActivity中直接new一个,并使用Bundle的putSerializable即可:
Student student = new Student("tom", 18);
Bundle bundle = new Bundle();
bundle.putSerializable("student", student);
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtras(bundle);
startActivity(intent);接着在SecondActivity中通过Intent取出Bundle后,使用其中的getSerializable()取出对象,最后使用强制类型转换:
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
Student student = (Student) bundle.getSerializable("student");
}
Serializable基于 Java 反射,性能较差;Android 官方提供了更快的Parcelable(putParcelable/getParcelable),但需要手动实现序列化/反序列化逻辑。逆向时见到Parcelable的类要留意writeToParcel/CREATOR中的字段顺序。
Activity 生命周期
Activity 生命周期由系统回调管理,逆向分析时经常通过 hook 这些回调定位关键逻辑:
graph TD onCreate --> onStart --> onResume --> Running["运行中"] Running --> onPause --> onStop onStop --> onRestart --> onStart onStop --> onDestroy onPause --> onResume
| 回调 | 触发时机 | 典型用途 |
|---|---|---|
onCreate() | 首次创建 | 初始化布局 setContentView()、绑定数据 |
onStart() | 变为可见 | 恢复可见状态下的资源 |
onResume() | 获取焦点,可交互 | 注册监听、恢复动画 |
onPause() | 失去焦点(半透明覆盖) | 暂停耗电操作,保存轻量数据 |
onStop() | 完全不可见 | 释放较大资源 |
onRestart() | 从停止状态重新启动 | — |
onDestroy() | 销毁前 | 释放所有资源 |
要点:
- 横竖屏切换默认会销毁并重建 Activity(
onDestroy→onCreate),除非在清单中声明android:configChanges="orientation|screenSize"。 - 异常销毁前系统会回调
onSaveInstanceState(Bundle),重建时在onCreate(Bundle)中恢复。 onCreate()里常见setContentView(R.layout.xxx),逆向时可据此快速定位布局文件。
启动模式(launchMode)
Activity 的启动模式决定了它在**任务栈(Task / 返回栈)**中的行为,在 <activity android:launchMode="..."> 中声明:
- standard(默认):每次启动都新建一个实例压入栈顶,不管栈中是否已存在。
- singleTop:如果栈顶已经是该 Activity 实例,则复用并回调
onNewIntent(),否则新建。 - singleTask:栈内全局唯一。若栈中已存在实例,则把它之上的所有 Activity 弹出,复用该实例并回调
onNewIntent()。 - singleInstance:独享一个任务栈,该栈中只有它一个实例,其他 Activity 不会进入这个栈。
也可以通过 Intent 的 flag 动态控制,优先级高于 launchMode:
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 类似 singleTask,从非 Activity 上下文启动时必须
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 清除目标之上的所有 Activity
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); // 类似 singleTop
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); // 离开后不留在栈中相关属性 taskAffinity 指定 Activity 倾向归属的任务栈名(默认为包名),allowTaskReparenting 允许其跨栈迁移。
显式 Intent 与隐式 Intent
- 显式 Intent:直接指定目标组件类名,只能启动自己应用内(或已知类名的)组件。
Intent intent = new Intent(this, SecondActivity.class);
intent.setClassName("com.example.other", "com.example.other.TargetActivity"); // 跨应用显式指定- 隐式 Intent:不指定类名,只声明
action/category/data,由系统匹配各应用清单中<intent-filter>声明匹配的组件。
Intent intent = new Intent("com.example.ACTION_TEST");
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);清单中对应的过滤器:
<activity android:name=".SecondActivity" android:exported="true">
<intent-filter>
<action android:name="com.example.ACTION_TEST" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>逆向要点:
intent-filter里声明了<data android:scheme="..." android:host="...">的 Activity 可通过 deeplink 从浏览器/其他应用唤起,是组件暴露面分析的重点。
Service
Service 是在后台执行长时间运行操作的组件,没有用户界面。典型场景:后台播放音乐、文件下载、网络长连接。
两种启动方式
Service 的生命周期取决于启动方式,两种方式可以同时存在:
1. startService(启动式)
startService(new Intent(this, MyService.class));生命周期:onCreate() → onStartCommand() → onDestroy()。
- 多次
startService()只会重复触发onStartCommand(),不会重复onCreate()。 - 启动后 Service 一直运行,必须显式调用
stopService()或内部stopSelf()停止。 onStartCommand()的返回值决定被系统杀死后的重启策略:START_STICKY:杀死后尝试重启,但不重新传递 Intent;START_NOT_STICKY:不重启;START_REDELIVER_INTENT:重启并重新传递最后一个 Intent。
2. bindService(绑定式)
bindService(new Intent(this, MyService.class), connection, BIND_AUTO_CREATE);生命周期:onCreate() → onBind() → onUnbind() → onDestroy()。
- 通过
ServiceConnection拿到IBinder,与 Service 直接通信(调用其方法)。 - 所有绑定者解绑后 Service 自动销毁;绑定者销毁时系统会自动解绑。
- 跨进程绑定时
IBinder常由 AIDL 生成。
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MyService.LocalBinder binder = (MyService.LocalBinder) service;
MyService myService = binder.getService(); // 拿到 Service 实例直接调方法
}
@Override
public void onServiceDisconnected(ComponentName name) {}
};前台服务
后台 Service 容易被系统杀死,且 Android 8.0+ 限制后台应用启动 Service。需要长期运行的服务应调用 startForeground(id, notification) 提升为前台服务,必须常驻一条通知:
Notification notification = ...;
startForeground(1, notification);Android 9+ 需要声明 FOREGROUND_SERVICE 权限,Android 14+ 还要求在清单中声明 foregroundServiceType(如 mediaPlayback、location)。
IntentService
IntentService 是 Service 的子类,内部自带工作线程串行处理 Intent,任务完成后自动停止,避免手动管理线程(Android 11 起废弃,由 JobIntentService/WorkManager 替代):
public class MyIntentService extends IntentService {
public MyIntentService() { super("MyIntentService"); }
@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 在子线程中执行耗时任务,执行完自动 stopSelf()
}
}注意:Service 默认运行在主线程,不能直接执行耗时操作,耗时逻辑必须自己开线程(或使用 IntentService/WorkManager)。
BroadcastReceiver
BroadcastReceiver 用于接收广播消息,实现应用间或应用内的事件通知。系统广播如开机完成(BOOT_COMPLETED)、网络变化、电量低等;应用也可以发送自定义广播。
两种注册方式
1. 静态注册(清单中声明)
<receiver android:name=".MyReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>- 应用未运行时也能接收(Android 8.0 起对大部分隐式系统广播的静态注册做了限制,
BOOT_COMPLETED等少数例外)。 - 接收
BOOT_COMPLETED需要RECEIVE_BOOT_COMPLETED权限。
2. 动态注册(代码中)
MyReceiver receiver = new MyReceiver();
IntentFilter filter = new IntentFilter("com.example.MY_ACTION");
registerReceiver(receiver, filter); // 通常在 onCreate/onResume
// 必须在合适的时机 unregisterReceiver(receiver); 否则内存泄漏动态注册的接收器只在注册期间有效,优先级控制更灵活。Android 13+ 动态注册时必须显式指定 RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED 标志。
接收与发送
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 处理广播;onReceive 运行在主线程,不能做耗时操作,
// 也不能开子线程(进程可能在回调结束后被回收),耗时任务应转交 Service
}
}发送广播:
// 普通广播:所有接收器同时收到,无法截断
sendBroadcast(new Intent("com.example.MY_ACTION"));
// 有序广播:按 priority 依次传递,可被截断/修改结果
sendOrderedBroadcast(new Intent("com.example.MY_ACTION"), null);有序广播中 abortBroadcast() 可以截断广播,使低优先级接收器收不到——恶意应用曾利用这一点劫持短信/验证码广播(Android 已逐步收紧)。
本地广播
LocalBroadcastManager(AndroidX 已废弃,推荐 LiveData/Flow 替代)提供进程内广播,不经过 Binder,其他应用无法收发,避免了全局广播的安全问题:
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("com.example.LOCAL"));ContentProvider
ContentProvider 用于在不同应用之间共享结构化数据,底层通常是 SQLite,也可以是文件。系统通讯录、媒体库、短信都通过 ContentProvider 暴露数据。访问方使用 ContentResolver,以 URI 定位数据。
URI 结构
content://com.example.provider/user/1
└──scheme──┘└────authority─────┘└path┘└id
content://:固定 scheme;authority:Provider 的唯一标识,即清单中的android:authorities;path:数据表或数据类型;id:可选,定位单条记录。
增删改查
ContentResolver resolver = getContentResolver();
// 查询,返回 Cursor
Cursor cursor = resolver.query(
uri, // content://...
projection, // 返回哪些列,null 为全部
selection, // WHERE 条件
selectionArgs, // 条件参数,防注入
sortOrder); // 排序
// 插入 / 更新 / 删除
Uri newUri = resolver.insert(uri, contentValues);
int rows = resolver.update(uri, contentValues, selection, selectionArgs);
int deleted = resolver.delete(uri, selection, selectionArgs);自定义 Provider
继承 ContentProvider 实现六个方法,并配合 UriMatcher 解析 URI:
public class MyProvider extends ContentProvider {
private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
matcher.addURI("com.example.provider", "user", 1); // 表
matcher.addURI("com.example.provider", "user/#", 2); // 单条
}
@Override public boolean onCreate() { /* 初始化数据库 */ return true; }
@Override public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) { ... }
@Override public Uri insert(@NonNull Uri uri, ContentValues values) { ... }
@Override public int update(@NonNull Uri uri, ContentValues values, String s, String[] sa) { ... }
@Override public int delete(@NonNull Uri uri, String s, String[] sa) { ... }
@Override public String getType(@NonNull Uri uri) { /* 返回 MIME 类型 */ return null; }
}清单中声明并可配置读写权限:
<provider
android:name=".MyProvider"
android:authorities="com.example.provider"
android:exported="false"
android:readPermission="com.example.permission.READ"
android:writePermission="com.example.permission.WRITE" />还可以通过 grantUriPermissions + Intent.FLAG_GRANT_READ_URI_PERMISSION 对单个 URI 做临时授权。