1. Android系统启动流程是什么?
==Android系统核心流程==:
- ==启动电源以及系统启动==:当电源按下时引导芯片从预定义的地方(固化在ROM)开始执行,加载引导程序BootLoader到RAM,然后执行。
- ==引导程序BootLoader==:BootLoader是在Android系统开始运行前的一个小程序,主要用于把系统OS拉起来并运行。
- ==Linux内核启动==:当内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。当其完成系统设置时,会先在系统文件中寻找init.rc文件,并启动init进程。
- ==init进程启动==:初始化和启动属性服务,并且启动Zygote进程。
- ==Zygote进程启动==:创建JVM并为其注册JNI方法,创建服务器端Socket,启动SystemServer进程。
- ==SystemServer进程启动==:启动Binder线程池和SystemServiceManager,并且启动各种系统服务。
- ==Launcher启动==:被SystemServer进程启动的AMS会启动Launcher,Launcher启动后会将已安装应用的快捷图标显示到系统桌面上。
进程启动过程:init进程 -> Zygote进程 –> SystemServer进程 –> 各种系统服务 –> 应用进程。
2. 启动一个程序,可以主界面点击图标进入,也可以从一个程序中跳转过去,二者有什么区别?
点击图标是因为启动程序(主界面也是一个app),发现了在这个程序中存在一个设置为入口的activity, 所以这个launcher会把icon提出来,放在主界面上。当用户点击icon的时候,发出一个Intent:
Intent intent = mActivity.getPackageManager().getLaunchIntentForPackage(packageName);
mActivity.startActivity(intent);
从另一个程序跳过去,系统会根据第三方程序向系统注册的功能,为你的Intent选择可以打开的程序或者页面。
所以唯一的一点不同的是从icon的点击启动的intent的action是相对单一的,从程序中跳转或者启动可能样式更多一些。本质是相同的。
3. AMS家族重要术语解释
-
ActivityManagerServices,简称AMS,服务端对象,负责系统中所有Activity的生命周期。
-
==ActivityThread==,App的真正入口。当开启App之后,调用main()开始运行,开启消息循环队列,这就是传说的UI线程或者叫主线程。与ActivityManagerService一起完成Activity的管理工作。
-
ApplicationThread,用来实现ActivityManagerServie与ActivityThread之间的交互。在ActivityManagerSevice需要管理相关Application中的Activity的生命周期时,通过ApplicationThread的代理对象与ActivityThread通信。
-
ApplicationThreadProxy,是ApplicationThread在服务器端的代理,负责和客户端的ApplicationThread通信。AMS就是通过该代理与ActivityThread进行通信的。
-
Instrumentation,每一个应用程序只有一个Instrumetation对象,每个Activity内都有一个对该对象的引用,Instrumentation可以理解为应用进程的管家,ActivityThread要创建或暂停某个Activity时,都需要通过Instrumentation来进行具体的操作。
-
ActivityStack,Activity在AMS的栈管理,用来记录经启动的Activity的先后关系,状态信息等。通过ActivtyStack决定是否需要启动新的进程。
-
ActivityRecord,ActivityStack的管理对象,每个Acivity在AMS对应一个ActivityRecord,来记录Activity状态以及其他的管理信息。其实就是服务器端的Activity对象的映像。
-
TaskRecord,AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。如果你清楚Activity的4种launchMode,那么对这概念应该不陌生。
4. ==App启动流程==
点击应用图标后会去启动应用的Launcher Activity,如果Launcer Activity所在的进程没有创建,还会创建新进程,整体的流程就是一个Activity的启动流程。
![](https://i-blog.csdnimg.cn/blog_migrate/f22b1eda5a76e2d168bef428913c1d6d.png)
整个流程涉及的主要角色有:
- Instrumentation: 监控应用与系统相关的交互行为。
- AMS:组件管理调度中心,什么都不干,但是什么都管。
- ActivityStarter:Activity启动的控制器,处理Intent与Flag对Activity启动的影响,具体说来有:
1、寻找符合启动条件的Activity,如果有多个,让用户选择;
2、校验启动参数的合法性;
3、 返回int参数,代表Activity是否启动成功。 - ActivityStackSupervisior:这个类的作用从它的名字就可以看出来,它用来管理任务栈。
- ActivityStack:用来管理任务栈里的Activity。
- ActivityThread:最终干活的人,Activity、Service、BroadcastReceiver的启动、切换、调度等各种操作都在这个类里完成。
注:这里单独提一下ActivityStackSupervisior,这是高版本才有的类,它用来管理多个ActivityStack,早期的版本只有一个ActivityStack对应着手机屏幕,后来高版本支持多屏以后,就有了多个ActivityStack,于是就引入了ActivityStackSupervisior用来管理多个ActivityStack。
==整个流程主要涉及四个进程==:
- ==调用者进程==,如果是在桌面启动应用就是Launcher应用进程。
- ActivityManagerService等所在的==System Server进程==,该进程主要运行着系统服务组件。
- ==Zygote进程==,该进程主要用来fork新进程。
- ==新启动的应用进程==,该进程就是用来承载应用运行的进程了,它也是应用的主线程(新创建的进程就是主线程),处理组件生命周期、界面绘制等相关事情。
有了以上的理解,==整个流程可以概括如下==:
- 1、点击桌面应用图标,==Launcher进程将启动Activity(MainActivity)的请求以Binder的方式发送给了AMS==。
- 2、==AMS接收到启动请求后,交付ActivityStarter处理Intent和Flag等信息==,然后再交给ActivityStackSupervisior/ActivityStack 处理Activity进栈相关流程。==同时以Socket方式请求Zygote进程fork新进程==。
- 3、==Zygote接收到新进程创建请求后fork出新进程==。
- 4、==在新进程里创建ActivityThread对象==,新创建的ActivityThread就是应用的主线程,==在主线程里开启Looper消息循环,开始处理创建Activity==。
- 5、==ActivityThread利用ClassLoader去加载Activity、创建Activity实例,并回调Activity的onCreate()方法==,这样便完成了Activity的启动。
![](https://i-blog.csdnimg.cn/blog_migrate/83249acf8a5dd06607b3b386c0589a98.png)
5. ==理解Window和WindowManager==
-
1.Window用于显示View和接收各种事件,Window有三种型:应用Window(每个Activity对应一个Window)、子Widow(不能单独存在,附属于特定Window)、系统window(toast和状态栏)
-
2.Window分层级,应用Window在1-99、子Window在1000-1999、系统Window在2000-2999. WindowManager提供了增改View的三个功能。
-
3.Window是个抽象概念:每一个Window对应着一个ViewRootImpl,Window通过ViewRootImpl来和View建立联系,View是Window存在的实体,只能通过WindowManager来访问Window。
-
4.WindowManager的实现是WindowManagerImpl,其再委托WindowManagerGlobal来对Window进行操作,其中有四种List分别储存对应的View、ViewRootImpl、WindowManger.LayoutParams和正在被删除的View。
-
5.Window的实体是存在于远端的WindowMangerService,所以增删改Window在本端是修改上面的几个List然后通过ViewRootImpl重绘View,通过WindowSession(每Window个对应一个)在远端修改Window。
-
6.Activity创建Window:Activity会在attach()中创建Window并设置其回调(onAttachedToWindow()、dispatchTouchEvent()),Activity的Window是由Policy类创建PhoneWindow实现的。然后通过Activity#setContentView()调用PhoneWindow的setContentView。
6. 应用程序安装到手机上时发生了什么?
![](https://i-blog.csdnimg.cn/blog_migrate/d3c25096e4b6a9dddd531a48fef9827a.png)
- 复制APK到/data/app目录下,解压并扫描安装包。
- 资源管理器解析APK里的资源文件。
- 解析AndroidManifest.xml文件,并在/data/data/目录下创建对应的应用数据目录。
- 然后对dex文件进行优化,并保存在dalvik-cache目录下。
- ,将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
- 安装完成后,发送广播。
7. Android的打包流程?(即清点击 Android Studio 的 build 按钮后发生了什么?)apk里有哪些东西?签名算法的原理?
apk打包流程
Android的包文件APK分为两个部分:代码和资源,所以==打包方面也分为资源打包和代码打包两个方面==,下面就来分析资源和代码的编译打包原理。
![](https://i-blog.csdnimg.cn/blog_migrate/201a072e80193a6d94f514018dad3b7c.png)
- 通过AAPT工具进行资源文件(包括AndroidManifest.xml、布局文件、各种xml资源等)的打包,生成R.java文件。
- 通过AIDL工具处理AIDL文件,生成相应的Java文件。
- 通过Java Compiler编译R.java、Java接口文件、Java源文件,生成.class文件。
- 通过dex命令,将.class文件和第三方库中的.class文件处理生成classes.dex,该过程主要完成Java字节码转换成Dalvik字节码,压缩常量池以及清除冗余信息等工作。
- 通过ApkBuilder工具将资源文件、DEX文件打包生成APK文件。
- 通过Jarsigner工具,利用KeyStore对生成的APK文件进行签名。
- 如果是正式版的APK,还会利用ZipAlign工具进行对齐处理,对齐的过程就是将APK文件中所有的资源文件距离文件的起始距位置都偏移4字节的整数倍,这样通过内存映射访问APK文件的速度会更快,并且会减少其在设备上运行时的内存占用。
==apk组成==
-
dex:最终生成的Dalvik字节码。
-
res:存放资源文件的目录。
-
asserts:额外建立的资源文件夹。
-
lib:如果存在的话,存放的是ndk编出来的so库。
-
META-INF:存放签名信息
MANIFEST.MF(清单文件):其中每一个资源文件都有一个SHA-256-Digest签名,MANIFEST.MF文件的SHA256(SHA1)并base64编码的结果即为CERT.SF中的SHA256-Digest-Manifest值。
CERT.SF(待签名文件):除了开头处定义的SHA256(SHA1)-Digest-Manifest值,后面几项的值是对MANIFEST.MF文件中的每项再次SHA256并base64编码后的值。
CERT.RSA(签名结果文件):其中包含了公钥、加密算法等信息。首先对前一步生成的MANIFEST.MF使用了SHA256(SHA1)-RSA算法,用开发者私钥签名,然后在安装时使用公钥解密。最后,将其与未加密的摘要信息(MANIFEST.MF文件)进行对比,如果相符,则表明内容没有被修改
-
androidManifest.xml:程序的全局清单配置文件。
-
resources.arsc:编译后的二进制资源文件。
==为什么要签名==
- 确保Apk来源的真实性。
- 确保Apk没有被第三方篡改。
什么是签名
签名是在Apk中写入一个“指纹”。指纹写入以后,Apk中有任何修改,都会导致这个指纹无效,Android系统在安装Apk进行签名校验时就会不通过,从而保证了安全性。
数字摘要
对一个任意长度的数据,通过一个Hash算法计算后,都可以得到一个固定长度的二进制数据,这个数据就称为“摘要”。
补充:
- 散列算法的基础原理:将数据(如一段文字)运算变为另一固定长度值。
- SHA-1:在密码学中,SHA-1(安全散列算法1)是一种加密散列函数,它接受输入并产生一个160 位(20 字节)散列值,称为消息摘要 。
- MD5:MD5消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。
- SHA-2:名称来自于安全散列算法2(英语:Secure Hash Algorithm 2)的缩写,一种密码散列函数算法标准,其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
特征:
- 唯一性
- 固定长度:比较常用的Hash算法有MD5和SHA1,MD5的长度是128拉,SHA1的长度是160位。
- 不可逆性
签名和校验的主要过程
签名就是在摘要的基础上再进行一次加密,对摘要加密后的数据就可以当作数字签名。
签名过程:
- 1、计算摘要:通过Hash算法提取出原始数据的摘要。
- 2、计算签名:再通过基于密钥(私钥)的非对称加密算法对提取出的摘要进行加密,加密后的数据就是签名信息。
- 3、写入签名:将签名信息写入原始数据的签名区块内。
校验过程:
- 1、首先用同样的Hash算法从接收到的数据中提取出摘要。
- 2、解密签名:使用发送方的公钥对数字签名进行解密,解密出原始摘要。
- 3、比较摘要:如果解密后的数据和提取的摘要一致,则校验通过;如果数据被第三方篡改过,解密后的数据和摘要将会不一致,则校验不通过。
数字证书
如何保证公钥的可靠性呢?答案是数字证书,数字证书是身份认证机构(Certificate Authority)颁发的,包含了以下信息:
- 证书颁发机构
- 证书颁发机构签名
- 证书绑定的服务器域名
- 证书版本、有效期
- 签名使用的加密算法(非对称算法,如RSA)
- 公钥等
接收方收到消息后,先向CA验证证书的合法性,再进行签名校验。
注意:Apk的证书通常是自签名的,也就是由开发者自己制作,没有向CA机构申请。Android在安装Apk时并没有校验证书本身的合法性,只是从证书中提取公钥和加密算法,这也正是对第三方Apk重新签名后,还能够继续在没有安装这个Apk的系统中继续安装的原因。
keystore和证书格式
keystore文件中包含了私钥、公钥和数字证书。根据编码不同,keystore文件分为很多种,Android使用的是Java标准keystore格式JKS(Java Key Storage),所以通过Android Studio导出的keystore文件是以.jks结尾的。
keystore使用的证书标准是X.509,X.509标准也有多种编码格式,常用的有两种:pem(Privacy Enhanced Mail)和der(Distinguished Encoding Rules)。jks使用的是der格式,Android也支持直接使用pem格式的证书进行签名。
两种证书编码格式的区别:
- DER(Distinguished Encoding Rules)
二进制格式,所有类型的证书和私钥都可以存储为der格式。 - PEM(Privacy Enhanced Mail)
base64编码,内容以-----BEGIN xxx----- 开头,以-----END xxx----- 结尾。
jarsigner和apksigner的区别
Android提供了两种对Apk的签名方式,一种是基于JAR的签名方式,另一种是基于Apk的签名方式,它们的主要区别在于使用的签名文件不一样:jarsigner使用keystore文件进行签名;apksigner除了支持使用keystore文件进行签名外,还支持直接指定pem证书文件和私钥进行签名。
在签名时,除了要指定keystore文件和密码外,也要指定alias和key的密码,这是为什么呢?
keystore是一个密钥库,也就是说它可以存储多对密钥和证书,keystore的密码是用于保护keystore本身的,一对密钥和证书是通过alias来区分的。所以jarsigner是支持使用多个证书对Apk进行签名的,apksigner也同样支持。
Android Apk V1 签名原理
- 1、解析出 CERT.RSA 文件中的证书、公钥,解密 CERT.RSA 中的加密数据。
- 2、解密结果和 CERT.SF 的指纹进行对比,保证 CERT.SF 没有被篡改。
- 3、而 CERT.SF 中的内容再和 MANIFEST.MF 指纹对比,保证 MANIFEST.MF 文件没有被篡改。
- 4、MANIFEST.MF 中的内容和 APK 所有文件指纹逐一对比,保证 APK 没有被篡改。
==V1签名和V2签名==
- V1签名靠META_INFO文件夹下的签名文件
- V2验证压缩文件的所有字节,因此,在签名后无法再更改
- 只勾选v1签名并不会影响什么,但是在7.0上不会使用更安全的验证方式
- 只勾选V2签名7.0以下会直接安装完显示未安装,7.0以上则使用了V2的方式验证
- V1和V2可以同是勾选
==v3签名key和v2还有v1有什么区别==?
- 在v1版本的签名中,签名以文件的形式存在于apk包中,这个版本的apk包就是一个标准的zip包,V2和V1的差别是V2是对整个zip包进行签名,而且在zip包中增加了一个apk signature block,里面保存签名信息。
- v2版本签名块(APK Signing Block)本身又主要分成三部分:
- SignerData(签名者数据):主要包括签名者的证书,整个APK完整性校验hash,以及一些必要信息
- Signature(签名):开发者对SignerData部分数据的签名数据
- PublicKey(公钥):用于验签的公钥数据
- v3版本签名块也分成同样的三部分,与v2不同的是在SignerData部分,v3新增了attr块,其中是由更小的level块组成。每个level块中可以存储一个证书信息。前一个level块证书验证下一个level证书,以此类推。最后一个level块的证书,要符合SignerData中本身的证书,即用来签名整个APK的公钥所属于的证书。
8. 说下Android虚拟机和Java虚拟机的区别?
JVM和Dalvik虚拟机(DVM)的区别:
- JVM编译产物:java --javac-> .class --jar-> .jar
架构:堆和栈的架构 - DVM编译产物:java --javac-> .class -> dx.bat -> .dex
架构:寄存器
==Android2个虚拟机的区别==(一个5.0之前,一个5.0之后)?
Dalvik:Dalvik是Google公司自己设计用于Android平台的Java虚拟机,它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
ART:ART代表Android Runtime,其处理应用程序执行的方式完全不同于Dalvik,Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。ART则完全改变了这套做法,在应用安装的时候就预编译字节码为机器语言,这一机制叫Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。
ART的优点:
- 系统性能的显著提升。
- 应用启动更快、运行更快、体验更流畅、触感反馈更及时。
- 更长的电池续航能力。
- 支持更低的硬件。
ART缺点:
- 更大的存储空间占用,可能会增加10%-20%。
- 更长的应用安装时间。
9. ==为什么系统不建议在子线程访问UI==?
- Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。
这时你可能会问为何系统不对UI控件的访问加上锁机制呢?
- 加锁机制会让UI访问逻辑变的复杂
- 加锁机制会降低UI的访问效率,因为加锁会阻塞某些线程的执行。
10. ==怎么保证应用启动不卡顿? 黑白屏怎么处理?==
应用启动速度,取决于你在application里面时候做了什么事情,比如你集成了很多sdk,并且sdk的init操作都需要在主线程里实现所以会有卡顿的感觉。在非必要的情况下可以把加载延后或则开启子线程处理
另外,影响界面卡顿的两大因素,分别是界面绘制和数据处理。
- ==布局优化==(使用include,merge标签,复杂布局推荐使用ConstraintLayout等)
- ==onCreate() 中不执行耗时操作 把页面显示的 View 细分一下,放在 AsyncTask 里逐步显示,用 Handler 更好==。这样用户的看到的就是有层次有步骤的一个个的 View 的展示,不会是先看到一个黑屏,然后一下显示所有 View。最好做成动画,效果更自然。
- 利用多线程的目的就是==尽可能的减少 onCreate() 和 onReume()的时间==,使得用户能尽快看到页面,操作页面。
- 减少主线程阻塞时间。
- 提高 Adapter 和 AdapterView 的效率
黑白屏处理:
- ==黑白屏产生原因==:当我们在启动一个应用时,系统会去检查是否已经存在这样一个进程,如果不存在,系统的服务会先检查startActivity中的intent的信息,然后在去创建进程,最后启动Acitivy,即冷启动。而启动出现白黑屏的问题,就是在这段时间内产生的。系统在绘制页面加载布局之前,首先会初始化窗口(Window),而在进行这一步操作时,系统会根据我们设置的Theme来指定它的Theme 主题颜色,我们在Style中的设置就决定了显示的是白屏还是黑屏。
- windowIsTranslucent和windowNoTitle,将这两个属性都设置成true (会有明显的卡顿体验,不推荐)
- 如果启动页只是是一张图片,那么为启动页专一设置一个新的主题,设置主题的android:windowBackground属性为启动页背景图即可
- 使用layer-list制作一张图片launcher_layer.xml,将其设置为启动页专一主题的背景,并将其设置为启动页布局的背景。
11. 如何通过Gradle配置多渠道包?
-
首先要了解设置多渠道的原因。在安装包中添加不同的标识,配合自动化埋点,应用在请求网络的时候携带渠道信息,方便后台做运营统计,比如说统计我们的应用在不同应用市场的下载量等信息。
-
这里以友盟统计为例
- 首先在manifest.xml文件中设置动态渠道变量:
![](https://i-blog.csdnimg.cn/blog_migrate/f263fde13c9e1444010e6f4352813e84.png)
- 接着在app目录下的build.gradle中配置productFlavors,也就是配置打包的渠道:
![](https://i-blog.csdnimg.cn/blog_migrate/a4a449c876c05a1d57d9bba8f13ea593.png)
最后在编辑器下方的Teminal输出命令行
- 执行./gradlew assembleRelease ,将会打出所有渠道的release包;
执行./gradlew assembleVIVO,将会打出VIVO渠道的release和debug版的包;
执行./gradlew assembleVIVORelease将生成VIVO的release包。
12. 对于应用更新这块是如何做的? (灰度,强制更新、分区域更新)
- 内部更新:
- 通过接口获取线上版本号,versionCode
比较线上的versionCode - 和本地的versionCode,弹出更新窗口
- 下载APK文件(文件下载)
- 安装APK
- 灰度更新:
- 找单一渠道投放特别版本。
- 做升级平台的改造,允许针对部分用户推送升级通知甚至版本强制升级。
- 开放单独的下载入口。
- 是两个版本的代码都打到app包里,然后在app端植入测试框架,用来控制显示哪个版本。测试框架负责与服务器端api通信,由服务器端控制app上A/B版本的分布,可以实现指定的一组用户看到A版本,其它用户看到B版本。服务端会有相应的报表来显示A/B版本的数量和效果对比。最后可以由服务端的后台来控制,全部用户在线切换到A或者B版本~
- 无论哪种方法都需要做好版本管理工作,分配特别的版本号以示区别。 当然,既然是做灰度,数据监控(常规数据、新特性数据、主要业务数据)还是要做到位,该打的数据桩要打。 还有,灰度版最好有收回的能力,一般就是强制升级下一个正式版。
- 强制更新:
一般的处理就是进入应用就弹窗通知用户有版本更新,弹窗可以没有取消按钮并不能取消。这样用户就只能选择更新或者关闭应用了,当然也可以添加取消按钮,但是如果用户选择取消则直接退出应用。
- 增量更新:
二进制差分工具bsdiff是相应的补丁合成工具,根据两个不同版本的二进制文件,生成补丁文件.patch文件。通过bspatch使旧的apk文件与不定文件合成新的apk。 注意通过apk文件的md5值进行区分版本。