概述

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 做临时授权。