Android开发学习笔记
Android开发学习笔记
四大组件篇
四大组件有哪些?
1️⃣ Activity
- 用于构建用户界面,代表一个独立的屏幕。
- 每个 Activity 都有完整的生命周期(如 onCreate、onStart、onResume 等),用于管理 UI 的创建与销毁。
- 常用于页面跳转、用户交互。
2️⃣ Service
- 用于在后台执行长时间运行的操作,不提供用户界面。
- 分为 Started Service 和 Bound Service,分别用于独立运行和与组件绑定交互。
- 典型应用如音乐播放、文件下载等。
3️⃣ BroadcastReceiver
- 用于接收并响应系统或应用发出的广播消息。
- 支持静态注册(Manifest 中声明)和 动态注册(代码中注册)。
- 常用于监听系统事件,如网络变化、电量低、电池充电等。
4️⃣ ContentProvider
- 用于在不同应用之间共享数据。
- 通过统一的 URI 接口对外提供数据访问能力。
- 常用于访问系统联系人、媒体库等,也可自定义 Provider 实现跨应用数据共享。
什么是Activity?
Activity(活动)是一个为实现交互而提供的Android应用组件。每个Activity都有一个窗口,该窗口可以全屏幕填充,也可以是一个小窗口浮动在其他窗口上。一个应用程序通常由多个Activity组成,它会指定应用程序中的某个Activity作为主Activity,这意味着当用户第一次启动应用程序时呈现给用户的活动,并且Activity可以相互跳转来执行不同的操作。
请介绍Activity的生命周期?

- Oncreate():创建:作为生命周期的第一种方法,仅在新的Activity创建时调用。在这个方法中,可以做一些初始化工作,比如加载接口布局资源和初始化活动所需的数据。
- OnStart():开始:表示Activity正在启动,并且即将启动。此时Activity已经出现,但还没有出现在前台,我们还不能交互。
- OnResume():活动:表示Activity已经出现在前台,可见可操作。
- OnPause():暂停:表示活动即将停止,仍然可见,但是不能操作了。
- OnStop():停止:表示活动停止,此时不可见,位于后台。
- OnDestory():销毁:表示活动即将被销毁。这是Activity循环的最后一个回调。你可以做一些回收工作和最后的资源回收。
最后介绍第七个生命周期onRestart: - OnRestart():表示Activity重启。在正常情况下,当Activity从不可见状态切换到可见状态,也就是onStop()到onStart()之间,就会调用OnRestart()。这种情况一般是由用户行为引起的。例如,用户在这个活动切换到桌面或打开另一个新的活动,然后用户返回到这个活动。
Activity生命周期的不同情况
1️⃣ 正常启动与销毁
- 流程:
onCreate()→onStart()→onResume()(进入前台)
用户退出或系统销毁:onPause()→onStop()→onDestroy() - 场景:用户点击图标打开应用,再按返回键退出。
- 说明:这是最标准的生命周期流程,适合理解各方法的调用顺序。
2️⃣ 从一个 Activity 跳转到另一个
- 流程:
当前 Activity:onPause()→onStop()
新 Activity:onCreate()→onStart()→onResume() - 场景:从首页跳转到详情页。
- 说明:前一个 Activity 进入后台,新的 Activity 进入前台。
3️⃣ 屏幕旋转(配置更改)
- 流程:
onPause()→onStop()→onDestroy()→onCreate()→onStart()→onResume() - 场景:用户横竖屏切换。
- 说明:Activity 被销毁并重建,需通过
onSaveInstanceState()保存状态。
4️⃣ 按 Home 键返回桌面
- 流程:
onPause()→onStop()(Activity 进入后台,但未销毁) - 场景:用户暂时离开应用。
- 说明:可以在
onStop()中释放资源,应用仍保留在任务栈中。
5️⃣ 应用被系统回收(内存不足)
- 流程:
系统可能直接销毁 Activity,不调用onDestroy()
恢复时调用onCreate(),可通过onSaveInstanceState()恢复状态 - 场景:后台运行时被系统杀死,用户重新打开应用。
- 说明:需做好状态保存与恢复,避免用户体验断层。
6️⃣ 弹出 Dialog 或透明 Activity
- 流程:
原 Activity 只调用onPause(),不会进入onStop() - 场景:弹出一个对话框或半透明界面。
- 说明:原 Activity 仍处于可见状态,适合轻量交互。
7️⃣ 多任务切换(Recent Apps)
- 流程:
切换到其他应用:onPause()→onStop()
切换回来:onRestart()→onStart()→onResume() - 场景:用户通过任务管理器切换应用。
- 说明:
onRestart()是从后台重新进入前台的关键方法。
切换横竖屏Activity生命周期的变化?
在 Android 中,切换横竖屏属于配置更改(Configuration Change),默认行为是系统会销毁并重建当前 Activity,因此会触发完整的生命周期流程:
onPause()→ onStop()→ onDestroy()→ onCreate()→ onStart()→ onResume()
- 系统会销毁当前 Activity 并重新创建一个新的实例。所有 UI 和数据状态都将被重置,除非你手动保存并恢复。
- 可通过 onSaveInstanceState(Bundle outState) 保存临时状态,在 onCreate() 或 onRestoreInstanceState() 中恢复。
- 如果你不希望 Activity 被重建,可以在 AndroidManifest.xml 中配置:
1
android:configChanges="orientation|screenSize"当屏幕方向(orientation)或屏幕尺寸(screenSize)发生变化时,当前 Activity 自行处理这些变化,系统不需要重新创建它。
- 然后在 onConfigurationChanged() 方法中手动处理布局更新。
为什么要加 screenSize?
从 Android 3.2(API 13)开始,横竖屏切换不仅改变方向,还可能改变屏幕尺寸(尤其在平板或多窗口设备上)。因此:
- 仅设置 orientation 不足以阻止重建
- 必须同时设置 screenSize 才能完整覆盖横竖屏切换场景
Activity的四种启动模式?
| 启动模式 | 描述 | 是否复用已有实例 | 是否创建新任务栈 |
|---|---|---|---|
standard |
默认模式,每次启动都会创建新实例 | ❌ 否 | ❌ 否 |
singleTop |
如果栈顶已有该 Activity,则复用 | ✅ 是(仅栈顶) | ❌ 否 |
singleTask |
如果任务栈中已有该 Activity,则复用并清除其上的所有 Activity | ✅ 是 | ✅ 是(可选) |
singleInstance |
独占任务栈,其他 Activity 无法进入该栈 | ✅ 是 | ✅ 是(强制) |
1️⃣ standard(默认模式)
- 每次启动都会创建新的 Activity 实例。
- 多次跳转会堆叠多个相同页面。
- 适用于普通页面跳转。
2️⃣ singleTop
- 如果目标 Activity 已在栈顶,则复用,不会创建新实例。
- 否则仍会创建新实例。
- 常用于通知栏点击跳转,避免重复页面。
3️⃣ singleTask
- 如果任务栈中已有该 Activity,则复用,并清除其上的所有 Activity。
- 适用于“主页”类页面,避免重复打开。
- 可配合 Intent.FLAG_ACTIVITY_CLEAR_TOP 使用。
4️⃣ singleInstance
- 该 Activity 独占一个任务栈,其他 Activity 无法进入。
- 常用于 WebView、视频播放等需要独立运行的场景。
- 多用于跨应用跳转或插件化架构。
如何设置启动模式?
Manifest 设置方式
在 AndroidManifest.xml 中为目标 Activity 添加:
1 | |
Intent 设置方式(部分模式)
1 | |
- singleInstance需要Manifest配合使用。它需要系统为该 Activity 创建一个独立任务栈,这个行为只能通过 Manifest 配置实现。
- 即使你在 Intent 中设置了 FLAG_ACTIVITY_NEW_TASK,如果没有 Manifest 配合,系统也不会真正创建独立栈。
什么是Service
Service意为“服务”,相对于Activity是一个有可见界面的、为实现交互而提供的Android应用组件,Service则是一个可以在后台长期运行但又不使用用户界面的Android应用组件。它可以和其他的组件形成一定联系,通过传达信息来联系多个组件共同执行某个操作。举个最简单的例子,你此时正在用手机听歌曲,播放歌曲的进程就是一个后台服务。
Service的生命周期

startService()
- 一个组件可以通过调用 startService()方法来启动特定的服务,此时Service生命周期中的onStartCommand()方法被调用。当调用startService()方法时,其他组件需要在方法中传递一个intent参数,服务会在onStartCommand()中接收到intent并获取一些数据。
- 比如ActivityA需要上报一些用户数据到服务器上面,就可以通过startService()来启动ServiceA,并将需要上报的数据传递给ServiceA,在ServiceA里面调用一些网络接口,把数据直接上报到服务器上面。
- 当一个服务以这种方式启动时,它的生命周期不再受启动它的组件的影响,它可以在后台无限期地运行,如上一段的例子里,就算ActvityA挂了,ServiceA仍在后台运行,直到ServiceA自己调用stopSelf()或者其他的组件手动调用stopService(ServiceA)时ServiceA才会停止。
sequenceDiagram participant Client participant Service Client->>Service: startService() Service->>Service: onCreate() Service->>Service: onStartCommand() Client-->>Service: 不需要保持连接 - onCreate():服务首次创建时调用。
- onStartCommand():每次调用 startService() 都会触发,适合处理任务。
- stopService() 或 stopSelf():服务终止时调用。
- onDestroy():服务销毁时调用。
⚠️ 即使启动者退出,服务仍会继续运行,直到显式停止。
bindService()
- 从名字上可以看出这个“绑定”一个服务,要创建一个支持绑定的Service,我们必须要重写它的onBind()方法,这个方法返回一个IBinder对象,它是客户端用来和服务器进行交互的接口。如ActivityA绑定ServiceA,此时ActivityA称为客户端,ServiceA称为服务端,ActivityA有从ServiceA返回的一个IBinder对象,通过该对象,ActivityA就可以调用ServiceA里面的方法。
- 这是一种比startService更复杂的启动方式,同时使用这种方式启动的service也能完成更多的事情,比如其他组件可向其发送请求,接受来自它的响应,甚至通过它来进行IPC(进程间通信)等等。
sequenceDiagram participant Client participant Service Client->>Service: bindService() Service->>Service: onCreate() Service->>Service: onBind() Client->>Service: 与服务通信 Client->>Service: unbindService() Service->>Service: onUnbind() Service->>Service: onDestroy()(若无其他绑定) - onCreate():服务首次创建时调用。
- onBind():返回一个 IBinder 对象供客户端通信。
- unbindService():解除绑定。
- onUnbind():最后一个客户端解绑时调用。
- onDestroy():无绑定且未通过 startService 启动时销毁。
⚠️ 服务生命周期受绑定者影响,所有客户端解绑后才会销毁。
startService
通过startService启动后,service会一直无限期运行下去,只有外部调用了stopService()或stopSelf()方法时,该Service才会停止运行并销毁。
要创建一个这样的Service,你需要让该类继承Service类,然后重写以下方法:
onCreate()
- 如果service没被创建过,调用startService()后会执行onCreate()回调;
- 如果service已处于运行中,调用startService()不会执行onCreate()方法。
也就是说,onCreate()只会在第一次创建service时候调用,多次执行startService()不会重复调用onCreate(),此方法适合完成一些初始化工作。
onStartCommand()
- 如果多次执行了Context的startService()方法,那么Service的onStartCommand()方法也会相应的多次调用。onStartCommand()方法很重要,我们在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等。
onBind()
- Service中的onBind()方法是抽象方法,Service类本身就是抽象类,所以onBind()方法是必须重写的,即使我们用不到。
onDestory()
- 在销毁的时候会执行Service该方法。
这几个方法都是回调方法,且在主线程中执行,由android操作系统在合适的时机调用。
代码实例
- TestOneService.java
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
27public class TestOneService extends Service{
@Override
public void onCreate() {
Log.i("Kathy","onCreate - Thread ID = " + Thread.currentThread().getId());
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Kathy", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().getId());
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i("Kathy", "onBind - Thread ID = " + Thread.currentThread().getId());
return null;
}
@Override
public void onDestroy() {
Log.i("Kathy", "onDestroy - Thread ID = " + Thread.currentThread().getId());
super.onDestroy();
}
} - MainActivity.java
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
28public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("Kathy", "Thread ID = " + Thread.currentThread().getId());
Log.i("Kathy", "before StartService");
//连续启动Service
Intent intentOne = new Intent(this, TestOneService.class);
startService(intentOne);
Intent intentTwo = new Intent(this, TestOneService.class);
startService(intentTwo);
Intent intentThree = new Intent(this, TestOneService.class);
startService(intentThree);
//停止Service
Intent intentFour = new Intent(this, TestOneService.class);
stopService(intentFour);
//再次启动Service
Intent intentFive = new Intent(this, TestOneService.class);
startService(intentFive);
Log.i("Kathy", "after StartService");
}
} - 输出:
1
2
3
4
5
6
7
8
9
1002-06 15:19:45.090 8938-8938/? I/Kathy: Thread ID = 1
02-06 15:19:45.090 8938-8938/? I/Kathy: before StartService
02-06 15:19:45.233 8938-8938/? I/Kathy: onCreate - Thread ID = 1
02-06 15:19:45.234 8938-8938/? I/Kathy: onStartCommand - startId = 1, Thread ID = 1
02-06 15:19:45.234 8938-8938/? I/Kathy: onStartCommand - startId = 2, Thread ID = 1
02-06 15:19:45.235 8938-8938/? I/Kathy: onStartCommand - startId = 3, Thread ID = 1
02-06 15:19:45.236 8938-8938/? I/Kathy: onDestroy - Thread ID = 1
02-06 15:19:45.237 8938-8938/? I/Kathy: onCreate - Thread ID = 1
02-06 15:19:45.237 8938-8938/? I/Kathy: onStartCommand - startId = 1, Thread ID = 1
02-06 15:19:45.238 8938-8938/? I/Kathy: after StartService作者:Big不吃鱼
链接:https://www.jianshu.com/p/4c798c91a613
来源:简书
bindService
- bindService启动的服务和调用者之间是典型的client-server模式。调用者是client,service则是server端。service只有一个,但绑定到service上面的client可以有一个或很多个。这里所提到的client指的是组件,比如某个Activity。
- client可以通过IBinder接口获取Service实例,从而实现在client端直接调用Service中的方法以实现灵活交互,这在通过startService方法启动中是无法实现的。
- bindService启动服务的生命周期与其绑定的client息息相关。当client销毁时,client会自动与Service解除绑定。当然,client也可以明确调用Context的unbindService()方法与Service解除绑定。当没有任何client与Service绑定时,Service会自行销毁。
代码实现
- 在Service的onBind()方法中返回IBinder类型的实例。
- onBInd()方法返回的IBinder的实例需要能够返回Service实例本身。通常,最简单的方法就是在service中创建binder的内部类,加入类似getService()的方法返回Service,这样绑定的client就可以通过getService()方法获得Service实例了。
- TestTwoService.java
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
50public class TestTwoService extends Service{
//client 可以通过Binder获取Service实例
public class MyBinder extends Binder {
public TestTwoService getService() {
return TestTwoService.this;
}
}
//通过binder实现调用者client与Service之间的通信
private MyBinder binder = new MyBinder();
private final Random generator = new Random();
@Override
public void onCreate() {
Log.i("Kathy","TestTwoService - onCreate - Thread = " + Thread.currentThread().getName());
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Kathy", "TestTwoService - onStartCommand - startId = " + startId + ", Thread = " + Thread.currentThread().getName());
return START_NOT_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i("Kathy", "TestTwoService - onBind - Thread = " + Thread.currentThread().getName());
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.i("Kathy", "TestTwoService - onUnbind - from = " + intent.getStringExtra("from"));
return false;
}
@Override
public void onDestroy() {
Log.i("Kathy", "TestTwoService - onDestroy - Thread = " + Thread.currentThread().getName());
super.onDestroy();
}
//getRandomNumber是Service暴露出去供client调用的公共方法
public int getRandomNumber() {
return generator.nextInt();
}
} - client端要做的事情:
- 创建ServiceConnection类型实例,并重写onServiceConnected()方法和onServiceDisconnected()方法。
- 当执行到onServiceConnected回调时,可通过IBinder实例得到Service实例对象,这样可实现client与Service的连接。
- onServiceDisconnected回调被执行时,表示client与Service断开连接,在此可以写一些断开连接后需要做的处理。
Activity、Service、intent之间的联系是什么?
概念
- Activity:用户界面组件,代表一个屏幕,用于与用户交互
- Service:后台组件,用于执行长时间运行的任务(如音乐播放、网络请求)
- Intent:消息传递机制,用于在组件之间传递数据和请求
它们之间的联系
- Activity 通过 Intent 启动 ServiceActivity 是发起者,Intent 是载体,Service 是被启动者。
1
2Intent intent = new Intent(this, MyService.class);
startService(intent); // 或 bindService(intent, conn, flags);
Intent 中可以携带数据(如 URL、参数等)供 Service 使用。
- Activity 通过 Intent 启动另一个 Activity同样通过 Intent 实现组件间跳转和数据传递。
1
2Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent); - Service 也可以通过 Intent 启动 Activity(如通知点击)注意:Service 启动 Activity 时必须加 FLAG_ACTIVITY_NEW_TASK。因为 Service 默认没有 Activity 的任务栈。
1
2
3Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
在Activtiy和在Service中创建一个Thread的区别?
首先需要了解Service的几个特点。
(1) 默认情况下,Service其实是运行在主线程中的,如果需要执行复杂耗时的操作,必须在Service中再创建一个 Thread来执行任务。
(2) Service的优先级高于后台挂起的Activity,当然,也高于Activity所创建的Thread,因此,系统可能在内存不足的 时候优先杀死后台的Activity或者Thread,而不会轻易杀死Service组件,即使被迫杀死Service,也会在资源可用时重启 被杀死的Service 其实,Service和Thread根本就不是一个级别的东西,Service是系统的四大组件之一,Thread只是一个用来执行后台任 务的工具类,它可以在Activity中被创建,也可以在Service中被创建。因此,我们其实不应该讨论该使用Service还是 Thread,而是应该讨论在什么地方创建Thread。
典型的应用中,它可以在以下三个位置被创建,不同的位置,其生命周期不一样,所以,我们应该根据该Thread的目标 生命周期来决定是在Service中创建Thread还是在Activity中创建它。
(1) 在Activity中被创建 这种情况下,一般在onCreate时创建,在onDestroy()中销毁,否则,Activity销毁后,Thread是会依然在后台运行着。这种情况下,Thread的生命周期即为整个Activity的生命周期。所以,在Activity中创建的Thread只适合完成一些依赖 Activity本身有关的任务,比如定时更新一下Activity的控件状态等。核心特点:该Thread的就是为这个Activity服务的,完成这个特定的Activity交代的任务,主动通知该Activity一些消息和 事件,Activity销毁后,该Thread也没有存活的意义了。
(2)在Application中被创建 这种情况下,一般自定义Application类,重载onCreate方法,并在其中创建Thread,当然,也会在onTerminate()方法 中销毁Thread,否则,如果Thread没有退出的话,即使整个Application退出了,线程依然会在后台运行着。这种情况下,Thread的生命周期即为整个Application的生命周期。所以,在Application中创建的Thread,可以执行一 些整个应用级别的任务,比如定时检查一下网络连接状态等等。核心特点:该Thread的终极目标是为这个APP的各个Activity服务的,包括完成某个Activity交代的任务,主动通知某个 Activity一些消息和事件等,APP退出之后该Thread也没有存活的意义了。以上这两种情况下,Thread的生命周期都不应该超出整个应用程序的生命周期,也就是,整个APP退出之后,Thread都 应该完全退出,这样才不会出现内存泄漏或者僵尸线程。那么,如果你希望整个APP都退出之后依然能运行该Thread, 那么就应该把Thread放到Service中去创建和启动了。
(3)在Service中被创建 这是保证最长生命周期的Thread的唯一方式,只要整个Service不退出,Thread就可以一直在后台执行,一般在Service 的onCreate()中创建,在onDestroy()中销毁。所以,在Service中创建的Thread,适合长期执行一些独立于APP的后台任务,比较常见的就是:在Service中保持与服 务器端的长连接。核心特点:该Thread可以为APP提供一些“服务”或者“状态查询”,但该Thread并不需要主动通知APP任何事件,甚至不 需要知道APP是谁。
Android进程优先级
前台进程(Foreground process)
前台进程是用户当前做的事所必须的进程,如果满足下面各种情况中的一种,一个进程被认为是在前台:
- 进程持有一个正在与用户交互的Activity。
- 进程持有一个Service,这个Service处于这几种状态:
- ①Service与用户正在交互的Activity绑定。
- ②Service是在前台运行的,即它调用了 startForeground()。
- ③Service正在执行它的生命周期回调函数(onCreate(), onStart(), or onDestroy())。
- 进程持有一个BroadcastReceiver,这个BroadcastReceiver正在执行它的 onReceive() 方法。
杀死前台进程需要用户交互,因为前台进程的优先级是最高的。
可见进程(Visible process)
如果一个进程不含有任何前台的组件,但仍可被用户在屏幕上所见。当满足如下任一条件时,进程被认为是可见的:
进程持有一个Activity,这个Activity不在前台,但是仍然被用户可见(处于onPause()调用后又没有调用onStop()的状态,比如,前台的activity打开了一个对话框,这样activity就会在其后可见)。
进程持有一个Service,这个Service和一个可见的(或者前台的)Activity绑定。
可见的进程也被认为是很重要的,一般不会被销毁,除非是为了保证所有前台进程的运行而不得不杀死可见进程的时候。
服务进程 (Service process)
如果一个进程中运行着一个service,这个service是通过 startService() 开启的,并且不属于上面两种较高优先级的情况,这个进程就是一个服务进程。
尽管服务进程没有和用户可以看到的东西绑定,但是它们一般在做的事情是用户关心的,比如后台播放音乐,后台下载数据等。所以系统会尽量维持它们的运行,除非系统内存不足以维持前台进程和可见进程的运行需要。
后台进程 (Background process)
如果进程不属于上面三种情况,但是进程持有一个用户不可见的activity(activity的onStop()被调用,但是onDestroy()没有调用的状态),就认为进程是一个后台进程。
后台进程不直接影响用户体验,系统会为了前台进程、可见进程、服务进程而任意杀死后台进程。
通常会有很多个后台进程存在,它们会被保存在一个LRU (least recently used)列表中,这样就可以确保用户最近使用的activity最后被销毁,即最先销毁时间最远的activity。
空进程
如果一个进程不包含任何活跃的应用组件,则认为是空进程。
例如:一个进程当中已经没有数据在运行了,但是内存当中还为这个应用驻留了一个进程空间。
保存这种进程的唯一理由是为了缓存的需要,为了加快下次要启动这个进程中的组件时的启动时间。系统为了平衡进程缓存和底层内核缓存的资源,经常会杀死空进程。
如何保证Service不被杀死?
使用前台服务(Foreground Service)
在onStartCommand里面调用 startForeground()方法把Service提升为前台进程级别,然后再onDestroy里面要记得调用stopForeground ()方法。
onStartCommand方法,返回START_STICKY
1 | |
| 返回值 | 含义 |
|---|---|
| START_STICKY | 被系统杀死后尝试重启服务,但不保留原 Intent |
| START_NOT_STICKY | 被杀后不重启服务 |
| START_REDELIVER_INTENT | 被杀后重启服务并重新传入原 Intent |
| START_STICKY_COMPATIBILITY | 与 START_STICKY 类似,兼容旧版本 |
使用双进程守护(不推荐但常见)
启动两个互相绑定的 Service(A 和 B),当一个被杀时另一个重启它。
⚠️ Android 8.0+ 后效果有限,且可能被系统判定为“滥用”。
绑定组件(如 Activity 或其他 Service)
使用 bindService() 绑定后,Service 生命周期受绑定者影响。若绑定者存活,系统不易杀死 Service。
Broadcast的定义
Broadcast Receiver是Android四大组件之一,是全局大喇叭,就像以前学校里面的大喇叭一样,有什么事情,就通过广播通知到全校师生。Android的广播机制是一种广泛运用的在应用程序之间传输信息的机制。主要有两大角色:广播接收者和广播发送者。因此,我们可以注册一个广播接收者,用来接收其他进程或者系统发出的广播;同样的,我们也可以通过广播发送者,向其他进程发送我们的广播,告诉其他进程需要做某个动作。
静态注册和动态注册是什么?
静态注册
在AndroidManifest.xml清单文件里直接声明的方式叫做静态注册。
1 | |
然后重写onReceive()函数。
1 | |
如上面代码,此时注册了mXRReceiver这个广播,当系统启动后,就会发送BOOT_COMPLETED的广播,符合mXRReceiver所设置的过滤条件,就会自动调用onReceive()函数,执行对应内容。
注意:onReceive()函数不能执行耗时流程。
动态注册
在运行时调用 registerReceiver() 注册,必须在合适的时机调用 unregisterReceiver() 取消,否则会导致内存泄漏。
1 | |
| 特点 | 说明 |
|---|---|
| 注册时机 | 在代码运行时调用 registerReceiver(),通常在 onCreate() 或 onStart() 中 |
| 取消时机 | 在 onDestroy() 或 onStop() 中调用 unregisterReceiver(),避免内存泄漏 |
| 生命周期 | 与注册它的组件(Activity/Service)生命周期绑定 |
| 灵活性 | 可以根据业务需要,随时注册/注销 |
| 常见用途 | 监听网络变化、电量变化、蓝牙状态、耳机插拔等只在前台需要的广播 |
请介绍Android里广播的分类?
普通广播
- 异步发送,所有接收者几乎同时收到,无法截断或修改广播内容
系统广播
- Android系统中内置了多个系统广播:只要涉及到手机的基本操作(如开机,网络状态变化,拍照等),都会发出相应的广播,每个广播都有特定的Intent-Filter(包括具体的action)
有序广播
| 特性 | 描述 |
|---|---|
| 发送方式 | 使用 sendOrderedBroadcast() 方法 |
| 接收顺序 | 按 IntentFilter 中设置的 priority 值从高到低,同级别随机 |
| 广播传播控制 | 接收者可以调用 abortBroadcast() 终止传播 |
| 数据修改能力 | 可以通过 setResult() 修改广播结果 |
| 同步执行 | 接收者按顺序同步执行,效率低于普通广播 |
| 适用场景 | 需要优先级控制或广播结果处理的场景 |
粘性广播
粘性广播是一种 特殊的广播机制,当广播被发送后,系统会将该广播的 Intent 保存在内存中。后续注册的接收者(即使在广播发送之后才注册)也能立即收到这条广播的最后一次内容。从 Android 5.0(API 21)开始已废弃,官方强烈不推荐使用(安全、性能)
App应用内广播
- 在同一个应用程序内部使用广播机制(Broadcast)来传递消息或事件,而不会被其他应用接收。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 发送广播
Intent intent = new Intent();
intent.setComponent(new ComponentName(context, MyReceiver.class));
intent.setAction("com.example.APP_EVENT");
intent.putExtra("msg", "hello");
context.sendBroadcast(intent);
// 接收广播
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("msg");
Log.d("MyReceiver", "收到应用内广播: " + msg);
}
} - 实现:常见方式是使用显式 Intent
- 优点:安全、解耦、简单。
- 缺点:仍在主线程执行,性能有限;
程序A能否接收到程序B的广播?
程序A能接收到程序B的广播,前提是程序B发送的是全局广播(非 LocalBroadcast),并且程序A正确注册了匹配的 Action,同时满足 Android 版本要求(如 8.0+ 动态注册限制)、android:exported=true 属性配置正确,以及必要的权限声明。
程序A不能接收到程序B的广播,常见原因包括:程序B使用了 LocalBroadcast(仅限应用内)、Action 不匹配、程序A未注册或注册方式受限(如隐式广播静态注册在新版本被限制)、权限不足,或者接收器未导出(exported=false)。
什么是内容提供者?(ContentProvider)
之前有说过可以用Intent在组件中传递数据,那么其数据的大小是否有限制呢?很明显是有限制的,Intent传递数据大小的限制大概在1M左右,超过这个限制就会静默崩溃。因此我们就可以通过ContentProvider进行进程间的数据传递,也就是ContentProvider是一种进程间的数据传递的方式。 一般来说,Android数据存储的方式有:文件,数据库,网络,SharePreferences,ContentProvider。
上图是网上找到的一个图片,然而从图上可以明显的知道,ContentProvider更准确来说只是一个中间者的身份,真正存储数据的是数据库和文件等形式,这一点要分清楚!
ContentProvider用法
创建类:继承 ContentProvider,重写以下方法:
- onCreate():初始化数据库或数据源
- query():查询数据
- insert():插入数据
- update():更新数据
- delete():删除数据
- getType():返回 MIME 类型
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
37public class MyProvider extends ContentProvider {
@Override
public boolean onCreate() {
// 初始化数据库
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 查询逻辑
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// 插入逻辑
return newUri;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// 更新逻辑
return count;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 删除逻辑
return count;
}
@Override
public String getType(Uri uri) {
return "vnd.android.cursor.dir/vnd.com.example.table";
}
}
在 AndroidManifest.xml 注册:
1 | |
访问
外部应用访问:通过 content://com.example.provider/… 的 URI 使用 ContentResolver 调用。
ContentProvider原理
ContentProvider 之所以能实现数据共享,核心在于 Binder 跨进程通信机制。应用 A(数据提供方)通过 ContentProvider 暴露统一的 CRUD 接口,应用 B(数据使用方)通过 ContentResolver + URI 访问,系统的 AMS(ActivityManagerService) 负责 Provider 的注册和调度,从而实现跨进程的数据共享。
核心角色
- ContentProvider:数据提供方,封装底层数据(SQLite、文件、网络等),对外暴露统一接口。
- ContentResolver:数据使用方的入口,开发者通过它调用 query/insert/update/delete。
- URI:统一资源标识符,用于定位具体 Provider 和数据表。
- AMS(ActivityManagerService):系统服务,负责管理所有 Provider 的注册、生命周期和进程间引用。
- Binder:Android IPC 基石,保证跨进程调用的安全与高效。
数据访问流程(以 query() 为例)
客户端调用:
1 | |
系统查找 Provider:
- ContentResolver 通过 AMS 请求获取目标 Provider 的 Binder 引用。
- 如果 Provider 所在进程未启动,AMS 会先启动该进程并调用其 onCreate()。
Binder 跨进程调用:
客户端进程通过 Binder 将请求发送到 Provider 进程。
Provider 内部执行数据库/文件操作,返回 Cursor。
结果返回:
- Cursor 通过 Binder 返回给调用方,调用方即可遍历数据。
Cursor 是数据库查询结果的接口对象,它相当于一个“结果集指针”,允许你逐行遍历查询到的数据,并通过列名或列索引获取具体字段的值。
说说 ContentProvider、ContentResolver、ContentObserver 之间的关系?
三者的关系可以概括为:ContentProvider 提供数据 → ContentResolver 访问数据 → ContentObserver 监听数据变化。它们共同构成了 Android 中跨进程数据共享与监听的完整机制。
ContentProvider
作用:对外共享数据,封装底层存储(SQLite、文件、网络等)。
特点:
- 通过 URI 暴露数据接口。
- 提供标准的 CRUD 方法(query、insert、update、delete)。
- 由系统 AMS 管理生命周期,支持跨进程访问。
ContentResolver
- 作用:应用访问 ContentProvider 的入口。
- 特点:
- 开发者不直接操作 Provider,而是通过 ContentResolver 调用。
- 内部会根据 URI 找到对应的 Provider,并通过 Binder IPC 调用其方法。
- 提供与 Provider 一致的 API:query()、insert()、update()、delete()。
ContentObserver
作用:监听指定 URI 对应数据的变化。
特点:
- 通过 ContentResolver.registerContentObserver() 注册。
- 当 Provider 数据发生变化时,系统会通知 Observer。
- 常用于 UI 自动刷新,比如联系人列表、短信收件箱。
为什么要使用通过ContentResolver类从而与ContentProvider类进行交互,而不直接访问ContentProvider类?
因为 ContentProvider 运行在不同进程,直接访问它并不现实。Android 设计了 ContentResolver 作为中介,统一对外提供数据访问接口,屏蔽了跨进程通信(Binder IPC)的复杂性,同时增强了安全性和解耦性。
ContentProvider的底层是采用Android中的Binder机制,既然已经有了binder实现了进程间通信了为什么还会需要contentProvider?
虽然 Binder 已经能实现进程间通信,但它只是一个 底层 IPC 机制,过于底层、复杂且不适合直接用于应用层的数据共享。ContentProvider 是对 Binder 的进一步封装,提供了统一的 数据访问接口(CRUD + URI)、权限管理 和 生命周期管理,让开发者能更安全、方便地在不同应用之间共享结构化数据。
抽象层级不同
- Binder:
- Android 的底层 IPC 框架,负责进程间对象代理和方法调用。
- 使用时需要定义 AIDL 接口、手动序列化/反序列化数据,开发成本高。
- 更适合 系统服务(如 AMS、WMS)这种复杂的进程间调用。
- ContentProvider:
- 封装了 Binder,专门面向 数据共享 场景。
- 提供统一的 CRUD 接口(query/insert/update/delete)。
- 通过 URI 标识数据,调用方式简单直观。
- 客户端只需用 ContentResolver,无需关心底层 IPC。
👉 类比:Binder 像“电线”,ContentProvider 像“插座 + 接口标准”,让大家能方便、安全地用电。
数据访问的统一接口
- 如果直接用 Binder,每个开发者都要自定义接口,调用方式不统一。
- ContentProvider 规定了标准的 URI + CRUD 访问模式,所有应用都能用相同方式访问数据。
- 系统内置的联系人、短信、媒体库等 Provider 都遵循这一模式。
安全性与权限管理
- Binder 本身不提供数据级别的权限控制。
- ContentProvider 可以在 Manifest 中配置 readPermission、writePermission、signature 等权限,保证数据安全。
- 还可以通过 checkCallingPermission() 做二次校验。
生命周期与系统管理
- Binder 服务需要开发者自己管理进程和生命周期。
- ContentProvider 的创建、销毁由 AMS 统一调度:
- 第一次访问时自动启动 Provider 进程并调用 onCreate()。
- 系统负责回收,开发者无需手动管理。