万安卓笔记
万安卓
知识点
OOM
如何避免OOM总结
- 减小对象内存的占用
- 使用更轻量级的数据结构
如使用SplashArray或ArrayMap替代HashMap
- 减少使用枚举
因为枚举会默认至少占用两个静态变量的空间
- 优化Bitmap对象的内存占用
- 使用更轻量级的数据结构
Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,通常来说有以下2个措施: `inSampleSize`:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。 `decode format`:解码格式,选择`ARGB_8888/RBG_565/ARGB_4444/ALPHA_8`,存在很大差异。
- 使用更小的图片
内存对象的复用
- 复用系统自带的资源
- 注意在ListView/GridView等出现大量重复子组件的视图里对ConvertView的复用
- Bitmap对象的复用。在ListView与GridView等显示大量图片的控件里,需要使用LRU的机制来缓存处理好的Bitmap
- 避免在onDraw方法里面执行对象的创建
类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动
- StringBuilder
在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”
注意内存泄漏
- 注意Activity的泄漏
通常来说,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广
- 内部类引用导致Activity的泄漏
- Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏
内部类引起的泄漏不仅仅会发生在Activity上,其他任何内部类出现的地方,都需要特别留意!我们可以考虑尽量使用static类型的内部类,同时使用WeakReference的机制来避免因为互相引用而出现的泄露
- 考虑使用Application Context而不是Activity Context
对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。
- 注意临时Bitmap对象的及时回收
虽然在大多数情况下,我们会对Bitmap增加缓存机制,但是在某些时候,部分Bitmap是需要及时回收的。例如临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。
需要特别留意的是Bitmap类里面提供的createBitmap()方法,如图http://www.jcodecraeer.com/uploads/20150920/1442716510719104.jpg这个函数返回的bitmap有可能和source bitmap是同一个,在回收的时候,需要特别检查source bitmap与return bitmap的引用是否相同,只有在不等的情况下,才能够执行source bitmap的recycle方法。
- 注意监听器的注销
在Android程序里面存在很多需要register与unregister的监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的listener,需要记得及时remove这个listener
- 注意缓存容器中的对象泄漏
有时候,我们为了提高对象的复用性把某些对象放到缓存容器中,可是如果这些对象没有及时从容器中清除,也是有可能导致内存泄漏的
- 注意Cursor对象是否及时关闭
在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭
内存使用策略优化
-
谨慎使用large heap
正如前面提到的,Android设备根据硬件与软件的设置差异而存在不同大小的内存空间,他们为应用程序设置了不同大小的Heap限制阈值。你可以通过调用getMemoryClass()来获取应用的可用Heap大小。在一些特殊的情景下,你可以通过在manifest的application标签下添加largeHeap=true的属性来为应用声明一个更大的heap空间。然后,你可以通过getLargeMemoryClass()来获取到这个更大的heap size阈值。
然而,声明得到更大Heap阈值的本意是为了一小部分会消耗大量RAM的应用(例如一个大图片的编辑应用)。不要轻易的因为你需要使用更多的内存而去请求一个大的Heap Size。只有当你清楚的知道哪里会使用大量的内存并且知道为什么这些内存必须被保留时才去使用large heap。因此请谨慎使用large heap属性。使用额外的内存空间会影响系统整体的用户体验,并且会使得每次gc的运行时间更长。在任务切换时,系统的性能会大打折扣。另外, large heap并不一定能够获取到更大的heap。在某些有严格限制的机器上,large heap的大小和通常的heap size是一样的。因此即使你申请了large heap,你还是应该通过执行getMemoryClass()来检查实际获取到的heap大小。 -
资源文件需要选择合适的文件夹进行存放
我们知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下
-
try catch某些大内存分配的操作
在某些情况下,我们需要事先评估那些可能发生OOM的代码,对于这些可能发生OOM的代码,加入catch机制,可以考虑在catch里面尝试一次降级的内存分配操作。例如decode bitmap的时候,catch到OOM,可以尝试把采样比例再增加一倍之后,再次尝试decode。
-
谨慎使用static对象因为static的生命周期过长,和应用的进程保持一致,使用不当很可能导致对象泄漏,在Android中应该谨慎使用static对象
-
特别留意单例对象中不合理的持有
虽然单例模式简单实用,提供了很多便利性,但是因为单例的生命周期和应用保持一致,使用不合理很容易出现持有对象的泄漏。
-
优化布局层次,减少内存消耗
越扁平化的视图布局,占用的内存就越少,效率越高。我们需要尽量保证布局足够扁平化,当使用系统提供的View无法实现足够扁平的时候考虑使用自定义View来达到目的。
-
谨慎使用“抽象”编程
很多时候,开发者会使用抽象类作为”好的编程实践”,因为抽象能够提升代码的灵活性与可维护性。然而,抽象会导致一个显著的额外内存开销:他们需要同等量的代码用于可执行,那些代码会被mapping到内存中,因此如果你的抽象没有显著的提升效率,应该尽量避免他们。
-
使用nano protobufs序列化数据Protocol buffers是由Google为序列化结构数据而设计的,一种语言无关,平台无关,具有良好的扩展性。类似XML,却比XML更加轻量,快速,简单。如果你需要为你的数据实现序列化与协议化,建议使用nano protobufs。关于更多细节,请参考 protobuf readme的”Nano version”章节
-
谨慎使用依赖注入框架
代码是简化了不少。然而,那些注入框架会通过扫描你的代码执行许多初始化的操作,这会导致你的代码需要大量的内存空间来mapping代码,而且mapped pages会长时间的被保留在内存中。除非真的很有必要,建议谨慎使用这种技术。
-
谨慎使用多进程
使用多进程可以把应用中的部分组件运行在单独的进程当中,这样可以扩大应用的内存占用范围,但是这个技术必须谨慎使用,绝大多数应用都不应该贸然使用多进程,一方面是因为使用多进程会使得代码逻辑更加复杂,另外如果使用不当,它可能反而会导致显著增加内存。当你的应用需要运行一个常驻后台的任务,而且这个任务并不轻量,可以考虑使用这个技术
一个典型的例子是创建一个可以长时间后台播放的Music Player。如果整个应用都运行在一个进程中,当后台播放的时候,前台的那些UI资源也没有办法得到释放。类似这样的应用可以切分成2个进程:一个用来操作UI,另外一个给后台的Service。
-
使用ProGuard来剔除不需要的代码
ProGuard能够通过移除不需要的代码,重命名类,域与方法等等对代码进行压缩,优化与混淆。使用ProGuard可以使得你的代码更加紧凑,这样能够减少mapping代码所需要的内存空间。
-
谨慎使用第三方libraries
很多开源的library代码都不是为移动网络环境而编写的,如果运用在移动设备上,并不一定适合。即使是针对Android而设计的library,也需要特别谨慎,特别是在你不知道引入的library具体做了什么事情的时候。例如,其中一个library使用的是nano protobufs, 而另外一个使用的是micro protobufs。这样一来,在你的应用里面就有2种protobuf的实现方式。这样类似的冲突还可能发生在输出日志,加载图片,缓存等等模块里面。另外不要为了1个或者2个功能而导入整个library,如果没有一个合适的库与你的需求相吻合,你应该考虑自己去实现,而不是导入一个大而全的解决方案
-
考虑不同的实现方式来优化内存占用
在某些情况下,设计的某个方案能够快速实现需求,但是这个方案却可能在内存占用上表现的效率不够好。例如:
对于上面这样一个时钟表盘的实现,最简单的就是使用很多张包含指针的表盘图片,使用帧动画实现指针的旋转。但是如果把指针扣出来,单独进行旋转绘制,显然比载入N多张图片占用的内存要少很多。当然这样做,代码复杂度上会有所增加,这里就需要在优化内存占用与实现简易度之间进行权衡了。
手机支付
- 支付流程_从生活出发
- 选择商品-->goodName,goodId,price,count
- 选择支付方式-->payType:1,支付宝;2,银联;3:微信
- 处理支付结果-->支付成功(购物流程),支付失败(重试,放弃)
- 支付流程_从程序角度出发
- 选择商品,组装支付数据-->拼接请求的jsonString
- 把支付数据post到后台server-->发送一个请求request
- 后台server(支付宝的服务)生成支付串码--->处理第二步的reponse
- 在客户端使用第三方平台的api调用插件完成支付-->调用第三方平台jar包里面的方法(集成过程),这一步才用到支付宝sdk
- 处理支付结果-->利用没有平台特有的通知机制处理支付结果
- 支付串码是啥?
支付方法需要的支付参数
- 支付流程_简明说法发起支付请求
- 拿到支付串码
- 调用api支付
- 处理支付结果
- 同步返回:支付后通知我们自己的apk
- 异步通知:支付后通知我们的server
支付宝
- demo运行问题:需要
ALIPAYPartnerID,ALIPAYSellerID,ALIPAYMD5KEY,ALIPAYPartnerPrivKey,ALIPAYPubKey
才可以运行.但是如果直接下载的demo.这些字段都是"",无法看到效果,这些数据的获得.是用公司运行去做; - 支付宝demo的流程和实际开发不一致.实际开发.生成支付串码的过程应该交给服务器.因为sign需要支付宝的privatekey,如果放到apk里面是会泄露privatekey的,所以最后简化之后的支付宝集成就几行代码;
RSA:RSA是一种公钥加密算法。能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。主要用于公钥加密私钥解密、私钥签名公钥验签。
- 支付宝公钥:开发者请求支付宝并获得返回时,开发者用于验签使用的公钥
- 我们自己要和支付宝签约(商户签约).-->运营
- 秘钥配置-->协助运营完成秘钥的配置(公钥互换),可能程序员会参与
- 集成支付宝-->必须是程序员去做.
RSA密钥生成命令
- 生成RSA私钥
openssl>genrsa -out rsa_private_key.pem 1024
- 生成RSA公钥
openssl>rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
- 将RSA私钥转换成PKCS8格式
openssl>pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt
注意:“>”符号后面的才是需要输入的命令。
E:\支付\支付宝\支付宝钱包支付接口开发包2.0标准版(20160120)\DEMO\openssl\bin\1目录下有俩个文件
开发者将私钥保留,将公钥提交给支付宝网关,用于信息加密及解密。
安卓消息机制
1、 概述
Handler 、 Looper 、Message 这三者都是Android异步消息处理线程相关的概念
异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待
Looper负责的就是创建一个MessageQueue,然后进入一个无限循环体不断从该MessageQueue中读取消息,而消息的创建者就是一个或多个Handler。
Looper主要作用:
1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
好了,我们的异步消息处理线程已经有了消息队列(MessageQueue),也有了在无限循环体中取出消息的哥们,现在缺的就是发送消息的对象了,于是乎:Handler登场了。
总结一下
- 首先
Looper.prepare()
在本线程中保存一个Looper
实例,然后该实例中保存一个MessageQueue
对象;因为Looper.prepare()
在一个线程中只能调用一次,所以MessageQueue
在一个线程中只会存在一个。 -
Looper.loop()
会让当前线程进入一个无限循环,不断从MessageQueue
的实例中读取消息,然后回调msg.target.dispatchMessage(msg)
方法。 -
Handler
的构造方法,会首先得到当前线程中保存的Looper
实例,进而与Looper
实例中的MessageQueue
想关联。 -
Handler
的sendMessage
方法,会给msg的target赋值为handler自身,然后加入MessageQueue
中。 - 在构造Handler实例时,我们会重写handleMessage方法,也就是
msg.target.dispatchMessage(msg)
最终调用的方法。
好了,总结完成,大家可能还会问,那么在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。
来张图解
[图片上传失败...(image-e65907-1516641993449)]
后话
其实Handler不仅可以更新UI,你完全可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建Handler实例的线程中运行。
Android事件分发机制https://www.jianshu.com/p/38015afcdb58
1.2 事件分发的本质将点击事件(MotionEvent)向某个View进行传递并最终得到处理
即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。
1.3 事件在哪些对象之间进行传递?
答:Activity、ViewGroup、View
Android中事件分发顺序,一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View
Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成的
事件分发过程由哪些方法协作完成?
答: dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()
[图片上传失败...(image-251ba7-1516641993449)]
[图片上传失败...(image-df094b-1516641993449)]
三者关系
下面将用一段伪代码来阐述上述三个方法的关系和点击事件传递规则
// 点击事件产生后,会直接调用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
//代表是否消耗事件
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
//如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
//则该点击事件则会交给当前View进行处理
//即调用onTouchEvent ()方法去处理点击事件
consume = onTouchEvent (ev) ;
} else {
//如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
//则该点击事件则会继续传递给它的子元素
//子元素的dispatchTouchEvent()就会被调用,重复上述过程
//直到点击事件被最终处理为止
consume = child.dispatchTouchEvent (ev) ;
}
return consume;
}
Context
提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被Android系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。就是说,它描述一个应用程序环境的信息(即上下文);是一个抽象类,Android提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动Activity,发广播,接受Intent等)
既然上面Context是一个抽象类,那么肯定有他的实现类咯,我们在Context的源码中通过IDE可以查看到他的子类最终可以得到如下关系图:
[图片上传失败...(image-6206ab-1516641993449)]
Context类本身是一个纯abstract类,它有两个具体的实现子类:ContextImpl和ContextWrapper。
其中ContextWrapper类,如其名所言,这只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。
ContextThemeWrapper类,如其名所言,其内部包含了与主题(Theme)相关的接口,这里所说的主题就是指在AndroidManifest.xml中通过android:theme为Application元素或者Activity元素指定的主题。当然,只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承于ContextWrapper,Application同理。而ContextImpl类则真正实现了Context中的所有函数,应用程序中所调用的各种Context类的方法,其实现均来自于该类。
一句话总结:Context的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。Activity,Application,Service虽都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。
一个应用程序有几个Context
Context数量=Activity数量+Service数量+1。
如何获取Context
通常我们想要获取Context对象,主要有以下四种方法
1:View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
2:Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。
3:ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
4:Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。
getApplication()和getApplicationContext()
通过上面的代码,打印得出两者的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,因为前面已经说过了,Application本身就是一个Context,所以这里获取getApplicationContext()得到的结果就是Application本身的实例。 实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了。
正确使用Context
一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:
1:当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
2:不要让生命周期长于Activity的对象持有到Activity的引用。
3:尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
安卓系统启动流程
[图片上传失败...(image-6bf078-1516641993449)]
下面来详解这张图
从系统的角度上来讲,Android系统的启动过程可以分为 bootloader 引导,装载和启动 linux内核 启动Android系统
BootLoader
bootloader 相当于电脑上的Bios 他的主要作用就是初始化基本的硬件设备,建立内存空间映射, 为装载linux内核准备好运行环境,当linux内核加载完毕之后,bootloder就会从内存中清除
对于FastBoot和Recover估计好多童鞋都不理解,fastboot是Android设计的一套通过usb来更新手机分区的映像协议,不过大部分厂商都搞掉了 google的nexus 上应该有的
Recovery模式是Android特有的升级系统,通过这个可以进行手机恢复出厂设置,或执行OTA,补丁和固件升级,实质是启动了一个文本模式的Linux。
bootloader启动后会向内存中装载boot.img镜像文件,这个镜像文件存放的是linux内核和一个根文件系统,linux内核进行初始化之后,装载完文件系统,就启动了init进程
Init进程
init进程是Linux创建的第一个进程,init进程会解析linux的脚本文件init.rc,根据这个文件的内容 init进程会装载Android的文件系统,创建系统目录,初始化属性系统,启动Android系统的重要的守护进程等,如上图,
下面简单的介绍一个下init进程fork出的几个重要的进程及阶段:
- Zygote进程: 当init进程初始化结束的时候,会启动Zygote进程,看上图可以看出来 zygote进程扶着fork出应用进程,是所有进程的父进程,Zygote进程初始化的时候会创建Dalivik虚拟机,预装载系统的资源文件和java类,所有从Zygote进程fork出的用户进程将继承和共享这些资源,不用浪费时间重新加载(看到这里是不是感觉突然明白了什么?)干完这些之后,Zygote进程也将变为守护进程,负责响应启动APK应用的启动请求。
- SystemServer进程:SystemServer进程是Zygote进程fork出的第一个进程,也是整个Android系统的核心进程,在SystemServer主要运行的是Binder服务,SystemServer首要启动本地服务 SensorService 接着启动 ActivityManagerService,WindowsManagerService,PackgeManagerService等在内的所有java服务。
- MediaServer进程:也是由init进程启动,他包含了一些多媒体相关的本地Binder服务,包括CameraService,AudioFilingerService ,和AudioPolicyService。
- 当SystemServer加载完所有Java服务后最后会调用ActivityManagerService的SystemReady()方法,其实是为了打开Launcher。还要去PackageManager去取一下所有类型为ACTION_MAIN的intent 然后才开的Launcher。
Android Application启动流程
1, App基础理论
要想优化App启动时间, 第一步就是了解App启动进程的工作原理. 有几个基础理论:
Android Application与其他移动平台有两个重大不同点:
- 每个Android App都在一个独立空间里, 意味着其运行在一个单独的进程中, 拥有自己的VM, 被系统分配一个唯一的user ID.
- Android App由很多不同组件组成, 这些组件还可以启动其他App的组件. 因此, Android App并没有一个类似程序入口的main()方法.
Android Application组件包括:
Android进程与Linux进程一样. 默认情况下, 每个apk运行在自己的Linux进程中. 另外, 默认一个进程里面只有一个线程—主线程. 这个主线程中有一个Looper实例, 通过调用Looper.loop()从Message队列里面取出Message来做相应的处理.
那么, 这个进程何时启动的呢?
简单的说, 进程在其需要的时候被启动. 任意时候, 当用户或者其他组件调取你的apk中的任意组件时, 如果你的apk没有运行, 系统会为其创建一个新的进程并启动. 通常, 这个进程会持续运行直到被系统杀死. 关键是: 进程是在被需要的时候才创建的.
举个例子, 如果你点击email中的超链接, 会在浏览器里面打开一个网页. Email App和浏览器App是两个不同的App, 运行在不同的进程中. 这次点击事件促使Android系统去创建了一个新的进程来实例化浏览器的组件.
首先, 让我们快速看下Android启动流程:
- 与众多基于Linux内核的系统类似, 启动系统时, bootloader启动内核和init进程. init进程分裂出更多名为”daemons(守护进程)”的底层的Linux进程, 诸如android debug deamon, USB deamon等. 这些守护进程处理底层硬件相关的接口.
- 随后, init进程会启动一个非常有意思的进程—“Zygote”. 顾名思义, 这是一个Android平台的非常基础的进程. 这个进程初始化了第一个VM, 并且预加载了framework和众多App所需要的通用资源.
- 然后它开启一个Socket接口来监听请求, 根据请求孵化出新的VM来管理新的App进程. 一旦收到新的请求, Zygote会基于自身预先加载的VM来孵化出一个新的VM创建一个新的进程.
- 启动Zygote之后, init进程会启动runtime进程. Zygote会孵化出一个超级管理进程—System Server. SystemServer会启动所有系统核心服务, 例如Activity Manager Service, 硬件相关的Service等. 到此, 系统准备好启动它的第一个App进程—Home进程了.
2, 启动App流程
用户点击Home上的一个App图标, 启动一个应用时:
[图片上传失败...(image-deb76a-1516641993449)]
Click事件会调用startActivity(Intent), 会通过Binder IPC机制, 最终调用到ActivityManagerService. 该Service会执行如下操作:
- 第一步通过PackageManager的resolveIntent()收集这个intent对象的指向信息.指向信息被存储在一个intent对象中.
- 下面重要的一步是通过grantUriPermissionLocked()方法来验证用户是否有足够的权限去调用该intent对象指向的Activity.
- 如果有权限, ActivityManagerService会检查并在新的task中启动目标activity.
现在, 是时候检查这个进程的ProcessRecord是否存在了,如果ProcessRecord是null, ActivityManagerService会创建新的进程来实例化目标activity.
2.1 创建进程
ActivityManagerService调用startProcessLocked()方法来创建新的进程, 该方法会通过前面讲到的socket通道传递参数给Zygote进程. Zygote孵化自身, 并调用ZygoteInit.main()方法来实例化ActivityThread对象并最终返回新进程的pid.
ActivityThread随后依次调用Looper.prepareLoop()和Looper.loop()来开启消息循环.[图片上传失败...(image-cd6dad-1516641993449)]
2.2 绑定Application
接下来要做的就是将进程和指定的Application绑定起来. 这个是通过上节的ActivityThread对象中调用bindApplication()
方法完成的. 该方法发送一个BIND_APPLICATION的消息到消息队列中, 最终通过handleBindApplication()
方法处理该消息. 然后调用makeApplication()
方法来加载App的classes到内存中.
流程如下:[图片上传失败...(image-732f65-1516641993449)]
2.3 启动Activity
经过前两个步骤之后, 系统已经拥有了该application的进程. 后面的调用顺序就是普通的从一个已经存在的进程中启动一个新进程的activity了.
实际调用方法是realStartActivity()
, 它会调用application线程对象中的sheduleLaunchActivity()
发送一个LAUNCH_ACTIVITY消息到消息队列中, 通过handleLaunchActivity()
来处理该消息.
假设点击的是一个视频浏览的App, 其流程如下:[图片上传失败...(image-2d5d15-1516641993449)]