概述安卓App的安装和启动

Android的安装和启动比较特别,很多机制和直观感受并不一样,如果这里出现误解,就很难透彻理解App的运行,这里把过去积累的问题统一梳理了一下。

安装

我们知道,Android的安装包Apk其实就是个资源和组件的容器压缩包,安装的过程主要是复制和解析的过程,这个过程大概分这样几步:

一、复制

安卓的程序目录是/data/app/,所以安装的第一步就是把apk文件复制到这个目录下。这里有四个问题:

  1. 安卓机有内部存储和SD卡两部分,很多安卓机的内存并不大,需要把apk安装到SD卡上节省内存空间,所以程序目录/data/app/实际上也是在内部存储和SD卡上各一个。
  2. 系统自带的App是安装在/system/app/目录下的,这个目录只有root权限才能访问,所以系统App在root之前是无法删除和修改的,也就是说,系统App升级时,实际上是在/data/app/里重新安装了一个App,这个路径会重新注册到系统那里,系统再打开App时,就会指向新App的地址。当然,这个新的App是可以卸载的,不过新的App卸载后,系统会把 /system/app/里那个旧的App提供给你,所以是卸掉新的,还你旧的。
  3. 还是系统App,在root后,我们可以操作/system/app/目录,但是系统安装Apk仍然会装到/data/app/里,所以如果想修改/system/app/目录里的app,必须自己手动push新的apk文件进去,这个新的apk文件不会自动被安装,需要重启设备,系统在重启时检查到apk被更新,才会去安装apk。
  4. 系统目录有个/system/priv-app/目录,这里面放的是权限更高的系统核心应用,如开机launcher、系统UI、系统设置等,这个目录我们最好不要动,保持系统干净简洁。

二、安装

安卓系统开机启动时,会启动一个超级管理服务SystemServer,这个SystemServer会启动所有的系统核心服务,其中就包括PackageManagerService,简称PMS,具体的apk安装过程,就是由这个PMS操作的。
PMS会监控/data/app/这个目录,在上一步中,系统安装程序向这个目录复制了一个apk,PMS自己就会定期扫描这个目录,找到后缀为apk的文件,如果这个apk没有被安装过,它就会自动开始安装,安装时会做这么几件事:

  1. 创建应用目录,路径为/data/data/your package(你的应用包名),App中使用的数据库、so库、xml文件等,都会放在这个目录下。
  2. 提取dex文件,dex是App的可执行文件,系统解压apk就能得到dex文件,然后把dex文件放到/data/dalvik-cache,这样可以提前缓存dex到内存中,能加快启动速度。系统还会把dex优化为odex,进一步加快启动速度。
  3. 判断是否可以安装apk,如检查apk签名等。
  4. 为应用分配并保存一个UID,UID是来自Linux的用户账户体系,不过在Android这种单用户系统里,UID被用来与App对应,这也是安全机制的一部分,每个App都有自己对应的UID,这种对应关系是持久化保存的,App更新或卸载重装后,系统还会给它分配原来那个UID。用adb pull /data/system/packages.list可以查看所有App的UID。GID(用户组)一般等于UID。
  5. 利用AndroidManifest文件,注册Apk的各项信息,包括但不限于:
    . 根据installLocation属性(internalOnly、auto、preferExternal),选择安装在内部存储器还是SD卡上。
    . 根据sharedUserId属性,为App分配UID,如果两个App使用同一个UID,打包时又使用了相同的签名,它们就被视为同一个用户,可以共享数据,甚至运行在同一个进程上。
    . 向/data/system/packages.xml文件中,记录App的包名、权限、版本号、安装路径等;同时在/data/system/packages.list中,更新所有已安装的app列表。
    . 注册App中的的四大组件(Activity、Service、Broadcast Receiver和Content Provider),包括组件的intent-filter和permission等。
    . 在桌面上添加App的快捷方式,如果AndroidManifest文件中有多个Activity被标注为<action android:name="android.intent.action.MAIN" />和
    <category android:name="android.intent.category.LAUNCHER" />,系统就会向桌面添加多个App快捷方式,所以有时候在安装一个App后,用户可能会感觉安装了多个App。

三、通知

apk安装完成后,PMS会发一个ACTION_PACKAGE_ADDED广播,如果是卸载,会发ACTION_PACKAGE_REMOVED广播。
整个安装过程大概是这样的:


App安装过程

启动

启动一个App,首先需要触发启动过程,然后分配系统资源,最后才启动要打开的App组件。

一、触发启动过程

在安卓系统开机启动时,启动的超级管理服务SystemServer会启动所有的系统核心服务,其中就包括ActivityManagerService,简称AMS,启动App具体都是AMS来负责的。
不过,一般Java程序都有个main函数入口,启动Java程序其实就是执行main函数去了。但是,安卓App不是这样设计的,App并没有统一的程序入口,一个App其实更像是一群组件的集合,启动App其实就是启动了某个组件,即便是从桌面点击应用图标打开某个App,也是系统桌面Home根据安装时注册的组件信息,找到这个图标对应的Activity信息,再由AMS去启动Activity组件。


触发启动过程

二、分配系统资源

在安卓系统里,除非人为设置为多进程(Activity的android:process属性),否则默认每个App都有1个独立的进程和虚拟机,所以在系统启动时,系统会建立一个Linux进程(Process),在这个进程里放一个虚拟机VM,在这个VM里,运行你的App。
在系统层面,它其实要做这么几件事:

  1. 分配UID,App要有UID才能有自己的系统资源,UID是在安装App时由系统分配的,一般每个App都有自己的UID,App的资源不能共享,因为它们不属于同一个用户。
  2. 分配进程Process,系统会给App一个进程,每个App的都有自己的进程,进程的PID是系统即时生成的,用完销毁。
    如果要让两个App共用进程,除了需要设置同一个进程(android:process),还需要分配同一个UID(android:sharedUserId)来共享系统资源,使用同一个应用签名(同一个签名证书才可以视为同一个程序)。
    有时候,如果某些业务特别消耗内存或特别耗时,还可以把1个App分成多个进程,让某些组件在独立的进程中工作,销毁该组建时,把整个进程一起用system.exit来销毁掉。
  3. 提供虚拟机VM,安卓App是java程序,需要在java虚拟机上运行,这个虚拟机需要由系统在分配进程时,和进程一起提供。
  4. 除非做了跨进程跨用户的配置,否则App之间是隔离的,不能直接互相访问,也不能直接共享资源。
  5. AMS管理启动过程,启动App的工作都是AMS统一负责的,AMS里保存了App对应的系统进程ID(PID),在启动App时,AMS会去找App对应的PID,如果找不到PID,说明需要创建进程,就会要求系统为App提供进程和VM。
  6. 提供进程和VM,创建VM是非常耗时的,为了加快App启动速度,安卓系统采用了复制的方式:系统开机启动时会启动一个Zygote进程,这个进程会初始化系统的第一个VM, 并预加载framework等app通用资源,当安卓要启动某个App时,Zygote通过fork复制Zygote的VM,就可以快速创建一个带VM的进程,为App运行提供载体。系统开机启动时的第一个进程一般是桌面Home进程。

提供系统资源的过程大概如下图:


提供系统资源

三、启动要打开的App组件

App本身没有main函数入口,但是系统在启动进程时,会创建一个主线程ActivityThread对象(Process.start("android.app.ActivityThread",...)),这个ActivityThread是一个final类,虽然不是线程,但是管理者主线程,它是有main函数入口的(Java终于找到组织了),ActivityThread有这么几个作用:

  1. 管理App的主线程,也就是UI线程,启动主线程的Looper,只有主线程可以调用View的onDraw函数来刷新界面(为了线程安全)。
    至于视频类控件,都不是View而是SurfaceView,所以可以用子线程刷新,而且SurfaceView是直接绘制到屏幕上的,和View是分开管理的。
    另外,BroadCast消息也是主线程处理的,主线程创建BroadCastReceiver对象,并调用其onReceive()函数,处理完就销毁,所以它的生命周期很短(10秒,超时就ANR)。
  2. 负责管理调度进程资源、Application和App四大组件中的三个(Activity,Service,ContentProvider),列表中的组件用Token区分,至于BroadCastReceiver,因为是随用随造,用完销毁,所以不需要保存和管理。
  3. 构建Context和Application,这个任务包括检查和加载LoadedApk对象、设置分辨率密度、是否高耗内存、获取package和component等启动信息、获取ClassLoader把类加载到内存等,最后,先创建一个context对象contextimpl.createAppContext(this,getSystemContext().mPackageInfo;),再用context去创建Application对象context.mPackageInfo.makeApplication(true,null)。
  4. 对接AMS,AMS自己有专门的系统进程,ActivityThread把一个ApplicationThread(一个Bindler对象)作为自己的Proxy交给AMS,以便由AMS来调度管理ActivityThread中的Activity。
  5. 处理消息,ActivityThread是通过消息机制来启动App组件的,ActivityThread有Message队列、Handler和Looper,在AMS启动Activity时,AMS会向ActivityThread发送LAUNCH_ACTIVITY消息启动Activity,ActivityThread收到这个消息后启动Activity,然后,就进入我们熟悉的组件onCreate生命周期了。
  6. 重要系统服务如SystemServer也是App,也有ActivityThread,也可能出现ANR之类的异常,为了避免系统“跑飞”,这些应用都有Watchdog看护,出现问题会重启设备。

启动过程大概是这样的:


启动过程

AMS是通过IPC向ActivityThread传递消息的。

另外,在创建组件时,组件之间有这样几个区别:

  1. Application和四大组件的启动时机:
    Application是主线程启动时创建的,这是应用程序运行的第一个类、
    ContentProvider是主线程启动时创建的(并发布到AMS)、
    BroadCastReceiver是主线程收到广播时创建的(前台10秒/后台60秒ANR)、
    Activity是AMS发消息让主线程创建的(5秒ANR)、
    Service是AMS通过ApplicationThread接口,让主线程创建的(并运行在主线程上,前台20秒/后台200秒ANR)。
  2. 关于Context:
    App里依靠context来提供资源和上下文,所以Application、Activity和Service有context(它们都extends Context)、
    BroadCastReceiver没有context,主线程在调用onReceive时会把Application的context作为参数传进去、
    ContentProvider也没有context、
    虽然组件有各自的context,但它们指向同一块资源,因为实现ContextImpl时,获取资源的ResourcesManager采用单例模式,所以同一个App的不同context都指向同一个Resource对象
    Activity的context多了主题Theme,而Application的context生命周期最长。
    这些context之间的关系如下:


    context关系图
  3. 关于对Application的共享:
    App中的Application是一个单例,整个App是共享同一个Application对象的、
    Activity和Service都有getApplication函数、这个Application是在创建组件时赋给组件的,比如Activity就是ActivityThread在performLaunchActivity时,把Application实体赋给Application的。
    组件有getApplication和getApplicationContext两个函数,这两个函数一个是组件本身的,一个contextwrapper要求实现的,很多情况下他们返回的是一个对象,但是官方并不建议把两者混淆。

引用

Android应用程序启动过程源代码分析
Android应用程序安装过程解析(源码角度)
android系统Context初始化过程
Android Application启动流程分析
ActivityThread的main方法究竟做了什么
深入理解ActivityManagerService
Android ActivityThread(主线程或UI线程)简介
到底getApplicationContext和getApplication是不是返回同一个对象
Activity的启动和创建
Android应用启动、退出分析
一次搞定Process和Task
startService源码从AMS进程到service的新进程启动过程分析

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

推荐阅读更多精彩内容