Skip to content
Go back

从使用场景理解 Android 四大组件

刚开始接触 Android 时,四大组件几乎是绕不开的一组概念:ActivityServiceBroadcastReceiverContentProvider

一开始很容易把它们理解成需要背下来的知识点。真正写了一些页面和交互之后,才发现它们更像系统为应用规定的几种边界:页面如何展示,后台工作如何延续,外部事件如何进入应用,数据又如何被其他模块或应用访问。

Activity:承载一段可以交互的界面

Activity 是最先接触到的组件。通常一个 Activity 会承载一个页面,接收用户操作,也负责在适当的生命周期中准备或释放界面资源。

例如从列表页进入详情页,可以通过 Intent 启动另一个 Activity

Intent intent = new Intent(this, DetailActivity.class);intent.putExtra("article_id", articleId);startActivity(intent);

这里传递的是详情页重新获得内容所需的标识,而不是把整个页面数据都塞过去。这样详情页可以根据 article_id 自己完成加载,也更容易应对页面重建。

Activity 最值得注意的不是页面跳转 API,而是它并不会一直存在。用户返回、旋转屏幕、系统回收资源,都可能使它被销毁。界面代码不能默认“这个实例永远活着”。

Service:处理不依附于某个页面的工作

页面退到后台以后,有些工作仍然可能需要继续,例如音乐播放、文件上传或者较长时间的数据同步。这些任务不应该完全依附于某一个 Activity,否则页面销毁后就很难管理。

启动一个简单的 Service

Intent intent = new Intent(this, DownloadService.class);intent.putExtra("download_url", downloadUrl);startService(intent);

Service 中可以处理对应任务:

public class DownloadService extends Service {    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        String url = intent.getStringExtra("download_url");        startDownload(url);        return START_NOT_STICKY;    }    @Override    public IBinder onBind(Intent intent) {        return null;    }}

需要注意的是,Service 并不等于子线程。它只表示一项没有界面的应用工作,默认的生命周期回调仍然运行在主线程中。真正耗时的操作仍需要放入工作线程,否则同样会卡住界面。

如果页面需要持续调用后台能力或者获取进度,则可以使用绑定服务 bindService();如果只是触发一次任务,启动服务的关系会更简单一些。

BroadcastReceiver:接收短暂的事件通知

系统中有很多事件不是由当前页面主动发起的,例如网络状态变化、电量变化或者设备启动完成。应用希望在某些事件发生时做出响应,可以使用 BroadcastReceiver

一个简单的接收器如下:

public class NetworkChangeReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {            Toast.makeText(context, "网络状态发生变化", Toast.LENGTH_SHORT).show();        }    }}

接收器可以在 AndroidManifest.xml 中注册,也可以由页面在运行期间动态注册:

private NetworkChangeReceiver mReceiver;@Overrideprotected void onStart() {    super.onStart();    mReceiver = new NetworkChangeReceiver();    IntentFilter filter = new IntentFilter(        ConnectivityManager.CONNECTIVITY_ACTION    );    registerReceiver(mReceiver, filter);}@Overrideprotected void onStop() {    super.onStop();    unregisterReceiver(mReceiver);}

BroadcastReceiveronReceive() 更适合做很短的处理,例如更新标记、发送通知或者启动后续任务。不应该把长时间网络请求或大量计算直接放进这里。

ContentProvider:用统一接口暴露数据

前三个组件更容易在普通页面开发中遇到,ContentProvider 刚开始看起来会比较陌生。它主要解决的是数据对外访问的问题:应用可以通过一套统一的 URI 与 ContentResolver 接口查询或修改数据,而不需要让调用者知道底层到底是数据库、文件还是其他存储方式。

Android 系统的通讯录就是很典型的例子。应用如果获得相应权限,可以通过 ContentResolver 查询联系人数据:

Cursor cursor = getContentResolver().query(    ContactsContract.Contacts.CONTENT_URI,    null,    null,    null,    null);if (cursor != null) {    while (cursor.moveToNext()) {        String name = cursor.getString(            cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)        );        Log.d("Contacts", name);    }    cursor.close();}

对于只在自己应用内部使用的数据,并不是都需要创建 ContentProvider。只有当数据需要以规范接口被其他组件或者其他应用访问时,它的边界价值才会体现出来。

Intent 串联组件之间的协作

学习四大组件时,会不断遇到 Intent。它不只是“页面跳转参数”,也是 Android 中描述一次动作与数据的一种方式。

可以把一些常见协作关系整理如下:

场景组件组合说明
从列表进入详情Activity -> Activity通过 Intent 传递内容标识
页面发起下载Activity -> Service页面退出后任务仍可继续
网络变化提示系统 -> BroadcastReceiver接收事件后快速响应
读取通讯录Activity -> ContentProvider通过 ContentResolver 查询数据

四大组件不是四套独立知识,而是一套应用结构中的不同入口。理解它们的职责以后,许多“这段逻辑应该写在哪里”的问题就会清晰很多。

小结

对四大组件的理解,可以先从边界而不是从 API 开始:

知道每一种组件为什么存在,比只记住如何声明和启动更重要。页面、任务、事件与数据各自有了合适的位置,应用的结构也会更容易维护。


Share this post on: