安卓四大组件

安卓四大组件

安卓四大组件,你真的掌握了?

Activity

活动:提供一个界面让用户点击和各种滑动操作。

生命周期

  • 启动活动:onCreate() -> onStart() -> onResume(),活动进入运行状态。
  • 退居后台:onPause() -> onStop(),当前活动转到新的活动界面或按Home键返回主屏,进入停滞状态。
  • 返回前台:onRestart() -> onStart() -> onResume(),再次回到运行状态。
  • 活动退居后台且系统内存不足,系统会杀死之,若再次回到此活动,则onCreate() -> onStart() -> onResume().
  • 锁定与解锁屏幕,只会调用onPause(),而不會調用onStop(),解锁後則調用onResume().
Activity的生命周期

两个活动间跳转

活动A启动活动B,回调如下:
活动A的onPause() -> 活动B的onCreate() -> onStart() -> onResume() -> 活动A的onStop()
若活动B是透明主题或者是DialogActivity,则不会回调活动A的unStop().

将Activity设置成窗口模式,在AndroidMainfest.xml中配置如下:

android:theme="@android:style/Theme.Dialog"

onSaveInstanceState

活动的onSaveInstanceState()和onRestoreInstanceState()并不是生命周期方法,不同于onCreate()和onPause()等生命週期方法,其并不一定会被触发。

onSaveInstanceState()的调用条件:当应用遇到意外情况而由系统销毁一个活动时,其此方法被调用;而用户主动销毁活动时,不会调用此方法;此方法只适合保存临时性状态,而onPause()适合数据持久化保存。

应用场景:

  • 屏幕方向切换
  • 用户按下Home键
  • 长按Home键,运行其他程序时
  • 启动一个新的活动时
  • 锁屏时

启动模式

  1. standard: 系统在启动它的task中创建活动的新实例。
    默认模式,无需写配置,可以有多个相同的实例,也允许多个相同的活动叠加。
  2. singleTop: 可以有多个相同的实例,但不允许多个相同的活动叠加。
    已有此活动在task栈顶,则不创建新实例而调用其onNewIntent().
  3. singleTask: 在同一应用中启动此活动,若其不存在则在当前task中创建一个新实例,否则把task中在其之上的其他活动销毁并调用此活动的onNewIntent().
    在其他应用中启动此活动,会新建一个task以启动此活动。
    singleTask活动允许其他活动与之在同一个task共存。
  4. singleInstance: 实例独立运行在task中,不允许其task有其他活动.

task

一个应用中有多个活动,这些活动以栈的形式管理。
task是指按各自打开顺序排列在栈中的一系列活动。

启动过程

应用活动启动过程分析

备注:
Android-Next公共组件库

启动活动流程

图片来源链接

  1. 点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求。
  2. system_server进程接收到请求后,向zygote进程发送创建进程的请求。
  3. Zygote进程fork出App进程,并执行ActivityThread的main方法,初始化MainLooper,主线程Handler,同时初始化ApplicationThread用于和AMS通信交互。
  4. App进程,通过Binder IPC向sytem_server进程发起attachApplication请求。
  5. system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送handleBindApplication请求(初始化Application并调用onCreate方法)和scheduleLaunchActivity请求(创建启动Activity).
  6. App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送BIND_APPLICATION和LAUNCH_ACTIVITY消息。
  7. 主线程在收到Message后,通过反射机制创建目标Activity,并回调Activity.onCreate()等方法。
  8. 至此,APP正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染后显示APP主界面。

名词解释

  • Instrumentation: 是Android系统里面的一套控制方法或者钩子。
  • ActivityManagerService(AMS): AMS是系统的引导服务,应用进程的启动、切换和调度,四大组件的启动和管理都需要AMS的支持。
  • ActivityStarter: 是加载Activity的控制类。
  • ActivityStackSupervisor: AMS通过操作ActivityStackSupervisor来管理Activity.
  • ActivityStack: 用来管理系统所有Activity的各种状态。它由ActivityStackSupervisor进行管理。
  • ApplicationThread: 是ActivityThread的私有内部类,也是一个Binder对象。
  • Zygote: 主要功能是执行Android应用程序。其运行时会初始化并启动Dalvik虚拟机。

Context和Application

Context继承关系
  • Activity和Application都是Context的子类。
  • Context描述了应用程序环境的信息,即上下文。
  • Context是抽象类,Android提供了其具体实现类(ContextImpl).
  • 通过Context,可以获取应用程序的资源和类,可以进行一些应用级别操作,例如:启动活动、发送广播、接受Intent和信息等。

Service

服务:可以在后台长时间运行而没有用户界面的应用组件。

  • Service务分为两种工作状态,启动状态主要用于执行后台计算,绑定状态主要用于和其他组件的交互。
  • 同一应用默认情况下,Service和Activity是在同一线程(主线程UI Thread)中的。

启动过程

Android - Service 启动过程

备注: Android 技术笔记

Android Service启动过程

startService

startService启动过程

图片来源链接

注:一般来说,同一进程内的线程间通信采用的是Handler消息队列机制,不同进程间的通信采用的是binder机制,另外与Zygote进程通信采用的Socket.

  1. Process A进程采用Binder IPC向system_server进程发起startService请求。
  2. system_server进程接收到请求后,向zygote进程发送创建进程的请求。
  3. zygote进程fork出新的子进程Remote Service进程。
  4. Remote Service进程,通过Binder IPC向sytem_server进程发起attachApplication请求。
  5. system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向remote Service进程发送scheduleCreateService请求。
  6. Remote Service进程的binder线程在收到请求后,通过handler向主线程发送CREATE_SERVICE消息。
  7. 主线程在收到Message后,通过反射机制创建目标Service,并回调Service.onCreate()方法。
  8. 至此,服务正式启动完成。当创建的是本地服务或者服务所属进程已创建时,则无需经过上述2、3步骤。

bindService

bindService启动过程

图片来源链接

说明:

  1. 图中蓝色代表的是Client进程(发起端),红色代表的是system_server进程,黄色代表的是target进程(service所在进程)。
  2. Client进程:通过getServiceDispatcher获取Client进程的匿名Binder服务端(即LoadedApk.ServiceDispatcher.InnerConnection),再通过bindService调用到system_server进程。
  3. system_server进程:依次通过scheduleCreateService和scheduleBindService方法,远程调用到target进程。
  4. target进程:依次执行onCreate()和onBind()方法,将onBind()方法的返回值IBinder(作为target进程的binder服务端)通过publishService传递到system_server进程。
  5. system_server进程:利用IServiceConnection代理对象向Client进程发起connected()调用,并把target进程的onBind返回Binder对象的代理端传递到Client进程。
  6. Client进程:回调到onServiceConnection()方法,该方法的第二个参数便是target进程的binder代理端。到此便成功地拿到了target进程的代理,可以畅通无阻地进行交互。

startService生命周期

仅仅开启一个后台服务,可以使用startService方法。
如果要获取Service中提供的代理对象,那么必须通过bindService方法。

手动调用startService()启动服务 -> [开始] onCreate() -> onStartCommand() -> 手动调用stopService()停止服务 -> onDestory() -> [结束].

如果服务已开启,不会重复执行onCreate(),一旦服务开启就与调用者无关,且开启者不能调用服务里面的方法。

bindService生命周期

手动调用bindService()绑定服务 -> [开始] -> onCreate() -> onBind() -> 手动调用unBindService()解锁服务 -> onUnbind() -> onDestory() -> [结束].

绑定服务不会调用onStart()或者onStartCommand(),其调用者和服务绑定(调用者挂了服务也挂),调用者可以调用服务方法。

startService和bindService同时使用

手动调用startService() -> [开始] -> onCreate() -> onStartCommand() -> 手动调用bindService() -> onBind() -> 手动调用unBindService() -> onUnbind() -> 手动调用stopService() -> onDestory() -> [结束].

onStartCommand的返回值

  • START_STICKY: 粘性的
    若此服务在启动时被终止,则保持在启动状态而不会保留Intent,稍后将重建服务并调用onStartCommand(),如果没有任何启动命令被传递到此服务,那么将以空的Intent调用之。
  • START_NOT_STICKY: 非粘性的
    若此服务在启动时被终止,并且没有启动新的Intent传递之,则此服务脱落启动状态,并且不会自动重新创建。
  • START_REDELIVER_INTENT: 重传Intent
    若此服务在启动时被终止,则将计划重新启动并将最后的Intent再次通过调用onStartCommand()传递。
  • START_STICKY_COMPATIBILITY:
    START_STICKY的兼容版本,不保证服务被终止后一定能重启。

Activity与Service通信

Activity向Service通信

  • 法一:添加一个继承Binder的内部类和其逻辑方法,重写Service的onBind方法以返回此内部类,再重写onServiceConnected和onServiceDisconnected以绑定服务。
  • 法二:通过接口Iservice调用Service方法。

前台服务

要启用前台服务,需在AndroidMainfest.xml中配置如下:

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

前台服务的系统优先级更高而不易被回收,且前台服务有一个正在运行的图标在系统状态栏显示。

[如何正确的使用Service?](https://github.com/jeanboydev/Android-ReadTheFuckingSourceCode/blob/master/article/android/basic/03_service.md 包含前台服务的使用)

IntentService

IntentService是Service的子类,是一个异步的、会自动停止的服务。

  • 特点:会创建独立的worker线程处理所有的Intent请求,处理完成后,IntentService会自动停止。
  • 绑定操作:其为Service的onBind()提供默认实现(返回null),为onStartCommand()提供默认实现(将请求Intent加入队列)。
  • 线程方面:其不会阻塞UI线程,而普通Service会导致ANR异常;其若未执行完上一次任务,将等待至完成后再执行新的任务。

Android多线程:IntentService使用教程

Broadcast Receiver

分享一些Broadcast使用技巧

广播用于进程/线程通信,无需关心接收方对数据如何。

广播分类

  • 按发送方式:
    • 标准广播:完全异步执行。
    • 有序广播:同步执行,有先后顺序,同一时刻仅有一个接收器可接收之,优先级高者可以先收到,并且收到此广播的接收器可以截断之。
  • 按注册方式:
    • 静态广播:不管程序是否处于活动状态都进行监听,每次触发都建立新的Receiver对象。
    • 动态广播:代码中注册,必须取消注册,通常在onDestory()中调用unregisterReceiver()来实现。无论触发几次都使用同一个Receiver对象。
  • 按定义方式:
    • 系统广播:系统内部特定事件发生时自动发出。
    • 自定义广播:应用程序自定义广播。
  • 按范围方式:
    • 全局广播:可被任意应用程序接收或接收来自任意应用程序的广播。
    • 本地广播:只能在应用程序内部进行传递或接收。

广播的使用

创建广播接收器

直接继承BroadcastReceiver创建子类并实现父类的onReceive()方法即可,如下示例代码:

1
2
3
4
5
6
7
8
public class MyReceiver extends BroadcastReceiver {
// 自定义 action
private static final String ACTION = "com.jeanboy.broadcast.MyReceiverFilter";
@Override
public void onReceive(Context context, Intent intent) {
//TODO: 接收到广播进行处理
}
}

静态广播

需在AndroidMainfest文件中定义:

1
2
3
4
5
6
7
8
9
10
<receiver android:name=".ui.broadcast.MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<!-- 例如:接收系统开机广播 -->
<action android:name="android.intent.action.BOOT_COMPLETED" />
<!-- 例如:接收自定义的广播 -->
<action android:name="com.jeanboy.broadcast.MyReceiverFilter" />
</intent-filter>
</receiver>

说明:

  • android:enabled 启用广播
  • android:exported 能接收外部APK发送的广播

动态广播

无需AndroidMainfest文件中定义,在需在代码中注册:

1
2
3
4
5
6
7
8
9
10
11
12
// 创建广播
MyReceiver myReceiver = new MyReceiver();
// 创建 IntentFilter
IntentFilter intentFilter = new IntentFilter();
// 例如:添加系统广播 action 接受网络变化
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
// 例如:添加自定义的 action
intentFilter.addAction(MyReceiver.ACTION);
// 注册广播
registerReceiver(myReceiver, intentFilter);
// 注销广播
unregisterReceiver(myReceiver);

发送广播

自定义广播的发送方式如下:

1
2
3
4
5
6
// 创建 Intent
Intent intent = new Intent();
// 例如:添加自定义的 action
intent.setAction(MyReceiver.ACTION);
// 发送广播
sendBroadcast(intent);

:Android 8.0废除大部分静态广播,对于代码需要修改某些部分(发送广播部分需要设置ComponetName):

1
2
3
4
5
6
Intent intent = new Intent(MyReceiver.ACTION);
// ComponetName("自定义广播的包名", "自定义广播的路径")
ComponentName component = new ComponentName("com.jeanboy.app.broadcast",
"com.jeanboy.app.broadcast.MyReceiver");
intent.setComponent(component);
sendBroadcast(intent);

带权限的广播

为避免其他应用监听广播造成数据泄露、冒充发送广播造成混乱,可在AndroidMainfest文件中添加权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<manifest ...>
<!-- 自定义一个自己的权限 -->
<permission android:name="com.jeanboy.permissions.MY_BROADCAST"/>
<!-- 使用自定义的权限 -->
<uses-permission android:name="com.jeanboy.permissions.MY_BROADCAST"/>
<application ...>
<!-- 添加权限 -->
<receiver android:name=".ui.broadcast.MyReceiver"
android:permission="com.jeanboy.broadcast.MY_BROADCAST"
android:enabled="true"
android:exported="true">
<intent-filter>
<!-- 例如:接收自定义的广播 -->
<action android:name="com.jeanboy.broadcast.MyReceiverFilter" />
</intent-filter>
</receiver>
</application>
</manifest>

并在发送时指定权限:

sendBroadcast(intent, "com.jeanboy.permissions.MY_BROADCAST");

本地广播

上述BroadcastReceiver用于应用之间的传递消息。
而LocalBroadcast(本地广播)用于应用内部传递消息,其创建仍然是继承BroadcastReceiver创建子类,并实现父类的onReceive()方法。
在注册、发送、注销广播时使用LocalBroadcastManager来进行相关操作,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建广播
MyReceiver myReceiver = new MyReceiver();
// 创建 IntentFilter
IntentFilter intentFilter = new IntentFilter();
// 例如:添加自定义的 action
intentFilter.addAction(MyReceiver.ACTION);
// 注册本地广播
LocalBroadcastManager.getInstance(this)
.registerReceiver(myReceiver, intentFilter);
// 发送广播
Intent intent = new Intent(MyReceiver.ACTION);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
// 注销本地广播
LocalBroadcastManager.getInstance(this).unregisterReceiver(myReceiver);

具体流程十分复杂,详见链接

Content Provider

ContentProvider介绍

内容提供者的主要作用是各应用间的数据共享,其他Android应用可以使用ContentResolver对象请求执行ContentProvider中的同名方法,如图:

统一资源标识符(URI)

URI代表要操作的数据,用以标识每个ContentProvider,在Android中其格式如下:

URI = <schema>://<authority>/<path>/<id>
  • <schema> URI前缀,说明由ContentProvider控制数据,是固定形式"content".
  • <authority> 唯一标识符以定位ContentProvider,一般是自定义ContentProvider类的完全限定名称。
  • <path> 路径片段。
  • <id> 记录。

MIME数据类型

MIME是指定某个扩展名的文件用一种应用程序来打开,格式:Type/Subtype

ContentProvider.geType(uri) 根据URI返回MIME类型。

标准MIME

由媒体类型Type和子类型Subtype组成,都很简单,如:text/html

自定义MIME

每个内容类型的Android MIME类型有两种形式:

  • 多条记录:vnd.android.cursor.dir/Subtype
  • 单条记录:vnd.android.cursor.item/Subtype

其中,Type是固定的,Subtype按照格式填写。

在使用Intent时,会用到MIME,根据MIME类型打开符合条件的Activity,例如:

1
2
3
4
5
6
<activity android:name=".TestActivity">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.dir/jeanboy.first" />
</intent-filter>
</activity>

创建ContentProvider

创建一个继承自ContentProvider的类,并重载onCreate,query,getType,insert,delete,update方法。
然後,在AndroidManifest.xml中注册:

1
2
3
<provider
android:name=".ui.provider.TestProvider"
android:authorities="com.jeanboy.testprovider" />

使用ContentProvider

获取ContentResolver实例的方法为:

ContentResolver resolver = getContentResolver();

ContentResolver有几个基本数据库操作方法:

1
2
3
4
5
6
public final Cursor query (Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder);
public final Uri insert (Uri url, ContentValues values);
public final int update (Uri uri, ContentValues values, String where,
String[] selectionArgs);
public final int delete (Uri url, String where, String[] selectionArgs);

ContentProvider权限

在AndroidManifest.xml中,如下代码:

1
2
3
4
5
6
7
<provider
android:name=".ui.provider.TestProvider"
android:authorities="com.jeanboy.testprovider"
android:exported="true"
android:readPermission="com.jeanboy.provider.permission.read"
android:writePermission="com.jeanboy.provider.permission.write"
android:permission="com.jeanboy.provider.permission"/>

参数解释:

  • exported: 此服务能否被其他组件调用或交互。
  • readPermission: 使用ContentProvider里的query()函数的权限。
  • writePermission: 修改权限,insert(),update()和delete()函数权限。
  • permission: 客户端访问ContentProvider必需的权限名称,此权限会被readPermission和writePermission覆盖。

权限使用:

在AndroidManifest文件的application标签的同级别位置注册permission标签(本应用)或uses-permission标签(第三方应用)以使用权限。

具体原理十分复杂,详见链接


安卓四大组件
https://blog.siantao.top/技术/计算机/软件/Android/框架/安卓四大组件/
作者
玉水仙楊
发布于
2022年4月1日
许可协议