android预加载activity
A. 安卓应用启动详解:从Zygote到你的Activity.onCreate()
翻译自: https://android.jlelse.eu/android-application-launch-explained-from-zygote-to-your-activity-oncreate-8a8f036864b
这篇文章讲解当用户点击应用图标时,安卓如何启动你的应用。安卓系统做了很多幕后工作,来使得你的launch activity对用户可见。本文通过重要阶段的讲解和调用序列详细讲解这一过程。
安卓应用在这两个方面是独特的:
多个入口点 :Android应用程序由不同的组件组成,它们可以调用其他应用程序拥有的组件。这些组件大致对应于任何应用程序的多个入口点。因此,它们不同于具有像main()方法那样的单个入口点的传统应用程序。
拥有自己的小世界 :每个Android应用程序都生活在自己的世界中,它在单独的进程中运行,拥有自己的Dalvik VM实例,并分配有唯一的用户ID。
必要时会启动Android进程。
每当用户或其他系统组件请求执行属于您应用程序的组件(可能是服务,活动或意图接收器)时,Android系统都会为您的应用程序启动一个新进程(如果尚未运行)。通常,进程一直运行直到被系统杀死。应用程序流程是按需创建的,在您看到应用程序的启动活动启动并运行之前,发生了许多事情。
每个应用程序都在其自己的进程中运行 :默认情况下,每个Android应用程序都在其自己的Android进程中运行,而这个进程只不过是一个Linux进程,而该进程首先需要一个执行线程。例如,当您单击电子邮件中的超链接时,网页将在浏览器窗口中打开。您的邮件客户端和浏览器是两个单独的应用程序,它们分别在两个单独的进程中运行。click事件使Android平台启动新进程,以便它可以在其自身进程的上下文中实例化浏览器活动。这对于应用程序中的任何其他组件同样适用。
让我们退后一会儿,快速浏览一下系统启动过程。与大多数基于Linux的系统一样,启动加载程序在启动时将加载内核并启动init进程。然后,init会生成称为“守护程序”的低级Linux进程,例如android debug守护程序,USB守护程序等。这些守护程序通常处理低级硬件接口,包括无线电接口。
然后,初始化过程会启动一个非常有趣的过程,称为“zygote'。
顾名思义,这是其余Android应用程序的开始。这是初始化Dalvik虚拟机的第一个实例的过程。它还预加载Android应用程序框架和系统上安装的各种应用程序使用的所有常见类。因此,它准备进行复制。它统计侦听套接字接口上的将来请求,以产生新的虚拟机(VM)来管理新的应用程序进程。收到新请求后,它会分叉以创建一个新进程,该进程将获取预先初始化的VM实例。
zygote之后,init启动运行时过程。
然后zygote分叉以启动一个名为System server的托管良好的进程。系统服务器在其自己的上下文中启动所有核心平台服务,例如活动管理器服务和硬件服务。
此时,完整的堆栈已准备就绪,可以启动第一个应用程序流程-主页应用程序,该应用程序显示主屏幕(也称为启动器应用程序)。
click事件被转换为 startActivity(intent), 并通过Binder IPC路由到 ActivityManagerService 。ActvityManagerService执行多个步骤
如您所见,当用户单击图标并启动新应用程序时,许多事情发生在幕后。这是全图:
流程创建:
ActivityManagerService 通过调用 startProcessLocked() 方法创建一个新进程,该方法通过套接字连接将参数发送到Zygote进程。Zygote派生自己并调用 ZygoteInit.main() ,然后实例化 ActivityThread 对象并返回新创建的进程的进程ID。
默认情况下,每个进程都有一个线程。主线程有一个 Looper 实例来处理来自消息队列的消息,并且它在 run() 方法的每次迭代中都调用 Looper.loop() 。 Looper 的工作是从消息队列中弹出消息并调用相应的方法来处理它们。然后,ActivityThread通过随后调用 Looper.prepareLoop() 和 Looper.loop()来 启动消息循环。
以下序列详细捕获了调用序列:
<figcaption class="jv jw di dg dh jx jy bo b fc cp ga" data-selectable-paragraph="" style="box-sizing: inherit; font-weight: 400; font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 20px; margin-left: auto; margin-right: auto; max-width: 728px; font-size: 14px; color: rgb(117, 117, 117); margin-top: 10px; text-align: center;">Android应用启动:单击事件以执行Looper调用顺序</figcaption>
应用程序绑定:
下一步是将此新创建的过程附加到特定应用程序。这是通过在线程对象上调用 bindApplication() 来完成的。此方法将 BIND_APPLICATION 消息发送到消息队列。该消息由 Handler 对象检索,该对象随后调用 handleMessage() 方法以触发特定于消息的操作 -handleBindApplication() 。此方法调用 makeApplication() 方法,该方法将应用程序特定的类加载到内存中。
下图描述了该调用序列。
<figcaption class="jv jw di dg dh jx jy bo b fc cp ga" data-selectable-paragraph="" style="box-sizing: inherit; font-weight: 400; font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 20px; margin-left: auto; margin-right: auto; max-width: 728px; font-size: 14px; color: rgb(117, 117, 117); margin-top: 10px; text-align: center;">Android应用启动:BIND_APPLICATION消息处理</figcaption>
启动活动:
在上一步之后,系统包含负责应用程序的进程,并将应用程序类加载到进程的私有内存中。在新创建的流程和现有流程之间,启动活动的调用顺序很常见。
实际的启动过程从 realStartActivity() 方法开始, 该 方法在应用程序线程对象上调用 sheleLaunchActivity() 。此方法将 LAUNCH_ACTIVITY 消息发送到消息队列。该消息由 handleLaunchActivity() 方法处理,如下所示。
假设用户单击“视频浏览器”应用程序。启动该活动的调用顺序如图所示。
<figcaption class="jv jw di dg dh jx jy bo b fc cp ga" data-selectable-paragraph="" style="box-sizing: inherit; font-weight: 400; font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 20px; margin-left: auto; margin-right: auto; max-width: 728px; font-size: 14px; color: rgb(117, 117, 117); margin-top: 10px; text-align: center;">Android应用启动: LAUNCH_ACTIVITY消息处理 </figcaption>
该活动通过 onCreate() 方法调用开始其托管生命周期。该活动通过 onRestart() 调用进入前台,并通过 onStart() 调用开始与用户进行交互。
B. Android —— Activity的四种启动模式
除了Activity的生命周期外,Activity的启动模式也是一个难点,有时候为了满足项目的特殊需求,就必须使用Activity的启动模式。
在默认情况下,当我们多次启动同一个Activity的时候,系统会创建多个实例并把它们放入任务栈中,但是有些场景重复创建多个实例,是没有必要且浪费资源的,这就需要启动模式来修改系统的默认行为。
下面,我将以理论+实践的形式为大家介绍Activity的启动模式。
这是系统的默认启动模式,采用这种模式的Activity无论是否已经存在实例,都会重新创建一个实例,在这种模式下谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。
实践:MainActivity 采用 standard 模式
在这种模式下,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的 NewIntent 方法将会被回调。如果新Activity的实例已存在但不是位于栈顶,那么新Activity依然会被创建。
实践:MainActivity 采用 singleTop 模式
MainActivity 采用 singleTop 模式,SecondActivity采用 standard 模式
这是一种单实例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,而是回调 onNewIntent() 。
实践:MainActivity 采用 singleTask 模式
MainActivity 采用 singleTask 模式,SecondActivity采用 standard 模式
这是一种加强的 singleTask 模式,它除了具有 singleTask 模式的所有特性外,还加强了一点,那就是具有此模式的Activity只能单独的位于一个任务栈中。
实践:MainActivity 采用 singleInstance 模式
MainActivity 采用 singleInstance 模式,SecondActivity采用 standard 模式
以上就是Activity启动模式的介绍。
欢迎留言指出错误。
C. Android之Activity全面解析,有些知识点容易忘记
Activity作为安卓四大组件之一,是最重要也是用得最多的组件,涉及的知识点非常多,有些知识点平时开发很少用到,但在某些场景下需要特别注意,本文详细整理了Activity涉及的知识点,供开发参考。
针对Activity可以提出很多问题,如:
Activity 的生命周期?
Activity 之间的通信方式?
Activity 各种情况下的生命周期?
横竖屏切换时 Activity 的生命周期?
前台切换到后台,然后再回到前台时 Activity 的生命周期?
弹出 Dialog 的时候按 Home 键时 Activity 的生命周期?
两个Activity之间跳转时的生命周期?
下拉状态栏时 Activity 的生命周期?
Activity 与 Fragment 之间生命周期比较?
Activity 的四种 LaunchMode(启动模式)的区别?
Activity 状态保存与恢复?
Activity的转场动画有哪些实现方式?
Activity的生命周期中怎么获取控件宽高?
onNewIntent的执行时机?
如何连续退出多个Activity?
如何把Acitivty设置成Dialog样式 ,android:theme="@android:style/Theme.Dialog"
关于横竖屏切换的生命周期,对应不同的手机,由于厂商定制的原因,会有不同的效果,如设置了configChanges="orientation”在有些手机会执行各个生命周期,但有些手机却不会执行。
网上常见的结论如下:
但实际的测试如下:
可以看出,不同厂商的手机切屏生命周期会有差异。
从API 13以上,当设备在横竖切屏时,“屏幕尺寸”也会发生变化,因此为了杜绝切屏导致页面销毁重建,需要加上screenSize,使用设置4,即 android:configChanges="orientation|keyboardHidden|screenSize" .
Activity的四种状态如下:
在activity处于paused或者stoped状态下,如果系统内存紧张,可能会被销毁,当重回该activity时会重建,正常返回和被回收后返回的生命周期如下:
如果是回收后返回,onCreate的参数savedInstanceState不为空。
有哪些场景会触发onNewIntent回调呢?跟启动模式有关,首先该Activity实例已经存在,再次启动才可能触发。一种情况是启动模式是singleTask或者singleInstance,无论该activity在栈中哪个位置,都会触发onNewIntent回调,并且把上面其他acitivity移除,另一种情况是启动模式是singleTop或者以FLAG_ACTIVITY_SINGLE_TOP启动,并且该activity实例在栈顶,会触发onNewIntent,如果不在栈顶是重新创建的,不会触发。
在实际业务开发中,往往碰到需要连续退出多个activity实例,下面整理了几种常见方法:
● 发送特定广播
1、在需要处理连续退出的activity注册该特定广播;
2、发起退出的activity发送该特定广播;
3、接收到该广播的activity 调用finish结束页面。
● 递归退出
1、用startActivityForResult启动新的activity;
2、前一个页面finish时,触发onActvityResult回调,再根据requestCode和resultCode处理是否finish,达到递归退出的效果。
● FLAG_ACTIVITY_CLEAR_TOP
通过intent.setFlag(Intent.FLAG_ACTIVITY_CLEAR_TOP)启动新activity,如果栈中已经有该实例,则会把该activity之上的所有activity关闭,达到singleTop启动模式的效果。
● 自定义activity栈
1、自定义activity列表,新打开activity则加入栈中,关闭则移除栈;
2、需要退出多个activity时,则循环从栈中移除activity实例,并调用finish。
在讨论Activity启动模式经常提到任务栈,那到底什么是任务栈?
任务是一个Activity的集合,它使用栈的方式来管理其中的Activity,这个栈又被称为返回栈(back stack),栈中Activity的顺序就是按照它们被打开的顺序依次存放的。返回栈是一个典型的后进先出(last in, first out)的数据结构。下图通过时间线的方式非常清晰地向我们展示了多个Activity在返回栈当中的状态变化:
taskAffinity 任务相关性,可以用于指定一个Activity更加愿意依附于哪一个任务,在默认情况下,同一个应用程序中的所有Activity都具有相同的affinity, 名字为应用的包名。当然了,我们可以为每个 Activity 都单独指定 taskAffinity 属性(不与包名相同)。taskAffinity 属性主要和 singleTask 启动模式和 allowTaskReparenting 属性配对使用,在其他情况下没有意义。
taskAffinity 有下面两种应用场景:
分为显示启动和隐式启动。
(1)显示启动
直接指定待调整的Activity类名。
(2)隐式启动
Intent 能够匹配目标组件的 IntentFilter 中所设置的过滤信息,如果不匹配将无法启动目标 Activity。IntentFilter 的过滤信息有 action、category、data。
IntentFilter 需要注意的地方有以下:
● 一个 Activity 中可以有多个 intent-filter
● 一个 intent-filter 同时可以有多个 action、category、data
● 一个 Intent 只要能匹配任何一组 intent-filter 即可启动对应 Activity
● 新建的 Activity 必须加上以下这句,代表能够接收隐式调用
<category android:name="android.intent.category.DEFAULT" />
只要匹配一个action即可跳转,注意的是action要区分大小写。
规则:如果intent中有category,则所有的都能匹配到intent-filter中的category,intent中的category数量可用少于intent-filter中的。另外,单独设置category是无法匹配activity的,因为category属性是一个执行Action的附加信息。
intent不添加category会匹配默认的,即 “android:intent.category.DEFAULT”
如果上面例子,如果去掉intent.setAction("action_name"),则会抛出异常:
规则:类似action,但data有复杂的结构,只要匹配一个data并且与data中所有属性都一致就能匹配到Activity,只要有1个属性不匹配,都无法找到activity。
data的结构:
data 主要是由 URI 和 mimeType 组成的。
URI 可配置很多信息,的结构如下:
与url类似,例如:
mineType:指资源类型包括文本、图片、音视频等等,例如:text/plain、 image/jpeg、video/* 等
下面看下data匹配的例子:
只匹配scheme
只匹配scheme也是能匹配到activity的。
匹配scheme、host、port
将上面的data改为
匹配mineType
如果有mineType,则不能仅设置setData或setMineType了,因为setData会把mineType置为null,而setMineType会把data置为null,导致永远无法匹配到activity,要使用setDataAndType。
使用scheme的默认值contentfile
注意该方法需要在startAtivity方法或者是finish方法调用之后立即执行,不能延迟,但可以在子线程执行。
而在windowAnimationStyle中存在四种动画:
activityOpenEnterAnimation // 打开新的Activity并进入新的Activity展示的动画
activityOpenExitAnimation // 打开新的Activity并销毁之前的Activity展示的动画
activityCloseEnterAnimation //关闭当前Activity进入上一个Activity展示的动画
activityCloseExitAnimation // 关闭当前Activity时展示的动画
overridePendingTransition的方式比较生硬,方法也比较老旧了,不适用于MD风格,google提供了新的转场动画ActivityOptions,并提供了兼容包ActivityOptionsCompat。
我们知道在onCreate和onResume里面直接获取到控件宽高为0,那有什么办法获取到控件的实际宽高?只要有onWindowFocusChanged、view.post、ViewTreeObserver三种方式获取。
当用户点击桌面图标启动APP时,背后的流程如下:
我们看到的手机桌面是Launch程序的界面,点击应用图标会触发点击事件,调用startActivity(intent),然后通过Binder IPC机制,与ActivityManagerService(AMS)通讯,AMS执行一系列操作,最终启动目前应用,大概流程如下:
通过PackageManager的resolveIntent()收集跳转intent对象的指向信息,然后通过grantUriPermissionLocked()方法来验证用户是否有足够的权限去调用该intent对象指向的Activity。如果有权限,则在新的task中启动目标activity,如果发现没有进程,则先创建进程。
如果进程不存在,AMS会调用startProcessLocked创建新的进程,在该方法中,会通过socket的通讯方式通知zygote进程孵化新的进程并返回pid,在新的进程中会初始化ActivityThread,并依次调用Looper.prepareLoop()和Looper.loop()来开启消息循环。
创建好进程后下一步要将Application和进程绑定起来,AMS会调用上一节创建的ActivityThread对象的bindAppliction方法完成绑定工作,该方法会发送一条BIND_APPLICATION的消息,最终会调用handleBindApplication方法处理消息,并调用makeApplication方法处理消息,加载APP的classes到内存中。
通过前面的步骤,系统已经拥有了该Application的进程,后续的启动则是从已存在其他进程中启动Acitivity,即调用realStartAcitvityLocked,该方法会调用Application的主线程对象ActivityThread的sheleLaunchActivity方法,在方法中会发送LAUNCH_ACTIVITY到消息队列,最终通过handleLaunchActivity处理消息,完成Acitivty的启动。
Activity
Activity 的 36 大难点,你会几个?“建议收藏”
[译]Android Application启动流程分析