1、一张图片100x100在内存中的大小
- 网络图片加载。看图片用的什么格式加载,如果是用的ARGB 8888.那么一个像素就是4个字节,那么一共4W字节。用RBG565的话就是2万字节。
- 本地图片加载,看在哪个目录下,以RBG565为例,一个像素2字节,面积会变成, 10000* 2 * (设备DPI/目录对应dpi)^2.比如设备dpi是150。那么属于mdpi。160.那么为10000* 2* (150/160) ^ 2。
2、内存抖动和内存泄漏。内存优化。
- 内存抖动
- 频繁创建销毁内存。让内存千疮百孔,没有连续内存。容易引起内存回收。不当操作如View的onDraw里创建对象。
- 内存回收则GC,不可比妙女STW,难免会卡。
- 内存泄漏
- 本该被回收的因为某种原因未释放或者无法释放,可用内存越来越少,直到OOM。
- 为啥会出现呢?
- 这里讲一下垃圾回收机制,引用计数法,根可达。
- 引用计数,有被引用就+1,但是2个垃圾可能相互引用
- 根可达,GCRoot可达就行
- 方法区:静态属性对象/常量对象
- 本地方法栈:Native方法中JNI引用对象
- 虚拟机栈:正在运行的方法的局部变量引用的变量
- 在跑的线程
3、什么时候会发生内存泄漏?举个例子
- 内存泄漏就是本应该本释放的对象因为一些原因没有被释放。时间一长,可用的越来越少,就OOM
- Java有自己的垃圾回收机制,能够自动回收内存,但有时候也有些不靠谱。
- Java运行时内存分配有静态存储区(方法区)、栈区域、堆区
- 一般来说有生命周期不一致、内部类引用可能发生。
- 单例。比如传入context被静态对象持有。那么静态的生命周期是远长于context,比如activity。
- 非静态持有外部类的引用
- 比如Handler的handlerMessage中隐式持有Activtiy,其实就是Message持有Handler。msgqueue持有msg。Looper持有msgqueue。threadlocal持有Looper。线程持有threadlocal。根可达
- AsyncTask
- 还是Threadlocal内存泄漏,用完entry,就remove
- ThreadLocalREf 强引用 持有ThreadLocal,作为ThreadLocalMap的一个Entry的key(弱引用)。当没有人调用这个key value的时候。ThreadREf对ThreadLocal的引用断了。那么ThreadLocalMap的entry的key,也就是ThreadLocal GC 被回收为null。然后set或者get时 可能调用 expungStaleEntry() 可能把key为null的evtry回收,但也不是一定,这样可能导致value一直被entry引用enrey被threadLocalmap引用,threadLocalmap被当前线程持有
- 资源未关闭,比如数据库,广播接收者,媒体文档啥的
- Webview引起的内存泄漏
- 最好不要在xml中定义webview。定义一个viewgroup,在在代码中添加 webview对象。在onDestory中把WebView移除。removeview
- 要么WebView开一个进程,结束时,用system.exit(0)
- 其它基本就是创建了没注销
4、Bitmap压缩,质量100%和90%的区别
- 首先Bitmap.compress对内存没啥影响,影响内存的只是你的压缩格式,比如ARGB8888 4个字节 RBG 565 2个字节。像素 长宽。这个3个因素影响内存。
- 其次Bitmap.compress对png无效,因为png是无损压缩格式
- 最后Bitmap.compress压缩jpg等,主要体现在图像的细节和文件大小上
5、TraceView的使用,查找CPU占用
- 打开profile,选择session,点击CPU
- 记录栈堆,java method sample recording 快速了解性能。然后record,过一段时间停止 记录
- 选择分析的线程,选择CPU片段
- 下端边会有一堆方法列出来,越长
- 横向表示执行时间,纵向表示调用关系
计数说明
- 橙色系统方法
- 蓝色第三方API,包括java 的api
- 绿色App自身方法
相关指标
Incl Cpu Time:方法在CPU中执行所有时间(包含其调用的方法所消耗的时间)
Excl Cpu Time: 方法在CPU中执行的时间(不包含其调用的方法所消耗的时间)
Incl Real Time:方法运行消耗的所有时间(包含子方法)
Excl Real Time:方法运行消耗的时间(不包含子方法)
Calls + Recur Calls/Total :方法调用、递归次数(重要指标,防止死循环)
Cpu Time/Call :该方法平均占用 CPU 的时间(重要指标,可以看出单个方法占用CPU的平均时间,但是要防止在个别调用处出现长时间占用,然后被平均了)
Real Time/Call :平均执行时间,包括切换、阻塞的时间(重要指标,可以看出单个方法执行的平均时间值,
但是要防止在个别调用处出现长时间调用,然后被平均了
6、内存泄漏查找
- App堆空间想象成一个杯子,内存就是里面的水。当App启动后,假设分配32M(可以通过activitymanager.getMemoryClass查看,如果设置了开启大堆,就用.getLargeMemoryClass),随着创建对象越来越多,可能就是 32-64-...。GC会定期扫描内存,发现不被引用的对象回收。
- 假设有个非静态内部类隐式持有了Activity,假设这个非静态内部类,比如Handler的派生类被msg持有 ,然后msg-》msgqueue-》looper-》ThreadLocal-》主线程。根可达。那么Activity就算推出了也不会销毁。那么越堆越多。这就是内存泄漏。本该释放,但是没释放。
- 或者说App onDestory 某个引用没有设置为null。那么多次打开,就会存在多个这样的未释放对象
- 如果 app莫名OOM了,可以Dump java head
- 会在代码区生成当前时间点的.hprof。看每个对象内存占用情况。找到占用高的Activity,看下里边的对象,如果多个还是根可达(倒过来的奔驰)。那么这个能被GC访问到,也就是无法回收。当然不同情况,不同分析。对于怀疑某个Activity,我一般就返回然后gc,看内存下降到之前水平没。
- 当然实际开发中,我一把用leakcanary。这个名字蛮有意思的,以前下矿,工人会带只金丝雀也就是canary。如果金丝雀有问题了,马上逃。因为金丝雀非常敏感,有毒气体,马上窒息。作为预警。所以leakcanary的logo是只凉凉的鸟。
7、Android四大组件(以及Application)的onCreate/onReceiver方法中Thread.sleep(),哪个产生几个ANR?
- Service 和 BroadcastReceiver
- 造成ANR的场景
- Service 20S
- 前台广播10s 后台广播60s
- ContentProvider 10s
- 输入事件分发超过5s
- Application中没有这些不ANR,Activity onCreate 没输入就不会触发输入事件分发超过5s的问题。输入事件分发超时5s,包括按键和触摸事件。只要你不再点就没事。ContentProvider中是publish超过 10s才ANR
- service 是20s,broadcast前台10s 后台60s。
8、当前项目中是如何进行性能优化分析的?
1、布局优化
- 以60帧为例,如果16毫秒系统VSYNC信号还没有完成绘制,这帧就会被丢弃,等待下次信号开始才绘制,导致2* 16ms内都是显示同一帧画面,这就是卡顿的原因
布局优化
- 减少布局层级,能使用RelativeLayout就别用Linearlayout,最好使用ConstraintLayout
- 使用include merge ViewStub
- include 复用布局,减少层级
- merge 减少自身的那一层布局,直接采用父include的布局
- ViewStub 延迟加载
绘制优化
- 移除重复的背景色,移除自View多余的背景色
- View的onDraw方法要避免执行大量的操作
内存优化
- 内存指的是收集RAM,主要包括:寄存器、栈、堆、 方法区(静态存储区和常量池)
- Bitmap优化
- 使用适当分辨率和大小的图片,使用图片缓存,对图片进行压缩.(色彩格式)
- FIle REceiver cursor 等对象使用后释放掉
- 用SurfaceVIew代替View进行大量,频繁的绘制操作
- 属性动画开启之后,记得在关闭页面的时候停止动画
- 减少使用不必要的成员变量
线程优化
用线程池避免存在大量的线程,也避免创建销毁线程带来的性能开销
启动速度优化
- 设置闪屏图片主题
- Application 中库加载用异步。有个工具的,回头补上。# App Startup? 解决加载依赖问题
- 首页布局优化,如果可以的话,用手写view,而不用xml。简单的话用x2c。这样就不用反射了。
- 任务异步加载处理。
9、冷启动、热启动的概念
- 冷启动:当应用启动时,后台没有该应用进程,这时系统会重新创建一个新的进程分配给该应用。
- 先创建和初始化Application 再创建和初始化Activity(测量 布局 绘制)
- 热启动:当启动应用时,后台已经有该应用的进程了,这种启动会从已有的进程中来启动应用。
- 直接走Actvity的(测量 布局 绘制)
讲一下 应用的启动流程
- 点击图标-》launch通过binder联系到SystemServer-》SystemManager-》
- AMS(10之后会再到ATMS),然后AMS 先ActivityManagerService.startActivity再判断合法性再判断冷启动还是热启动。
- 都会调用ActivityStack.startActivityMayWait来准备要启动的Activity的相关信息
- 热启动直接找到原进程打开就好了。冷启动就通过socket转到Zygote请求-》Zygote fork一个app进程-》app进程启动之后,反射执行ActivityThread的main函数-》Looper.prepare->new ActivityThread().attach()->looper.loop()
- attach方法里
- ATMS调用 attachApplication
- ATMS通过AMS与ApplicationThread通信
- ApplicationThread的bindeApplication
- 函数执行,开始初始化加载资源和设置。中后期完成初始化和设置
- ActivityStack.startActivityMayWait。
- ActivityStack通知ApplicationThread要进行Activity启动调度了启动Activity相关流程
流程化一点的
- 用户点击桌面图标:Launcher应用被触发,根据配置信息启动该程序的默认Activity
- 启动Activity的机制:必须要用AMS,它负责启动和管理Activity和Service。通过SystermService拿到SystemServer的代理再拿到SystemManager的代理,最后拿到AMS的代理,安卓10之后activty的管理交给了ATMS。这里还是用AMS继续吧。
- 进程间通信:无论是Launcher启动Activity还是,其它activity应用内调用startActvity来启动新Activity,都是通过binder走到AMS,并且调用AMS.startActivity接口
- 准备启动Activity:AMS收到启动请求后,调用ActivityStack.startActivityMayWait方法来准备要启动得Activity的相关信息。
- 通知发起者的ApplicationThread:ActivityStack准备好后,通知之前的ApplicationThread也就是launcher进程的。
- 判断是否需要创建新的进程:ApplicationThread不会直接执行启动操作,会调用AMS的activityPaused方法问是否需要创建新的进程.判断热启动还是冷启动,热启动直接打开原有进程就好,冷启动就会通过,AMS.startProcessLocked方法,通过socket去通知zygote请求创建新进程
- 创建新进程:zygote进程会根据请求fork,这一步涉及到进程的快速创建和初始化,而Zygote进程通过预加载应用所需的 代码和资源,大大加速了这一过程。
- 调度启动操作:新应用进程创建完成之后,AMS通过scheduleLaunchActivity方法通知调用者的ApplicationThread执行操作启动操作。这一步涉及到将启动请求转发给应用程序的主线程ActivityThread
- 启动Activity调用者(Launcher)ApplicatonThread将启动操作转发给被调用者的ActivityThread,ActivityThread通过反射执行main函数
- Looper.prepare->new ActivityThread().attach->Looper.loop
- 这里讲一下attach
- 如果是安卓10之后就是ATMS调用attachApplication-》ams与被调用者的ApplicationThread通信
- ApplicationThread的bindApplication开始执行,初始化应用,加载资源和设置
- application生命周期中后期完成,完成了初始化和设置
- Activity启动
- ActivityThread.schedulerLaunchActivity
- ActivityThread通过反射Activity的attach方法,将Activity的ApplicationThread关联起来。
- 调用onCreate
黑白屏幕问题
- style文件
- < item name="android:windowIsTranslucent"> true
- < item name="android:windowNoTitle">true
- 加入了两个属性,windowIsTranslucent和windowNoTitle,将这两个属性都设置成true,就可以让程序在初始化的时候窗口是透明的,初始化结束后程序主界面才会显示出来,从而也就完全看不到白屏界面了
- 为了防止白屏,我一般在闪屏页面的style里整个
windowBackground
启动事件的优化
- 日志Displayed查看显示成功的时间
- ReportFullyDrawn。记得捕获异常
- adb shell am start 对应activivty 查看时间
12、优化View层次过深的问题。选择哪个布局比较好?
- constraintLayout》Relayoutlayout》Linearlayout
- 其次可以用include提高复用,merge 减少层级 ViewStub延迟加载
13、为何要用ContentProvider?和sql的实现上有什么区别?
- ContentProvider的作用是为了不同应用之前共享数据,提供统一接口。因为不同应用进程间隔离的,想让外部应用使用自己的数据,就可以用ContentProvider对外暴露指定的数据。
- ContentProvider屏蔽操作细节,用户只需关心操作数据的url就可以在同步app之间共享。具体使用sqlite还是sp、网络还是文件不关心。
- sql只是一种数据增删改查的方式。
14、App堆内存是如何限制的?如何更加合理使用内存。
- Android 运行时(ART)和Dalvik虚拟机使用分页和内存映射来管理内存。
- 这意味着应用修改的任何内存,无论是分配新对象还是轻触内存映射(写),都会一直驻留在RAM中。
- 要从应用释放内存,只能释放引用,然后等垃圾回收器来回收。
- 这种情况有一个例外:对于任何未经修改的内存映射文件(如代码),如果系统想要在其他位置使用其内存,可将其从 RAM 中换出。
垃圾回收
- 垃圾回收是找到程序中无法被访问(根可达)的数据对象,并回收这些对象使用的资源
- 内存堆是分代的。新生代分为eden:from:to = 8;1:1。还有个老年代。比例1:2
- 对象分配在伊甸园区域 新生代空间不足会触发Minor GC,伊甸园和form的复制到to当中,存活年龄加1,然后from和to调换下名字。Minor GC会短暂触发stop the world,暂停其它用户的线程,等待垃圾回收结束,用户线程才恢复运行。对象寿命超过阈值15,会进入老年代,其实也就是对象头中的4位。当然各个垃圾回收器不一样。,大对象会直接进入老年代,当老年代空间满了的时候就会进行full GC。老年代用的是标记整理和标记清除算法。看用哪个收集器了
共享内存
为了在RAM中容纳所需的一切,Android会尝试跨进程共享RAM页面。它可以通过以下方式实现
- Zygote fork出来的进程都用到了大量相同的数据,这其中就用到了共享内存
- 大多数静态数据会在进程加载时,映射到进程中。这种方法使得数据不仅可以在进程之间共享,还可以在需要时换出(内存换到硬盘文件)。静态数据示例包括:odex 应用资源 so文件中的原生代码
分配与回收应用内存
限制应用内存
- 给每个应用设置了内存上限,调用 getMemoryClass() 向系统查询此数值。此方法返回一个整数,表示应用堆的可用兆字节数。
切换应用
- 当用户在应用之间切换时,Android会将非前台应用保存在缓存中。如果应用具有缓存的进程且保留了目前不需要的资源,那么即使用户未使用此应用,还是会影响到系统的整体性能。当资源不足,会终止缓存中的进程。系统还会考虑终止占用最多内存的进程以释放RAM
进程间的内存分配
- Android系统通过充分利用内存来提高性能,同时保留关闭的应用以快速恢复,但这也导致可用内存较少,因此需要进行有效的内存管理。
内存类型
- RAM是最快的内存类型,就是内存条那个。
- Storage就是持久化数据不怕断电那个。速度慢些。
- zRam,是RAM把一些数据移到Storage中用的交换空间的RAM分区,进入会压缩,复制出会解压。但是Android虚拟机不会用
- 虽然Dalvik虚拟机的换出与交换空间的目的都是为了扩展可用内存,但它们实现的方式和机制有所不同。Dalvik虚拟机通过内存分页技术和内存映射技术来实现内存的动态分配和回收,而不是直接使用交换空间。这种策略有助于提高系统的性能和响应速度,并确保应用程序的流畅运行。
内存页面
RAM被分为多个页面,通常每个页面未4KB内存。 系统会将页面是为可用和已使用。
- 已使用
- 缓存页:在存储器中的文件(例如代码或内存映射文件)支持的内存。相当于是提前把磁盘中的文件加载到了内存中。缓存内存有两种类型:
- 干净页:存储器中未经修改的文件副本,可由kswapd删除以增加可用内存
- 脏页:存储器中经过修改的文件副本,可由kswapd移动 到zRAM或在zRAM中进行压缩以增加可用内存。以更有效地利用存储空间。如果系统内存紧张,zRAM可能会被填满,这时操作系统会将一些脏页写回到磁盘上,以便为其他进程释放内存。
- 共享页:由多个进程使用
- 干净页:存储器中未经修改的文件副本,可由kswapd删除以增加可用内存
- 脏页:存储器中经过修改的文件副本;允许通过 kswapd 或者通过明确使用 msync() 或 munmap()将更改写回存储器中的文件,以增加可用空间
- 匿名页
- 没有存储器中的文件支持的内存
- 具体来说,当用户通过malloc、mmap等系统调用申请内存时,系统会分配匿名页来存储这些数据。这些匿名页可能是堆内存,也可能是栈内存,因为它们在内存管理中通常被视为文件页,可以随时被使用和释放。
- 脏页:可由 kswapd 移动到 zRAM/在 zRAM 中进行压缩以增加可用内存 随着系统积极管理 RAM,可用和已使用页面的比例会不断变化。
- 缓存页:在存储器中的文件(例如代码或内存映射文件)支持的内存。相当于是提前把磁盘中的文件加载到了内存中。缓存内存有两种类型:
内存不足管理
Android中由两处处理内存不足的主要机制:内核交换守护进程和低内存终止守护进程
- 内核交换守护进程kwapd
- kswapd 可以删除干净页来回收他们。如果某个进程尝试处理已删除的干净页,则系统会将页面从存储器复制回RAM。这个操作称为请求分页。
- kswapd 可以将缓存的私有脏页和匿名脏页移动到zRAm进行压缩。
- 低内存终止守护进程LMK
- kswapd释放不够用。会用onTrimMemory通知应用内存不够,你们自己看着吧。如果还是不够,内核回开始终止进程以释放内存。会调用LMK低内存终止守护来执行操作。
- LMK会使用OOM_adj_score的内存不足分值来确定正在运行的进程的优先级。反正后台应用最开始,最最后才是系统进程。其中用到RSS和uss。
管理应用内存
Dalvik 虚拟机都执行例行的垃圾回收任务,我们仍然需要避免引入内存泄漏问题(通常因在静态成员变量中保留对象引用而引起),并在适当时间(如生命周期回调所定义)释放所有 Reference 对象。
监听内存和内存使用量
- 用profile
- 记录应用的内存分配情况,然后检查所有分配的对象、查看每个分配的堆栈轨迹,并在 Android Studio 编辑器中跳转到相应代码。
释放内存以响应事件
- 继承ComponentCallbacks2接口,实现onTrimMemory方法
应用使用了多少内存
memoryInfo.lowMemory查看是否低内存,也可以用activityManager.getMemoryInfo查看还有多少内存。
谨慎使用服务
- 在不需要某项服务让其保持运行状态,很费内存。如果可以用WorkManager 和 JobScheduler代替
- 如果要用 建议用 IntentService,使用完之后自己结束。
使用经过优化的数据容器
选择合适的容器,Hashmap其实性能低,如果key是int ,建议用SparseArray。
针对序列化数据使用精简版 Protobuf(类似于Gson)
避免内存抖动,
移除会占用大量内存的资源和库
谨慎使用外部库,尽量用精简的。
少用反射,耗费性能的事情尽量在 编译时解决
14、冷启动为什么会有黑白屏问题?
- 主要原因实在界面显示前需要做一些初始化操作,不可避免。所以在View显示之前,尽量不要做耗时操作,放在后面或者子线程。
- 解决方案,style 设置透明背景 设置windows背景。
- 界面是在handleResumeActivity中执行,调用wm.addView(decor).在onCreate onstart onResume都执行完了才显了。
15、如何对Apk进行瘦身?
- lint工具检测res/中是否有没有被使用到的资源,当然反射检测不出来,要注意
- 混淆压缩代码
- 移除备用的资源,比如第三方库的语言,如果APP没有国际化,就用resconfigs"ZH"
- 图片转为webp或者你可以使用pngcrush, pngquant 或 zopflipng来压缩PNG图片。 使用packJPG 或 guetzli 来压缩JPG图片。启动图标最好不要用WebP,因为谷歌商店只接受启动图片格式为PNG的APK。
- 合理选择第三方库,选择最小化资源的库
- 只支持特定屏幕密度的图片
- 简单图片用drawable绘制,不用img。
- 复用资源,一些颜色啥的,tint 和tintmode解决。角度用rotate。
- 通过代码渲染代替图片。
- 删除不必要的生成代码。例如有很多的protocol buffer tools会自动生成大量你可能用不到的代码。
- 少用枚举
- 维护多个精简版APK,分别比如不同CPU指令集的版本
16、说一下 冷启动和热启动是什么,区别?如何优化,使用场景等。
- 冷启动,就是系统中没有该进程,那么会先创建和初始化这个Application,再创建和初始化MainActivity (测量、布局、绘制),最后显示
- 主要流程,Application构造方法-》attachBaseContext-》oncreate》activity构造方法-》onCreate-》配置主题中的背景等操作-》onStart-》onResume-》测量 布局 绘制 显示
- 冷启动优化
- 减少主线程耗时
- 减少布局复杂度和层级。如果可以用代码写view,不要用xml,因为通过反射创建view 慢。电报app就用的手写view。或者x2c。
- Application中少做耗时操作。
- 用启动框架做库加载,也就是实现加载的图的最优拓扑排序。AndroidStartup
- 少用静态变量的方式在Application保存数据或者参与业务的操作。
- MainActivity 中onCreate onstart onResume 中能用异步的就用异步,如果可以甚至可以异步加载setContentVIew
- 热启动。系统中有该进程,就不会走Application 直接走 MainActivity(测量、布局、绘制),
- 除了Application相关的都是
启动白屏黑屏
- style windowNoTitle为false。
- windowbackground设置图片,看起来感觉快点
- 或者windowIsTranslucent 透明,甩锅给手机厂商
- Application中的一些初始化懒加载。界面显示后再加载一些不那么重要的库。
17、LeakCanary2.0为什么不需要再application里调install
LeakCanary2.0为什么不需要再application里调install?因为人家整了个ContentProvider的onCreate里初始化,比application oncreate还早
18、如何避免大图片OOM
- 压缩。压缩尺寸和色彩模式
- 色彩模式,比如ARGB_8888改为rgb565.节约一般内存
- 压缩尺寸
- 获取原始尺寸。本地文件用decodeFle,网络图片用的codeStream。但是这些方法会为已经构建的bitmap分配内存,容易OOM。将BitmapFactory.Options.injustDecodeBounds设置为true则不会为bitmap分配内存。但outHeight/outWidth/outMimeType会被赋值。
- 压缩图片尺寸
- 通过传入需要的宽高,计算出 BitmapFactory.Options中inSampleSize的值。
- 然后通过inSampleSize传入options
- 最后bitmapFactory.decodeResource返回压缩后的bitmap
- 加载大量图片则 用内存缓存计数。lruCache,强引用存储在 LinkedHashMap.实际开发中用glide,有3级缓存。活动缓存,内存缓存,磁盘缓存。
19、 怎样检测函数执行是否卡顿
- 本地 CPU Profiler,JVMTI
- 生产环境 BlockCanary、ArgusAPM、LogMonitor。不是函数,
- 实现方式主要是依赖主线程Looper,监控每一次dispatchMessage的执行时间(BlockCanary)
- 依赖Chorographer,监控相邻两次 Vsync 事件通知的时间差(ArgusAPM\LogMonitor)
- 上述缺点:无法获取到各个函数 的执行耗时,对于稍微复杂一点的栈堆,,很难找到耗时函数。不好定位。
- 精准函数的话的话,字节码插桩,修改字节码。编译器修改所有class为念的函数字节码,堆所有函数前后进行大点插桩。让我想起字节以前以后热修复的库,直接字节码插桩在每个函数,最前面。
20、ANR线上问题提如何监控
就是应用未响应。四大组件。Activiyt\广播(前台10s 后台60s)、ContentProvider(10s)、Service(10s)以及用户交互进行超时监控(5s)。
App如何判断ANR
主线程watchdog机制
- 核心思想是应用层定期向主线程设置探测消息,并异步设置超时监测。规定时间没收到发送的探测消息的更新,则判定为可能发生ANR。比如设置一个volatile的值,默认未false。子线程 将修改该为true丢到UI线程。1s后检测它是否修改。没有则计数+1,有就继续修改为false.当计数大于等于5就触发java层获取线程堆栈,持久化或者DUMP file(费时间 可能要10s),配合*
ProcessLifecycleOwner
* 在前台才做。
监听signalquit信号
Tencent Matrix、爱奇艺 xCrash 友盟。都是用的这个。微信也用过。监听Singalquit 进程终止信号。收到该信号是,过滤场景,确定是发生用户可感知的ANR之后,从java层获取线程堆栈。重写下SignalHandler。 - 你重写handle 比如开个线程先把堆栈搞出来 再dump一堆其他信息出来 然后再重启应用 - 因为查询到anr和确认anr是两件事 查询到anr不一定确定anr 所以在信号量获取时就已经有轮询的trace了 ,ActivityManager.getProcessErrInfo时确定anr就可以报了
应用层如何获取ANR Info
- 无论是WatchDog还是监听信号,都需要进一步过滤,以去额宝手机我们 想要的 ANR。ActivityManager#getProcessesInErrorState,获得进程错误信息的list
20、Android 有哪些存储数据的方法?
- sqlite
- sp
- 文件。下/data/data/包名/file
- 要说的话ContentProvider也行
21、SharePreference原理,commit与apply的区别是什么?使用时需要注意的有哪些?
- SharepreFerence本质上是把数据存在一个xml文件里。/data/data/<包名>/shared_prefs/。使用时加载到内存。通过键值对的形式存储数据
- 关于commit和apply的区别:
- 提交方式:commit方法会将数据一次性提交到XML文件中,这个过程是同步的,不能被打断。而apply方法会将数据先保存到内存中,然后再异步地提交到XML文件中,这个过程没有返回值。
- 效率:由于commit是同步提交,所以在数据量较大时,提交过程会比apply方法慢。而apply方法采用了异步提交,效率较高。
- 返回值:commit方法有返回值,可以判断数据是否成功保存。而apply方法没有返回值,无法判断数据是否成功保存。
- 使用注意:
- 少使用:因为SP会将整个文件加载到内存中,如果文件过大会过多占用内存,甚至outofmemmory。
- 不适用跨进程通信。因为缓存策略。用sqlite或者ContentProvier更好
- 注意异常处理。
- 避免频繁调用editor与commit方法。因为前者每次都会创建EditorImpl对象,后组合每次都会将所有数据序列胡并使用IO(写文件)
- getXXX()可能阻塞线程
- apply可能ANR
- 因为apply会调用QueuedWork.addFinisher(awaitCommit),在Activity执行onStop时会需要等待apply完成执行完awaitCommit才能继续往后执行。
22、为什么使用parcelable?好处是什么?
什么是序列化?
就是将对象状态信息转为可存储运输的形式的过程。序列化主要永驻就是在传递和保存对象的时候,保证对象的完整性和可传递性。
- Serializable接口
- 对JVM来说,要进行持久化的类必须要有一个标记,而标记就是Serializable接口。而在反序列化的过程中则需要使用serialVersionUID来确定哪个类来加载这个对象(将目标类也生成serialVersionUID,看是否一致),如果我们在序列化中没有显示地声明serialVersionUID,则序列化运行时将会根据该类的各个方面计算该类默认的serialVersionUID值。Java官方强烈建议所有要序列化的类都显示地声明serialVersionUID字段,因为如果高度依赖于JVM默认生成serialVersionUID,可能会导致其与编译器的实现细节耦合,为了保证跨不同Java编译器实现的serialVersionUID值的一致,实现Serializable接口的必须显示地声明serialVersionUID字段。
- 总之实现了Serializable接口就相当于给类打上了一个标记,JVM就能够对类对象信息按照(Serializable)规则记录,而反序列化就按照规则解析即可。
- Parcelable的原理
- 它是android特有的序列化方式,不需要存到文件中,只是在内存中而已。用Parcelable更高效。因为Parcelable依赖于Parcel。原理是在内存中建立 一块共享数据块,序列化和反序列化都是操作这一块的数据,而不是像Serializable需要使用IO。
对比
- Serilizable是Java序列化的方式,存取的过程有频繁的IO,性能差,但实现简单。
- Parcelable是android序列化的方式,采用共享内存的方式实现用户空间和内核空间的交换,性能很好,但是实现方式比较复杂。
- Serializable可以持久化存储,Parcelable是存储在内存中,不能持久化存储。