Android


系统启动

Android系统启动流程:

1.Bootloader引导

当电源按下时,引导芯片代码 从 ROM (4G)开始执行。Bootloader引导程序把操作系统映像文件拷贝到RAM中去,然后跳转到它的入口处去执行,启动Linux内核。Linux kernel 内核启动,会做设置缓存,加载驱动等一些列操作当内核启动完成之后,启动init 进程,作为第一个系统进程, init 进程从内核态转换成用户态。

2.init进程启动

fork 出ServerManager 子进程。 ServerManager主要用于管理我们的系统服务,他内部存在一个server服务列表,这个列表中存储的就是那些已经注册的系统服务。解析init.rc 配置文件并启动Zygote 进程

3.Zygote进程启动

孵化其他应用程序进程,所有的应用的进程都是由zygote进程fork出来的。 通过创建服务端Socket,等待AMS的请求来创建新的应用程序进程。
创建SystemServer进程, 名字为system_server,在Zygote进程启动之后,会通过ZygoteInit的main方法fork出SystemServer进程

4.SystemServer进程启动

创建SystemServiceManager,它用来对系统服务进行创建、启动和生命周期管理。ServerManager.startService启动各种系统服务:WMS/PMS/AMS等,调用ServerManager的addService方,将这些Service服务注册到ServerManager里面启动桌面进程,这样才能让用户见到手机的界面。

5.Launcher进程启动

开启系统Launcher程序来完成系统界面的加载与显示。

Android系统框架

AMS

Activity启动流程

1.Launcher进程通过调用mInstrumentation.execStartActivity,向system_server进程中的AMS服务发起startActivity请求。

2.AMS先检查是否系统已有该进程,有的就直接发起scheduleLaunchActivity请求,没有该进程,就通过socket方式向Zygote进程发送创建应用进程请求,Zygote进程接受请求并fork出应用进程。

3.应用进程通过Binder向AMS发起attachApplication请求, AMS绑定ApplicationThread,

bindApplication –> BIND_APPLICATION消息 –> handleBindApplication –> mInstrumentation.callApplicationOnCreate –> Application.onCreate –> ActivityStackSupervisor.attachApplicationLocked–>realStartActivityLocked –> scheduleLaunchActivity

4.AMS向应用进程发送scheduleLaunchActivity请求,(9.0之后是scheduleTransaction , 10.0改为ATMS)

5.ApplicationThread通过Handler向主线程ActivityThread发送H.LAUNCH_ACTIVITY请求,

6.ActivityThread执行handleLaunchActivity – performLaunchActivity – mInstrumentation.callActivityOnCreate – activity.performCreate – onCreate

变动:

Android8.0 及之前使用scheduleLaunchActivity (H.LAUNCH_ACTIVITY = 100)

Android9.0 之后使用scheduleTransaction (H.EXECUTE_TRANSACTION = 159)

Android10.0 之后用来管理Activity的AMS,变成了ATMS(ActivityTaskManagerService

Server启动流程

Zygote的IPC通信机制为什么使用socket而不采用binder

1.Zygote是通过fork生成进程的
2.因为fork只能拷贝当前线程,不支持多线程的fork,fork的原理是copy-on-write机制,当父子进程任一方修改内存数据时(这是on-write时机),才发生缺页中断,从而分配新的物理内存(这是copy操作)。zygote进程中已经启动了虚拟机、进行资源和类的预加载以及各种初始化操作,App进程用时拷贝即可。Zygote fork出来的进程A只有一个线程,如果Zygote有多个线程,那么A会丢失其他线程。这时可能造成死锁。
3.Binder通信需要使用Binder线程池, binder维护了一个15个线程的线程池,fork()出的App进程的binder通讯没法用

ActivityThread 与 ApplicationThread

ActivityThread 在Android中就代表了Android的主线程,它是创建完新进程之后, main函数被加载,然后执行一个loop的循环使当前线程进入消息循环。

ApplicationThread是ActivityThread的内部类,是一个Binder对象。在此处它是作为IApplicationThread对象的,server端等待client端的请求然后进行处理,最大的client就是AMS。

Instrumentation

AMS与ActivityThread之间诸如Activity的创建、暂停等的交互工作实际上是由Instrumentation具体操作的。每个Activity都持有一个Instrumentation对象的一个引用, 整个进程中是只有一个Instrumentation。mInstrumentation的初始化在ActivityThread::handleBindApplication函数。

ActivityRecord、TaskRecord、ActivityStack,ActivityStackSupervisor,ProcessRecord

ActivityStackSupervisor内部有两个不同的ActivityStack对象:mHomeStackmFocusedStack,用来管理不同的任务。

四种启动模式:standerd, singleTop, singleTask, singleInstance

WMS

Window

视图承载器,是一个视图的顶层窗口, 包含了View并对View进行管理, 是一个抽象类,具体的实现类为PhoneWindow,内部持有DecorView。 通过WindowManager创建,并通过WindowManger将DecorView添加进来。

WindowManager

WindowManager是一个接口,继承自只有添加、删除、更新三个方法的ViewManager接口。它的实现类为WindowManagerImpl。

WindowManagerImpl通过WindowManagerGlobal代理实现addView, 最后调用到ViewRootImpl的setView 使ViewRoot和Decorview相关联。

如果要对Window进行添加和删除就需要使用WindowManager, 具体的工作则由WMS来处理,WindowManager和WMS通过Binder来进行跨进程通信。

ViewRootImpl

ViewRootImpl是View和WindowManager的桥梁,View通过WindowManager来转接调用ViewRootImpl。View的三大流程: 测量(measure),布局(layout),绘制(draw))均通过ViewRootImpl来完成。 Android的所有触屏事件、按键事件、界面刷新等事件都是通过ViewRootImpl进行分发的。

DecorView

DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图, 一般情况下它内部包含一个竖直方向的LinearLayout。

ApplicationThread.performLaunchActivity() – Activity.attach() 中初始化 Phonewindow

Activity.onCreate中setContentView就是把需要添加的View的结构添加保存在DecorView中(Xml解析)

DecorView什么时候被WindowManager添加到Window中:handleResumeActivity - WindowManager.add(DecorView)

WindowMager的实现者WindowManagerImpl会在内部委托给WindowManagerGlobal。WindowManagerGlobal会创建出ViewRootImpl,后续DecoreView通过WindowManager跟ViewRootImpl进行交互

Binder

IPC方式

Binder来作为主要的IPC方式

主要是出于以上三个方面的考量:
1、效率:一次数据拷贝,传输速率高,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程。而对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,数据传递需要经历:用户空间 –> 内核缓存区 –> 用户空间,需要2次数据拷贝,效率不高。 共享内存不需要拷贝,Binder的性能仅次于共享内存。
2、稳定性:Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较好。而共享内存的性能优于Binder,但需要处理并发同步问题,容易出现死锁和资源竞争,稳定性较差。
3、安全性:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID,且在Binder通信时会根据UID/PID进行有效性检测。

Binder跨进程通信机制

基于C/S架构,由Client、Server、ServerManager和Binder驱动组成。

Client、Server、ServiceManager均在用户空间中实现,而Binder驱动程序则是在内核空间中实现的;

注册服务

Server进程向Binder驱动发起服务注册
Binder驱动将注册请求转发给ServiceManager进程
ServiceManager进程添加这个Server进程

获取服务

Client进程向Binder驱动发起获取服务的请求,传递要获取的服务名称
Binder驱动将该请求转发给ServiceManager进程
ServiceManager进程查到到Client进程需要的Server进程信息
通过Binder驱动将上述服务信息返回给Client进程 –> 已建立连接

使用服务

1.Binder驱动通过内存映射在内核空间创建一块的接收缓存区,调用 mmap()
2.根据ServiceManager查到的Server进程,实现内核缓存区和Server进程的用户空间地址建立映射
3.Client进程通过系统调用 copy_from_user() 将数据拷贝到内核缓存区 (当前线程被挂起)
4.Binder驱动通知Server进程,Server进程从线程池取出线程,进行数据解包,根据Client进程要求调用目标方法
5.Server进程将目标方法的结果写入缓存区,Binder驱动唤醒Client进程的线程,然后通过系统调用 copy_to_user() 从内核缓存区拷贝结果数据

Binder机制

Binder内存大小是不到1M的,准确说是 1M - 8K (一个内存物理页 4K,两个进程共享了8K的内存)

特殊进程:ServiceManager进程,它为自己申请的Binder内核空间是128K

Handler

Handler的实现原理

Handler: 负责消息的发送和处理
Message: 消息对象, 创建的方式:Message.obtain() ,handler.obtainMessage(), 避免new新对象,防止内存抖动
MessageQueue: 消息队列,用于存放消息对象的数据结构; 不是真正的队列,而是采用单链表的数据结构
Looper: 消息队列的处理者(用于轮询消息队列的消息对象)

ThreadLocal:线程内部的数据存储类,负责存储和获取本线程的Looper

ThreadLocal.ThreadLocalMap threadLocals = null; // Thread.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); // Looper

Handler发送消息时调用MessageQueue的enqueueMessage(),插入一条信息到MessageQueue, Looper不断轮询调用MessageQueue的next()方法,如果发现有message并且消息的时间戳<=当前系统开机时间,就调用handler的dispatchMessage(),接着调用handlerMessage()

单链表的插入操作 如果消息队列被阻塞回调用nativeWake去唤醒。 用synchronized代码块去进行同步。

// 一个线程只有一个Looper, 初始化时只new一个MessageQueue
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

// Looper类中的sThreadLocal对象
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

// ThreadLocal 的内部类 ThreadLocalMap
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

事件的处理如果阻塞会导致ANR,而不能说looper 的无限循环会ANR

nativePollOnce(ptr, nextPollTimeoutMillis); // 进行精准时间的挂起
nativeWake(mPtr);                           // 唤醒

不阻塞的原因是用到了Linux中的一种epoll机制(一种IO多路复用技术),在native层会有一个读取端和一个写入端,当有消息发送过来的时候会去唤醒读取端,然后进行消息发送与处理,没消息的时候是处于休眠状态,所以它不会阻塞。

调用 MessageQueue.next() 方法的时候会调用 Native 层的 nativePollOnce() 方法进行精准时间的阻塞。在 Native层,将进入 pullInner() 方法,使用 epoll_wait 阻塞等待以读取管道的通知。如果没有从 Native 层得到消息,那么这个方法就不会返回。此时主线程会释放 CPU 资源进入休眠状态。

消息屏障,同步屏障机制

message.target == null;
message.setAsynchronous(true);

同步屏障只在Looper死循环中获取待处理消息时才会起作用,也就是说同步屏障在MessageQueue.next函数中发挥着作用。

在next()方法中,有一个屏障的概念(message.target ==null为屏障消息), 遇到target为null的Message,说明是同步屏障,循环遍历找出一条异步消息,然后处理。 在同步屏障没移除前,只会处理异步消息,处理完所有的异步消息后,就会处于堵塞当出现屏障的时候,实现异步消息优先执行的功能

如何实现

1.Handler构造方法中传入async参数,设置为true,使用此Handler添加的Message都是异步的

2.创建Message对象时,直接调用setAsynchronous(true)

移除同步屏障 : MessageQueue.removeSyncBarrier()

应用

在 View 更新时,draw、requestLayout、invalidate 等很多地方都调用了ViewRootImpl.scheduleTraversals()
Android应用框架中为了更快的响应UI刷新事件在ViewRootImpl.scheduleXXX中使用了同步屏障

View绘制

addView

View绘制流程

Measure:测量视图宽高。根据父容器的MeasureSpec和子View的LayoutParams等信息计算子View的MeasureSpec,合并所有子View计算出ViewGroup的尺寸

Layout:布局,确定位置。先通过 measure 测量出 ViewGroup 宽高,ViewGroup 再通过 layout 方法根据自身宽高来确定自身位置。当 ViewGroup 的位置被确定后,就开始在 onLayout 方法中调用子元素的 layout 方法确定子元素的位置。子元素如果是 ViewGroup 的子类,又开始执行 onLayout,如此循环往复,直到所有子元素的位置都被确定,整个 View树的 layout 过程就执行完了。

Draw:绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布图层(Layer);③、绘制View的内容;④、绘制View子视图,如果没有就不用;⑤、还原画布图层(Layer);⑥、绘制View的装饰(例如滚动条等等)。

MeasureSpec表示的是一个32位的整形值,它的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize。

EXACTLY:精确测量模式;AT_MOST:最大值测量模式;UNSPECIFIED:不指定测量模式,

SurfaceView

优点: 使用双缓冲机制,可以在一个独立的线程中进行绘制,不会影响主线程,播放视频时画面更流畅

缺点:Surface不在View hierachy中,它的显示也不受View的属性控制,SurfaceView 不能嵌套使用。在7.0版本之前不能进行平移,缩放等变换,也不能放在其它ViewGroup中,在7.0版本之后可以进行平移,缩放等变换。

View和SurfaceView的区别

View适用于主动更新的情况,而SurfaceView则适用于被动更新的情况,比如频繁刷新界面。 View在主线程中对页面进行刷新,而SurfaceView则开启一个子线程来对页面进行刷新。 View在绘图时没有实现双缓冲机制,SurfaceView在底层机制中就实现了双缓冲机制。

SurfaceView为什么可以直接子线程绘制

通常View更新的时候都会调用ViewRootImpl中的performXXX()方法,在该方法中会首先使用checkThread()检查是否当前更新位于主线线程,SurfaceView提供了专门用于绘制的Surface,可以通过SurfaceView来控制Surface的格式和尺寸,SurfaceView更新就不需要考虑线程的问题,它既可以在子线程更新,也可以在主线程更新。

class MapView extends SurfaceView implements SurfaceHolder.Callback
class GlobalView extends GLSurfaceView implements SurfaceHolder.Callback
public interface Callback{
    public void surfaceCreated(SurfaceHolder holder);
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);
    public void surfaceDestroyed(SurfaceHolder holder);
}

区别:

SurfaceView:使用双缓冲机制,有自己的 surface,在一个独立的线程里绘制,Android7.0之前不能平移、缩放

TextureView:不会在WMS中单独创建窗口,而是作为一个普通View,可以和其它普通View一样进行移动,旋转,缩放,动画等变化。值得注意的是TextureView必须在硬件加速的窗口中。

SurfaceTexture:SurfaceTexture和SurfaceView不同的是,它对图像流的处理并不直接显示,而是转为OpenGL外部纹理,因此可用于图像流数据的二次处理(如Camera滤镜,桌面特效等)。

GLSurfaceView:SurfaceView不同的是,它加入了EGL的管理,并自带了渲染线程。

画图:通过Bitmap转成TextureId,根据顶点数据Buffer进行纹理贴图

一般情况下在onLayout方法中使用getMeasuredWidth方法,而在除onLayout方法之外的地方用getWidth方法。

requestLayout:会触发三大流程:调用 measure()过程 和 layout()过程, 不一定会触发OnDraw。

invalidate:触发 onDraw 流程,在 UI 线程调用。

postInvalidate:触发onDraw 流程,在非 UI 线程中调用。

View加载流程(setContentView)

1.DecorView初始化
2.通过LayoutInflate对象去加载View,主要步骤是
(1)通过XmlResourcesParser去解析xml布局文件,获取xml信息,并保存缓存信息. (AssetManager.openXmlAssetNative)
(2)根据xml的tag标签通过反射创建View,逐层构建View
(3)递归构建其中的子View,并将子View添加到父ViewGroup中

View事件分发

ViewRootImpl事件分发

ViewRootImpl分发流程

硬件 -> ViewRootImpl.dispatchInputEvent()

-> DecorView.dispatchTouchEvent()

-> PhoneWindow.Callback.dispatchTouchEvent()

-> Activity.dispatchTouchEvent() (Activity的attach方法中 mWindow.setCallback(this);)

-> View事件分发:

View事件分发

优先级:dispatchTouchEvent->onTouch->onInterceptTouchEvent->onTouchEvent->onClick

解决View的事件冲突

外部拦截法:父View在ACTION_MOVE中开始拦截事件,那么后续ACTION_UP也将默认交给父View处理!
内部拦截法:即父View不拦截任何事件,所有事件都传递给子View,子View根据需要决定是自己消费事件还是给父View处理

如果父容器需要获取点击事件则调用 parent.requestDisallowInterceptTouchEvent(false)方法,让父容器去拦截事件

子View在onInterceptTouchEvent的ACTION_DOWN之后调用requestDisallowInterceptTouchEvent(true),则此子View的所有父ViewGroup会跳过onInterceptTouchEvent回调.

在 ViewGroup 中的 onTouchEvent 中消费 ACTION_DOWN 事件,ACTION_UP事件是怎么传递

一个事件序列只能被一个View拦截且消耗。因为一旦一个元素拦截了此事件,那么同一个事件序列内的所有事件都会直接交给它处理(即不会再调用这个View的拦截方法去询问它是否要拦截了,而是把剩余的ACTION_MOVE、ACTION_DOWN等事件直接交给它来处理)。

Activity、ViewGroup和View都不消费ACTION_DOWN,那么ACTION_UP事件是怎么传递的

ACTION_DOWN:-> Activity.dispatchTouchEvent() -> ViewGroup1.dispatchTouchEvent() -> ViewGroup1.onInterceptTouchEvent() -> view1.dispatchTouchEvent() -> view1.onTouchEvent() -> ViewGroup1.onTouchEvent() -> Activity.onTouchEvent();
ACTION_MOVE > Activity.dispatchTouchEvent() ->Activity.onTouchEvent(); -> 消费

性能优化

1.内存优化

注意代码中常见内存泄漏场景,

1.非静态内部类和匿名内部类都会隐式持有当前类的外部引用

2.单例造成的内存泄漏

3.资源性对象未关闭,注册对象未注销, 接口反注册,Bitmap.recycle()

4.避免在循环里创建对象,建立合适的缓存复用对象,避免在onDraw里创建对象

ListView使用 convertView + ViewHolder

WebView:

frameLayout.addView(mWebView); // Layout动态添加WebView
// Activity.onDestroy()中动态移除WebView
if (mWebView != null) {
    if (mWebView.getParent() != null) {
        ((ViewGroup) mWebView.getParent()).removeView(mWebView);
    }
    mWebView.setWebViewClient(null);
    mWebView.clearCache(true);
    mWebView.removeAllViews();
    mWebView.destroy();
}
mWebView = null;
使用工具查找内存泄漏具体位置

LeakCanary, Android Profiler

图片优化

Bitmap 内存占用的计算,对图片进行内存压缩,高分辨率的图片放入对应文件夹,缓存LruCache & DiskLruCache, 及时回收

内存占用分析优化

静态内存分析优化:打开”不保留后台活动”, 将app退到后台,GC,dump出内存快照。 SharePreferenc

动态内存分析优化:Android Profiler,避免内存抖动,实时监控(AOP原理)

OOM内存溢出

1.过大内存,超出了进程的内存限制

2.内存碎片,没有足够可用的连续内存,频繁创建和回收对象,导致频繁GC

3.文件描述符太多,too many open file

4.线程创建太多

APM(Application Performance Monitor) 应用性能监控软件

JVMTI (JVM Tool Interface) 虚拟机工具接口

2.启动优化

计算耗时

AMS会通过Zygote创建应用程序的进程后,执行Application构造方法 –> attachBaseContext() –>Application.onCreate() –> Activity.onCreate() –>–> onStart() –> onResume() –> 测量布局绘制显示在界面上–>onWindowFocusChanged()绘制完毕

在Application的attachBaseContext()方法中开始计算冷启动计时,然后在真正首页Activity的onWindowFocusChanged()中停止冷启动计时。

启动优化方案

启动闪屏,设置android:windowBackground属性,

application初始化内存, 异步改造,延迟加载

首页部分接口合并为一,减少网络请求次数,降低频率;

首页布局优化

3.布局优化

绘制流程

startActivity->ActivityThread.handleLaunchActivity->onCreate ->完成DecorView和Activity的创建-handleResumeActivity->onResume()->DecorView添加到WindowManager->ViewRootImpl.performTraversals()方法,测量(measure),布局(layout),绘制(draw), 从DecorView自上而下遍历整个View树。

原因:性能瓶颈在于LayoutInflater.inflater过程,主要包括如下两点:
1 xmlPullParser IO操作,布局越复杂,IO耗时越长。
2 createView 反射,View越多,反射调用次数越多,耗时越长

优化

AsyncLayoutInflater 异步加载布局文件,通过 UIHandler 将 InflateRequest 回调到主线程中。

X2C: 采用APT(Annotation Processor Tool)+ JavaPoet技术来完成编译期间视图xml布局生成java代
码,这样布局依然是用xml来写,编译期X2C会将xml转化为动态加载视图的java代码

减少View树层级,布局尽量宽而浅,避免窄而深 。约束布局ConstraintLayout 实现几乎完全扁平化布局。

merge标签使用

ViewStub 延迟化加载标签,在使用前是作为占位符存在

避免过度绘制,去掉多余的background,减少复杂shape, layer-list的使用,避免层级叠加

4.卡顿优化

卡顿产生的原因是错综复杂的,它涉及到代码、内存、绘制、IO、CPU等等

1.消息dispatchMessage处理耗时的代码。绘制自定义View使用复杂计算,不合理算法增减时间复杂度。

2.内存抖动导致频繁GC,触发Stop the world, 所有线程挂起

3.并发编程加锁,内存资源竞争导致主线程挂起等待,或者是死锁。

4.系统其他APP占用CPU,占用内存或存储空间,IO繁忙。

卡顿监控

1.BlockCanary: 动态检测消息执行耗时

2.ANR-WatchDog

3.单点问题监控,耗时统计

4.StrictMode严苛模式

开发者选项:GPU呈现模式分析–条形图,调试GPU过度绘制

卡顿分析

Android Profiler, 火焰图, 知道那个线程那个方法耗时

Systrace

用Chrome 浏览器打开生成的trace 文件,如下图所示,里面会包含每个CPU,以及图形渲染,输入事件等等内容。Systrace对于检查应用程序的UI性能特别有用,因为它可以分析您的代码和帧速率,进而识别问题区域,同时提供可工参考的解决方案。使用Systrace 检测卡顿丢帧问题,Systrace报告列出了每个进程呈现UI frame,并显示沿着时间线的每个渲染帧。Systrace不会在应用程序进程中收集有关代码执行的信息。 有关您的应用程序执行哪些方法以及使用多少CPU资源的更多详细信息,请使用Android Studio的内置CPUProfiler,或生成跟踪日志并使用Traceview查看它们。

代码中添加trace 标记方法:通常我们在怀疑引起jank代码地方,添加如下内容:
Trace.beginSection("MyAdapter.onCreateViewHolder");Trace.endSection(); 可以查看自定义的从开始到结束期间的Systrace信息。这两个是成对出现的,不能在一个线程上调用beginSection()并从另一个线程结束 - 您必须从同一线程调用endSection()

StrictMode严苛模式

Android 提供的一种运行检查机制, 方便强大,容易被忽视,包含线程策略与虚拟机检测策略。

注意:在线上环境即Release版本不建议开启严格模式。严格模式无法监控JNI中的磁盘IO和网络请求。

线程策略

  1. 自定义耗时调用:detectCustomSlowCalls()
  2. 磁盘读取策略:detectDiskReads()
  3. 网络操作:detectNetwork()

虚拟机策略

  1. Activity泄漏:detectActivityLeaks()
  2. Sqlite泄漏:detectLeakedSqlitebjects()
  3. 检测实例数量:setClassInstanceLimit()
ANR

ANR由消息处理机制保证,Android在系统层实现了一套精密的机制来发现ANR,核心原理是消息调度超时处理。ANR机制主体实现在系统层。所有与ANR相关的消息,都会经过系统进程(system_server)调度,然后派发到应用进程完成对消息的实际处理,同时,系统进程设计了不同的超时限制来跟踪消息的处理。 一旦应用程序处理消息不当,超时限制就起作用了,它收集一些系统状态,譬如CPU/IO使用情况、进程函数调用栈,并且报告用户有进程无响应了(ANR对话框)。

整个ANR机制的代码也是横跨了Android的几个层:

App层:应用主线程的处理逻辑;
Framework层:ANR机制的核心,主要有AMS、BroadcastQueue、ActiveServices、InputmanagerService、InputMonitor、InputChannel、ProcessCpuTracker等;
Native层:InputDispatcher.cpp;

解决:平时的代码经验积累,避免之前踩过得坑,提高代码质量

5.网络优化

Network Profiler 工具

流量维度

区分类型, 监控异常(流量消耗过多,请求次数过多, 下载文件过大), 上报日志

数据缓存,数据压缩 ,图片压缩,Json、protobuf

质量维度

对于网络请求质量的监控,可以从请求时长请求成功率失败率Top 失败接口等维度进行区分,以便后续能快速定位和解决问题

6.电量优化

优化网络请求,谨慎使用 WakeLock,监听手机充电状态,Doze 瞌睡,App Standy 应用待机,定位中使用 GPS,及时关闭,计算优化,熄屏后停止一些和 UI 效果有关的操作,比如动画

7.应用瘦身

图片资源处理,图片压缩,减少预置图片,使用 WebP 格式图片

开启 minifyEnabled 混淆代码,可以压缩文件

使用 shrinkResources 去除无用资源

Android Lint 分析去除无用资源

动画使用 SVG 图片格式

支持插件化

8.存储优化

SharePreferences : apply 方法

数据库优化:批量提交,事务原子提交,合适索引,加快了数据库检索的速度

打包流程

流程

1.aapt 生成R 文件
2.aidl 生成java 文件
3.将全部java 文件编译成class 文件
4.将全部class 文件和第三方包合并成dex 文件
5.将资源、so 文件、dex 文件整合成apk
6.apk 签名
7.apk 字节对齐

RecyclerView

RecyclerView根据不同的状态可以分为:屏幕内缓存、屏幕外缓存、自定义缓存、缓存池。RecyclerView是通过内部类Recycler来管理缓存。

分为四级缓存

一级缓存:屏幕内缓存(mAttachedScrap)

屏幕内缓存指在屏幕中显示的ViewHolder,这些ViewHolder会缓存在mAttachedScrap、mChangedScrap中 :

  • mChangedScrap 表示数据已经改变的ViewHolder列表,需要重新绑定数据(调用onBindViewHolder)
  • mAttachedScrap 未与RecyclerView分离的ViewHolder列表

二级缓存:屏幕外缓存(mCachedViews)

用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会优先移除旧 ViewHolder,把旧ViewHolder移入到缓存池RecycledViewPool 中。

三级缓存:自定义缓存(ViewCacheExtension)

给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存,可通过Recyclerview.setViewCacheExtension()设置。

四级缓存:缓存池(RecycledViewPool )

ViewHolder 缓存池,在mCachedViews中如果缓存已满的时候(默认最大值为2个),先把mCachedViews中旧的ViewHolder 存入到RecyclerViewPool。如果RecyclerViewPool缓存池已满,就不会再缓存。从缓存池中取出的ViewHolder ,需要重新调用bindViewHolder绑定数据。

  • 按照 ViewType 来查找 ViewHolder
  • 每个 ViewType 默认最多缓存 5 个
  • 可以多个 RecyclerView 共享 RecycledViewPool

RecyclerViewPool底层是使用了SparseArray来分开存储不同ViewType的ViewHolder集合


文章作者: GPC
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 GPC !
评论
  目录