Android四大组件的作用以及是否可以开启多进程

Android四大组件是指 Activity、Service(服务)、BroadcastReceiver(广播)、ContentProvider。

在注册方面,Activity、Service、ContentProvider 必须在 AndroidManifest 中注册,而 BroadcastReceiver 既可以在 AndroidManifest 中注册,也可以通过代码来注册。如下图所示:


Android四大组件注册方法

在调用方式上,Activity、Service、BroadcastReceiver 需要借助 Intent,而 ContentProvider 不需要借助 Intent。

1. Activity

Activity 是一种展示型组件,起作用就在于向用户直接展示一个界面,并且接收用户操作信息从而进行交互。Activity 是唯一一种可以直接感知的组件,可以说,在用户看来 Activity 就是一个Android应用的全部。

1.1 Activity 的启动方式

Activity 启动方式有两种,显式启动隐式启动

  • 显式启动
    明确指定一个 Activity 组件,正如我们平常大部分时间所使用的方式:(文中全部代码使用Kotlin)
    startActivity(Intent(this, SampleActivity::class.java))或者startActivityForResult(Intent(this, SampleActivity::class.java), 0)

  • 隐式启动
    指向一个或多个 Activity 组件,隐式启动需要我们再 AndroidManifest 中为 Activity 配置 intent-filter ,比如:

<!--Activity注册-->
<activity android:name=".SampleActivity">
    <intent-filter>
        <action android:name="sampleActivity"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

这个时候,我们可以使用如下方法启动 SampleActivity

// 创建Intent,并制定action
val intent = Intent("sampleActivity")  
startActivity(intent)

隐式启动这种方式,相信我们平常也是用的比较多的,比如调用系统拨号功能。

1.2 Activity 的启动模式

Activity 的启动模式有4种,standard、singleTop、singleTask、singleInstance

  • standard 标准模式,系统的默认启动模式。每次启动 Activity,都会在当前栈中创建一个实例。
  • singleTop 栈顶复用模式。启动 Activity 时,如当前任务栈顶已经存在该 Activity 的实例,则不会重新创建,同时它的 onNewIntent 方法被调用。
  • singleTask 栈内复用模式。这是一种单实例模式,只要 Activity 在一个任务栈中存在,多次启动时都不会重新创建实例,而是将栈顶到该 Activity 实例之前的 Activity 全部 finish,使该 Activity 处于栈顶,并调用其 onNewIntent 方法。
  • singleInstance 单实例模式。这是一种加强的 singleTask 模式,除了拥有 singleTask 全部特征外,此模式的 Activity 只能单独位于一个任务栈中。

1.3 Activity 的启动过程

关于 Activity 的启动过程,这里就简单文字描述下,就不贴源码了,关于源码的解析,有机会单独记录。

启动 Activity 都是调用 startActivity(ForResult) 方法,所以,就从 startActivity(ForResult) 方法开始:

  1. startActivity(ForResult) 方法有多种重载方式,但它们最终都会调用 public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options)
  2. startActivityForResult 方法中调用 Instrumentation 的public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options, UserHandle user),该方法的参数中传入了 mMainThread.getApplicationThread(),它的类型是 ApplicationThread,ApplicationThread 是 ActivityThread 的一个内部类。
  3. Instrumentation.execStartActivity 方法中,由 ActivityManagerNative.getDefault() 的 startActivity 方法完成 Activity 启动。ActivityManagerService(下面简称AMS)继承自 ActivityManagerNative,而 ActivityManagerNative 继承自 Binder 并实现了 IActivityManager 这个 Binder 接口,因此 AMS 也是一个 Binder,它是 IActivityManager 的具体实现。由于 ActivityManagerNative.getDefault() 其实是一个 IActivityManager 类型的 Binder 对象,因此它的具体实现是 AMS。所以,Activity 的启动过程有转移到了 AMS 中。
  4. AMS 的 startActivity 方法调用 ActivityStackSupervisor 的 startActivityMayWait 方法。startActivityMayWait 中又调用 startActivityLocked 方法。startActivityLocked 又调用了 startActivityUncheckedLocked 方法。接着 startActivityUncheckedLocked 又调用了 ActivityStack 的 resumeTopActivitiesLocked 方法。
  5. 这个时候启动过程已经从 ActivityStackSupervisor 转移到了 ActivityStack,ActivityStack 的 resumeTopActivitiesLocked 又会调用 resumeTopActivityInnerLocked 方法,resumeTopActivityInnerLocked 又调用了 ActivityStackSupervisor 的 startSpecificActivityLocked 方法。
  6. 此时,启动过程又回到了 ActivityStackSupervisor,在 startSpecificActivityLocked 方法中,调用 realStartActivityLocked 方法。
  7. 在 realStartActivityLocked 中,有这样一段代码 app.thread.scheduleLaunchActivity(**),app.thread 的类型为 IApplicationThread,其实现者是 ActivityThread 中的内部类 ApplicationThread。饶了一大圈,Activity 的启动过程最终回到了 ApplicationThread 中,ApplicationThread 通过 scheduleLaunchActivity 方法来启动 Activity。
  8. scheduleLaunchActivity 做的事情也很简单,就是发送一个启动 Activity 的消息交由 Handler 处理,这个 Handler 有一个很简洁的名字:H。
  9. H 对 Activity 启动消息的处理是调用 ActivityThread 的 handleLaunchActivity 方法来实现 Activity 的启动。
  10. 最终,在 handleLaunchActivity 中,调用 performLaunchActivity 方法完成 Activity 对象的创建和启动,之后,又通过 HandlerResumeActivity 方法来调用被启动 Activity 的 onResume 这一生命周期方法。

2. Service

Service 是一种计算型组件,用于在后台执行耗时操作。由于 Service 组件工作在后台,因此用户无法直接感知到它的存在。

2.1 Service 的运行模式

Activity 组件只有一种运行模式,即 Activity 处于启动状态,Service 组件略有不同,它有两种状态:启动状态绑定状态

  • 启动状态
    Service 内部可以做一些后台计算,并且不需要和外界有直接的交互。该运行模式可以使用 Context 的 startService(Intent) 方法启动Service。
  • 绑定状态
    Service 内部同样可以进行后台计算,但是处于这种状态时,外界可以很方便地和 Service 组件进行通信。该运行模式使用 Context 的 bindService(Intent, ServiceConnection, int) 方法启动 Service。

2.2 Service 的启动过程

2.2.1 startService 方式

示例代码:startService(Intent(this, SampleService::class.java))

Context 的实现类只有 ContextWrapper,所以该 Service 的启动从 ContextWrapper 的 startService 方法开始:

  1. startService 方法调用 ContextImpl 的 startService 方法。
  2. 在 ContextImpl 中,startService 方法会调用 startServiceCommon 方法,而 startServiceCommon 方法又通过调用 ActivityManagerNative.getDefault().startService(**) 来启动一个 Service。对于 ActivityManagerNative.getDefault() 这个对象,在 Activity 的启动过程中第 3 点分析了,它实际上就是 AMS (ActivityManagerService),这里就不在重复说明了。需要注意的是,通过 AMS 来启动 Service 的行为是一个远程过程调用。
  3. 在 AMS 的 startService 中,会通过 ActiveServices 对象来完成 Service 后续的启动过程。也就是调用 ActiveServices 的 startServiceLocked 方法。
  4. 在 ActiveServices 的 startServiceLocked 方法尾部会调用 startServiceInnerLocked 方法,startServiceInnerLocked 又调用了 realStartServiceLocked 方法。
  5. 在 realStartServiceLocked 方法中,首先通过 app.thread 的 scheduleCreateService 方法来创建并调用其 onCreate,接着再调用 sendServiceArgsLocked 方法来调用 Service 的其它方法,比如 onStartCommand,这两个过程均是进程间通信。
  6. 由 Activity 的启动过程第 10 点可知,app.thread 是 ApplicationThread 的实例,也就是调用了 ApplicationThread 的 scheduleCreateService 方法来创建 Service。这个过程和 Activity 的启动过程是类似的,都是通过发送消息给 Handler H 来完成。
  7. H 通过 ActivityThread 的 handleCreateService 方法来完成 Service 的最终启动。handleCreateService 主要完成如下几件事:
    1. 通过类加载器创建 Service 的实例。
    2. 创建 Application 对象并调用其 onCreate,当然 Application 的创建过程只有一次。
    3. 创建 ContextImpl 对象并通过 Service 的 attach 方法建立二者之间的联系,这个过程和 Activity 实际上是类似的,毕竟 Service 和 Activity 都是一个 Context。
    4. 最后调用 Service 的 onCreate 方法并将 Service 对象存储到 ActivityThread 中的一个列表。
    5. 由于 Service 的 onCreate 方法被执行了,这也意味着 Service 已经启动了。除此之外, ActivityThread 中还会通过 handleServiceArgs 方法调用 Service 的 onStartCommand 方法。

2.2.2 bindService 方式

示例代码:

val service = Intent(this, SampleService::class.java)
bindService(service, mServiceConnection, Service.BIND_AUTO_CREATE)

和 startService 一样,bindService 的过程也是从 ContextWrapper 开始的:

  1. bindService 方法调用 ContextImpl 的 bindService 方法,然后 bindService 方法调用 bindServiceCommon 方法。
  2. 在 bindServiceCommon 中,首先将客户端的 ServiceConnection 对象转化为 ServiceDispatcher.InnerConnection 对象。接着通过 AMS 来完成 Service 的具体绑定过程,方式是调用ActivityManagerNative.getDefault().bindService(**)
  3. AMS 的 bindService 方法调用 ActiveServices 的 bindServiceLocked 方法,bindServiceLocked 方法再调用 bringUpServiceLocked,bringUpServiceLocked 又会调用 realStartServiceLocked 方法。
  4. realStartServiceLocked 方法的逻辑和2.2.1中的逻辑类似,最终都是通过 ApplicationThread 来完成 Service 实例的创建并执行其 onCreate 方法。和 startService 方式不同的是,Service 的绑定过程会调用 app.thread的 scheduleBindService 方法。
  5. 和上述类似,scheduleBindService 也是通过发送消息给 Handler H 来中转的。
  6. H 接收到绑定 Service 的消息时,调用 ActivityThread 的 handleBindService 方法来处理。
  7. 在 handleBindService 中,首先根据 Service 的 token 取出 Service 对象,然后调用 Service 的 onBind 方法。接着通过 ActivityManagerNative.getDefault() 的 publishService 方法调用客户端的 ServiceConnection 中的 onServiceConnection。

3 BroadcastReceiver

BroadcastReceiver 也叫广播接收者,是一种消息型组件,用于在不同的组件甚至不同的应用之间传递消息。 BroadcastReceiver 同样是用户无感知的。

3.1 BroadcastReceiver 的注册方式

BroadcastReceiver 的注册方式有两种,静态注册动态注册

  • 静态注册
    指在 AndroidManifest 中注册广播,这种广播在应用安装时被系统解析,不需要启动应用就可以收到相应的广播。应用的开机启动就是用到了这类 BroadcastReceiver 来监听手机的启动。注册代码可参考上文的图。
  • 动态注册
    在代码中调用 Context.registerReceiver() 来实现注册。此类注册要在不使用的时候调用 Context.unRegisterReceiver() 进行反注册,否则容易因持有 Context 实例造成内存泄漏。

4. ContextProvider

ContentProvider 是一种数据共享型组件,用于向其它组件甚至其它应用共享数据。由于只是用来组件或者应用间共享数据,用户同样无法直接感知。

一个 ContentProvider 组件内部要实现数据的增删改查四种操作,其内部维持着一份数据集合。这个数据集合有多种实现方式,ContentProvider 本身对其没有任何要求,常见的使用数据库来实现。

四大组件是否可以开启多进程

上面对Android的四大组件的作用进行了一个大致的说明,由于篇幅原因,这里只简单描述了下 Activity 和 Service 启动过程,找机会再整理下 BroadcastReceiver 和 ContentProvider 的工作过程。

我们知道,Android 应用是可以开启多个进程的,就是在 AndroidManifest 中使用 android:process 属性,比如要给某一 Activity 指定运行进程,则在其 <activity> 标签中添加 android:process 属性即可。那么,其它的三种组件是否也可以为其指定运行进程呢?也就是说,Android的四大组件是否都可以开启多进程?

这里我大胆假设小心求证下,我先假设都可以,并且开启方式都和 Activity 相同,如果不行的话,再根据问题进行相应调整,并最终得出结论。

demo:

AndroidManifest中注册

<!--Activity注册-->
<activity android:name=".SampleActivity"
    android:process=":SampleActivity">
    <intent-filter>
        <action android:name="sampleActivity"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

<!--Service注册-->
<service
    android:name=".SampleService"
    android:process=":SampleService"/>

<!--BroadcastReceiver静态注册-->
<receiver
    android:name=".SampleReceiver"
    android:process=":SampleReceiver">
    <intent-filter>
        <action android:name="com.cy.receiver.sample"/>
    </intent-filter>
</receiver>

<!--ContentProvider注册-->
<provider
    android:name=".SampleContentProvider"
    android:process=":SampleContentProvider"
    android:authorities="com.cy.multiprocess.SampleContentProvider"
    android:exported="true">
</provider>

MainActivity中启动 Activity、Service,发送广播,访问ContentProvider

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d("MainActivity", "主进程号为:${getCurProcessName()}")

        // 启动 Activity
        startActivity(Intent(this, SampleActivity::class.java))

        // 启动 Service
        val service = Intent(this, SampleService::class.java)
        startService(service)

        // 发送广播
        sendBroadcast(Intent("com.cy.receiver.sample"))

        // 访问ContentProvider
        val uri = Uri.parse("content://com.cy.multiprocess.SampleContentProvider")
        contentResolver.query(uri, null, null, null, null)
    }
}

/**
 * Context 的扩展方法:获取当前进程号
 */
fun Context.getCurProcessName(): String? {
    val pid = android.os.Process.myPid()
    val mActivityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    for (appProcess in mActivityManager.runningAppProcesses) {
        if (appProcess.pid == pid) {
            return appProcess.processName
        }
    }
    return null
}

最后在各自生命周期方法中打印进程名,运行结果如下:

Android四大组件开启多进程运行结果

由图可知,每个组件所运行的进程id、进程名和主进程都不一样,所以结论是:Android的四大组件都可以开启多进程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容