目录
SystemServer、ServiceManager、SystemServiceManager关系
孵化应用进程这种事为什么不交给SystemServer来做,而专门设计一 个Zygote
Zygote的IPC通信机制为什么使用socket而不采用binder
Android
四大组件
AMS
- AMS是什么:a、ActivityManagerService主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块类似;b、在系统启动流程中,SystemServer进程开启时就会初始化AMS;c、打开一个新App,需要AMS去通知zygote进程,并且所有Activity生命周期都由AMS控制
- ActivityThread与ApplicationThread:ActivityThread(Android主线程,在创建完新进程之后,ActivityThread的main函数被加载,然后执行一个loop的循环使当前线程进入消息循环)、ApplicationThread(是ActivityThread的内部类,是一个Binder对象。在此处它是作为IApplicationThread对象的server端等待client端请求,然后进行Handler分发处理,最大的client就是AMS)
- Instrumentation:a、AMS与ActivityThread之间诸如Activity的创建、暂停等交互工作实际上是由Instrumentation具体操作的;b、每个Activity都持有一个Instrumentation对象的一个引用,而且整个进程中只有一个Instrumentation;c、mInstrumentation的初始化在ActivityThread::handleBindApplication函数;d、Activity.startActivity方法内部会调用mInstrumentation.execStartActivity(),调用到AMS,然后AMS通过socket通信告知Zygote进程fork子进程
- AMS如何与zygote进程通信:应用启动时,Launcher进程请求AMS,AMS发送创建应用进程请求,Zygote进程接受请求并fork应用进程。具体通信过程:a、客户端进程发起请求;b、调用Process.start()方法新建进程;c、内部调用ZygoteProcess.start进而调用ZygoteState.connect(),ZygoteState是ZygoteProcess内部类;d、Zygote服务端接收到参数之后调用ZygoteConnection.processOneCommand()处理参数,并fork进程;e、最后通过findStaticMain()找到 ActivityThread类的main()方法并执行,子进程就启动了
- 任务栈:ActivityRecord(a、是Activity管理的最小单位,对应着一个用户界面;b、是应用层Activity组件在AMS中的代表,每一个在应用中启动的Activity,在AMS中都有一个ActivityRecord实例来与之对应,这个ActivityRecord伴随着Activity的启动而创建、终止而销毁)、TaskRecord(任务栈, 每一个TaskRecord都可能存在一个或多个ActivityRecord,栈顶的ActivityRecord表示当前可见的界面)、ActivityStack(是系统中用于管理TaskRecord的,内部维护了一个ArrayList)、ActivityStackSupervisor(管理着多个ActivityStack,但当前只会有一个获取焦点(Focused)的ActivityStack,AMS通过它来管理Activity并且与AMS一样唯一)、ProcessRecord(记录着属于一个进程的所有ActivityRecord,运行在不同TaskRecord中的ActivityRecord可能是属于同一个 ProcessRecord)
- AMS原理:ActivityManager.getRunningServices里通过ActivityManagerNative.getDefault得到此代理对象 ActivityManagerProxy。ActivityManager(与系统所有运行着的Acitivity进行交互,并对Activity相关信息进行管理和维护,持有ActivityManagerPorxy,只需要操作这个代理对象就能操作其业务方法AMS)、ActivityManagerNative(是个抽象类,继承Binder而Binder实现了IBinder接口,实现IActivityManager接口,真正发挥作用的是它的子类AMS)、ActivityManagerProxy(ActivityManagerNative的内部代理类,同样实现IActivityManager接口)
Activity
- 生命周期:常规7个生命周期+特殊情况下生命周期。a、ActivityA跳转ActivityB(A:onPause-B:onCreate onStart onResume-A:onStop);b、ActivityB按返回键(B:onPause-A:onRestart onStart onResume-B:onStop onDestory);c、当ActivityB的launchMode为singleInstance,singleTask且对应的ActivityB有可复用的实例时(A:onPause-B:onNewIntent onRestart onStart onResume-A:onStop( 如果A被移出栈的话还有一个A:onDestory))d、当ActivityB的launchMode为singleTop且ActivityB已经在栈顶时(一些特殊情况如通知栏点击、连点),此时只有B页面自己有生命周期变化(B:onPause onNewIntent onResume)
- onSaveInstanceState与onRestoreInstanceState调用时机:a、横竖屏切换时成对调用(onPause-onSaveInstanceState-onStop-onDestroy-onCreate-onStart-onRestoreInstanceState-onResume、注:如果配置这个属性:androidconfigChanges="orientation|screenSize" 就不会再调用Activity的生命周期,只会调用onConfigurationChanged方法);b、处于后台被回收并从最近程序打开时调用onRestoreInstanceState;c、Menu键、Home键、电源键、打开另一个Activity等会调用onSaveInstanceState,back键不会调用onSaveInstanceState
- 启动模式:4个。减少资源浪费。SingleTop(Activity在栈顶->onNewIntent;非栈顶->创建新Activity;应用于->通知页面、登录页面、耗时操作返回页面)、SingleTask(Activity已存在归属栈中->该Activity上所有Activity出栈并调用onNewIntent;应用于->主界面)、SingleIntance(整个系统中只会存在一个这样的实例。不同的应用去打开这个 activity 共享同一个 activity。创建一个新栈,然后创建该Activity实例并压入新栈中,新栈中只会存在这一个Activity实例;应用于->呼叫来电界面)
- 启动方式:显示启动(Intent构造、setComponent(componentName)方法、setClass/setClassName方法)、隐式启动(隐式Intent是通过在AndroidManifest文件中设置action、data、category,让系统来筛选出合适的Activity)
- IntentFilter匹配规则:action(有IntentFilter规则则至少包含一个action,那么隐式启动时必须指定其中一个)、category(规则中有则可指定或不指定)、data((Uri+MiniType)规则中有则必须指定,需要全匹配)
- scheme:a、使用场景(APP根据URL跳转到另外一个APP指定页面、可以通过h5页面跳转app原生页面、服务器可以定制化跳转app页面);b、协议格式(scheme://host:8080/path?query,协议名称://地址域:路径端口号/指定页面路径?传递参数,如:local://agg:8080/test?testId=1935);c、使用( );d、调用(startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("local://agg:8080/test?testId=1935"))))
- 启动过程(流程):跨进程启动(a、当前进程请求AMS:当前进程采用Binder IPC向system_server进程(AMS)发起startActivity请求;b、AMS发送创建应用进程请求:system_server进程接收到请求后,向zygote进程发送创建进程的请求;c、Zygote进程接受请求并fork应用进程,即App进程;d、App进程通过Binder向AMS发起attachApplication请求,AMS绑定ApplicationThread;e、AMS发送启动Activity请求:system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求;f、ActivityThread的Handler处理启动Activity请求:App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程(ActivityThread)发送LAUNCH_ACTIVITY消息;主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate、onResume等方法,UI渲染结束后便可以看到App主界面)、进程内启动(a、当前进程请求AMS;b、AMS发送启动Activity请求:system_server进程接收到请求后,解析Activity信息、处理启动参数,并给当前进程发送 scheduleLaunchActivity请求...( 与跨进程启动相似,主要没有创建新进程那部分步骤))
- Activity任务栈:FLAG_ACTIVITY_NEW_TASK(为Activity指定“singleTask”启动模式,其效果和在XML中指定相同 android:launchMode="singleTask")、FLAG_ACTIVITY_SINGLE_TOP(为Activity指定“singleTop”启动模式,其效果和在XML中指定相同 android:launchMode="singleTop")、FLAG_ACTIVITY_CLEAR_TOP(具有此标记位的Activity,当它启动时,在同一个任务栈中位于它上面的Activity都要出栈。此标记位一般会和 singleTask启动模式一起出现,此情况下,若被启动的Activity实例存在,则系统会调用它的onNewIntent)、FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS(不会出现在历史Activity列表)、taskAffinity(使用包名形式指定任务栈)
- Activity卡顿原因:频繁GC(内存泄漏、加载大数据、大图片)、UI绘制(UI线程耗时操作、过度绘制)
Service
- startService与bindService区别:生命周期回调不同(3个与4个)。startService(onCreate() -> onStartCommand() -> onDestroy() 如果服务已经开启,多次执行startService不会重复的执行onCreate(), 而是会调用onStartCommand()和onStart())、bindService(onCreate() -> onBind() -> onUnbind()-> onDestroy() 如果服务已经开启,多次执行bindService时,onCreate和onBind方法并不会被多次调用)
- 启动流程:与Activity启动流程一致,将Activity部分换为Service即可
- bindService流程:a、Activity调用bindService,通过Binder通知AMS去启动Service;b、AMS创建ServiceRecord,并利用ApplicationThreadProxy回调,通知APP新建并启动Service;c、AMS把Service启动起来后,让Service返回一个Binder对象给自己,以便传递给Client;d、AMS把从Service处得到这个Binder对象通过onServiceConnected传给Activity;e、在onServiceConnected回调中,使用Stub的asInterface函数将Binder转换为代理Proxy,完成业务代理的转换,之后就能利用Proxy进行通信
- 系统服务与bindService启动的服务的区别:a、启动方式:系统服务(本身实现了Binder接口,一般是在系统启动时,由SystemServer进程创建并作为Binder实体注册到ServiceManager中,被ServiceManager管理,位于SystemServer进程中,例如:AMS,WMS,PMS)、普通服务(一般是通过Activity的startService或其他context的startService调用AMS启动的服务,这里的Service组件只是个封装,主要是里面Binder服务实体类,同Activity管理类似);b、服务注册与管理:系统服务(一般都是通过ServiceManager的addService进行注册,这些服务一般都需要拥有特定权限才能注册到ServiceManager)、普通服务(注册到AMS,AMS管理服务方式同ServiceManager不一样,采用了Activity的管理模型);c、服务的请求使用方式:系统服务(通过ServiceManager的getService得到服务句柄,这个过程其实就是去ServiceManager中查询注册系统服务)、普通服务(去AMS中去查找相应的Service组件,最终会将Service内部Binder的句柄传给Client)
- Service、Activity互相通信:Activity可通过Binder对象与Service通信(a、Service中添加一个继承Binder的内部类,并添加相应逻辑方法;b、Service中重写Service的onBind方法,返回刚刚定义的那个内部类实例;c、Activity中绑定服务,重写ServiceConnection,onServiceConnected返回的IBinder即是Service中的binder,这样在Activity中直接调用Service对应逻辑方法即可),也可通过广播、EventBus与Service通信;Service可通过广播、EventBus与Activity通信
- IntentService:a、默认开启一个工作线程HandlerThread逐一处理所有启动请求,所有任务执行完毕后自动停止服务;b、应用场景:后台下载任务、静默上传;c、实现机制:1.创建一个名叫ServiceHandler的内部Handler;2.把内部Handler与HandlerThread所对应的子线程进行绑定;3.HandlerThread开启线程创建自己的looper;4.通过onStartCommand()将任务依次插入到工作队列中,并发送给onHandleIntent()逐个处理
- onStartCommand返回值:START_NOT_STICKY(如果系统在onStartCommand()返回后终止服务,则除非有挂起Intent要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时,以及应用能够轻松重启所有未完成作业时,运行服务)、START_STICKY(如果系统在onStartCommand()返回后终止服务,则会重建服务并调用onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起Intent要启动服务(在这种情况下,将传递这些 Intent),否则系统会通过空Intent调用onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)) 、START_REDELIVER_INTENT(如果系统在onStartCommand()返回后终止服务,则会重建服务,并通过传递给服务的最后一个Intent调用onStartCommand(),适用于主动执行应该立即恢复的作业(例如下载文件)服务)
- bindService和startService混合使用:a、先startService,再bindService:onCreate() -> onStartCommand() -> onBind();b、先bindService,再startService:onCreate() -> onBind() -> onStartCommand();c、只调用stopService:Service的OnDestroy()方法不会立即执行,在Activity退出时才会执行OnDestroy;d、只调用unbindService:只有onUnbind方法会执行,onDestory不会执行,如果要完全退出Service,需要调用unbindService()以及stopService
参考:Android四大组件之Service_Swuagg的博客-CSDN博客
BroadcastReceiver
- 广播分类:普通全局广播(可并行处理,针对应用间、应用与系统间、应用内部进行通信的一种方式,采用binder方式实现跨进程间通信)、系统广播(只需在注册广播接收者时定义相关的action即可,不需要手动发送广播(网络变 化,锁屏,飞行模式))、有序广播(按Priority属性值从大到小排序,Priority属性相同者,动态注册广播优先)、应用内本地广播(LocalBroadcast,安全性与效率更高,应用内使用且只能动态注册,基于观察者模式和Handler实现消息总线模型)、粘性广播(StickyBroadcast,不安全,任何人可访问和修改它,在Android5.0 & API 21中已经失效,不建议使用)
- 广播使用场景:消息通信(App内部通信(多线程通信)、不同App通信、系统与App通信)
- 静态注册与动态注册区别:静态注册(常驻系统,不受组件生命周期影响,即便应用退出,广播还是可以被接收,耗电、占内存)、动态注册(非常驻,跟随组件的生命变化,组件结束,广播结束,在组件结束前,需要先移除广播,否则容易造成内存泄漏)
ContentProvider
- ContentProvider优点:a、提供一种应用间数据共享的方式(通讯录、天气等);b、封装和解耦底层数据存储方式,使数据操作变得简单、高效和安全
- ContentProvider、ContentResolver、ContentObserver:ContentProvider(管理数据,提供数据的增删改查操作,数据源可以是数据库、文件、XML、网络等)、ContentResolver(应用可通过ContentResolver与ContentProvider进行交互,不同URI操作ContentProvider中不同的数据)、 ContentObserver(观察ContentProvider中数据变化,并将变化通知给外界)
- URI:Uniform Resource Identifier,统一资源标识符,唯一标识ContentProvider和其中的数据,URI分为系统预置和自定义,分别对应系统内置数据(如通讯录、日程表等)和自定义数据库。自定义URI:content://com.agg.provider/User/1(content://:Android规定URI标准前缀;com.agg.provider:ContentProvider唯一标识符;User:数据库中某个表名;1:记录ID(表中某个记录,若无指定则返回全部记录))
Fragment
- 生命周期:6个状态(INITIALIZING、CREATED、ACTIVITY_CREATED、STOPPED、STARTED、RESUMED)、11个生命周期(onAttach、onCreate、onCreateView、onActivityCreated(废弃API,onViewCreated代替)、onStart、onResume——onPause、onStop、onDestoryView、onDestory、onDetach)
- Fragment与Activity、其他Fragment通信方式:接口回调、EventBus、共用ViewModel、Bundle的setArgs(Activity重建时会通过空参构造方法反射出新的fragment,并且给mArgments初始化为原先Bundle的setArgs数据)
- FragmentPagerAdapter、FragmentStatePageAdapter:FragmentPagerAdapter(调用detach而不是remove,只执行onDestroyView,不会onDestroy,不会摧毁Fragment实例,只会摧毁Fragment的View,内存不释放)、FragmentStatePageAdapter(页面切换时调用remove,执行onDestroy,直接摧毁Fragment。可以使用ViewPager2和FragmentStateAdapter代替)
- Fragment懒加载:通过判断当前Fragment是否对用户可见,在add+show+hide模式下使用onHiddenChanged(),在ViewPager+Fragment模式下使用setUserVisibleHint,当可见且onViewCreated()回调,表明View已经加载完毕,此时再调用懒加载方法。
Handler:线程间通信的机制
Hander消息分发机制
- Handler实现原理:a、Hander发送消息(post、sendMessageDelayed、sendMessageAtTime、enqueueMessage);b、MessageQueue插入消息队列(enqueueMessage);c、Looper循环阻塞取出消息(prepare、loop、loopOnce、MessageQueue.next);d、Handler处理消息(dispatchMessage、handleMessage)
- 子线程new Handler:需要先调用Looper.prepare实例化Looper,并调用loop方法开启循环,最后方可在子线程new Handler
- Handler内存泄漏:java中非静态内部类和匿名内部类都会隐式持有当前类的外部引用,当在Activity中使用非静态内部类初始化Handler后,此Handler就会持有当前Activity的引用,在Handler消息队列还有未处理消息或正在处理消息时导致Activity不会被回收,从而造成内存泄漏。解决方案:a、Handler设置成静态内部类,并使用WeakReference持有Activity实例;b、Activity生命周期结束时,清空Handler消息队列
- Message对象创建方式:a、new Message:每次new一个Message,去堆内存开辟对象存储空间;b、Message.obtain:先判断消息池是不是为空,如果非空就将消息池表头Message取走,再把表头指向 next;如果消息池为空说明还没有Message被放进去,那么就new一个Message对象。消息池使用Message链表结构实现,消息池默认最大值50,消息在loop中被handler分发消费之后会执行回收的操作,将该消息内部数据清空并添加到消息链表的表头;c、handler.obtainMessage():其内部也是调用的obtain()方法
- postDelay执行逻辑:postDelayed传入的时间,先和当前时间SystemClock.uptimeMillis()做加和,然后和当前消息队列里消息头的执行时间做对比,如果比头的时间靠前,则会做为新的消息头,不然则会从消息头开始向后遍历,找到合适的位置插入延时消息。 a、postDelay()一个10秒钟的Runnable A、消息进队, MessageQueue调用nativePollOnce()阻塞,Looper阻塞;b、紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列头部(A的前面),然后调用nativeWake()方法唤醒线程;c、MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;d、Looper 处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;e、直到阻塞时间到或者下一次有Message进队
- MessageQueue数据结构:单链表数据结构,按照消息待发送时间的先后顺序存储
- Looper与ThreadLocal、ThreadLocalMap:ThreadLocal是与线程绑定的,本质上操作线程中ThreadLocalMap来实现本地线程变量的存储(sThreadLocal.set(new Looper(quitAllowed))),ThreadLocalMap是采用table数组方式存储数据,通过ThreadLocal计算出哈希key作为数组key,数组value是Entry内部类(table[key] = new Entry(ThreadLocal, Looper))
- 同步屏障:为了让异步消息优先执行。a、MessageQueue.next判断message.target ==null为屏障消息,会滤过此同步消息,直接循环遍历找出一条异步消息来处理, 这样就能实现异步消息优先执行。在同步屏障没移除前,只会处理异步消息,处理完所有的异步消息后,就会处于堵塞;b、使用同步屏障:Handler构造方法中传入async参数true 或 创建Message对象时调用setAsynchronous(true);c、同步屏障应用:Android应用框架中为了更快的响应UI刷新事件,在View更新时draw、requestLayout、invalidate等很多地方都调用ViewRootImpl.scheduleTraversals,使用了MessageQueue.next同步屏障
- 子线程更新UI:每次更新UI时,ViewRootImpl.checkThread()检验线程是否是View的创建线程,onResume之前VIewRootImpl未被创建,此时子线程中更新UI也行,但只能更新自己创建的View。Android不建议子线程更新UI,如果每个线程都可以对ui进行访问,界面可能就会变得混乱不堪,如果它们操作同一资源可能会造成线程安全问题,而加锁会降低性能
- Handler其他面试题:a、一个线程可以有多个Handler,只有一个Looper对象,只有一个MessageQueue对象;b、Looper.loop()源码:for无限循环,阻塞于消息队列的next方法,取出消息后调用msg.target.dispatchMessage(msg)进行消息分发;c、子线程中可以用MainLooper去创建Handler,在子线程中Handler handler = new Handler(Looper.getMainLooper())时,Looper和Handler不在一个线程中
HandlerThread
- 问:项目中经常要执行耗时操作, 如果经常要开启线程,接着又销毁线程,这无疑是很消耗性能的,怎么解决?——线程池或HandlerThread
- HanderThread好处:a、将loop运行在子线程中处理,减轻主线程压力使其更流畅;b、开启一个线程起到多个线程的作用,用来执行多个耗时操作,而不需要多次开启线程
- HanderThread劣势:不能并发只能串行执行、消息延时——此时可用线程池解决
- run方法运行结束后,线程依旧存活,因为MessageQueue.next的nativePollOnce会阻塞,等待下一消息到来
IdleHandler
- 触发时机:Looper循环出现空闲的时候,采取执行任务的一种机制
- 使用场景:启动优化、想要View绘制完成之后添加其他依赖于这个View的View
View
Window
- 什么是Window:a、视图承载器(是一个视图的顶层窗口, 包含了View并对View进行管理);b、是一个抽象类(具体的实现类为PhoneWindow);c、内部持有DecorView(通过WindowManager创建,并通过WindowManger添加DecorView)
- 什么是WindowManager:a、是一个接口(继承自只有添加、删除、更新三个方法的ViewManager接口,它的实现类为 WindowManagerImpl,WindowManagerImpl通过WindowManagerGlobal代理实现addView);b、使用WindowManager对Window进行添加和删除(具体工作则由WMS来处理,WindowManager和WMS通过Binder来进行跨进程通信)
- 什么是ViewRootImpl:a、是View和WindowManager的桥梁(ViewRootImpl.setView使View和WindowManager的Decorview相关联);b、View三大流程均通过ViewRootImpl完成;c、 Android所有触屏事件、 按键事件、界面刷新等事件都是通过ViewRootImpl进行分发
- 什么是DecorView:a、是FrameLayout的子类,可以被认为是Android视图树的根节点视图(View根布局),内部绑定Window;b、包含一个竖直方向LinearLayout,LinearLayout里面有上下三个部分, 上面是个ViewStub延迟加载的视图(根据Theme设置ActionBar), 中间的是标题栏(根据Theme设置,有的布局没有),下面的是内容栏;c、setContentView就是把需要添加的View添加在DecorView的内容栏
- Activity、Window、View三者关系:Activity控制生命周期和处理事件,持有Window;Window作为视图承载器,负责视图控制,持有DecorView,DecorView就是View的根布局;View就是视图,在Activity.setContentView中将View视图添加到DecorView中
- DecorView什么时候被WindowManager添加到Window中:即使Activity布局已成功添加到DecorView中,但DecorView此时还没有添加到Window中。在ActivityThread的handleResumeActivity方法中,首先会调用Activity.onResume方法,接着调用Activity.makeVisible方法,在makeVisible()中完成DecorView的添加和显示
View绘制
- 布局加载流程:a、setContentView中通过LayoutInflate.inflate加载对应布局;b、inflate方法中首先调用Resources.getLayout通过pull方式IO加载Xml布局解析器到内存中;c、createViewFromTag根据xml的Tag标签反射创建View到内存(内部主要是按优先顺序为Factory2和Factory的onCreatView、createView方法进行View的创建);d、递归构建其中子View,并将子View添加到父ViewGroup中。性能瓶颈:布局文件解析中的IO过程;创建View对象时的反射过程
- 绘制流程-addView流程:ActivityThread.handleResumeActivity——WindowManagerImpl.addView——WindowMangerGlobel.addView——ViewRootImpl.setView——ViewRootImpl.scheduleTraversals——ViewRootImpl.doTraveral——ViewRootImpl.performTraversals
- 绘制流程-performTraversals流程:a、performMeasure——DecorView.measure——DecorView.onMeasure——View.measure;b、performLayout——DecorView.layout——DecorView.onLayout——View.layout;c、performDraw——DecorView.draw——DecorView.onDraw——View.draw
- 绘制流程-Measure、Layout、Draw三大流程:startActivity->ActivityThread.handleLaunchActivity->onCreate ->完成DecorView和Activity的创建- >handleResumeActivity->onResume()->DecorView添加到WindowManager->ViewRootImpl.performTraversals() 方法,测量(measure),布局(layout),绘制(draw), 从DecorView自上而下遍历整个View树
- MeasureSpec:是View类的一个静态内部类,用来说明应该如何测量这个View,32位int型,前2位是测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize
- SpecMode:EXACTLY(精确测量模式,视图宽高指定为match_parent或具体数值时生效,表示父视图已经决定了子视图的 精确大小,这种模式下View的测量值就是SpecSize的值)、AT_MOST(最大值测量模式,当视图的宽高指定为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸)、UNSPECIFIED(不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少用到)
- Activity获取View宽高时机:a、onWindowFocusChanged为true时;b、view.post()(将runnable放入ViewRootImpl的RunQueue中,执行时机是在下一个performTraversals到来时,也就是view完成layout之后的第一时间获取宽高);c、ViewTreeObserver.addOnGlobalLayoutListener(当View树的状态发生改变或者View树内部的View的可 见性发生改变时,onGlobalLayout方法将被回调)
- View.post与Handler.post区别:View已经attach到window时,View.post直接调用handler的实现;如果还未attach,就将runnable放进ViewRootImpl的RunQueue,RunQueue是在下一个performTraversals时执行,而MessageQueue是在下一次loop时执行
- View绘制屏幕刷新:CPU准备数据——通过Driver层把数据交给GPU渲染——Display负责消费显示内容。CPU(主要负责Measure、Layout、Record、Execute数据计算工作)、GPU(负责Rasterization(栅格化(向量图形格式表示的图像转换成位图用于显示器))、渲染,渲染好后放到buffer(图像缓冲区)里存起来)、Display(屏幕或显示器会以一定的帧率刷新,每次刷新时,就会从缓存区将图像数据读取显示出来,如果缓存区没有新数据,就一直用旧数据,这样屏幕看起来就没有变)
- View、SurfaceView、TextureView、SurfaceTexture区别:View(View适用于主动更新的情况,而SurfaceView则适用于被动更新、频繁刷新界面,比如游戏画面、摄像头或视频播放等;View在主线程中对页面进行刷新,而SurfaceView则开启一个子线程来对页面进行刷新;View在绘图时没有实现双缓冲机制, SurfaceView在底层机制中就实现了双缓冲机制)、SurfaceView(使用双缓冲机制(后台缓冲区接受数据,填充完整后交换给前台缓冲区,保证前台缓冲区数据都是完整的),在独立线程有自己用于绘制的Surface,不过Surface不在View hierachy中,它的显示不受View的属性控制,SurfaceView不能嵌套使用,并且在7.0版本之前不能进行平移,缩放等变换,也不能放在其它ViewGroup中)、TextureView(需要在硬件加速的窗口,和View一样使用)、SurfaceTexture(和SurfaceView不同的是,它对图像流的处理并不直接显示,而是采用OpenGL处理,可用于图像流数据的二次处理(如Camera滤镜,桌面特效等))、GLSurfaceView(和SurfaceView不同的是,它加入了EGL的管理,并自带渲染线程)
- getWidth、getMeasuredWidth区别:onLayout之后,onMeasure之后。getMeasuredWidth(获得的值是setMeasuredDimension方法设置的值,它的值在measure方法运行后就会确定)、getWidth(获得的值是layout方法中传递的四个参数中的mRight-mLeft,它的值是在layout方法运行后确定)、使用(一般情况下在onLayout方法中使用getMeasuredWidth方法,而在除onLayout方法之外的地方用getWidth方法)
- invalidate、postInvalidate、requestLayout区别:invalidate(触发onDraw流程,在UI线程调用)、postInvalidate(可以在非UI 线程中调用,内部通过Handler发送消息将线程切回到UI线程通知重新绘制,最终还是调用invalidate)、requestLayout(会触发三大流程。会直接递归调用父窗口requestLayout,直到ViewRootImpl,然后触发peformTraversals,由于mLayoutRequested为true,会导致onMeasure和onLayout(ViewGroup实现,对每个子视图布局)被调用。不一定会触发OnDraw(View实现,ViewGroup不需要实现),将会根据标志位判断是否需要onDraw)
自定义View
- 步骤:构造-onMeasure(setMeasuredDimession)-onSizeChanged-onLayout-onDraw-提供回调使用。onMeasure(可以不重写,不重写的话就要在外面指定宽高,建议重写)、onDraw(看情况重写,如果需要画东西就要重写)、onTouchEvent(也是看情况,如果要做能跟手指交互的View,就重写)
- 注意事项:如果有自定义布局属性,在构造方法中取得属性后应及时调用recycle方法回收资源;onDraw和onTouchEvent方法中都应尽量避免创建对象,过多操作可能会造成卡顿
- 考虑机型适配:合理使用warp_content,match_parent。 尽可能地使用RelativeLayout。 针对不同的机型,使用不同的布局文件放在对应的目录下,android会自动匹配。 尽量使用点9图片。 使用与密度无关的像素单位dp,sp。 引入 android的百分比布局。 切图的时候切大分辨率的图,应用到布局当中,在小分辨率的手机上也会有很好显示效果
View事件分发机制
- 定义:将点击事件(MotionEvent)传递到某个具体的View & 处理的整个过程
- dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent——Activity、ViewGroup、View
- View.setOnTouchListener{onTouch}、onTouchEvent、onClickListener关系:onTouch优先级最高;onClick事件是在 onTouchEvent的MotionEvent.ACTION_UP事件通过performClick() 触发的
- 事件先到DecorView然后到Window:当屏幕被触摸input系统事件从Native层分发Framework层然后到ViewRootImpl的DecorView -> Activity -> PhoneWindow -> DecorView -> ViewGroup
- 解决滑动冲突:外部拦截法(推荐)——父View根据需要对事件进行拦截onInterceptTouchEvent;内部拦截法——父View不拦截任何事件,子View根据需要决定是自己消费事件还是给父View处理,使用requestDisallowInterceptTouchEvent方法返回true就不会执行父类的onInterceptTouchEvent(),可将点击事件传到下面的View, 剥夺了父view 对除了ACTION_DOWN以外的事件的处理权。相同:2种方式父View的ACTION_DOWN 都返回false,因为一个事件序列只能被一个View拦截且消耗,一旦一个元素拦截了此事件,那么同一个事件序列内的所有事件都会直接交给它处理(即不会再调用这个View的拦截方法去询问它是否要拦截了,而是把剩余的ACTION_MOVE、 ACTION_DOWN等事件直接交给它来处理)
- 同时对父View和子View设置点击方法,优先响应哪个:优先响应子View,父view要优先响应事件,须调用 onInterceptTouchEvent ,然后在父view的onTouchEvent中处理
RecyclerView
- 四级缓存:mAttachedScrap、mChangedScrap——mCachedViews——ViewCacheExtension——RecycledViewPool。mAttachedScrap/mChangedScrap(屏幕内缓存,针对的是屏幕可见itemView信息发生变化时的回收与复用,notifyItemChanged/rangeChange,此时如果Holder发生了改变那么就放入mChangeScrap中,反之放入到mAttachScrap)、mCachedViews(当列表滑出屏幕时,ViewHolder会被缓存在mCachedViews ,其大小由mViewCacheMax决定,默认为2,可通过Recyclerview.setItemViewCacheSize()动态设置)、ViewCacheExtension(可以自己实现ViewCacheExtension类实现自定义缓存,可通过Recyclerview.setViewCacheExtension()设置)、 RecycledViewPool缓存池(ViewHolder在首先会缓存在mCachedViews中,当超过了个数(比如默认为2), 就会添加到RecycledViewPool中,
- 为什么要先放入到mCachedViews而不是直接放入mRecyclerPool,这样做的原因:因为刚滑出屏幕的itemView可能会被滑动进来,所以加了一层mCachedViews缓存,而从mCachedViews中获取的holder是不需要重新bind数据的,mRecyclerPool取出的holder会被重置信息,重新bind数据的
WebView
- 优化加载速度:a、预加载WebView;b、加载WebView同时请求H5页面数据;c、DNS和链接慢,想办法复用客户端使用的域名和链接;d、脚本执行慢,就让脚本在最后运行,不阻塞页面解析;e、后端处理慢,可以让服务器分trunk输出,在后端计算的同时前端也加载网络静态资源
- WebView与JS互调:Android调用JS(通过WebView.loadUrl()或WebView.evaluateJavascript())、JS调用Android(a、通过WebView的addJavascriptInterface()进行对象映射;b、通过WebViewClient的shouldOverrideUrlLoading()方法回调拦截url;c、Android通过WebChromeClient下面三个方法回调onJsAlert()、onJsConfirm()、onJsPrompt,分别拦截JS对话框,得到消息内容然后解析即可)
- WebView的漏洞:a、任意代码执行漏洞(JS调用Android通过WebView的addJavascriptInterface()进行对象映射,当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。Android 4.2以前,需要采用拦截prompt()的方式进行漏洞修复,4.2以后,则只需要对被调用的函数以 @JavascriptInterface进行注解即可);b、密码明文存储漏洞(解决:WebSettings.setSavePassword(false)关闭密码保存提示,就不会选择保存密码到webview.db中);c、域控制不严格漏洞(对于不需要使用file协议的应用,禁用file协议。setAllowFileAccess(false); setAllowFileAccessFromFileURLs(false); setAllowUniversalAccessFromFileURLs(false); 对于需要使用file协议的应用,禁止file协议加载JavaScript)
动画
- 动画分类:视图动画(包括补间动画(平移、旋转、缩放、透明度)和帧动画)、属性动画
- 补间动画和属性动画的区别:a、补间动画只能作用在View上,属性动画作用于View的某个属性 & 对象而不是整个View;b、补间动画没有改变View属性(未改变真实位置、大小等),只是改变视觉效果;c、补间动画效果单一(只能实现平移、旋转、缩放 & 透明度这些简单的动画需求)
- ObjectAnimator和ValueAnimator区别:二者都是属性动画,ValueAnimator作为ObjectAnimator父类,都是先改变值,然后赋值给对象属性,从而实现动画操作,不同的是ValueAnimator是手动赋值给对象属性,ObjectAnimator是自动赋值给对象属性
- TimeInterpolator插值器:设置属性值从初始值过渡到结束值的变化规律。根据时间流逝的百分比来计算出当前属性值改变的百分比。Interpolator负责控制动画变化的速率,使得基本的动画效果能够以匀速、加速、减速、抛物线速率等 各种速率变化。自定义插值器:根据动画的进度(0%-100%)计算出当前属性值改变的百分比
- TypeEvaluator估值器:设置属性值从初始值过渡到结束值的变化具体数值。插值器决定值的变化规律、变化趋势,接下来的具体变化数值则交给估值器,插值器动画都有,而估值器是属性动画特有属性
Bitmap
- 内存占用计算:加载res/raw资源图片 = 宽*高*像素点大小*缩放系数(dpi);加载SD卡图片 = 宽*高*像素点大小
- 压缩方式:宽高压缩(Bitmap.createScaledBitmap(bitmap, width, height, true))、缩放法压缩(Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true))可针对Bitmap按图片缩放比例进行Matrix比例压缩操作,而采样率压缩(BItmapFactory.Options设置inJustDecodeBounds只加载Bitmap边界宽高信息,设置inSampleSize的值(int类型:取2的次方数)后,假如设为n,则宽和高都为原来的1/n,宽高都减少)和质量压缩(Bitmap.compress(Bitmap.CompressFormat.JPEG, quality, ByteArrayOutputStream),options从100往下降,循环压缩,直到内存大小合适位置)针对于File、Resource操作,RGB_565法(改变一个像素所占的内存,默认使用ARGB8888配置来处理色彩BItmapFactory.Options设置色彩模式inPreferredConfig为Bitmap.Config.RGB_565,内存减少一半) https://blog.csdn.net/Agg_bin/article/details/128026082
- 采样率压缩:a、this.inJustDecodeBounds = true;b、BitmapFactory.decodeFile(filePath, this);c、this.inSampleSize = calculateInSampleSize(this, width, height);d、this.inJustDecodeBounds = false;e、BitmapFactory.decodeFile(filePath, this)——(this = BitmapFactory.Options())
- Bitmap优化策略/设计一个图片加载库:a、高分辨率图片放入对应文件夹(不同密度设备,将dpi图片资源放置在相应dpi-drawable文件夹,使用decodeResource方法会根据屏幕dpi适配。在图片资源没有做dpi区分时,将图片资源放置在高dpi是比较好的选择,能在最大程度上减少OOM的几率);b、图片内存压缩优化(质量压缩、采样率压缩);c、做图片缓存(三级缓存——内存、磁盘、网络。内存缓存:通常使用最近最少使用算法LruCache实现;磁盘缓存:所使用的算法为DiskLruCache);d、及时回收(bitmap.recycler、采用软引用)
- LruCache & DiskLruCache原理:a、核心思想:当缓存满了的时候,就会优先的去淘汰最近最少使用的缓存对象,主要是3步(初始化,查找和添加);b、算法原理:把最近使用的对象用强引用存储在 LinkedHashMap(数组+双向链表) 中,当缓存满时,把最近最少使用的对象从内存中移除,提供了get(会更新该元素到队头)和put(将该元素添加到队头,并通过trimToSize()方法判断缓存是否已满,如果满了就从队尾开始删除,直到缓存大小小于缓存最大值)方法来完成缓存的获取和添加操作;c、LruCache是个泛型类,采用 LRU算法的缓存有两种:LrhCache内存缓存和DisLruCache硬盘缓存,使用LinkedHashMap中双向链表的访问顺序(另一个是插入顺序)特性实现LRU,即最近访问的最后输出
- drawable文件夹加载优先级:当前密度文件夹——更高密度文件夹——drawable- nodpi文件夹(是与密度无关的文件夹,放在这里的图片系统就不会对它进行自动缩放,当在匹配密度文件夹和更高密度文件夹都找不到的情况下才会去这里查找图片的)——更低密度文件夹(drawable-xhdpi -> drawable- hdpi -> drawable-mdpi -> drawable-ldp)
Binder
序列化
- 序列化:将内存数据对象转换成二进制的流程称之为对象的序列化(Serialization)
- 反序列化:将二进制流恢复为内存数据对象的过程称之为反序列化(Deserialization)
- 为什么需要使用序列化和反序列化:内存中的数据对象只有转换成二进制才可以进行数据持久化和网络传输(内存数据对象——序列化转成二进制——持久化、网络传输——反序列化为内存数据对象)
- Serializable:Java原生序列化,只是一个识别类可被序列化的标志,Serializable接口没有方法和属性,实现Serializable接口的类建议设置serialVersionUID字段值,不推荐使用Java序列化
- Parcelable:将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel从这块共享内存中读出字节流,并反序列化成对象
- Serializable和Parcelable区别:存储媒介不同(Serializable使用IO读写存储在硬盘上,Parcelable直接在内存中读写。明显地内存读写速度大于IO读写)、效率不同(Serializable需要大量IO和反射操作,Parcelable自已实现封送和解封,操作不需要用反射,数据也存放在Native内存中,效率要快很多)
- transient:仅适用于变量,执行序列化时,JVM会忽略transient变量的原始值并将默认值保存到文件中
- serialVersionUID:反序列化时,不仅取决于类路径和功能代码是否一致,还需要看两个类的序列化ID是否一致
- 为什么建议显示指定serialVersionUID值:如果类写完后不再修改,不会有问题,一旦类被修改了,那旧对象反序列化就会报错(因为如果不显示指定serialVersionUID,JVM在序列化时会根据属性、方法等自动生成一个serialVersionUID,然后与属性一起序列化,再进行持久化或网络传输;在反序列化时, JVM会再根据属性、方法等自动生成一个新版serialVersionUID,然后将这个新版serialVersionUID与序列化时生成的旧版serialVersionUID进行比较,此时反序列化就会报InvalidCastException错)
- JSON序列化:JSON=JavaScript Object Notation,推荐使用
IPC通信有哪些
- 进程和线程区别:进程(是系统进行资源分配和调度的一个独立单位);线程(是CPU调度和分派的基本单位,不拥有系统资源,在运行时只是暂用一些计数器、寄存器和栈,多个线程共享进程数据)
- 多进程通信问题:a、静态成员和单例模式失效;b、线程同步机制失效;c、SharedPreferences可靠性降低;d、Application被多次创建
- IPC通信方式、使用场景和优缺点:Intent(4大组件,只能传输Bundle支持的数据类型)、AIDL(最常用,支持一对多并发实时通信,注意线程同步)、Messenger(AIDL简化版,只支持一对多串行实时通信)、ContentProvider(受约束的AIDL,进程间大量数据共享,主要对外提供数据的CRUD)、文件共享(File文件,无高并发的简单数据共享)、Socket(网络通信,只能传输原始的字节流)
- Android为何不使用Linux IPC:管道、消息队列、套接字(Socket)、信号量和共享内存。效率、稳定性和安全性。效率:内存拷贝次数越少,传输速率越高,管道、消息队列和套接字需要2次拷贝(数据先从发送方缓存区拷贝到内核开辟缓存区中,再从内核缓存区拷贝到接收方缓存区),Binder1次拷贝(接收方缓存区与内核缓存区是映射到同一块物理地址),共享内存无拷贝,Binder的性能仅次于共享内存;稳定性:但共享内存需要处理并发同步问题,容易出现死锁和资源竞争,稳定性较差。Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较好。Socket也是基于C/S架构,但传输效率低,开销大;安全性:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID,且在Binder通信时会根据UID/PID进行有效性检测
- 一个应用为啥使用多进程:突破进程内存限制、保持功能稳定性、隔离风险避免主进程崩溃
Binder是什么
- 从进程间通信角度看,是Android中的一种进程间通信机制
- 从传输过程角度看,是一个可以跨进程传输的对象,Binder驱动会自动完成代理对象和本地对象之间的转换
- 从Framework角度看,是ServiceManager(使得客户端可以获取服务端binder实例对象的引用)连接各种Manager(ActivityManager、WindowManager等)和相应ManagerService的桥梁
- 从Android应用层角度看,是客户端和服务端进行通信的媒介,Server进程中的Binder实体对象(Binder类IBinder),Client进程中的Binder实体对象的一个远程代理
Binder原理
- 核心元素:Binder驱动、mmap、一次拷贝(come_from_user)+物理地址映射,发送进程——come_from_user到内核缓存区----数据接收缓存区----接收进程用户空间
- Binder跨进程通信机制:基于C/S架构,由Client、Server、ServerManager和Binder驱动组成。进程空间分为用户空间和内核空间,用户空间不可以进行数据交互,内核空间可以进行数据交互,所有进程共用一个内核空间,Client、Server在用户空间中实现,而Binder驱动程序则是在内核空间中实现
- Binder原理:a、创建内核缓存区:Binder驱动使用 mmap() 在内核空间创建内核缓存区(数据接收缓存区);b、实现地址映射关系:实现内核缓存区和接收进程用户空间地址,同时映射到同一块物理地址;c、发送数据到内核缓存区:发送进程通过系统调用copy_from_user()将数据copy到内核空间的内核缓存区(数据拷贝1次);d、接收进程通过内存映射接收到数据:由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信
Intent大小限制
- Intent中携带的数据从APP进程传输到AMS进程,再由AMS进程传输到目标进程
- 当使用Intent来传递数据时,用到了Binder机制,数据就存放在Binder事务缓冲区里面,而事务缓冲区是有大小限制的,普通由Zygote孵化而来的用户进程,映射的Binder内存大小不到1M
- 所以Intent传输数据大小为:1M-8K-打包数据(1K以内)。——受binder IPC中的mmap申请的共享内存(1M-8K)大小影响。
问:Activity跳转传输数据量大,有哪些解决方案?
- 进程内:单例模式、静态内存、EventBus、Application、SP、File、SQLite等
- IPC:MMKV、共享内存(Android 匿名共享内存的使用 - 简书 通过MemoryFile开辟内存空间,获得FileDescriptor; 将FileDescriptor传递给其他进程; 往共享内存写入数据; 从共享内存读取数据)、Socket或管道性能不太好,涉及到至少两次拷贝
AIDL通信
- AIDL是什么:AIDL是Android提供的接口定义语言,简化Binder的使用,轻松实现IPC进程间通信。AIDL会生成一个服务端对象的代理类,客户端可以通过它实现间接调用服务端对象的方法
- AIDL使用步骤:a、书写 AIDL——创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化;新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件;Make project ,生成 Binder 的 Java 文件。b、编写服务端——创建 Service,在Service中创建生成的Stub实例,实现接口定义的方法;在 onBind() 中返回Binder实例。c、编写客户端——实现 ServiceConnection 接口,在其中通过asInterface拿到 AIDL 类;bindService();调用 AIDL 类中定义好的操作请求。
- AIDL关键类方法:a、AIDL接口——编译完生成的接口继承IInterface;b、Stub——服务实体,Binder的实现类,服务端一般会实例化一个Binder对象,在服务端onBind中绑定, 客户端asInterface获取到Stub。 这个类在编译aidl文件后自动生成,继承自Binder,是一个抽象类,实现了IInterface接口,子类需要实现Server将要提供的具体能力(即aidl文件中声明的方法);c、Stub.Proxy——服务的代理,客户端asInterface获取到Stub.Proxy,实现了IInterface接口;d、asInterface——客户端在ServiceConnection通过Person.Stub.asInterface(IBinder), 根据是同一进程通信或不同进程通信,返回Stub()实体或Stub.Proxy()代理对象;e、transact——运行在客户端,当客户端发起远程请求时,内部会把信息包装好,通过transact()向服务端发送,并将当前线程挂起,Binder驱动唤醒Server进程并调用onTransact()来处理客户端请求;f、onTransact——运行在服务端的Binder线程池中,onTransact()根据Client传来的code调用相关函数,并将结果写入reply。g、Binder驱动——将server进程的目标方法执行结果,拷贝到client进程的内核空间,通知client进程,之前挂起的线程被唤醒,并收到返回结果。
- AIDL支持数据结构:八大基本数据类型、String、CharSequence、List(接收方必须是ArrayList)、Map(接收方必须是HashMap)、实现Parcelable的类、AIDL类
- 如何优化多模块都使用AIDL的情况:a、每个业务模块创建自己的AIDL接口并创建Stub的实现类,向服务端提供自己的唯一标识和实现类;b、服务端只需要一个Service,创建Binder连接池接口,跟据业务模块的特征来返回相应的Binder对象;c、客户端调用时通过Binder连接池, 将每个业务模块Binder请求统一转发到一个远程Service中去执行,从而避免重复创建Service
性能优化
ANR
- 四大组件四种ANR场景:Activity(Input Dispatching timeout 5s 内未响应键盘输入、触摸屏幕等事件);BroadCast(BroadCastQueue TimeOut 未在规定时间内处理完广播 前台广播 10s , 后台 60s );ContentProvider(ContentProvider TimeOut publish 在 10s 内没有完成);Service(Service TimeOut 未在规定时间执行完成 前台服务 20s,后台 200s)
- Activity 的生命周期回调的阻塞并不在触发 ANR 的场景里面,所以并不会直接触发 ANR。 只不过死循环阻塞了主线程,如果系统再有上述的四种事件发生,就无法在相应的时间内处理从而触发 ANR。
内存优化
内存泄漏优化
- 什么是内存泄漏:长生命周期的对象持有短生命周期对象的引用,短生命周期对象不再被使用后不能被GC。如:Thread(或Handler)长久持有Activity引用会导致Activity和其关联的资源、视图无法被回收
- 内存泄漏原因:a、非静态内部类隐式持有外部类的引用(Handler、Thread);b、listener、callback注册后未移除;c、使用Activity的context而不是Application的context;d、单例模式持有Activity引用未释放;e、资源(数据库、IO、Bitmap)使用后未关闭;f、static集合类持有的对象未被释放
- 内存溢出原因:a、手机内存不足;b、app进程内存达到上限(只有两种原因——申请内存速度超出GC释放内存速度(加载大图、循环创建大量对象等);出现内存泄漏)
- 内存泄漏工具:LeakCanary、Android Studio Profiler
- LeakCanary原理:a、Activity调用onDestory后将其放进WeakReference中;b、弱引用关联到一个引用队列ReferenceQueue(弱引用关联的对象被回收后,就会将弱引用加入到ReferenceQueue里);c、延时5秒检测ReferenceQueue中是否存在当前弱引用;d、如果检测不到说明可能发生泄露,通过gcTrigger.runGc()手动调用GC,如果还是没有则断定发生内存泄漏;e、然后找出内存泄漏对象到GC roots的最短路径,输出分析结果展示到页面。
- Profiler使用:使用App后,Profiler中先触发GC,然后dump内存快照,之后点击按package分类,就可以迅速查看到你的App目前在内存中残留的class,点击class即可在右边查看到对应的实例以及引用对象。
图片优化
- 参考(View-Bitmap-Bitmap优化策略)
内存优化实战
- 静态内存分析优化:应用主页2张背景大图优化(a、对保底图和动态加载图进行RGB565和质量压缩等优化;b、动态加载图回来后释放保底图)或轮播背景图置空优化、SP一次性加载到内存且静态保存(a、分多个SP文件;b、较大Json字符串数据迁移到文件或数据库)
- 动态内存分析优化:不一定显示的View占用大内存(使用ViewStub做懒加载)、ViewPager内存一直未释放(使用FragmentStatePagerAdapter代替FragmentPagerAdapter,只保留前后page,不会保留所有page)
- 内存抖动优化:避免在循环里创建对象、建立合适的缓存复用对象(onDraw、getView中对象的创建尽量进行复用)、减少不合理的对象创建
启动优化
- 统计启动时间:a、adb shell am start –W 包/全路径类名,TotalTime为应用自身启动耗时;b、Logcat过滤Displayed;c、Application的attachBaseContext到Activity的onWindowFocusChanged(true)的时间;d、视频采集卡
- 启动耗时分析:a、logcat打印代码执行段的时间差;b、AOP(Aspect);c、Profiler查看启动过程的CPU,看线程具体代码耗时
- 启动优化方案:a、设置windowBackground(做到快速响应以及避免白屏);b、子线程异步初始化Application以及主Activity;c、使用有向无环图对所有任务进行排序,启动有依赖的初始化任务;d、延迟执行任务(handler delay以及使用MessageQueue.IdleHandler(主线程空闲时才执行任务));e、对首页进行布局优化(减少层级嵌套、使用merge、ViewStub、include、动态布局);f、合并首页部分网络请求接口;g、使用本地缓存先加载
布局优化(绘制优化)
- 布局流程:主要包括布局加载流程、布局绘制流程,布局优化主要从布局绘制流程出发进行优化
- 布局分析工具:a、adb shell dumpsys activity top,查看最上层activity信息,找到layout信息;b、使用Layout Inspector
- 布局优化-减少View树层级:a、复杂布局使用ConstraintLayout;b、不嵌套使用RelativeLayout(造成多次触发measure、layout);c、不在嵌套LinearLayout中使用weight;d、使用merge减少一个根ViewGroup层级;e、使用ViewStub延迟加载标签(当布局整体被inflater,ViewStub也会被解析但是其内存占用非常低,它在使用前是作为占位符存在,对ViewStub的inflater操作只能进行一次,也就是只能被替换1次)
- 布局优化-避免过度绘制:a、去掉多余的background;b、减少复杂shape使用;c、避免层级叠加;d、自定义View使用clipRect屏蔽被遮盖View绘制
- 布局优化-避免视图与数据绑定耗时:由于网络请求或者复杂数据处理逻辑耗时导致与视图绑定不及时,可以使用缓存、占位图等
- 布局监控:布局加载监控(使用AspectJ做面向aop的非侵入性的监控)、布局绘制监控(Choreographer)
卡顿优化(帧率优化)
- 帧率检测:a、看大概卡顿程度——adb shell dumpsys gfxinfo 包名;b、看准确值——使用python脚本抓取systrace.html,平均帧率(fps/s) = frames/tatol_time(ms/1000转成s)
- 卡顿优化:a、开发app的性能目标就是保持60fps,意味着每一帧只有16ms≈1000/60的时间来处理所有的任务,如果某个操作花费时间20ms就会产生丢帧现象,用户在32ms看到的会是同一帧画面,就会感觉不流畅卡了一下;b、Android系统每隔16ms发出VSYNC信号,卡顿优化就是监控和分析由哪些因素导致绘制渲染任务没有在一个vsync时间内完成;c、卡顿产生原因是错综复杂的,涉及到代码、内存、绘制、IO、CPU(检查代码、内存使用、布局绘制、IO操作、CPU使用)等
- 卡顿优化分析:查看systemtrace.html。a、布局耗时查看UI线程;b、过渡绘制结合渲染线程和UI线程;c、调度延迟在对应卡顿的帧上查看CPU core占用情况,拉取一段CPU占用信息,按耗时排序来分析
- 卡顿监控:BlockCanary、ANR-WatchDog、单点问题监控
网络优化
- 网络优化方案:a、合并多个网络请求,减少流量消耗;b、缓存网络请求回来数据;c、弱网优化
CPU优化
安全优化
- PIA(Privacy Impact Assessment)隐私影响评估
- SIA:Fireline火线扫描、娜迦扫描、Converity扫描
系统启动
系统启动流程
Android系统架构分为应用层、Framework层、系统运行库层(Native)、Linux内核层,启动按照Android系统架构流程进行(Loader->kernel->Framework->Application)
- Bootloader引导:a、当电源按下时,引导芯片代码从ROM开始执行;b、Bootloader引导程序把操作系统映像文件拷贝到RAM中,然后跳转到它的入口处去执行,启动Linux内核;c、Linux kernel内核启动,会做设置缓存、加载驱动等一些列操作;d、当内核启动完成之后,启动init进程,作为第一个系统进程,init进程从内核态转换成用户态
- init进程启动:a、fork出ServerManager子进程;b、解析init.rc配置文件并启动Zygote进程
- Zygote进程启动:a、孵化其他应用程序进程,所有应用进程都由zygote进程fork出来的,通过创建服务端Socket,等待 AMS的求来创建新的应用程序进程;b、创建SystemServer进程(在Zygote进程启动之后,会通过ZygoteInit的main方法fork出SystemServer进程)
- SystemServer进程启动:a、创建SystemServiceManager,它用来对系统服务进行创建、启动和生命周期管理;b、ServerManager.startService启动各种系统服务(WMS/PMS/AMS等),SystemServiceManager调用ServerManager的addService将这些Service服务注册到ServerManager里面;c、启动桌面进程,这样才能让用户见到手机的界面
- Launcher进程启动:开启系统Launcher程序来完成系统界面的加载与显示
SystemServer、ServiceManager、SystemServiceManager关系
在SystemServer进程中创建SystemServiceManager,SysytemServiceManager启动一些继承自SystemService的服务,并将这些服务的Binder注册到ServiceManager中,ServiceManager是系统服务管理者,对于其他的一些继承于IBinder的服务,通过ServiceMaanager的addService方法添加
- SystemServer:是一个由zygote孵化出来的进程, 名字为system_server,叫做系统服务进程,大部分Android提供的一些系统服务都运行在该进程中,包括AMS、WMS、PMS,这些系统的服务都是以一个线程的方式存在于SysyemServer进程中
- SystemServiceManager:管理一些系统的服务,在SystemServer中初始化
- ServiceManager:像是一个路由,Service把自己注册在ServiceManager中,客户端通过ServiceManager查询服务 。a、维护一个svclist列表来存储service信息;b、向客户端提供Service的代理,也就是BinderProxy;c、维护一个死循环,不断查看是否有service操作请求,如果有就读取相应的内核binder driver
孵化应用进程这种事为什么不交给SystemServer来做,而专门设计一 个Zygote
- Zygote进程是所有Android进程的母体,包括system_server和各个App进程
- zygote利用fork()方法生成新进程,新进程复用Zygote进程本身资源,再加上新进程相关资源,构成新的应用进程。应用在启动的时候需要做很多准备工作,包括启动虚拟机,加载各类系统资源等等,这些都是非常耗时的, 如果能在zygote里就给这些必要的初始化工作做好,子进程在fork的时候就能直接共享,效率就会非常高
- SystemServer里跑了一堆系统服务,这些不能继承到应用进程
Zygote的IPC通信机制为什么使用socket而不采用binder
- Zygote是通过fork生成进程的,而fork只能拷贝当前线程,不支持多线程的fork,Zygote fork出来的进程A只有一个线程,如果Zygote有多个线程,那么进程A会丢失其他线程,这时可能造成死锁
- Binder通信需要使用Binder线程池,binder维护了一个16个线程的线程池,fork()出的进程A没法使用Binder通信
App启动、打包、安装
应用启动流程
参考四大组件-Activity-启动流程
Apk组成
- classes.dex:.dex文件,最终生成的Dalvik字节码
- res:uncompiled resources,存放资源文件的目录
- resources.arsc:编译后的二进制资源文件
- AndroidManifest.xml:程序的全局清单配置文件
- META-INF:签名文件夹,存放签名信息
Apk打包过程
- AAPT工具:通过AAPT工具进行资源文件打包,生成R.java、resources.arsc和res文件
- AIDL工具:通过AIDL工具处理AIDL文件,生成对应Java接口文件
- Java Compiler:通过Java Compiler编译R.java、Java接口文件、Java源文件,生成.class文件
- dex命令:通过dex命令,将.class文件和第三方库中的.class文件处理生成classes.dex,该过程主要完成Java字节码转换成 Dalvik字节码,压缩常量池以及清除冗余信息等工作
- ApkBuilder工具:通过ApkBuilder工具将dex文件、so、编译过的资源、原始资源等打包成Apk文件
- Jarsigner工具:通过Jarsigner工具,利用KeyStore对生成的APK文件进行签名
- 资源文件对齐,减少运行时内存
assets与res/raw异同
- 相同点:这两个文件目录里的文件都会直接在打包Apk的时候直接打包到Apk中,不会被编译成二进制
- 不同点:a、assets中文件资源不会映射到R中,res中文件都会映射到R中,所有raw文件夹资源都有对应的ID;b、assets可以能有更深的目录结构,而res/raw里面只能有一层目录;c、资源存取方式不同,assets中利用AssetsManager,而res/raw直接利用getResource(),openRawResource(R.raw.fileName)
Apk签名流程
- 签名工具:Android应用签名工具有两种,jarsigner和signAPK,它们的签名算法没什么区别,主要是签名使用的文件不同
- jarsigner和signAPK区别:一种是基于JAR的签名方式,另一种是基于Apk的签名方式,jarsigner使用keystore文件(包含了私钥、公钥和数字证书)进行签名,apksigner除了支持使用keystore文件进行签名外,还支持直接指定pem证书文件和私钥进行签名
- 签名过程:a、计算摘要(通过Hash算法提取出原始数据的摘要);b、计算签名(通过基于密钥(私钥)的非对称加密算法对提取出的摘要进行加密,加密后的数据就是签名信息);c、写入签名(将签名信息写入原始数据的签名区块内)
- 校验过程:签名验证是发生在APK的安装过程中。a、计算摘要(首先用同样的Hash算法从接收到的数据中提取出摘要);b、解密签名(使用发送方的公钥对数字签名进行解密,解密出原始摘要);c、比较摘要(如果解密后的数据和提取的摘要一致,则校验通过,如果数据被第三方篡改过,解密后的数据和摘要将会不一致,则校验不通过)
- 除了要指定keystore文件和密码外,也要指定alias和key的密码,这是为什么:keystore是一个密钥库,也就是说它可以存储多对密钥和证书,keystore的密码是用于保护keystore本身的,一 对密钥和证书是通过alias来区分的
Apk的安装流程
- 复制Apk到/data/app目录下,解压并扫描安装包
- 资源管理器解析Apk里的资源文件
- 解析AndroidManifest文件,并在/data/data/包名目录下创建对应的应用数据目录
- 然后对dex文件进行优化,并保存在dalvik-cache目录下
- 将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中
- 安装完成后,发送广播
SystemServer、ServiceManager、SystemServiceManager关系
孵化应用进程这种事为什么不交给SystemServer来做,而专门设计一 个Zygote
Zygote的IPC通信机制为什么使用socket而不采用binder
Android开发面试系列文章:
- Android开发面试:Android知识答案精解
- Android开发面试:Java知识答案精解
- Android开发面试:架构设计和网络知识答案精解
- Android开发面试:数据结构与算法知识答案精解
- Android开发面试:Kotlin面试知识答案精解