刚开始接触 Android 时,四大组件几乎是绕不开的一组概念:Activity、Service、BroadcastReceiver 和 ContentProvider。
一开始很容易把它们理解成需要背下来的知识点。真正写了一些页面和交互之后,才发现它们更像系统为应用规定的几种边界:页面如何展示,后台工作如何延续,外部事件如何进入应用,数据又如何被其他模块或应用访问。
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);}
BroadcastReceiver 的 onReceive() 更适合做很短的处理,例如更新标记、发送通知或者启动后续任务。不应该把长时间网络请求或大量计算直接放进这里。
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 开始:
Activity面向用户正在操作的界面。Service面向不应该依附页面的后台工作。BroadcastReceiver面向来自系统或应用之间的短暂事件。ContentProvider面向需要通过统一契约访问的数据。
知道每一种组件为什么存在,比只记住如何声明和启动更重要。页面、任务、事件与数据各自有了合适的位置,应用的结构也会更容易维护。