Android 四大组件

九层高塔,起于累土。

Table of Contents

  1. 初识活动——Activity
    1. Activity生命周期
    2. 常见生命周期流
    3. 注意事项
    4. 异常情况下的生命周期
      1. 情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建
      2. 情况2:资源内存不足导致低优先级的Activity被杀死
      3. 防止重新创建Activity
    5. Activity与Fragment生命周期关系
    6. Activity和Menu创建先后顺序
    7. Activity启动模式
  2. 探究服务——Service
    1. 本地服务(LocalService)
      1. 通过Start方式开启服务
      2. 通过Bind方式开启服务
      3. 代码示例
    2. 远程服务
      1. 绑定远程服务
      2. IntentService
  3. 广播接收器——BroadcastReceiver
    1. 普通广播 Normal Broadcasts
    2. 有序广播 Ordered Broadcasts
    3. BroadcastReceiver生命周期
    4. 注意!BroadcastReceiver的生命周期很短
    5. 代码示例
      1. 定义BroadcastReceiver定义
      2. 在AndroidManifest.xml中对其注册
    6. 静态注册和动态注册的区别
    7. 有序广播和普通广播的区别
    8. BroadcastReceiver小结
  4. 内容提供者——ContentProvider
    1. 使用系统的ContentProvider
    2. 自定义ContentProvider
  5. 参考文章

初识活动——Activity

Activity生命周期

Lifecycle Description
onCreate()
onStart() 表示Activity可见了,但是还没有出现在前台,还无法和用户交互
onResume()
onPause() 表示Activity正在停止,此时可以做一些存储数据、停止动画等工作,注意不能太耗时,因为这会影响到新Activity的显示:onPause()必须先执行完,新的Activity的onResume()才会执行
onStop()
onRestart() 表示Activity正在重新启动,一般情况下,当前Activity从不可见重新变为可见状态时就会被调用,这种情形一般是用户行为所导致的
onDestroy()
Activity生命周期Activity生命周期

常见生命周期流

  1. 启动Activity :系统先调用onCreate(),然后调用onStart(),最后调用onResume()方法,Activity进入运行状态。
  2. 覆盖系统锁屏 :系统先调用onPause()方法,暂停当前Activity的运行,进入暂停状态。
  3. 回到前台唤醒 :系统调用onResume()方法,再次进入运行状态。
  4. 跳转Home键 :系统先调用onPause()方法,再调用onStop()方法,Activity退居后台,进入停滞状态。
  5. 通过 Back键 返回 :系统先调用onRestart(),然后调用onStart(),最后调用onResume()方法,再次进入运行状态。
  6. 清理 后用户退回当前Activity :再次调用onCreate()onStart()onResume()方法,进入运行状态。
  7. 用户 退出当前Activity : 系统先调用onPause(),然后调用onStop(),最后调用onDestroy()方法,结束当前Activity。

注意事项

  1. 从Activity是否可见来说, onStart()onStop() 是配对的
  2. 从Activity是否在前台来说, onResume()onPause() 是配对的
  3. 旧Activity先 onPause ,然后新Activity再启动
  4. 当弹出Dialog对话框时,不会回调 onPause()
  5. 当启动Dialog风格的Activity时,会回调 onPause() 函数

异常情况下的生命周期

比如当系统资源配置发生改变或者系统资源不足时,Activity就可能被杀死。

情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建

比如说当前Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下Activity就会被销毁并且重新创建,当然我们也可以组织系统重新创建我们的Activity。

意外情况下的生命周期流意外情况下的生命周期流

系统配置发生改变后,Activity会销毁,其 onPause()onStop()onDestroy() 方法均会被调用。由于是在异常情况下终止的,系统会调用 onSaveInstanceState() 方法来保存当前Activity状态,这个方法在 onStop() 之前调用,与 onPause() 没有既定的时序关系。

当Activity重新创建后,系统会调用 onRestoreInstanceState() ,并把之前保存的 Bundle 对象作为参数同时传递给 onRestoreInstanceState()onCreate() 方法,而 onStart() 方法则在 onRestoreInstanceState() 方法执行之后回调。

onSaveInstanceState()onRestoreInstanceState() 方法中,View相关的状态系统都能够默认为我们恢复。

生命周期日志打印如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
04-11 09:44:57.350 11757-11757/cn.hotwoo.play:remote I/MainActivity: onCreate
04-11 09:44:57.354 11757-11757/cn.hotwoo.play:remote I/MainActivity: onStart
04-11 09:44:57.356 11757-11757/cn.hotwoo.play:remote I/MainActivity: onResume
04-11 09:44:57.425 11757-11757/cn.hotwoo.play:remote I/MainActivity: onCreateOptionsMenu
04-11 09:44:59.149 11757-11757/cn.hotwoo.play:remote I/MainActivity: onPause
04-11 09:44:59.151 11757-11757/cn.hotwoo.play:remote I/MainActivity: onSaveInstanceState
04-11 09:44:59.151 11757-11757/cn.hotwoo.play:remote I/MainActivity: onStop
04-11 09:44:59.151 11757-11757/cn.hotwoo.play:remote I/MainActivity: onDestroy
04-11 09:44:59.234 11757-11757/cn.hotwoo.play:remote I/MainActivity: onCreate
04-11 09:44:59.235 11757-11757/cn.hotwoo.play:remote I/MainActivity: onStart
04-11 09:44:59.236 11757-11757/cn.hotwoo.play:remote I/MainActivity: onRestoreInstanceState
04-11 09:44:59.237 11757-11757/cn.hotwoo.play:remote I/MainActivity: onResume
04-11 09:44:59.270 11757-11757/cn.hotwoo.play:remote I/MainActivity: onCreateOptionsMenu
04-11 10:02:32.320 11757-11757/cn.hotwoo.play:remote I/MainActivity: onPause
04-11 10:02:32.516 11757-11757/cn.hotwoo.play:remote I/MainActivity: onStop
04-11 10:02:32.516 11757-11757/cn.hotwoo.play:remote I/MainActivity: onDestroy

情况2:资源内存不足导致低优先级的Activity被杀死

这里的情况和之前情况1中数据存储和恢复是完全一致的。Activity按照优先级从高到低可以分为以下三种:

  1. 前台Activity——正在和用户交互的Activity,优先级最高
  2. 可见但非前台的Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台,无法和用户直接交互
  3. 后台Activity——已经被暂停的Activity,优先级最低
防止重新创建Activity

可为Activity指定configChange属性来防止系统重新创建Activity

1
android : configChanges = "orientation"

Activity与Fragment生命周期关系

创建过程

创建过程系统日志创建过程系统日志

销毁过程

销毁过程系统日志销毁过程系统日志

Activity和Menu创建先后顺序

在Activity创建完回调 onResume() 后创建 menu ,回调 onCreateOptionMenu()

1
2
3
4
04-05 00:35:03.452 2292-2292/cn.hotwoo.play:remote I/MainActivity: onCreate
04-05 00:35:03.453 2292-2292/cn.hotwoo.play:remote I/MainActivity: onStart
04-05 00:35:03.454 2292-2292/cn.hotwoo.play:remote I/MainActivity: onResume
04-05 00:35:03.482 2292-2292/cn.hotwoo.play:remote I/MainActivity: onCreateOptionsMenu

Activity启动模式

共有 StandardSingleTopSingleTaskSingleInstance 四种启动模式

Type Description
Standard 在这种模式下,Activity默认会进入启动它的Activity所属的任务栈中。但是,非Activity类型的Context(如ApplicationContext)并没有所谓的任务栈,所以不能通过ApplicationContext去启动Standard模式的Activity。
SingleTop 栈顶复用模式。如果新Activity位于任务栈栈顶的话,Activity不会被重新创建,同时它的onNewIntent()方法会被回调。不过,这个Activity的onCreate()onStart()onResume()方法不会被回调,因为他们并没有发生改变。
SingleTask 栈内复用模式。只要Activity在一个栈中存在,那么多次启动此Activity不会重新创建单例,系统会回调onNewIntent()。比如activityA,系统会首先寻找是否存在A想要的任务栈,如果没有则创建一个新的任务栈,然后把activityA压入栈;如果存在任务栈,然后再看有没有activityA的实例,如果实例存在,那么就会把A调到栈顶并调用它的onNewIntent()方法,如果不存在则把它压入栈。
SingleInstance 单实例模式。这种模式的Activity只能单独地位于一个任务栈中。由于栈内复用特性,后续的请求均不会创建新的Activity实例。

默认情况下,所有Activity所需的任务栈的名字为应用的包名,可以通过给Activity指定TaksAffinity属性来指定任务栈,该属性不能和包名相同,否则没有意义

探究服务——Service

本地服务(LocalService)

本地服务指的是调用者和Service在同一个进程里,运行在主进程的main线程中,所以不能进行耗时操作,可以采用在Service里创建一个Thread的方法来执行任务。

任何Activity都可以控制同一个Service,而系统也只会创建一个对应Service的实例

通过Start方式开启服务

使用Service的步骤如下

  1. 定义一个类继承Service
  2. manifest.xml文件中配置Service
  3. 使用context的 startService(Intent) 方法启动Service
  4. 不再使用时,调用 stopService(Intent) 方法停止服务

使用start方式启动的生命周期如下

1
onCreate()-->onStartCommand()-->onDestroy()

特点:如果服务已经开启就跟调用者(开启者)没有任何关系了。开启者即便退出了,服务还在后台长期的运行,而开启者不能调用服务里面的方法

通过Bind方式开启服务

使用Service的步骤如下

  1. 定义一个类继承Service
  2. 在manifest.xml文件中注册Service
  3. 使用context的 bindService(Intent,ServiceConnection,int) 方法启动Service
  4. 不再使用时,调用 unbindService(ServiceConnection) 方法停止该服务

使用bind方式启动的生命周期如下

1
onCreate()-->onBind()-->onUnbind()-->onDestroy()

可以看到的是,绑定服务不会调用 onStart() 或者 onStartCommand() 方法

特点:通过bind方式开启并绑定服务,如果调用者挂了,服务也会跟着挂掉。绑定者可以调用服务里面的方法

代码示例

定义一个类继承Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//本地service不涉及进程间通信
public class MyService extends Service {
private String TAG = "MyService";
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG,"onCreate");
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
Log.i(TAG,"onStart");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
//绑定服务时调用这个方法,返回一个IBinder对象
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"onBind");
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG,"onUnbind");
return super.onUnbind(intent);
}
// 停止服务,通过调用Context.unbindService(),别忘了service也继承了Context类
// @Override
// public void unbindService(ServiceConnection conn) {
// super.unbindService(conn);
// Log.i(TAG,"unbindService");
// }
//服务挂了
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG,"onDestroy");
}
public interface MyIBinder{
void invokeMethodInMyService();
}
public class MyBinder extends Binder implements MyIBinder{
public void stopService(ServiceConnection serviceConnection){
unbindService(serviceConnection);
}
@Override
public void invokeMethodInMyService() {
for(int i =0; i < 20; i ++){
System.out.println("service is opening");
}
}
}

在AndroidManifest.xml中注册Service

1
2
3
4
5
6
7
8
9
//Service 必须要注册
<service android:name=".server.MyService" android:exported="true">
<intent-filter>
<action
android:name="cn.hotwoo.play.server.MyService"/>
<category
android:name="android.intent.category.default"/>
</intent-filter>
</service>

绑定自定义的Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class CustomActivity extends AppCompatActivity {
private Button startService, unbindService;
private MyService.MyBinder myBinder;
private ServiceConnection serviceConnection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom);
startService = (Button) findViewById(R.id.service_start);
unbindService = (Button) findViewById(R.id.unbind_service);
startService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// startService(new Intent(CustomActivity.this, MyService.class));
serviceConnection = new MyServiceConnection();
bindService(new Intent(CustomActivity.this, MyService.class), serviceConnection, Context.BIND_AUTO_CREATE);
}
});
unbindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
unbindService(serviceConnection);
}
});
}
class MyServiceConnection implements ServiceConnection {
//这里的第二个参数IBinder就是Service中的onBind方法返回的
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i("MyService", "onServiceConnected");
myBinder = (MyService.MyBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i("MyService", "onServiceDisconnected");
}
}
}

startService输出日志

1
2
3
04-01 19:56:09.846 22845-22845/cn.hotwoo.play I/MyService: onCreate
04-01 19:56:09.854 22845-22845/cn.hotwoo.play I/MyService: onStartCommand
04-01 19:56:09.854 22845-22845/cn.hotwoo.play I/MyService: onStart

bindService输出日志

1
2
3
4
5
6
04-01 19:53:21.459 14704-14704/cn.hotwoo.play I/MyService: onCreate
04-01 19:53:21.460 14704-14704/cn.hotwoo.play I/MyService: onBind
04-01 19:53:21.461 14704-14704/cn.hotwoo.play I/MyService: onServiceConnected
点击back键关闭activity或者调用Context.unbindService()方法后:
04-05 01:16:27.508 11427-11427/cn.hotwoo.play I/MyService: onUnbind
04-05 01:16:27.508 11427-11427/cn.hotwoo.play I/MyService: onDestroy

远程服务

远程服务是指调用者和Service不在同一个进程中,Service在单独的进程中的main线程中,是一种跨进程的通信方式。参考文章

绑定远程服务
  1. 在服务的内部创建一个内部类,提供一个可以间接调用服务的方法
  2. 把暴露的接口文件的扩展名改为 .aidl 文件,并去掉访问修饰符
  3. 实现服务的 onbind() 方法,继承Binder并实现aidl定义的接口,提供给外界可调用的方法
  4. 在Activity中使用 bindService() 方法绑定服务
  5. 在服务成功绑定的时候会回调 onServiceConnected() 方法传递一个 IBinder 对象
  6. aidl定义的接口 .Stub.asInterface(binder) 调用接口里面的方法
IntentService

IntentService是Service的子类,比普通的Service增加了额外的功能。Service本身存在以下两个问题

  1. Service不会专门启动一条单独的进程,Service与它所在应用位于同一个进程中
  2. Service也不是专门一条新线程,因此不应该在Service中直接处理耗时的任务

IntentService有以下几点特征

  1. 会创建独立的 worker 线程来处理所有的Intent请求
  2. 会创建独立的 worker 线程来处理 onHandleIntent() 方法实现的代码,无需处理多线程问题
  3. 所有请求处理完成后,IntentService会自动停止,无需调用 stopSelf() 方法来停止Service
  4. 为Service的 onBind() 提供默认实现,返回null
  5. 为Service的 onStartCommand() 提供默认实现,将请求Intent添加到队列中

广播接收器——BroadcastReceiver

普通广播 Normal Broadcasts

普通广播是完全异步的,理论上可以再同一时刻被所有接收者接收到,消息传递的效率比较高,但缺点是接收者不能将处理结果传递给下一个接收者,并且不发终止广播Intent的传播。

1
2
Context.sendBroadcast();
//发送的是普通广播,所有订阅者都有机会获得并进行处理

有序广播 Ordered Broadcasts

有序广播是按照接收者声明的优先级别,按优先级依次接收广播。比如:A > B > C,那么广播先传给A,再传给B,最后传给C。A得到广播后,可以往广播里存入数据,当广播传给B时,B可以从广播中得到A存入的数据。

有序广播的优先级别声明在 intent-filter 元素的 android:priority 属性中,数值越大优先级别越高,取值范围从 -1000 ~ 1000 ,也可以调用 IntentFilter 对象的 setPriority() 方法进行设置

1
2
3
4
5
6
7
8
9
Context.sendOrderedBroadcast();
//发送的是有序广播,系统会根据接收者声明的优先级别逐个发送
BroadcastReceiver.abortBroadcast();
//前面的接收者有权调用以上方法终止广播
//如果广播被前面的接收者终止,后面的接收者就再也无法获取到广播
setResultExtras(Bundle);
//前面的处理者可以将处理结果通过以上方法存放进结果对象,然后传给下一个接收者
Bundle bundle = getResultExtras(true);
//调用以上方法可以获取上一个接收者存入在结果对象中的数据

例如系统收到短信发出的广播,就属于有序广播。如果想组织用户收到短信,可以通过设置优先级,让自定义的接收者先获取到广播,然后终止广播,这样用户就接收不到短信了。

BroadcastReceiver生命周期

如果一个广播接收器的onReceive方法被调用,那么系统将认定此对象不再是一个活动的对象,进而将其回收。

BroadcastReceiver生命周期BroadcastReceiver生命周期
  1. 自定义一个类继承自BroadcastReceiver
  2. 重写onReceive方法
  3. manifest.xml文件中注册

注意!BroadcastReceiver的生命周期很短

如果需要在onReceive方法中完成一些耗时操作,应该考虑在Service中开启一个新线程处理耗时操作,不应该在BroadcastReceiver中开启新线程。

这是因为BroadcastReceiver的生命周期很短,在执行完onReceive方法后就结束了。如果在其中开启新线程,可能出现BroadcastReceiver退出后线程还在的情况。

而如果BroadcastReceiver所在的进程结束了,该线程就会被标记为一个空线程,根据Android的内存管理策略,在系统内存紧张的时候,会按照优先级,结束优先级低的线程。而空线程的优先级无疑是最低的,这样就可能导致BroadcastReceiver启动的子线程不能执行完成。

代码示例

定义BroadcastReceiver定义
1
2
3
4
5
6
7
8
9
10
11
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("fuck","intent-action : " + intent.getAction());
if(intent.getAction().equals("test")){
Toast.makeText(context,"fuck",Toast.LENGTH_LONG).show();
}
}
}
在AndroidManifest.xml中对其注册
1
2
3
4
5
6
7
8
9
//广播接收器
<receiver android:name=".broadcast.MyBroadcastReceiver">
<intent-filter>
<action
android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action
android:name="test"/> //这里自定义一个广播动作
</intent-filter>
</receiver>

当然,广播也可以动态注册

1
registerReceiver(new MyBroadcastReceiver(),new IntentFilter("test"));

注意:动态注册一定要记得加上以下权限

1
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>

静态注册和动态注册的区别

  1. 动态注册广播不是常驻型广播,也就是说广播跟随Activity的生命周期。注意:要在Activity结束前移除BroadcastReceiver
  2. 静态注册是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行

有序广播和普通广播的区别

当广播为有序广播时

  1. 优先级高的先接收
  2. 同优先级的BroadcastReceiver,动态优先于静态
  3. 同优先级的同类BroadcastReceiver,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的

当广播为普通广播时

  1. 无视优先级,动态BroadcastReceiver优先于静态BroadcastReceiver
  2. 同优先级的同类BroadcastReceiver,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的

BroadcastReceiver小结

  1. 在Android中如果要发送一个广播必须使用 sendBroadcast() 向系统发送对其感兴趣的BroadcastReceiver中
  2. 使用广播必须有一个Intent对象,也必须设置其Action动作对象
  3. 使用广播必须在配置文件中显式的指明该广播对象
  4. 每次接收广播都会重新生成一个接收广播的对象
  5. 在BroadcastReceiver中尽量不要处理太多逻辑问题,建议复杂的逻辑交给Activity或者Service去处理
  6. 静态注册在应用程序关闭后也会收到广播,动态注册在程序关闭后不会收到广播

关于小结第5点,援引如下解释

  • 在Android系统中,程序的响应(Resposive)被活动管理器(Activity Manager)和窗口管理器(Window Manager)这两个系统服务监视。当BroadcastReceiver在10秒内没有执行完毕,系统会认为该程序无响应。所以在BroadcastReceiver里不能做一些耗时操作,否则会弹出ANR(Applicaiton No Response)的对话框。如果需要完成耗时操作,应该通过发送Intent给Service的方式,由Service来完成。

内容提供者——ContentProvider

ContentProvider是Android四大组件之一的内容提供器,它的主要作用是将程序内部的数据和外部进行共享,为数据提供外部访问接口。被访问的数据主要存储在数据库中,而且还可以选择共享哪一部分的数据,例如可以选择隐私数据不共享,从而保障了安全性。ContentProvider是Android系统中一种跨程序共享数据的重要组件。

使用系统的ContentProvider

Android系统为我们提供的ContentProvider有很多,如通话记录、短信、通讯录等,都需要和第三方App共享数据。使用ContentProvider的步骤如下:

  1. 获取 ContentResolver 实例
  2. 确定 Uri 的内容,并解析为具体的 Uri 实例
  3. 通过 ContentResolver 实例来调用相应的方法,传递相应的参数。但是第一个参数总是 Uri ,它制定了我们要操作的数据的具体地址

自定义ContentProvider

系统的ContentProvider在与我们进行交互的时候,只接受了一个Uri参数,然后根据我们的操作返回我们所需要的结果。那么Android系统到底是如何根据一个Uri就能够提供给我们准确结果的呢?

和之前的步骤一样,要想自定义程序中的四大组件,就必须重新实现一个类,并重写这个类中的抽象方法,之后在AndroidManifest.xml文件中进行注册,最后才能够正常使用。

重新实现ContentProvider后,发现我们重写了6个重要的抽象方法:

  1. onCreate
  2. query
  3. update
  4. insert
  5. delete
  6. getType

其中除了关于数据库CRUD操作的抽象方法之外,有两个方法需要注意:

  1. onCreateContentProvider 创建时所执行的一个回调方法,负责数据库的创建和更新操作。这个方法只有当我们在程序中获取 ContentResolver 实例之后准备访问共享数据的时候,才会被执行
  2. getType 方法是获取我们通过参数传递进去的 UriMIME 类型

ContentProvider首先要做的就是将我们传递过来的Uri解析出来,确定其他程序到底想访问哪些数据。Uri的形式一般有以下两种:

  1. 以路径名为结尾,这种 Uri 请求的是整张表的数据,如 content://com.demo.androiddemo.provider/table1 表示我们要访问 table1 表中的所有数据
  2. id 列的值为结尾,这种 Uri 请求的是该表中和其提供的列值相等的单条数据,如 content://com.demo.androiddemo.provider/table1/1 表示我们要访问 table1 表中 id 列值为1的数据

参考文章

  1. 《Android四大组件》—— 简书
  2. 《URI和URL的区别》—— cnblogs
  3. 《Activity你真的熟悉吗?看了才知道》—— 简书
  4. 《彻底弄懂彻底弄懂Activity四大启动模式》—— CSDN
  5. 《Android 多线程之IntentService 完全详解》—— CSDN
  6. 《安卓面试题:2-关于Service》—— Iwfu
  7. 《Service要点全解析》—— CSDN
  8. 《Android中的Service:Binder,Messenger,AIDL》—— CSDN
  9. 《Android探索之BroadcastReceiver具体使用以及安全性探究》—— 掘金
  10. 《BroadcastReceiver的工作过程》—— muhao的程序世界
  11. 《使用ContentProvider跨进程共享数据》—— 简书
  12. 《Android探索之ContentProvider熟悉而又陌生的组件》—— 掘金