安卓应用启动详解:从Zygote到你的Activity.onCreate()

安卓应用启动详解:从Zygote到你的Activity.onCreate(),第1张

翻译自: 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 的工作是从消息队列中d出消息并调用相应的方法来处理它们。然后,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: inheritfont-weight: 400font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serifline-height: 20pxmargin-left: automargin-right: automax-width: 728pxfont-size: 14pxcolor: rgb(117, 117, 117)margin-top: 10pxtext-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: inheritfont-weight: 400font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serifline-height: 20pxmargin-left: automargin-right: automax-width: 728pxfont-size: 14pxcolor: rgb(117, 117, 117)margin-top: 10pxtext-align: center">Android应用启动:BIND_APPLICATION消息处理</figcaption>

启动活动:

在上一步之后,系统包含负责应用程序的进程,并将应用程序类加载到进程的私有内存中。在新创建的流程和现有流程之间,启动活动的调用顺序很常见。

实际的启动过程从 realStartActivity() 方法开始, 该 方法在应用程序线程对象上调用 sheduleLaunchActivity() 。此方法将 LAUNCH_ACTIVITY 消息发送到消息队列。该消息由 handleLaunchActivity() 方法处理,如下所示。

假设用户单击“视频浏览器”应用程序。启动该活动的调用顺序如图所示。

<figcaption class="jv jw di dg dh jx jy bo b fc cp ga" data-selectable-paragraph="" style="box-sizing: inheritfont-weight: 400font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serifline-height: 20pxmargin-left: automargin-right: automax-width: 728pxfont-size: 14pxcolor: rgb(117, 117, 117)margin-top: 10pxtext-align: center">Android应用启动: LAUNCH_ACTIVITY消息处理 </figcaption>

该活动通过 onCreate() 方法调用开始其托管生命周期。该活动通过 onRestart() 调用进入前台,并通过 onStart() 调用开始与用户进行交互。

需要在程序内模拟用户输入,比如点击屏幕,或者输入键盘。模拟用户的滑动等。具体的需求,比如测试的时候,测试打开浏览器1000次。或者通过网络发送命令给手机,在手机上执行点击或者输入。再或者,平板和蓝牙鼠标通过蓝牙通信,通过鼠标让平板上的鼠标能移动和点击。这些都需要用到事件注入。

分析:

模拟用户输入的方式有几种,一是monkeyrunner,这个的原理是在PC上,通过python调用android的一些包,然后通过机器的调试端口和机器通信,机器接收到相应的命令后再往硬件写入相应的事件。这个常用语测试。并且,不是所有的机器都开了调试端口,并且需要连接PC。二是IwindowManager的injectInputEventNoWait,这个调用方便,也很简单,但是从1.5(1.6?)后android系统做了限制,不允许跨进程注入,这个方法只能在自己这个程序内用,home出去就不行了。三是直接往linux底层念掘/dev/input/event*写事件,这个实现起来复杂,需要root权限,但是却能实现跨进程,比如蓝牙鼠标的需求,也只能用这种方法实现。讲这个具体实现的不多,本文详细介绍下。对linux了解些的人应该一看就懂知道怎么回事。android上实现只不过有些地方比较绕而已。

1.android界面点击事件流程。

有必要先说下android界面捕获事件的流程。用户在屏幕上点击一下后,程序里面的OnClickListener是怎样收到这个事件的。大致流程如下

用户点搭败击-(硬件驱动部分)硬件产生一个中断,往/dev/input/event*写入一个相应的信号->jni部分,android循环读取/dev/input/event*的事件,再分发给WindowManagerServer,最后再发到相应的ViewGroup和View。这里可以通过往/dev/input/event*写信号的方式,来达到模拟事件的目的,接下来关心的就是信号的协议了。

2.按键协议分析

连接手机,adb shell,输入getevent,关仔枝核掉手机的自动旋转屏幕,按一下手机的menu键,会看到类似如下输出。

linux上的硬件会分别对应/dev/input/event*,这里的*一般是0-9的数字,getevent开头那部分已经显示,event2是keypad,event1是touchscreen等。

最下面的0001 008b 00000001分别叫做type,code,value。

参考linux input,type 对应 【#define EV_KEY 0x01】,code 对应【#define KEY_MENU 139】(8b == 139),value 1表示按下,0表示松开。那么按键的协议就很清楚了,试着在adb shell里面输入“sendevent /dev/input/event2 0 139 1”和”sendevent /dev/input/event2 0 139 2“后发现menud出来了,和按键的效果一样。

3.触摸协议分析

ok,来点复杂的。触摸协议稍微麻烦点,分单点触摸和多点触摸。

先说单点触摸,打开模拟器。同样关闭自动旋屏,进入adb shell。鼠标点击一下屏幕,要足够快,不然数据太多。得到输出和下面类似。

可以看到模拟器上的设备数少了很多,单点触摸的协议每次点击会写6条信号。参考linux_input对应的值以及分析分别如下

/dev/input/event0: 0003 0000 00000117 EV_ABS ABS_X 0x117

触摸点的x坐标

/dev/input/event0: 0003 0001 0000020fEV_ABS ABS_Y 0x20f

触摸点的y坐标

/dev/input/event0: 0001 014a 00000001 EV_KEY BTN_TOUCH 1

touch down

/dev/input/event0: 0000 0000 00000000 EV_SYN 0 0

同步信号量

/dev/input/event0: 0001 014a 00000000 EV_KEY BTN_TOUCH 0

touch up

/dev/input/event0: 0000 0000 00000000 EV_SYN 0 0

同步信号量

使用4.0的模拟器,settings-developer options-show touches 和pointer locations勾上后,可以看到点击的轨迹,adb shell后分别用sendevent输入以上消息,可以看到屏幕上出现点击效果。

再看多点触摸协议,使用adb shell 进入手机,关掉旋屏,getevent后快速点一下屏幕,可以看到类似如下输出。(每个厂商的协议可能不同,以下数据为小米1的)

第一个坐标

/dev/input/event1: 0003 0039 00000000 EV_ABS ABS_MT_TRACKING_ID 0

/dev/input/event1: 0003 0035 000001b0 EV_ABS ABS_MT_POSITION_X 0x1b0

/dev/input/event1: 0003 0036 000000d7 EV_ABS ABS_MT_POSITION_Y 0xd7

/dev/input/event1: 0003 003a 00000001 EV_ABS ABS_MT_PRESSURE 0x1

/dev/input/event1: 0003 0032 00000001 EV_ABS ABS_MT_WIDTH_MAJOR 0x1

/dev/input/event1: 0000 0002 00000000 EV_SYN SYN_MT_REPORT 0

/dev/input/event1: 0000 0000 00000000 EV_SYN SYN_REPORT 0

第二个坐标

/dev/input/event1: 0003 0039 00000000 EV_ABS ABS_MT_TRACKING_ID 0

/dev/input/event1: 0003 0035 000001b0 EV_ABS ABS_MT_POSITION_X 0x1b0

/dev/input/event1: 0003 0036 000000d7 EV_ABS ABS_MT_POSITION_Y 0xd7

/dev/input/event1: 0003 003a 00000001 EV_ABS ABS_MT_PRESSURE 0x1

/dev/input/event1: 0003 0032 00000001 EV_ABS ABS_MT_WIDTH_MAJOR 0x1

/dev/input/event1: 0000 0002 00000000 EV_SYN SYN_MT_REPORT 0

/dev/input/event1: 0000 0000 00000000 EV_SYN SYN_REPORT 0

第三个坐标

/dev/input/event1: 0003 0039 00000000 EV_ABS ABS_MT_TRACKING_ID 0

/dev/input/event1: 0003 0035 00000191 EV_ABS ABS_MT_POSITION_X 0x191

/dev/input/event1: 0003 0036 00000098 EV_ABS ABS_MT_POSITION_Y 0x98

/dev/input/event1: 0003 003a 00000001 EV_ABS ABS_MT_PRESSURE 0x1

/dev/input/event1: 0003 0032 00000001 EV_ABS ABS_MT_WIDTH_MAJOR 0x1

/dev/input/event1: 0000 0002 00000000 EV_SYN SYN_MT_REPORT 0

/dev/input/event1: 0000 0000 00000000 EV_SYN SYN_REPORT 0

松开

/dev/input/event1: 0000 0002 00000000 EV_SYN SYN_MT_REPORT 0

/dev/input/event1: 0000 0000 00000000 EV_SYN SYN_REPORT 0

这里是一次点击,注意到ABS_MT_TRACKING_ID都是一样的。系统检测到三个点,每次会发送点的x,y,以及收到的压力,触摸的范围。最后两条表示松开这个点。如果两个手指同时点击,可以发现ABS_MT_TRACKING_ID会有两个不同的值,分别是两个点。据说最多支持5点。

每个厂商实现协议不一样。htc g3如下

/dev/input/event1: 0003 003a 002a0002

/dev/input/event1: 0003 0039 8b8c0ddc

/dev/input/event1: 0003 003a 00000002

/dev/input/event1: 0003 0039 8bac0dde

/dev/input/event1: 0003 003a 00000000

/dev/input/event1: 0003 0039 802814b1

4.可能遇到的问题

实际实现的时候,还可能遇到问题

一是root,getevent和sendevent需要/dev/input/event*的权限。一般应用是没有这个权限的,需要在程序里面获取su后,执行chmod 666 /dev/input/event*。

二是设备名称。因为你不知道触摸屏或者按键到底对应的event*是多少。需要有一个初始化的过程,大致思路是往event0-event9分别写入按键和触摸信号,同时监听activity里的onkeydown和view的onclick,这样来侦测设备。

三是厂商的实现不一样,这个没办法,只能一个一个适配了,一般来说都还是标准的,有些厂商会有单独的实现。

Android开发中,当需要创建在后台运行的程序的时候,就要使用到Service。Service 可以分为有无限生命和有限生命两种橡樱。特别需要注意的是Service跟梁慎丛Activities是不同的(简单来说可以理解为后台与前台的区别),例如,如果需要使用Service的话,需要调用startService(),从而利用startService()去调用Service中的OnCreate()和onStart()方法来启动一个后台的Service。 启动一个Service的过程如下:context.startService() ->onCreate()- >onStart()->Service running其中onCreate()可以进行一些服务的初始化工作,onStart()则启动服务。 停止一个Service的过程如下:context.stopService() | ->onDestroy() ->Service stop 接孝毕下来的实例是一个利用后台服务播放音乐的小例子,点击start运行服务,点击stop停止服务。ServicesDemo.java(是一个Activity)

package com.android.myservice

import android.app.Activity

import android.content.Intent

import android.os.Bundle

import android.util.Log

import android.view.View

import android.view.View.OnClickListener

import android.widget.Button

public class ServiceDemo extends Activity implements OnClickListener {

private static final String TAG = "ServiceDemo"

Button buttonStart, buttonStop

}

除此之外还要在Manifest里面声明服务:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android=" http://schemas.android.com/apk/res/android "

package="com.android.myservice">

<application android:label="@string/app_name">

<activity android:name=".ServiceDemo" android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>

</intent-filter>

</activity>

<service android:enabled="true" android:name=".MyService"/>

</application>

</manifest>

定义Service(MyService.java

package com.android.myservice

import android.app.Service

import android.content.Intent

import android.media.MediaPlayer

import android.os.IBinder

import android.util.Log

import android.widget.Toast

public class MyService extends Service {

private static final String TAG = "MyService"

MediaPlayer player

}

layout文件夹中是main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android=" http://schemas.android.com/apk/res/android "

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:gravity="center">

<TextView

android:layout_width="fill_parent"

android:layout_height="wrap_content" android:text="@string/services_demo" android:gravity="center" android:textSize="20sp" android:padding="20dp"/>

<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/buttonStart" android:text="@string/start"></Button>

<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/stop" android:id="@+id/buttonStop"></Button>

</LinearLayout>

values 文件夹中是strings.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>

<string name="start">Start</string>

<string name="stop">Stop</string>

<string name="services_demo">Service Demo</string>

</resources>


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/12539264.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-26
下一篇 2023-05-26

发表评论

登录后才能评论

评论列表(0条)

保存