Android 内存优化[腾讯BugLy]

(copy from:https://mp.weixin.qq.com/s/2MsEAR9pQfMr1Sfs7cPdWQ)

导语

智能手机发展到今天已经有十几个年头,手机的软硬件都已经发生了翻天覆地的变化,特别是Android阵营,从一开始的一两百M到今天动辄4G,6G内存。然而大部分的开发者观看下自己的异常上报系统,还是会发现各种内存问题仍然层出不穷,各种OOM为crash率贡献不少。Android开发发展到今天也是已经比较成熟,各种新框架,新技术也是层出不穷,而内存优化一直都是Android开发过程一个不可避免的话题。 恰好最近做了内存优化相关的工作,这里也对Android内存优化相关的知识做下总结。

在开始文章之前推荐下公司同事翻译整理的《Android性能优化典范 – 第6季》,因为篇幅有限这里我对一些内容只做简单总结,同时如果有不正确内容也麻烦帮忙指正。

本文将会对Android内存优化相关的知识进行总结以及最后案例分析(一二部分是理论知识总结,你也可以直接跳到第三部分看案例):

、 Android内存分配回收机制
、Android常见内存问题和对应检测,解决方式。
、 JOOX内存优化案例
四 、总结

工欲善其事必先利其器,想要优化App的内存占用,那么还是需要先了解Android系统的内存分配和回收机制。

一、Android内存分配回收机制

参考Android 操作系统的内存回收机制[1],这里简单做下总结:

从宏观角度上来看Android系统可以分为三个层次

  1. Application Framework,
  2. Dalvik 虚拟机
  3. Linux内核。

这三个层次都有各自内存相关工作:

1. Application Framework

Anroid基于进程中运行的组件及其状态规定了默认的五个回收优先级:

img

  • Empty process(空进程)
  • Background process(后台进程)
  • Service process(服务进程)
  • Visible process(可见进程)
  • Foreground process(前台进程)

系统需要进行内存回收时最先回收空进程,然后是后台进程,以此类推最后才会回收前台进程(一般情况下前台进程就是与用户交互的进程了,如果连前台进程都需要回收那么此时系统几乎不可用了)。

由此也衍生了很多进程保活的方法(提高优先级,互相唤醒,native保活等等),出现了国内各种全家桶,甚至各种杀不死的进程。

Android中由ActivityManagerService 集中管理所有进程的内存资源分配。

2. Linux内核

img

参考QCon大会上阿里巴巴的Android内存优化分享[2],这里最简单的理解就是ActivityManagerService会对所有进程进行评分(存放在变量adj中),然后再讲这个评分更新到内核,由内核去完成真正的内存回收(lowmemorykiller, Oom_killer)。这里只是大概的流程,中间过程还是很复杂的,有兴趣的同学可以一起研究,代码在系统源码ActivityManagerService.java中。

3. Dalvik虚拟机

Android进程的内存管理分析[3],对Android中进程内存的管理做了分析。

Android中有Native Heap和Dalvik Heap。Android的Native Heap言理论上可分配的空间取决了硬件RAM,而对于每个进程的Dalvik Heap都是有大小限制的,具体策略可以看看android dalvik heap 浅析[4]。

Android App为什么会OOM呢?其实就是申请的内存超过了Dalvik Heap的最大值。这里也诞生了一些比较”黑科技”的内存优化方案,比如将耗内存的操作放到Native层,或者使用分进程的方式突破每个进程的Dalvik Heap内存限制。

Android Dalvik Heap与原生Java一样,将堆的内存空间分为三个区域,Young Generation,Old Generation, Permanent Generation。

img

最近分配的对象会存放在Young Generation区域,当这个对象在这个区域停留的时间达到一定程度,它会被移动到Old Generation,最后累积一定时间再移动到Permanent Generation区域。系统会根据内存中不同的内存数据类型分别执行不同的gc操作。

GC发生的时候,所有的线程都是会被暂停的。执行GC所占用的时间和它发生在哪一个Generation也有关系,Young Generation中的每次GC操作时间是最短的,Old Generation其次,Permanent Generation最长。

GC时会导致线程暂停,导致卡顿,Google在新版本的Android中优化了这个问题, 在ART中对GC过程做了优化揭秘 ART 细节 —— Garbage collection[5],据说内存分配的效率提高了10倍,GC的效率提高了2-3倍(可见原来效率有多低),不过主要还是优化中断和阻塞的时间,频繁的GC还是会导致卡顿。

上面就是Android系统内存分配和回收相关知识,回过头来看,现在各种手机厂商鼓吹人工智能手机,号称18个月不卡顿,越用越快,其实很大一部分Android系统的内存优化有关,无非就是利用一些比较成熟的基于统计,机器学习的算法定时清理数据,清理内存,甚至提前加载数据到内存。

二、Android常见内存问题和对应检测,解决方式

1. 内存泄露

不止Android程序员,内存泄露应该是大部分程序员都遇到过的问题,可以说大部分的内存问题都是内存泄露导致的,Android里也有一些很常见的内存泄露问题[6],这里简单罗列下:

  • 单例(主要原因还是因为一般情况下单例都是全局的,有时候会引用一些实际生命周期比较短的变量,导致其无法释放)
  • 静态变量(同样也是因为生命周期比较长)
  • Handler内存泄露[7]
  • 匿名内部类(匿名内部类会引用外部类,导致无法释放,比如各种回调)
  • 资源使用完未关闭(BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap)

对Android内存泄露业界已经有很多优秀的组件其中LeakCanary最为知名(Square出品,Square可谓Android开源界中的业界良心,开源的项目包括okhttp, retrofit,otto, picasso, Android开发大神Jake Wharton就在Square),其原理是监控每个activity,在activity ondestory后,在后台线程检测引用,然后过一段时间进行gc,gc后如果引用还在,那么dump出内存堆栈,并解析进行可视化显示。使用LeakCanary可以快速地检测出Android中的内存泄露。

正常情况下,解决大部分内存泄露问题后,App稳定性应该会有很大提升,但是有时候App本身就是有一些比较耗内存的功能,比如直播,视频播放,音乐播放,那么我们还有什么能做的可以降低内存使用,减少OOM呢?

2. 图片分辨率相关

分辨率适配问题。很多情况下图片所占的内存在整个App内存占用中会占大部分。我们知道可以通过将图片放到hdpi/xhdpi/xxhdpi等不同文件夹进行适配,通过xml android:background设置背景图片,或者通过BitmapFactory.decodeResource()方法,图片实际上默认情况下是会进行缩放的。在Java层实际调用的函数都是或者通过BitmapFactory里的decodeResourceStream函数

public static Bitmap decodeResourceStream(Resources res, TypedValue value,
        InputStream is, Rect pad, Options opts) 
{
    if (opts == null)
    {
        opts = new Options();
    }
    if (opts.inDensity == 0 && value != null)
    {
       final int density = value.density;
       if (density == TypedValue.DENSITY_DEFAULT)
       {
           opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
       }
       else if (density != TypedValue.DENSITY_NONE)
       {
           opts.inDensity = density;
       }
    }    
    if (opts.inTargetDensity == 0 && res != null)
    {
        opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
    }    
    return decodeStream(is, pad, opts);
}

decodeResource在解析时会对Bitmap根据当前设备屏幕像素密度densityDpi的值进行缩放适配操作,使得解析出来的Bitmap与当前设备的分辨率匹配,达到一个最佳的显示效果,并且Bitmap的大小将比原始的大,可以参考下腾讯Bugly的详细分析Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存?

关于Density、分辨率、-hdpi等res目录之间的关系:

img

举个例子,对于一张1280×720的图片,如果放在xhdpi,那么xhdpi的设备拿到的大小还是1280×720而xxhpi的设备拿到的可能是1920×1080,这两种情况在内存里的大小分别为:3.68M和8.29M,相差4.61M,在移动设备来说这几M的差距还是很大的。

尽管现在已经有比较先进的图片加载组件类似Glide,Facebook Freso, 或者老牌Universal-Image-Loader,但是有时就是需要手动拿到一个bitmap或者drawable,特别是在一些可能会频繁调用的场景(比如ListView的getView),怎样尽可能对bitmap进行复用呢?这里首先需要明确的是对同样的图片,要 尽可能复用,我们可以简单自己用WeakReference做一个bitmap缓存池,也可以用类似图片加载库写一个通用的bitmap缓存池,可以参考GlideBitmapPool[8]的实现。

我们也来看看系统是怎么做的,对于类似在xml里面直接通过android:background或者android:src设置的背景图片,以ImageView为例,最终会调用Resource.java里的loadDrawable:

Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException 
{
    // Next, check preloaded drawables. These may contain unresolved theme
    // attributes.
    final ConstantState cs;
    if (isColorDrawable)
    {
        cs = sPreloadedColorDrawables.get(key);
    }else{
        cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
    }

    Drawable dr;
    if (cs != null) {
        dr = cs.newDrawable(this);
    } else if (isColorDrawable) {
        dr = new ColorDrawable(value.data);
    } else {
        dr = loadDrawableForCookie(value, id, null);
    }

    ...

    return dr;
}

可以看到实际上系统也是有一份全局的缓存,sPreloadedDrawables, 对于不同的drawable,如果图片时一样的,那么最终只会有一份bitmap(享元模式),存放于BitmapState中,获取drawable时,系统会从缓存中取出这个bitmap然后构造drawable。而通过BitmapFactory.decodeResource()则每次都会重新解码返回bitmap。所以其实我们可以通过context.getResources().getDrawable再从drawable里获取bitmap,从而复用bitmap,然而这里也有一些坑,比如我们获取到的这份bitmap,假如我们执行了recycle之类的操作,但是假如在其他地方再使用它是那么就会有”Canvas: trying to use a recycled bitmap android.graphics.Bitmap”异常。

3. 图片压缩

BitmapFactory 在解码图片时,可以带一个Options,有一些比较有用的功能,比如:

  • inTargetDensity 表示要被画出来时的目标像素密度
  • inSampleSize 这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4
  • inJustDecodeBounds 字面意思就可以理解就是只解析图片的边界,有时如果只是为了获取图片的大小就可以用这个,而不必直接加载整张图片。
  • inPreferredConfig 默认会使用ARGB_8888,在这个模式下一个像素点将会占用4个byte,而对一些没有透明度要求或者图片质量要求不高的图片,可以使用RGB_565,一个像素只会占用2个byte,一下可以省下50%内存。
  • inPurgeableinInputShareable 这两个需要一起使用,BitmapFactory.java的源码里面有注释,大致意思是表示在系统内存不足时是否可以回收这个bitmap,有点类似软引用,但是实际在5.0以后这两个属性已经被忽略,因为系统认为回收后再解码实际会反而可能导致性能问题
  • inBitmap 官方推荐使用的参数,表示重复利用图片内存,减少内存分配,在4.4以前只有相同大小的图片内存区域可以复用,4.4以后只要原有的图片比将要解码的图片大既可以复用了。

4. 缓存池大小

现在很多图片加载组件都不仅仅是使用软引用或者弱引用了,实际上类似Glide 默认使用的事LruCache,因为软引用 弱引用都比较难以控制,使用LruCache可以实现比较精细的控制,而默认缓存池设置太大了会导致浪费内存,设置小了又会导致图片经常被回收,所以需要根据每个App的情况,以及设备的分辨率,内存计算出一个比较合理的初始值,可以参考Glide的做法。

5. 内存抖动

什么是内存抖动呢?Android里内存抖动是指内存频繁地分配和回收,而频繁的gc会导致卡顿,严重时还会导致OOM。

img

一个很经典的案例是string拼接创建大量小的对象(比如在一些频繁调用的地方打字符串拼接的log的时候), 见Android优化之String篇[9]。

而内存抖动为什么会引起OOM呢?

主要原因还是有因为大量小的对象频繁创建,导致内存碎片,从而当需要分配内存时,虽然总体上还是有剩余内存可分配,而由于这些内存不连续,导致无法分配,系统直接就返回OOM了。

比如我们坐地铁的时候,假设你没带公交卡去坐地铁,地铁的售票机就只支持5元,10元,而哪怕你这个时候身上有1万张1块的都没用(是不是觉得很反人类..)。当然你可以去兑换5元,10元,而在Android系统里就没那么幸运了,系统会直接拒绝为你分配内存,并扔一个OOM给你(有人说Android系统并不会对Heap中空闲内存区域做碎片整理,待验证)。

其他

常用数据结构优化,ArrayMap及SparseArray是android的系统API,是专门为移动设备而定制的。用于在一定情况下取代HashMap而达到节省内存的目的,具体性能见HashMap,ArrayMap,SparseArray源码分析及性能对比[10],对于key为int的HashMap尽量使用SparceArray替代,大概可以省30%的内存,而对于其他类型,ArrayMap对内存的节省实际并不明显,10%左右,但是数据量在1000以上时,查找速度可能会变慢。

枚举,Android平台上枚举是比较争议的,在较早的Android版本,使用枚举会导致包过大,在个例子里面,使用枚举甚至比直接使用int包的size大了10多倍 在stackoverflow上也有很多的讨论, 大致意思是随着虚拟机的优化,目前枚举变量在Android平台性能问题已经不大,而目前Android官方建议,使用枚举变量还是需要谨慎,因为枚举变量可能比直接用int多使用2倍的内存。

ListView复用,这个大家都知道,getView里尽量复用conertView,同时因为getView会频繁调用,要避免频繁地生成对象

谨慎使用多进程,现在很多App都不是单进程,为了保活,或者提高稳定性都会进行一些进程拆分,而实际上即使是空进程也会占用内存(1M左右),对于使用完的进程,服务都要及时进行回收。

尽量使用系统资源,系统组件,图片甚至控件的id

减少view的层级,对于可以 延迟初始化的页面,使用viewstub

数据相关:序列化数据使用protobuf可以比xml省30%内存,慎用shareprefercnce,因为对于同一个sp,会将整个xml文件载入内存,有时候为了读一个配置,就会将几百k的数据读进内存,数据库字段尽量精简,只读取所需字段。

dex优化,代码优化,谨慎使用外部库, 有人觉得代码多少于内存没有关系,实际会有那么点关系,现在稍微大一点的项目动辄就是百万行代码以上,多dex也是常态,不仅占用rom空间,实际上运行的时候需要加载dex也是会占用内存的(几M),有时候为了使用一些库里的某个功能函数就引入了整个庞大的库,此时可以考虑抽取必要部分,开启proguard优化代码,使用Facebook redex使用优化dex(好像有不少坑)。

三、案例

JOOX是IBG一个核心产品,2014年发布以来已经成为5个国家和地区排名第一的音乐App。东南亚是JOOX的主要发行地区,实际上这些地区还是有很多的低端机型,对App的进行内存优化势在必行。

上面介绍了Android系统内存分配和回收机制,同时也列举了常见的内存问题,但是当我们接到一个内存优化的任务时,我们应该从何开始?下面是一次内存优化的分享。

1. 首先是解决大部分内存泄露。

不管目前App内存占用怎样,理论上不需要的东西最好回收,避免浪费用户内存,减少OOM。实际上自JOOX接入LeakCanary后,每个版本都会做内存泄露检测,经过几个版本的迭代,JOOX已经修复了几十处内存泄露。

img

2. 通过MAT查看内存占用,优化占用内存较大的地方。

JOOX修复了一系列内存泄露后,内存占用还是居高不下,只能通过MAT查看到底是哪里占用了内存。关于MAT的使用,网上教程无数,简单推荐两篇MAT使用教程[11],MAT – Memory Analyzer Tool 使用进阶[12]。

点击Android Studio这里可以dump当前的内存快照,因为直接通过Android Sutdio dump出来的hprof文件与标准hprof文件有些差异,我们需要手动进行转换,利用sdk目录/platform-tools/hprof-conv.exe可以直接进行转换,用法:hprof-conv 原文件.hprof 新文件.hprof。只需要输入原文件名还有目标文件名就可以进行转换,转换完就可以直接用MAT打开。

img

下面就是JOOX打开App,手动进行多次gc的hprof文件。

这里我们看的是Dominator Tree(即内存里占用内存最多的对象列表)。

img

  • Shallo Heap:对象本身占用内存的大小,不包含其引用的对象内存。
  • Retained Heap: Retained heap值的计算方式是将retained set中的所有对象大小叠加。或者说,由于X被释放,导致其它所有被释放对象(包括被递归释放的)所占的heap大小。

第一眼看去 居然有3个8M的对象,加起来就是24M啊 这到底是什么鬼?

img

我们通过List objects->with incoming references查看(这里with incoming references表示查看谁引用了这个对象,with outgoing references表示这个对象引用了谁)

img

通过这个方式我们看到这三张图分别是闪屏,App主背景,App抽屉背景。

img

这里其实有两个问题:

  • 这几张图原图实际都是1280×720,而在1080p手机上实测这几张图都缩放到了1920×1080
  • 闪屏页面,其实这张图在闪屏显示过后应该可以回收,但是因为历史原因(和JOOX的退出机制有关),这张图被常驻在后台,导致无谓的内存占用。

优化方式:我们通过将这三张图从xhdpi挪动到xxhdpi(当然这里需要看下图片显示效果有没很大的影响),以及在闪屏显示过后回收闪屏图片。
优化结果:

img

从原来的8.29×3=24.87M 到 3.68×2=7.36M 优化了17M(有没一种万马奔腾的感觉。。可能有时费大力气优化很多代码也优化不了几百K,所以很多情况下内存优化时优化图片还是比较立竿见影的)。

同样方式我们发现对于一些默认图,实际要求的显示要求并不高(图片相对简单,同时大部分情况下图片加载会成功),比如下面这张banner的背景图:

img

优化前1.6M左右,优化后700K左右。

同时我们也发现了默认图片一个其他问题,因为历史原因,我们使用的图片加载库,设置默认图片的接口是需要一个bitmap,导致我们原来几乎每个adapter都用BitmapFactory decode了一个bitmap,对同一张默认图片,不但没有复用,还保存了多份,不仅会造成内存浪费,而且导致滑动偶尔会卡顿。这里我们也对默认图片使用全局的bitmap缓存池,App全局只要使用同一张bitmap,都复用了同一份。

另外对于从MAT里看到的图片,有时候因为看不到在项目里面对应的ID,会比较难确认到底是哪一张图,这里stackoverflow上有一种方法,直接用原始数据通过GIM还原这张图片。

这里其实也看到JOOX比较吃亏一个地方,JOOX不少地方都是使用比较复杂的图片,同时有些地方还需要模糊,动画这些都是比较耗内存的操作,Material Design出来后,很多App都遵循MD设计进行改版,通常默认背景,默认图片一般都是纯色,不仅App看起来比较明亮轻快,实际上也省了很多的内存,对此,JOOX后面对低端机型做了对应的优化。

3. 我们也对Bugly上的OOM进行了分析,发现其实有些OOM是可以避免的。

下面这个crash就是上面提到的在LsitView的adapter里不停创建bitmap,这个地方是我们的首页banner位,理论上App一打开就会缓存这张默认背景图片了,而实际在使用过一段时间后,才因为为了解码这张背景图而OOM, 改为用全局缓存解决。

img

下面这个就是传说中的内存抖动

img

实际代码如下,因为打Log而进行了字符串拼接,一旦这个函数被比较频繁地调用,那么就很有可能会发生内存抖动。这里我们新版本已经改为使用stringbuilder进行优化。

img

还有一些比较奇怪的情况,这里是我们扫描歌曲文件头的时候发生的,有些文件头居然有几百M大,导致一次申请了过大的内存,直接OOM,这里暂时也无法修复,直接catch住out of memory error。

img

4. 同时我们对一些逻辑代码进行调整,比如我们的App主页的第三个tab(Live tab)进行了数据延迟加载,和定时回收。

img

这里因为这个页面除了有大图还有轮播banner,实际强引用的图片会有多张,如果这个时候切到其他页面进行听歌等行为,这个页面一直在后台缓存,实际是很浪费耗内存的,同时为优化体验,我们又不能直接通过设置主页的viewpager的缓存页数,因为这样经常都会回收,导致影响体验,所以我们在页面不可见后过一段时间,清理掉adapter数据(只是清空adapter里的数据,实际从网络加载回来的数据还在,这里只是为了去掉界面对图片的引用),当页面再次显示时再用已经加载的数据显示,即减少了很多情况下图片的引用,也不影响体验。

5. 最后我们也遇到一个比较奇葩的问题,在我们的Bugly上报上有这样一条上报

img

我们在stackoverflow上看到了相关的讨论,大致意思是有些情况下比如息屏,或者一些省电模式下,频繁地调System.gc()可能会因为内核状态切换超时的异常。这个问题貌似没有比较好的解决方法,只能是优化内存,尽量减少手动调用System.gc()

优化结果

我们通过启动App后,切换到我的音乐界面,停留1分钟,多次gc后,获取App内存占用

优化前:

img

优化后:

img

多次试验结果都差不多,这里只截取了其中一次,有28M的优化效果。

当然不同的场景内存占用不同,同时上面试验结果是通过多次手动触发gc稳定后的结果。对于使用其他第三方工具不手动gc的情况下,试验结果可能会差异比较大。

对于上面提到的JOOX里各种图片背景等问题,我们做了动态的优化,对不同的机型进行优化,对特别低端的机型设置为纯色背景等方式,最终优化效果如下:

img

img

平均内存降低41M。

本次总结主要还是从图片方面下手,还有一点逻辑优化,已经基本达到优化目标。

四、总结

上面写了很多,我们可以简单总结,目前Andorid内存优化还是比较重要一个话题,我们可以通过各种内存泄露检测组件,MAT查看内存占用,Memory Monitor跟踪整个App的内存变化情况, Heap Viewer查看当前内存快照, Allocation Tracker追踪内存对象的来源,以及利用崩溃上报平台从多个方面对App内存进行监控和优化。上面只是列举了一些常见的情况,当然每个App功能,逻辑,架构也都不一样,造成内存问题也是不尽相同,掌握好工具的使用,发现问题所在,才能对症下药。

文中引用参考链接

1.Android 操作系统的内存回收机制
https://www.ibm.com/developerworks/cn/opensource/os-cn-android-mmry-rcycl/

2.阿里巴巴的Android内存优化分享
http://www.infoq.com/cn/presentations/android-memory-optimization

3.Android进程的内存管理分析
http://blog.csdn.net/gemmem/article/details/8920039

4.android dalvik heap 浅析
http://blog.csdn.net/cqupt_chen/article/details/11068129

5.揭秘 ART 细节 —— Garbage collection
http://www.cnblogs.com/jinkeep/p/3818180.html

6.Android性能优化之常见的内存泄漏
http://blog.csdn.net/u010687392/article/details/49909477

7.Android App 内存泄露之Handler
http://blog.csdn.net/zhuanglonghai/article/details/38233069

8.GlideBitmapPool
https://github.com/amitshekhariitbhu/GlideBitmapPool

9.Android 性能优化之String篇
http://blog.csdn.net/vfush/article/details/53038437

10.HashMap,ArrayMap,SparseArray源码分析及性能对比
http://www.jianshu.com/p/7b9a1b386265

11.MAT使用教程
http://blog.csdn.net/itomge/article/details/48719527

12.MAT – Memory Analyzer Tool 使用进阶
http://www.lightskystreet.com/2015/09/01/mat_usage/

Android 系统进程和应用进程交互

系统进程初始化

拉起ZygoteInit

Android系统启动的入口是Init进程,它是由init语言编写,通常init.rc中会导入其他路径的配置文件,这样就提供了开机配置的可能性。

init.zygote32.rc负责拉起Zygote,其在AOSP中的路径如下:

/system/core/rootdir/init.zygote32.rc

包含如下基本信息(部分):

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks

第一行:

service zygote /system/bin/app_process -Xzygote /system/bin –zygote –start-system-server

Zygote是以service配置的,所以当解析该完成时,init进程就会调用fork去创建Zygote进程,并且执行app_main.cpp文件中的main函数。

作为Zygote进行的启动入口,app_main.cpp文件的目录路径为:frameworks\base\cmds\app_process\app_main.cpp

部分源码如下:

int main(int argc, char* const argv[])
{
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
        // Older kernels don't understand PR_SET_NO_NEW_PRIVS and return
        // EINVAL. Don't die on such kernels.
        if (errno != EINVAL) {
            LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
            return 12;
        }
    }

    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    // Process command line arguments
    // ignore argv[0]
    argc--;
    argv++;

    // Everything up to '--' or first non '-' arg goes to the vm.
    //
    // The first argument after the VM args is the "parent dir", which
    // is currently unused.
    //
    // After the parent dir, we expect one or more the following internal
    // arguments :
    //
    // --zygote : Start in zygote mode
    // --start-system-server : Start the system server.
    // --application : Start in application (stand alone, non zygote) mode.
    // --nice-name : The nice name for this process.
    //
    // For non zygote starts, these arguments will be followed by
    // the main class name. All remaining arguments are passed to
    // the main method of this class.
    //
    // For zygote starts, all remaining arguments are passed to the zygote.
    // main function.
    //
    // Note that we must copy argument string values since we will rewrite the
    // entire argument block when we apply the nice name to argv0.

    int i;
    for (i = 0; i < argc; i++) {
        if (argv[i][0] != '-') {
            break;
        }
        if (argv[i][1] == '-' && argv[i][2] == 0) {
            ++i; // Skip --.
            break;
        }
        runtime.addOption(strdup(argv[i]));
    }

    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;

    ++i;  // Skip unused "parent dir" argument.
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }

    Vector<String8> args;
    if (!className.isEmpty()) {
        // We're not in zygote mode, the only argument we need to pass
        // to RuntimeInit is the application argument.
        //
        // The Remainder of args get passed to startup class main(). Make
        // copies of them before we overwrite them with the process name.
        args.add(application ? String8("application") : String8("tool"));
        runtime.setClassNameAndArgs(className, argc - i, argv + i);
    } else {
        // We're in zygote mode.
        maybeCreateDalvikCache();

        if (startSystemServer) {
            args.add(String8("start-system-server"));
        }

        char prop[PROP_VALUE_MAX];
        if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) {
            LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.",
                ABI_LIST_PROPERTY);
            return 11;
        }

        String8 abiFlag("--abi-list=");
        abiFlag.append(prop);
        args.add(abiFlag);

        // In zygote mode, pass all remaining arguments to the zygote
        // main() method.
        for (; i < argc; ++i) {
            args.add(String8(argv[i]));
        }
    }

    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string(), true /* setProcName */);
    }

    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
}

可以看到app_main会先创建一个AppRuntime,然后用它拉起Zygote。

AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) :
        mExitWithoutCleanup(false),
        mArgBlockStart(argBlockStart),
        mArgBlockLength(argBlockLength)
{
    SkGraphics::Init();
    // There is also a global font cache, but its budget is specified by
    // SK_DEFAULT_FONT_CACHE_COUNT_LIMIT and SK_DEFAULT_FONT_CACHE_LIMIT.

    // Pre-allocate enough space to hold a fair number of options.
    mOptions.setCapacity(20);

    assert(gCurRuntime == NULL);        // one per process
    gCurRuntime = this;
}

在app_main.cpp的最后几行代码可以看到,启动Zygote是交给AndroidRuntime去完成的:

runtime.start(“com.android.internal.os.ZygoteInit”, args, zygote);

在Runtime的start函数中包含了大量对args的检查,具体可以看AndroidRuntime::startVm这个函数。

至此ZygoteInit.main(),就会被执行。

ZygoteInit.main()

系统核心进程,一切系统服务的开始。

main函数大概完成以下操作:

  1. 开启DDMS服务
  2. 读取传递过来的参数(是否启动系统服务等)
  3. 注册Zygote和其他进程通信的Socket
  4. 预加载进程的公共资源到共享内存(主要包含framework相关的基础类以及resource资源)
    • 加快应用启动速度
    • 通过共享内存的方式节约内存(Linux中,父进程已加载的内容可以在子进程中共享)
  5. 启动system_server
  6. 通过死循环开启Loop,从之前注册Socket中读取信息,完成Process的Fork

系统进程创建应用进程

上面提到ZygoteInit创建的时候会注册Socket用于监听其他进程Fork的需求,下面从源码开看看是如何创建出另外进程的:

注册Socket

private static void registerZygoteSocket(String socketName) {
    if (sServerSocket == null) {
        int fileDesc;
        final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
        try {
            String env = System.getenv(fullSocketName);
            fileDesc = Integer.parseInt(env);
        } catch (RuntimeException ex) {
            throw new RuntimeException(fullSocketName + " unset or invalid", ex);
        }

        try {
            FileDescriptor fd = new FileDescriptor();
            fd.setInt$(fileDesc);
            sServerSocket = new LocalServerSocket(fd);
        } catch (IOException ex) {
            throw new RuntimeException(
                    "Error binding to local socket '" + fileDesc + "'", ex);
        }
    }
}

在Socket通信一文中,提到Socket是通过初始化的时候传入的描述符确定使用的那种协议的,在这里也有体现。

需要注意的是,LocalServerSocket一旦初始化就开始监听。

public LocalServerSocket(FileDescriptor fd) throws IOException
{
    impl = new LocalSocketImpl(fd);
    impl.listen(LISTEN_BACKLOG);//开始监听
    localAddress = impl.getSockAddress();
}

轮询是否有新的消息并Fork进程

保证ZygoteInit一直存活的轮询:

private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
    ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
    ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

    fds.add(sServerSocket.getFileDescriptor());
    peers.add(null);

    while (true) {
        StructPollfd[] pollFds = new StructPollfd[fds.size()];
        for (int i = 0; i < pollFds.length; ++i) {
            pollFds[i] = new StructPollfd();
            pollFds[i].fd = fds.get(i);
            pollFds[i].events = (short) POLLIN;
        }
        try {
            Os.poll(pollFds, -1);
        } catch (ErrnoException ex) {
            throw new RuntimeException("poll failed", ex);
        }
        for (int i = pollFds.length - 1; i >= 0; --i) {
            if ((pollFds[i].revents & POLLIN) == 0) {
                continue;
            }
            if (i == 0) {
                ZygoteConnection newPeer = acceptCommandPeer(abiList);
                peers.add(newPeer);
                fds.add(newPeer.getFileDesciptor());
            } else {
                boolean done = peers.get(i).runOnce();
                if (done) {
                    peers.remove(i);
                    fds.remove(i);
                }
            }
        }
    }
}
  • 如果没有已创建的链接需要处理了,那么调用ZygoteInit.acceptCommandPeer()创建新的链接(ZygoteConnection)并调用sServerSocket.accept()获取数据。
  private static ZygoteConnection acceptCommandPeer(String abiList) {
      try {
          return new ZygoteConnection(sServerSocket.accept(), abiList);
      } catch (IOException ex) {
          throw new RuntimeException(
                  "IOException during accept()", ex);
      }
  }
  • 如果还有,那么调用ZygoteConnection.runOnce()。
    boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
    
      String args[];
      Arguments parsedArgs = null;
      FileDescriptor[] descriptors;
    
      try {
          args = readArgumentList();
          descriptors = mSocket.getAncillaryFileDescriptors();
      } catch (IOException ex) {
          Log.w(TAG, "IOException on command socket " + ex.getMessage());
          closeSocket();
          return true;
      }
    
      if (args == null) {
          // EOF reached.
          closeSocket();
          return true;
      }
    
      /** the stderr of the most recent request, if avail */
      PrintStream newStderr = null;
    
      if (descriptors != null && descriptors.length >= 3) {
          newStderr = new PrintStream(
                  new FileOutputStream(descriptors[2]));
      }
    
      int pid = -1;
      FileDescriptor childPipeFd = null;
      FileDescriptor serverPipeFd = null;
    
      try {
          parsedArgs = new Arguments(args);
    
          if (parsedArgs.abiListQuery) {
              return handleAbiListQuery();
          }
    
          if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
              throw new ZygoteSecurityException("Client may not specify capabilities: " +
                      "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
                      ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
          }
    
          applyUidSecurityPolicy(parsedArgs, peer);
          applyInvokeWithSecurityPolicy(parsedArgs, peer);
    
          applyDebuggerSystemProperty(parsedArgs);
          applyInvokeWithSystemProperty(parsedArgs);
    
          int[][] rlimits = null;
    
          if (parsedArgs.rlimits != null) {
              rlimits = parsedArgs.rlimits.toArray(intArray2d);
          }
    
          if (parsedArgs.invokeWith != null) {
              FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
              childPipeFd = pipeFds[1];
              serverPipeFd = pipeFds[0];
              Os.fcntlInt(childPipeFd, F_SETFD, 0);
          }
    
          /**
           * In order to avoid leaking descriptors to the Zygote child,
           * the native code must close the two Zygote socket descriptors
           * in the child process before it switches from Zygote-root to
           * the UID and privileges of the application being launched.
           *
           * In order to avoid "bad file descriptor" errors when the
           * two LocalSocket objects are closed, the Posix file
           * descriptors are released via a dup2() call which closes
           * the socket and substitutes an open descriptor to /dev/null.
           */
    
          int [] fdsToClose = { -1, -1 };
    
          FileDescriptor fd = mSocket.getFileDescriptor();
    
          if (fd != null) {
              fdsToClose[0] = fd.getInt$();
          }
    
          fd = ZygoteInit.getServerSocketFileDescriptor();
    
          if (fd != null) {
              fdsToClose[1] = fd.getInt$();
          }
    
          fd = null;
    
          pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                  parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                  parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,
                  parsedArgs.appDataDir);
      } catch (ErrnoException ex) {
          logAndPrintError(newStderr, "Exception creating pipe", ex);
      } catch (IllegalArgumentException ex) {
          logAndPrintError(newStderr, "Invalid zygote arguments", ex);
      } catch (ZygoteSecurityException ex) {
          logAndPrintError(newStderr,
                  "Zygote security policy prevents request: ", ex);
      }
    
      try {
          if (pid == 0) {
              // in child
              IoUtils.closeQuietly(serverPipeFd);
              serverPipeFd = null;
              handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
    
              // should never get here, the child is expected to either
              // throw ZygoteInit.MethodAndArgsCaller or exec().
              return true;
          } else {
              // in parent...pid of < 0 means failure
              IoUtils.closeQuietly(childPipeFd);
              childPipeFd = null;
              return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
          }
      } finally {
          IoUtils.closeQuietly(childPipeFd);
          IoUtils.closeQuietly(serverPipeFd);
      }
    }
    

    最终会调用Zygote.forkAndSpecialize完成进程的创建。

    public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags,
        int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
        String instructionSet, String appDataDir) {
      VM_HOOKS.preFork();
      int pid = nativeForkAndSpecialize(
                uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
                instructionSet, appDataDir);
      // Enable tracing as soon as possible for the child process.
      if (pid == 0) {
          Trace.setTracingEnabled(true);
    
          // Note that this event ends at the end of handleChildProc,
          Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
      }
      VM_HOOKS.postForkCommon();
      return pid;
    }
    

    最终调用的是native的方法完成进程的fork。

Android IPC

IPC(Inter-Process Communication)的几种方式

  • 文件共享
  • Bundle
  • AIDL(Messenger)
  • Socket
  • ContentProvider

文件共享

两个进程间通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据

Bundle

作为四大组件交换数据的基本对象,其本身就支持在多线程中传递。

Intent.migrateExtraStreamToClipData();
Intent.prepareToLeaveProcess();

AIDL

常规AIDL

基于Binder的进程通信语言。

通过该语言,Android在编译的时候回自动生成对应的Binder接口实现类以及相关代理。

Messenger

对轻量级进程通信提供的快捷方式,实际上还是通过AIDL实现的。

Socket

来源于网络编程中的“套接字”,即对C端和S端通信协议的封装,常规来说只要指定了协议,那么就可以用它来通信,详见https://www.areahash.com/?p=423

ContentProvider

内容提供者,Android提供的通过URI形式获取轻量数据的一种方式。

小结

  • AIDL,ContentProvider都是 通过Binder机制实现进程通信的。

  • 几种方式的优缺点:

名称 优点 缺点 适用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件间的进程通信
文件共享 简单易用 并不适合高并发场景,并且无法做到进程间的及时通信 无高并发访问情形,交换简单的数据实时性不高的场景
AIDL 功能抢到,支持一对多并发通信,支持实时通信 使用稍复杂,需要处理好线程同步 一对多通信且有RPC需求
Messenger 功能一般,支持一对多串行通信,支持实时通信 不能很好处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 低并发的一对多即时通信,无RPC需求,或者无需要返回结果的RPC需求
ContentProvider 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 一对多的进程间数据共享
Socket 功能抢到,可以通过网络传输字节流,支持一对多并发实时通信 实现细节繁琐,不支持直接的RPC 数据交换

RPC:远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.

Socket通信

概述

Socket是为实现某种通信协议(如:TCP/IP),在客户端和服务端进行的封装

客户端通过初始化其描述符(descriptor)的时候指定和服务端交互的传输协议(如TCP等)
通过connect和服务端建立连接

服务端通过bind绑定到端口,通过listen监听客户端请求,通过accept接收请求

在TCP/IP协议下,基本交互过程

常见缓存策略

常见缓存策略FIFO,LRU,LFU,TimeOut

FIFO(First In First Out)

先进先出,使用队列保存顺序。

LRU(Least Recently Used)

最近最少使用,根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高

通常使用链表保存数据节点,具体算法如下:

  1. 新数据插入到链表头部;
  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
  3. 当链表满的时候,将链表尾部的数据丢弃。

LFU(Least Frequently Used)

使用次数最少,根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。

  1. 新加入数据插入到队列尾部(因为引用计数为1);
  2. 队列中的数据被访问后,引用计数增加,队列重新排序;
  3. 当需要淘汰数据时,将已经排序的列表最后的数据块删除。

TimeOut

超时自动失效,如果数据可以容忍在一段时间内不一致,那么可以对一个数据设置一定时间的过期时间,在数据过期后,再从真实数据源获取数据,重新放到缓存中,继续设置过期时间。


几种策略的比较

策略 缓存一致性 维护成本
FIFO,LRU,LFU
超时(TimeOut)失效 较差

前三种策略都是为移除缓存指定大概方向,并不明确是否能够正确保证数据正确,所以一致性较超时删除较差。

Android Service源码浅析

Service是什么

官方定义:

在Android里面,服务有两种存在的形式

1、依托于其他组件存在,例如:AIDL
这种服务在启动的时候会调用bindService()将指定的Context绑定到Service中(一个service允许绑定多个组件)
一旦Service中绑定的组件被清空,该服务就会被停止

2、独立存在,自己悄悄咪咪做一些事情的
这类服务启动的时候调用startService()启动,并调用stopSelf()或者stopService()停止服务

Service生命周期

直接上官方的一张图:

Tips

• 无论是那种方式结束Service, onDestory()都会被回调;

同样,无论以哪种方式启动service,onCreate()都会回调

• Service只要没有指定运行的process都是运行在主线程

• 通过startService启动的服务依然可以调用bindService绑定组件

Service如何创建

从上面生命周期以及Service简介中,我们知道启动服务有两种方式

1、bindService()

2、startService()

下面会从源码(API 28)来分析这两种方式的差异

bindService() 方法调用链

startService() 方法调用链

可以看出最终start或者bind的任务都是交到AMS中的ActiveServices执行的,

bindService到底和startService到底有什么区别呢?


先看startService的核心代码: ActiveServices.java
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
        int callingPid, int callingUid, String callingPackage, int userId)
        throws TransactionTooLargeException {
    if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
            + " type=" + resolvedType + " args=" + service.getExtras());

    final boolean callerFg;
    if (caller != null) {
        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
        if (callerApp == null) {
            throw new SecurityException(
                    "Unable to find app for caller " + caller
                    + " (pid=" + Binder.getCallingPid()
                    + ") when starting service " + service);
        }
        callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
    } else {
        callerFg = true;
    }

    //创建ServiceRecord
    ServiceLookupResult res =
        retrieveServiceLocked(service, resolvedType, callingPackage,
                callingPid, callingUid, userId, true, callerFg);
    if (res == null) {
        return null;
    }
    if (res.record == null) {
        return new ComponentName("!", res.permission != null
                ? res.permission : "private to package");
    }

    ServiceRecord r = res.record;
    //执行启动前的检查

    return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
        boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
    ProcessStats.ServiceState stracker = r.getTracker();
    if (stracker != null) {
        stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
    }
    r.callStart = false;
    synchronized (r.stats.getBatteryStats()) {
        r.stats.startRunningLocked();
    }
    //调用bringUpServiceLocked拉起对应service
    String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
   //...
    return r.name;
}

概括起来分为3步:

  1. 为启动的Service创建ServiceRecord
  2. 执行启动前的一系列检查
  3. 含有BIND_AUTO_CREATE标记,调用bringUpServiceLocked()执行最终的拉起操作

bindService()核心代码 ActiveServices.java
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
        String resolvedType, IServiceConnection connection, int flags,
        String callingPackage, int userId) throws TransactionTooLargeException {
    if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
            + " type=" + resolvedType + " conn=" + connection.asBinder()
            + " flags=0x" + Integer.toHexString(flags));
    final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
    if (callerApp == null) {
        throw new SecurityException(
                "Unable to find app for caller " + caller
                + " (pid=" + Binder.getCallingPid()
                + ") when binding service " + service);
    }
    //根据传入的token,从ActivityStack中获取对应的ActivityRecord
    ActivityRecord activity = null;
    if (token != null) {
        activity = ActivityRecord.isInStackLocked(token);
        if (activity == null) {
            Slog.w(TAG, "Binding with unknown activity: " + token);
            return 0;
        }
    }

    final boolean callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
    //创建对应的ServiceRecord
    ServiceLookupResult res =
        retrieveServiceLocked(service, resolvedType, callingPackage,
                Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg);
    if (res == null) {
        return 0;
    }
    if (res.record == null) {
        return -1;
    }
    ServiceRecord s = res.record;

    final long origId = Binder.clearCallingIdentity();

    try {


        mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
                s.appInfo.uid, s.name, s.processName);
        //建立ConnectionRecord
        AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
        ConnectionRecord c = new ConnectionRecord(b, activity,
                connection, flags, clientLabel, clientIntent);

        IBinder binder = connection.asBinder();
        ArrayList<ConnectionRecord> clist = s.connections.get(binder);
        if (clist == null) {
            clist = new ArrayList<ConnectionRecord>();
            s.connections.put(binder, clist);
        }
        clist.add(c);
        b.connections.add(c);
        //添加ConnectionRecord到Activity.connections中
        //Activity会在被ActivityStack移除的时候调用ActiveServices.removeConnectionLocked
        //清理相关Connection并完成生命周期的同步
        if (activity != null) {
            if (activity.connections == null) {
                activity.connections = new HashSet<ConnectionRecord>();
            }
            activity.connections.add(c);
        }
        b.client.connections.add(c);
        if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
            b.client.hasAboveClient = true;
        }
        if (s.app != null) {
            updateServiceClientActivitiesLocked(s.app, c, true);
        }
        //添加传入的Connection到ActiveServices中
        clist = mServiceConnections.get(binder);
        if (clist == null) {
            clist = new ArrayList<ConnectionRecord>();
            mServiceConnections.put(binder, clist);
        }
        clist.add(c);

        if ((flags&Context.BIND_AUTO_CREATE) != 0) {
            s.lastActivity = SystemClock.uptimeMillis();
            if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {
                return 0;
            }
        }

    } finally {
        Binder.restoreCallingIdentity(origId);
    }

    return 1;
}

概括起来可以分为以下4个步骤:

  1. 获取需要bind的Activity对应的ActivityRecord
  2. 创建对应的ServiceRecrod
  3. 创建对应的ConnectionRecord,并添加到ActivityRecord.connections中,会在Activity被ActivityStack移除的时候调用ActiveServices.removeConnectionLocked清理相关Connection并完成生命周期的同步
  4. 含有BIND_AUTO_CREATE标记,调用bringUpServiceLocked()执行最终的拉起操作

bindService和startService两种方式小结:

生命周期的区别:

  1. bindService会有bind以及unBind的回调,没有onStartCommand回调,监听是否绑定是通过IServiceConnection完成的
  2. startService会有onStartCommand回调,没有绑定相关回调

源码启动时候的区别:

  1. startService就只会有创建ServiceRecord的逻辑,没有其他特殊逻辑
  2. bindService因为需要bind到对应Activity,所以多一个建立连接(ConnectionRecord)并和ActivityRecord建立关系的逻辑

如何设置为前台服务

调用函数Service.startForeground(int id, Notification notification)就可以将Service设置为前台服务。

调用setFpreground之后的方法调用链如下:

final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
        boolean oomAdj) {
    if (isForeground != proc.foregroundServices) {
        proc.foregroundServices = isForeground;
        ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
                proc.info.uid);
        if (isForeground) {
            if (curProcs == null) {
                curProcs = new ArrayList<ProcessRecord>();
                mForegroundPackages.put(proc.info.packageName, proc.info.uid, curProcs);
            }
            if (!curProcs.contains(proc)) {
                curProcs.add(proc);
                mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_FOREGROUND_START,
                        proc.info.packageName, proc.info.uid);
            }
        } else {
            if (curProcs != null) {
                if (curProcs.remove(proc)) {
                    mBatteryStatsService.noteEvent(
                            BatteryStats.HistoryItem.EVENT_FOREGROUND_FINISH,
                            proc.info.packageName, proc.info.uid);
                    if (curProcs.size() <= 0) {
                        mForegroundPackages.remove(proc.info.packageName, proc.info.uid);
                    }
                }
            }
        }
        //更新进程优先级
        if (oomAdj) {
            updateOomAdjLocked();
        }
    }
}

进程优先级

Handler常见面试问题

从源码解释Handler常见面试问题

Tip:问题均从网上收集,参照了部分解答,在此谢过。

Q1:消息机制Hander是什么?有什么作用?有哪些要素?流程是怎样的?

1、Handler是什么?

是Android中一套在应用内使用的消息机制。

2、有什么作用

通过该机制,我们得以方便的发送消息以及收消息,我们常见的UI更新机制也是基于Handler运行。

3、有哪些要素

Handler的核心有这“三大件”:

Handler:提供给使用者的入口(发送、收消息)

MessageQueue:消息(Message)的队列,存储消息顺序

Looper:消息分发的驱动器,loop()一直在轮询(while(true))是否有消息,并分发消息

4、流程是怎么样的?

使用知乎大佬的图解释一下:

其他元素的职责在上面介绍过,这里说下Message的分类大概有哪些:

Message:消息,分为硬件产生的消息(如:按键、触摸、绘制等)和软件产生的消息

事实上,在整个消息循环的流程中,并不只有Java层参与,很多重要的工作都是在C++层来完成的。我们来看下这些类的调用关系。(copy from :https://zhuanlan.zhihu.com/p/38373877)

注:虚线表示关联关系,实线表示调用关系。

在这些类中MessageQueue是Java层与C++层维系的桥梁,MessageQueue与Looper相关功能都通过MessageQueue的Native方法来完成,而其他虚线连接的类只有关联关系,并没有直接调用的关系,它们发生关联的桥梁是MessageQueue。

Q2:为什么系统不建议在子线程访问UI?

避免多线程更新造成的一系列问题。

Q3:一个Thread可以有几个Looper?几个Handler?

1、一个Thread只有一个Looper,这个是通过ThreadLocal来保证的。

2、可以有多个Hander,Handler和Looper的关系是多对一的。

Q4:如何将一个Thread线程变成Looper线程?Looper线程有哪些特点?

调用Loop.prepareMainLooper,使线程的Looper变成主线程。

调用Looper.prepare(),就可以为当前线程创建一个Looper副本。

Q5:可以在子线程直接new一个Handler吗?那该怎么做?

不可以的,因为在子线程,所以这个Handler一定需要指定由哪个Looper分发消息;

所以在创建之前一定需要调用Looper.prepare()方法。

主线程在ActivityThread已经调用了,创建了mainLooper。

Q6:Message可以如何创建?哪种效果更好,为什么?

Message创建的时候android官方提倡使用Handler.obtainMessage()。

因为Message中保存了一个静态的Message对象链表,使用享元模式,减少了消息内存的占用

从复用的链表中获取一个对象,如果没有就创建。

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

什么时候加入到对象链表中呢?

public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

消息使用完成调用recycle就可以回收该对象。

Q7:这里的ThreadLocal有什么作用?

ThreadLocal为每个线程保存了一个Looper副本。

ThreadLocal可以看这里:https://www.cnblogs.com/jasongj/p/8079718.html

Q8:主线程中Looper的轮询死循环为何没有阻塞主线程?

主线程中的Looper是保证应用一直运行的关键,我们常说的ANR实际上是占用主线程时间过长。

ActivityThread中初始化MainLooper

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    SamplingProfilerIntegration.start();
    //...
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();//mainLooper一直循环,保证应用进程一直存活

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

Q9:使用Hanlder的postDealy()后消息队列会发生什么变化?

调用了postDealy之后,MessageQueue会设置Message.when(保存消息应该什么时候执行)的值,

通过该值,MessageQueue会该消息插入到自己保存的消息队列(链表实现)中。

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;//设置应该什么时候执行
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            //在链表中插入当前消息,根据执行的时间
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

Q10:如何解决Handler导致的泄漏问题

Handler导致泄漏的根本原因是Handler创建的时候为应用的内部类,持有了外部类的引用,

这个时候遇到有消息执行比较久(如:网络请求等),就会导致已经退出Activity了但是其引用还被Handler持有。

解决方案有两个建议:

1、实例化Handler的时候尽量创建一个静态内部类

2、保存外部Context的引用的时候使用弱引用

Activity启动流程

Activity 启动流程

主要分为如下两个方面介绍:

1、Activity启动基本流程

2、Activity任务栈

Activity启动基本流程

从Context发出startActivity指令到页面可见大抵会经过如下类:

Instrumentation:对外提供管理Activity接口

ActivityManagerService(AMS):系统服务,管理应用的Activity、Process、Service等

ActivityStackSupervisor:管理ActivityStack

ActivityStack:Activity任务栈

ActivityThread:应用进程

First Part 启动前的准备工作

startActivity有多个重载函数,最终都会调用这个重载函数

public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
    if (mParent == null) {
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode, options);
        if (ar != null) {
            mMainThread.sendActivityResult(
                mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                ar.getResultData());
        }

    } else {
        if (options != null) {
            mParent.startActivityFromChild(this, intent, requestCode, options);
        } else {
            // Note we want to go through this method for compatibility with
            // existing applications that may have overridden it.
            mParent.startActivityFromChild(this, intent, requestCode);
        }
    }
}

无论是否有父级Activity最后都会调用到 Instrumentation.execStartActivity这个函数,该函数通过AIDL与AMS通信,并正式进入Activity启动流程。

public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    IApplicationThread whoThread = (IApplicationThread) contextThread;
    Uri referrer = target != null ? target.onProvideReferrer() : null;
    if (referrer != null) {
        intent.putExtra(Intent.EXTRA_REFERRER, referrer);
    }
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess();
        int result = ActivityManagerNative.getDefault()
            .startActivity(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, target != null ? target.mEmbeddedID : null,
                    requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}

ActivityManagerService并不直接负责拉起Activity(毕竟是一个系统级的老大),它提供了调用其他服务的入口,并转发相关请求。

public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
        Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
        int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
    enforceNotIsolatedCaller("startActivity");
    userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
            false, ALLOW_FULL_ONLY, "startActivity", null);
    // TODO: Switch to user app stacks here.
    return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
            resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
            profilerInfo, null, null, options, false, userId, null, null);
}

一个Activity要启动需要知道,启动之后位于哪个栈(Activity启动模式、Activity任务栈)。所以在真正拉起Activity之前,ActivityStackSupervisor会进行如下流程:

1、对AMS加同步锁,通过PMS收集目标Activity的基本信息(startActivityMayWait)
2、初步检查Activity是否可以启动,如果不可以返回相关错误(startActivityLocked)
3、确定在哪个栈(启动的时候携带的Flag,例如NEW_TASK会在这里判断并得出最终
目标Activity所在的栈),分发到相应栈开启Activity(startActivityUncheckedLocked)

在确定Activity可以启动且查找到应该在的任务栈之后,ActivityStack会通过启动传入的标志位(如NEW_TASK等)调整ActivityRecord在TaskRecord中的位置。

最终会调用到ActivityStack的resumeTopActivityInnerLocked:

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {

    final ActivityRecord next = topRunningActivityLocked(null);

    try {
        AppGlobals.getPackageManager().setPackageStoppedState(
                next.packageName, false, next.userId); /* TODO: Verify if correct userid */
    } catch (RemoteException e1) {
    } catch (IllegalArgumentException e) {
        Slog.w(TAG, "Failed trying to unstop package "
                + next.packageName + ": " + e);
    }

    ActivityStack lastStack = mStackSupervisor.getLastStack();
    if (next.app != null && next.app.thread != null) {
        //ActivityRecord在创建的时候并不会指定其所在的进程(ProcessRecord),
        //只有在第一次开启了对应的activity之后才会指定所在进程;
        //所以这也是如何判断Activity是否直接resume的标志
        updateLRUListLocked(next);
        mService.updateOomAdjLocked();

        try {
            next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
                    mService.isNextTransitionForward(), resumeAnimOptions);
            mStackSupervisor.checkReadyForSleepLocked();

            if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " + next);
        } catch (Exception e) {

            return true;
        }

        // From this point on, if something goes wrong there is no way
        // to recover the activity.
        try {
            next.visible = true;
            completeResumeLocked(next);
        } catch (Exception e) {
            // If any exception gets thrown, toss away this
            // activity and try the next one.
            Slog.w(TAG, "Exception thrown during resume of " + next, e);
            requestFinishActivityLocked(next.appToken, Activity.RESULT_CANCELED, null,
                    "resume-exception", true);
            if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
            return true;
        }
        next.stopped = false;

    } else {

        if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Restarting " + next);
        mStackSupervisor.startSpecificActivityLocked(next, true, true);
    }
    //没有进程记录(即宿主APP的ProcessRecord),执行start流程
    if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
    return true;
}

该函数通过Activity是否包含宿主App的ProcessCord来判断是resume还是start。

Second Part 开始启动

从第一部分末尾我们可以知道,所有Activity最终的启动发起是交给ActivityStackSupervisor.startSpecificActivityLocked来完成的。

void startSpecificActivityLocked(ActivityRecord r,
        boolean andResume, boolean checkConfig) {
    // Is this activity's application already running?
    //检查activity所在的应用是否有进程记录
    ProcessRecord app = mService.getProcessRecordLocked(r.processName,
            r.info.applicationInfo.uid, true);

    r.task.stack.setLaunchTime(r);
    //如果有记录,说明已经拉起过,执行启动Activity流程
    if (app != null && app.thread != null) {
        try {
            if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
                    || !"android".equals(r.info.packageName)) {
                // Don't add this if it is a platform component that is marked
                // to run in multiple processes, because this is actually
                // part of the framework so doesn't make sense to track as a
                // separate apk in the process.
                app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
                        mService.mProcessStats);
            }
            realStartActivityLocked(r, app, andResume, checkConfig);
            return;
        } catch (RemoteException e) {
            Slog.w(TAG, "Exception when starting activity "
                    + r.intent.getComponent().flattenToShortString(), e);
        }

        // If a dead object exception was thrown -- fall through to
        // restart the application.
    }
    //没有进程记录,应该先拉起对应的Application
    mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
            "activity", r.intent.getComponent(), false, false, true);
}

来到最后一个函数realStartActivityLocked(从名字也可以看出是最后的流程):

final boolean realStartActivityLocked(ActivityRecord r,
        ProcessRecord app, boolean andResume, boolean checkConfig)
        throws RemoteException {
    //...
    mService.updateLruProcessLocked(app, true, null);
    mService.updateOomAdjLocked();

    final TaskRecord task = r.task;
    if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE ||
            task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV) {
        setLockTaskModeLocked(task, LOCK_TASK_MODE_LOCKED, "mLockTaskAuth==LAUNCHABLE", false);
    }

    final ActivityStack stack = task.stack;
    try {
        if (app.thread == null) {
            throw new RemoteException();
        }
        //...
        app.forceProcessStateUpTo(mService.mTopProcessState);
        app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
                new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
                task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
                newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
        //...
    } catch (RemoteException e) {
        if (r.launchFailed) {
            // This is the second time we failed -- finish activity
            // and give up.
            Slog.e(TAG, "Second failure launching "
                  + r.intent.getComponent().flattenToShortString()
                  + ", giving up", e);
            mService.appDiedLocked(app);
            stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
                    "2nd-crash", false);
            return false;
        }

        // This is the first time we failed -- restart process and
        // retry.
        app.activities.remove(r);
        throw e;
    }
    //...

    return true;
}

可以看出,每次启动Activity,都会触发AMS更新进程优先级以及Lru记录。

最终启动工作是交由Application的主进程ActivityThread.handleLaunchActivity来完成的(实际上是先通过AIDL发送到ApplicationThread,再转发到ActivityThread的)。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    unscheduleGcIdler();
    //...
    Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed);

        //...
            r.paused = true;
        }
    } else {
        // If there was an error, for any reason, tell the activity
        // manager to stop us.
        try {
            ActivityManagerNative.getDefault()
                .finishActivity(r.token, Activity.RESULT_CANCELED, null, false);
        } catch (RemoteException ex) {
            // Ignore
        }
    }
}

该函数主要执行两个流程:

performLaunchActivity();

1、使用ClassLoader加载并创建对应的Activity
2、通过ActivityClientRecord中的LoadApk创建Application(makeApplication函数 —–> 如果创建了那么直接返回;如果没有,创建AppContext以及Application)
3、创建Activity的baseContext
4、执行Activity.attatch 真正的Activity参数初始化方法
5、Instrumentation通知Activity.onCreate
6、Activity执行performStart完成部分初始化,同样由Instrumentation通知Activity.onStart

handleResumeActivity();

至此,Activity启动基本完成。

启动入口Activity有哪些区别?

在Activity启动的第二部分开始,我们知道ActivityStackSupervisor.startSpecificActivityLocked中会通过AMS中是否存在进程记录来判断应用是否启动,如果没有启动,那么调用AMS.startProcessLocked先fork出对应进程

注意:实际上调用了多个重载函数,代码有精简
private final void startProcessLocked(ProcessRecord app, String hostingType,
        String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
    long startTime = SystemClock.elapsedRealtime();
    checkTime(startTime, "startProcess: starting to update cpu stats");
    updateCpuStats();
    checkTime(startTime, "startProcess: done updating cpu stats");
    try {
        int uid = app.uid;
        int[] gids = null;
        int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
        Process.ProcessStartResult startResult = Process.start(entryPoint,
                app.processName, uid, uid, gids, debugFlags, mountExternal,
                app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                app.info.dataDir, entryPointArgs);
        checkTime(startTime, "startProcess: returned from zygote!");
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    } catch (RuntimeException e) {
    }
}

Process大概流程如下图:

主要是通过Socket进行进程通信传递消息到ZygoteInit,然后由Zygote fork出进程。

而后会通过反射调用到ActivityThread.main()创建新的应用主进程,其中ActivityThread.attatch()会调用到AMS.attachApplicationLocked进而创建对应进程的ProcessRecord,创建之后会检查是否有Activity需要拉起,从而,拉起根Activity。

Window何时创建?

在ActivityThread.performLaunchActivity()中有一个步骤是调用

Activity.attatch()初始化Activity的Context以及其他参数,这里就包含对PhoneWindow的创建并加入到WindowManagerService。

DecorView何时创建,并开始第一次绘制?

在ActivityThread.handleResumeActivity()中,

执行performResumeActivity之后,会从window中获取到DecorView,然后将decor加入到WindowManagerService中(WindowManagerService.addView —> WindowManagerGlobal.addView);

WindowManagerGlobal.addView:

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    //...
    ViewRootImpl root;
    View panelParentView = null;
    //...
    synchronized (mLock) {
        // Start watching for system property changes.
        if (mSystemPropertyUpdater == null) {
            mSystemPropertyUpdater = new Runnable() {
                @Override public void run() {
                    synchronized (mLock) {
                        for (int i = mRoots.size() - 1; i >= 0; --i) {
                            mRoots.get(i).loadSystemProperties();
                        }
                    }
                }
            };
            SystemProperties.addChangeCallback(mSystemPropertyUpdater);
        }
        //...
        //创建绘制的时候根View
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }

    // do this last because it fires off messages to start doing things
    try {
        //调用setView,触发第一次绘制并开始接收垂直同步消息
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        synchronized (mLock) {
            final int index = findViewLocked(view, false);
            if (index >= 0) {
                removeViewLocked(index, true);
            }
        }
        throw e;
    }
}

所以,所有元素开始可见是在Activity.onResume之后

Activity任务栈

管理ActivityRecord的终极老大是ActivityStackSupervisor,它包含两个基本的ActivityStack.

实际上Android 4.4以前只有一个mHistory 来管理所有的 activity;在后期,芯片厂商为了增加新feature有可能在这里新增一个或者多个栈,比如Multi-window的实现,所以才新增了ActivityStackSupervisor并变更为新的栈管理架构。

其他的基本对应关系如下:

Activity ————————- ActivityRecord

一个任务栈 ————————- TaskRecord

img

Android 解决依赖冲突(duplicate entry)方法

现在Android 均使用Gradle进行构建,使用Gradle导入第三方库文件的时候经常会遇到多个依赖同时被导入的情况,但是我们项目的build.gradle通常都是这样导入的:

导入通常只有一句话,这时候想要知道冲突在什么地方就比较麻烦了。

方法一:

在Gradle Panel中有如下功能。

你问Gradle Panel在哪儿?

在Gradle Panel中打印,Project和Module都支持。

 

双击运行,输出如下内容:

15:43:32: Executing external task 'androidDependencies'...
Configuration on demand is an incubating feature.
Configuration 'compile' in project ':dataprovider' is deprecated. Use 'implementation' instead.
:app:androidDependencies
debug
debugCompileClasspath - Dependencies for compilation
+--- com.android.support:appcompat-v7:26.1.0@aar
+--- com.android.support.constraint:constraint-layout:1.0.2@aar
+--- :dataprovider (variant: debug)
+--- com.android.support:animated-vector-drawable:26.1.0@aar
+--- com.android.support:support-vector-drawable:26.1.0@aar
+--- com.android.support:support-v4:26.1.0@aar
+--- com.android.support:support-media-compat:26.1.0@aar
+--- com.android.support:support-fragment:26.1.0@aar
+--- com.android.support:support-core-utils:26.1.0@aar
+--- com.android.support:support-core-ui:26.1.0@aar
+--- com.android.support:support-compat:26.1.0@aar

会给出所有的依赖,当然,这样看起来比较麻烦。

方法二:使用Andorid Studio gradle view plugin

安装:Android Studio->Preferences->Plugins->Browser Repositories,搜索Gradle View安装。(如果显示或下载有问题,科学上网,你懂的)
结果:

gradle

Android——View之视图组成以及其关系

1、该层级关系比较重要,后面和view相关的事件分发以及绘制流程,都严格按照该流程执行的。

2、mContentParent 实际上就是在onCreate的时候的setContentView()

3、PhoneWindow持有DecorView、(View)mContentParent 、(Context)Activity的引用。因为在Android中window是一个窗体的承载体,其管理了和这个页面显示有关的细节。

4、Window之所以没有描述为页面的承载体,是因为对于Dialog或者Toast也是窗体(在他们的构造方法中,都实例化了PhoneWindow的对象)。

5、综上,一个页面所拥有的window数应当是大于等于1的。