RecyclerView与ListView 对比浅析:缓存机制
重要
一. 背景
PS:相关知识:
ListView与RecyclerView缓存机制原理大致相似,如下图所示:
滑动过程中,离屏的ItemView即被回收至缓存,入屏的ItemView则会优先从缓存中获取,只是ListView与RecyclerView的实现细节有差异.(这只是缓存使用的其中一个场景,还有如刷新等)
二. 正文
2.1 缓存机制对比
1. 层级不同:
RecyclerView比ListView多两级缓存,支持多个离ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。
具体来说:
ListView(两级缓存):
RecyclerView(四级缓存):
ListView和RecyclerView缓存机制基本一致:
1). mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;
2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用.
3). RecyclerView的优势在于a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;b.mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势.客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。
2. 缓存不同:
1). RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:
View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
2). ListView缓存View。
缓存不同,二者在缓存的使用上也略有差别,具体来说:
ListView获取缓存的流程:
RecyclerView获取缓存的流程:
1). RecyclerView中mCacheViews(屏幕外)获取缓存时,是通过匹配pos获取目标位置的缓存,这样做的好处是,当数据源数据不变的情况下,无须重新bindView:
而同样是离屏缓存,ListView从mScrapViews根据pos获取相应的缓存,但是并没有直接使用,而是重新getView(即必定会重新bindView),相关代码如下:
//AbsListView源码:line2345
//通过匹配pos从mScrapView中获取缓存
final View scrapView = mRecycler.getScrapView(position);
//无论是否成功都直接调用getView,导致必定会调用createView
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
if (child != scrapView) {
mRecycler.addScrapView(scrapView, position);
} else {
...
}
}
2). ListView中通过pos获取的是view,即pos-->view;
RecyclerView中通过pos获取的是viewholder,即pos --> (view,viewHolder,flag);
从流程图中可以看出,标志flag的作用是判断view是否需要重新bindView,这也是RecyclerView实现局部刷新的一个核心。
2.2 局部刷新
由上文可知,RecyclerView的缓存机制确实更加完善,但还不算质的变化,RecyclerView更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView。
(RecyclerView和ListView添加,移除Item效果对比)
结合RecyclerView的缓存机制,看看局部刷新是如何实现的:
以RecyclerView中notifyItemRemoved(1)为例,最终会调用requestLayout(),使整个RecyclerView重新绘制,过程为:
onMeasure()-->onLayout()-->onDraw()
其中,onLayout()为重点,分为三步:
dispathLayoutStep1():记录RecyclerView刷新前列表项ItemView的各种信息,如Top,Left,Bottom,Right,用于动画的相关计算;
dispathLayoutStep2():真正测量布局大小,位置,核心函数为layoutChildren();
dispathLayoutStep3():计算布局前后各个ItemView的状态,如Remove,Add,Move,Update等,如有必要执行相应的动画.
其中,layoutChildren()流程图:
当调用notifyItemRemoved时,会对屏幕内ItemView做预处理,修改ItemView相应的pos以及flag(流程图中红色部分):
当调用fill()中RecyclerView.getViewForPosition(pos)时,RecyclerView通过对pos和flag的预处理,使得bindview只调用一次.
需要指出,ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是"一锅端",将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。
三.结论
1、在一些场景下,如界面初始化,滑动等,ListView和RecyclerView都能很好地工作,两者并没有很大的差异:
文章的开头便抛出了这样一个问题,微信Android客户端卡券模块,大部分UI都是以列表页的形式展示,实现方式为ListView,是否有必要将其替换成RecyclerView呢?
答案是否定的,从性能上看,RecyclerView并没有带来显著的提升,不需要频繁更新,暂不支持用动画,意味着RecyclerView优势也不太明显,没有太大的吸引力,ListView已经能很好地满足业务需求。
2、数据源频繁更新的场景,如弹幕:http://www.jianshu.com/p/2232a63442d6 等RecyclerView的优势会非常明显;
进一步来讲,结论是:
列表页展示界面,需要支持动画,或者频繁更新,局部刷新,建议使用RecyclerView,更加强大完善,易扩展;其它情况(如微信卡包列表页)两者都OK,但ListView在使用上会更加方便,快捷
如果有个100M大的文件,需要上传至服务器中,而服务器form表单最大只能上传2M,可以用什么方法。
重要
首先来说使用http协议上传数据,特别在android下,跟form没什么关系。
传统的在web中,在form中写文件上传,其实浏览器所做的就是将我们的数据进行解析组拼成字符串,以流的方式发送到服务器,且上传文件用的都是POST方式,POST方式对大小没什么限制。
回到题目,可以说假设每次真的只能上传2M,那么可能我们只能把文件截断,然后分别上传了,断点上传。
即时通讯是是怎么做的?
重要
使用asmark 开源框架实现的即时通讯功能.该框架基于开源的 XMPP 即时通信协议,采用 C/S 体系结构,通过 GPRS 无线网络用 TCP 协议连接到服务器,以架设开源的Openfn'e 服务器作为即时通讯平台。
客户端基于 Android 平台进行开发。负责初始化通信过程,进行即时通信时,由客户端负责向服务器发起创建连接请求。系统通过 GPRS 无线网络与 Internet 网络建立连接,通过服务器实现与Android 客户端的即时通信脚。
服务器端则采用 Openfire 作为服务器。 允许多个客户端同时登录并且并发的连接到一个服务器上。服务器对每个客户端的连接进行认证,对认证通过的客户端创建会话,客户端与服务器端之间的通信就在该会话的上下文中进行。
说说 LruCache 底层原理
重要
LruCache 使用一个 LinkedHashMap 简单的实现内存的缓存,没有软引用,都是强引用。
如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。maxSize 是通过构造方法初始化的值,他表示这个缓存能缓存的最大值是多少。
size 在添加和移除缓存都被更新值, 他通过 safeSizeOf 这个方法更新值。 safeSizeOf 默认返回 1,但一般我们会根据 maxSize 重写这个方法,比如认为 maxSize 代表是 KB 的话,那么就以 KB 为单位返回该项所占的内存大小。
除异常外,首先会判断 size 是否超过 maxSize,如果超过了就取出最先插入的缓存,如果不为空就删掉,并把 size 减去该项所占的大小。这个操作将一直循环下去,直到 size 比 maxSize 小或者缓存为空。
andorid 应用第二次登录实现自动登录
重要
前置条件是所有用户相关接口都走 https,非用户相关列表类数据走 http。
步骤
第一次登陆 getUserInfo 里带有一个长效 token,该长效 token 用来判断用户是否登陆和换取短 token
把长效 token 保存到 SharedPreferences
接口请求用长效 token 换取短token,短 token 服务端可以根据你的接口最后一次请求作为标示,超时时间为一天。
所有接口都用短效 token
如果返回短效 token 失效,执行第3步,再直接当前接口
如果长效 token 失效(用户换设备或超过一月),提示用户登录
谈谈你对 Bitmap 的理解, 什么时候应该手动调用 bitmap.recycle()
重要
Bitmap 是 android 中经常使用的一个类,它代表了一个图片资源。 Bitmap 消耗内存很严重,如果不注意优化代码,经常会出现 OOM 问题,优化方式通常有这么几种:
使用缓存;
压缩图片;
及时回收;复制代码
至于什么时候需要手动调用 recycle,这就看具体场景了,原则是当我们不再使用 Bitmap 时,需要回收之。另外,我们需要注意,2.3 之前 Bitmap 对象与像素数据是分开存放的,Bitmap 对象存在java Heap 中而像素数据存放在 Native Memory 中, 这时很有必要调用 recycle 回收内存。 但是 2.3之后,Bitmap 对象和像素数据都是存在 Heap 中,GC 可以回收其内存。
属性动画,例如一个 button 从 A 移动到 B 点,B 点还是可以响应点击事件,这个原理是什么?
重要
补间动画只是显示的位置变动,View 的实际位置未改变,表现为 View 移动到其他地方,点击事件仍在原处才能响应。而属性动画控件移动后事件相应就在控件移动后本身进行处理
都使用过哪些自定义控件
pull2RefreshListView
LazyViewPager
SlidingMenu
SmoothProgressBar
自定义组合控件
ToggleButton
自定义Toast
Android与服务器交互的方式中的对称加密和非对称加密是什么?
重要
对称加密,就是加密和解密数据都是使用同一个key,这方面的算法有DES。
非对称加密,加密和解密是使用不同的key。发送数据之前要先和服务端约定生成公钥和私钥,使用公钥加密的数据可以用私钥解密,反之。这方面的算法有RSA。ssh 和 ssl都是典型的非对称加密。
如何修改 Activity 进入和退出动画
重要
可 以 通 过 两 种 方 式 , 一 是 通 过 定 义 Activity 的 主 题 , 二 是 通 过 覆 写 Activity 的overridePendingTransition 方法。
通过设置主题样式在 styles.xml 中编辑如下代码:
添加themes.xml文件:
在AndroidManifest.xml中给指定的Activity指定theme。
覆写 overridePendingTransition 方法
overridePendingTransition(R.anim.fade,R.anim.hold);
请解释下 Android 程序运行时权限与文件系统权限的区别?
重要
apk 程序是运行在虚拟机上的,对应的是 Android 独特的权限机制,只有体现到文件系统上时才
使用 linux 的权限设置。
linux 文件系统上的权限
-rwxr-x--xsystemsystem41562010-04-3016:13test.apk
代表的是相应的用户/用户组及其他人对此文件的访问权限,与此文件运行起来具有的权限完全不相关。比如上面的例子只能说明system用户拥有对此文件的读写执行权限;system组的用户对此文件拥有读、执行权限;其他人对此文件只具有执行权限。而 test.apk 运行起来后可以干哪些事情,跟这个就不相关了。千万不要看 apk 文件系统上属于system/system用户及用户组,或者root/root 用户及用户组,就认为 apk 具有system或 root 权限复制代码
Android 的权限规则
Android中的apk必须签名
基于UserID的进程级别的安全机制
默认apk生成的数据对外是不可见的
AndroidManifest.xml中的显式权限声明
网络框架有哪些?他们之间的区别是什么?
重要
Xutils
这个框架非常全面,可以进行网络请求,可以进行图片加载处理,可以数据储存,还可以对view进行注解,使用这个框架非常方便,但是缺点也是非常明显的,使用这个项目,会导致项目对这个框架依赖非常的严重,一旦这个框架出现问题,那么对项目来说影响非常大的
OKhttp
Android开发中是可以直接使用现成的api进行网络请求的。就是使用HttpClient,HttpUrlConnection进行操作。okhttp针对Java和Android程序,封装的一个高性能的http请求库,支持同步,异步,而且okhttp又封装了线程池,封装了数据转换,封装了参数的使用,错误处理等。API使用起来更加的方便。但是我们在项目中使用的时候仍然需要自己在做一层封装,这样才能使用的更加的顺手。
Volley
Volley是Google官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley里面也封装了ImageLoader,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,稍复杂点的需求还是需要用到专门的图片加载框架。Volley也有缺陷,比如不支持post大数据,所以不适合上传文件。不过Volley设计的初衷本身也就是为频繁的、数据量小的网络请求而生。
Retrofit
Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格, 并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。
Volley VS OkHttp
Volley的优势在于封装的更好,而使用OkHttp你需要有足够的能力再进行一次封装。而OkHttp的优势在于性能更高,因为 OkHttp基于NIO和Okio ,所以性能上要比 Volley更快。IO 和 NIO这两个都是Java中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作这种是最简单的也叫阻塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是 NIO 的方式,非阻塞式, 所以NIO当然要比IO的性能要好了,而 Okio是 Square 公司基于IO和NIO基础上做的一个更简单、高效处理数据流的一个库。理论上如果Volley和OkHttp对比的话,更倾向于使用 Volley,因为Volley内部同样支持使用OkHttp,这点OkHttp的性能优势就没了, 而且 Volley 本身封装的也更易用,扩展性更好些。
OkHttp VS Retrofit
毫无疑问,Retrofit 默认是基于 OkHttp 而做的封装,这点来说没有可比性,肯定首选 Retrofit。
Volley VS Retrofit
这两个库都做了不错的封装,但Retrofit解耦的更彻底,尤其Retrofit2.0出来,Jake对之前1.0设计不合理的地方做了大量重构, 职责更细分,而且Retrofit默认使用OkHttp,性能上也要比Volley占优势,再有如果你的项目如果采用了RxJava ,那更该使用 Retrofit 。所以这两个库相比,Retrofit更有优势,在能掌握两个框架的前提下该优先使用 Retrofit。但是Retrofit门槛要比Volley稍高些,要理解他的原理,各种用法,想彻底搞明白还是需要花些功夫的,如果你对它一知半解,那还是建议在商业项目使用Volley吧。
打包原理
重要
Android的包文件APK分为两个部分:代码和资源,所以打包方面也分为资源打包和代码打包两个方面,这篇文章就来分析资源和代码的编译打包原理。
具体说来:
通过AAPT工具进行资源文件(包括AndroidManifest.xml、布局文件、各种xml资源等)的打包,生成R.java文件。
通过AIDL工具处理AIDL文件,生成相应的Java文件。
通过Javac工具编译项目源码,生成Class文件。
通过DX工具将所有的Class文件转换成DEX文件,该过程主要完成Java字节码转换成Dalvik字节码,压缩常量池以及清除冗余信息等工作。
通过ApkBuilder工具将资源文件、DEX文件打包生成APK文件。
利用KeyStore对生成的APK文件进行签名。
如果是正式版的APK,还会利用ZipAlign工具进行对齐处理,对齐的过程就是将APK文件中所有的资源文件举例文件的起始距离都偏移4字节的整数倍,这样通过内存映射访问APK文件的速度会更快。
性能优化
重要
参考链接:https://blog.csdn.net/csdn_aiyang/article/details/74989318
Android的性能优化,主要是从以下几个方面进行优化的:
稳定(内存溢出、崩溃)
流畅(卡顿)
耗损(耗电、流量)
安装包(APK瘦身)
影响稳定性的原因很多,比如内存使用不合理、代码异常场景考虑不周全、代码逻辑不合理等,都会对应用的稳定性造成影响。其中最常见的两个场景是:Crash 和 ANR,这两个错误将会使得程序无法使用。所以做好Crash全局监控,处理闪退同时把崩溃信息、异常信息收集记录起来,以便后续分析;合理使用主线程处理业务,不要在主线程中做耗时操作,防止ANR程序无响应发生。
(一)稳定——内存优化
(1)Memory Monitor 工具:
它是Android Studio自带的一个内存监视工具,它可以很好地帮助我们进行内存实时分析。通过点击Android Studio右下角的Memory Monitor标签,打开工具可以看见较浅蓝色代表free的内存,而深色的部分代表使用的内存从内存变换的走势图变换,可以判断关于内存的使用状态,例如当内存持续增高时,可能发生内存泄漏;当内存突然减少时,可能发生GC等,如下图所示。
LeakCanary工具:
LeakCanary是Square公司基于MAT开发的一款监控Android内存泄漏的开源框架。其工作的原理是:
监测机制利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果被回收,该WeakReference引用会被放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收(在ReferenceQueue中说明可以被回收,不存在泄漏;否则,可能存在泄漏,LeakCanary是执行一遍GC,若还未在ReferenceQueue中,就会认定为泄漏)。
如果Activity被认定为泄露了,就抓取内存dump文件(Debug.dumpHprofData);之后通过HeapAnalyzerService.runAnalysis进行分析内存文件分析;接着通过HeapAnalyzer (checkForLeak—findLeakingReference—findLeakTrace)来进行内存泄漏分析。最后通过DisplayLeakService进行内存泄漏的展示。
(3)Android Lint 工具:
Android Lint Tool 是Android Sutido种集成的一个Android代码提示工具,它可以给你布局、代码提供非常强大的帮助。硬编码会提示以级别警告,例如:在布局文件中写了三层冗余的LinearLayout布局、直接在TextView中写要显示的文字、字体大小使用dp而不是sp为单位,就会在编辑器右边看到提示。
(二)流畅——卡顿优化
卡顿的场景通常是发生在用户交互体验最直接的方面。影响卡顿的两大因素,分别是界面绘制和数据处理。
界面绘制:主要原因是绘制的层级深、页面复杂、刷新不合理,由于这些原因导致卡顿的场景更多出现在 UI 和启动后的初始界面以及跳转到页面的绘制上。
数据处理:导致这种卡顿场景的原因是数据处理量太大,一般分为三种情况,一是数据在处理 UI 线程,二是数据处理占用 CPU 高,导致主线程拿不到时间片,三是内存增加导致 GC 频繁,从而引起卡顿。
(1)布局优化
在Android种系统对View进行测量、布局和绘制时,都是通过对View数的遍历来进行操作的。如果一个View数的高度太高就会严重影响测量、布局和绘制的速度。Google也在其API文档中建议View高度不宜哦过10层。现在版本种Google使用RelativeLayout替代LineraLayout作为默认根布局,目的就是降低LineraLayout嵌套产生布局树的高度,从而提高UI渲染的效率。
布局复用,使用标签重用layout;
提高显示速度,使用延迟View加载;
减少层级,使用标签替换父级布局;
注意使用wrap_content,会增加measure计算成本;
删除控件中无用属性;
(2)绘制优化
过度绘制是指在屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次重叠的 UI 结构中,如果不可见的 UI 也在做绘制的操作,就会导致某些像素区域被绘制了多次,从而浪费了多余的 CPU 以及 GPU 资源。如何避免过度绘制?
布局上的优化。移除 XML 中非必须的背景,移除 Window 默认的背景、按需显示占位背景图片
自定义View优化。使用 canvas.clipRect() 帮助系统识别那些可见的区域,只有在这个区域内才会被绘制。
(3)启动优化
应用一般都有闪屏页SplashActivity,优化闪屏页的 UI 布局,可以通过 Profile GPU Rendering 检测丢帧情况。
(三)节省——耗电优化
在 Android5.0 以前,关于应用电量消耗的测试即麻烦又不准确,而5.0 之后Google专门引入了一个获取设备上电量消耗信息的API—— Battery Historian。Battery Historian 是一款由 Google 提供的 Android 系统电量分析工具,直观地展示出手机的电量消耗过程,通过输入电量分析文件,显示消耗情况。
最后提供一些可供参考耗电优化的方法:
(1)计算优化。算法、for循环优化、Switch…case替代if…else、避开浮点运算。
浮点运算:计算机里整数和小数形式就是按普通格式进行存储,例如1024、3.1415926等等,这个没什么特点,但是这样的数精度不高,表达也不够全面,为了能够有一种数的通用表示法,就发明了浮点数。浮点数的表示形式有点像科学计数法(.×10***),它的表示形式是0.*****×10,在计算机中的形式为 .*** e ±**),其中前面的星号代表定点小数,也就是整数部分为0的纯小数,后面的指数部分是定点整数。利用这样的形式就能表示出任意一个整数和小数,例如1024就能表示成0.1024×10^4,也就是 .1024e+004,3.1415926就能表示成0.31415926×10^1,也就是 .31415926e+001,这就是浮点数。浮点数进行的运算就是浮点运算。浮点运算比常规运算更复杂,因此计算机进行浮点运算速度要比进行常规运算慢得多。
(2)避免 Wake Lock 使用不当。
Wake Lock是一种锁的机制,主要是相对系统的休眠而言的,,只要有人拿着这个锁,系统就无法进入休眠意思就是我的程序给CPU加了这个锁那系统就不会休眠了,这样做的目的是为了全力配合我们程序的运行。有的情况如果不这么做就会出现一些问题,比如微信等及时通讯的心跳包会在熄屏不久后停止网络访问等问题。所以微信里面是有大量使用到了Wake_Lock锁。系统为了节省电量,CPU在没有任务忙的时候就会自动进入休眠。有任务需要唤醒CPU高效执行的时候,就会给CPU加Wake_Lock锁。大家经常犯的错误,我们很容易去唤醒CPU来工作,但是很容易忘记释放Wake_Lock。
(3)使用 Job Scheduler 管理后台任务。
在Android 5.0 API 21 中,google提供了一个叫做JobScheduler API的组件,来处理当某个时间点或者当满足某个特定的条件时执行一个任务的场景,例如当用户在夜间休息时或设备接通电源适配器连接WiFi启动下载更新的任务。这样可以在减少资源消耗的同时提升应用的效率。
(四)安装包——APK瘦身
(1)安装包的组成结构
assets文件夹。存放一些配置文件、资源文件,assets不会自动生成对应的 ID,而是通过 AssetManager 类的接口获取。
res。res 是 resource 的缩写,这个目录存放资源文件,会自动生成对应的 ID 并映射到 .R 文件中,访问直接使用资源 ID。
META-INF。保存应用的签名信息,签名信息可以验证 APK 文件的完整性。
AndroidManifest.xml。这个文件用来描述 Android 应用的配置信息,一些组件的注册信息、可使用权限等。
classes.dex。Dalvik 字节码程序,让 Dalvik 虚拟机可执行,一般情况下,Android 应用在打包时通过 Android SDK 中的 dx 工具将 Java 字节码转换为 Dalvik 字节码。
resources.arsc。记录着资源文件和资源 ID 之间的映射关系,用来根据资源 ID 寻找资源。
(2)减少安装包大小
代码混淆。使用IDE 自带的 proGuard 代码混淆器工具 ,它包括压缩、优化、混淆等功能。
资源优化。比如使用 Android Lint 删除冗余资源,资源文件最少化等。
图片优化。比如利用 PNG优化工具 对图片做压缩处理。推荐目前最先进的压缩工具Googlek开源库zopfli。如果应用在0版本以上,推荐使用 WebP图片格式。
避免重复或无用功能的第三方库。例如,百度地图接入基础地图即可、讯飞语音无需接入离线、图片库GlidePicasso等。
插件化开发。比如功能模块放在服务器上,按需下载,可以减少安装包大小。
可以使用微信开源资源文件混淆工具——AndResGuard。一般可以压缩apk的1M左右大。
冷启动与热启动
参考链接:https://www.jianshu.com/p/03c0fd3fc245
冷启动
在启动应用时,系统中没有该应用的进程,这时系统会创建一个新的进程分配给该应用;
热启动
在启动应用时,系统中已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程还是保留在后台);
区别
冷启动:系统没有该应用的进程,需要创建一个新的进程分配给应用,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。 热启动: 从已有的进程中来启动,不会创建和初始化Application类,直接创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
冷启动流程
Zygote进程中fork创建出一个新的进程; 创建和初始化Application类、创建MainActivity; inflate布局、当onCreate/onStart/onResume方法都走完; contentView的measure/layout/draw显示在界面上。
冷启动优化
减少在Application和第一个Activity的onCreate()方法的工作量; 不要让Application参与业务的操作; 不要在Application进行耗时操作; 不要以静态变量的方式在Application中保存数据; 减少布局的复杂性和深度;
屏幕适配
重要
基本概念
屏幕尺寸
含义:手机对角线的物理尺寸 单位:英寸(inch),1英寸=2.54cm
Android手机常见的尺寸有5寸、5.5寸、6寸,6.5寸等等
屏幕分辨率
含义:手机在横向、纵向上的像素点数总和
一般描述成屏幕的”宽x高”=AxB 含义:屏幕在横向方向(宽度)上有A个像素点,在纵向方向
(高)有B个像素点 例子:1080x1920,即宽度方向上有1080个像素点,在高度方向上有1920个像素点
单位:px(pixel),1px=1像素点
UI设计师的设计图会以px作为统一的计量单位
Android手机常见的分辨率:320x480、480x800、720x1280、1080x1920
屏幕像素密度
含义:每英寸的像素点数 单位:dpi(dots per ich)
假设设备内每英寸有160个像素,那么该设备的屏幕像素密度=160dpi
适配方法
1.支持各种屏幕尺寸: 使用wrap_content, match_parent, weight.要确保布局的灵活性并适应各种尺寸的屏幕,应使用 “wrap_content”、“match_parent” 控制某些视图组件的宽度和高度。
2.使用相对布局,禁用绝对布局。
3.使用LinearLayout的weight属性
假如我们的宽度不是0dp(wrap_content和0dp的效果相同),则是match_parent呢?
android:layout_weight的真实含义是:如果View设置了该属性并且有效,那么该 View的宽度等于原有宽度(android:layout_width)加上剩余空间的占比。
从这个角度我们来解释一下上面的现象。在上面的代码中,我们设置每个Button的宽度都是match_parent,假设屏幕宽度为L,那么每个Button的宽度也应该都为L,剩余宽度就等于L-(L+L)= -L。
Button1的weight=1,剩余宽度占比为1/(1+2)= 1/3,所以最终宽度为L+1/3*(-L)=2/3L,Button2的计算类似,最终宽度为L+2/3(-L)=1/3L。
4.使用.9图片
今日头条屏幕适配
参考链接:https://juejin.im/post/5bce688e6fb9a05cf715d1c2
Activity启动流程
重要
Activity启动流程
用户从Launcher程序点击应用图标可启动应用的入口Activity,Activity启动时需要多个进程之间的交互,Android系统中有一个zygote进程专用于孵化Android框架层和应用层程序的进程。还有一个system_server进程,该进程里运行了很多binder service。例如ActivityManagerService,PackageManagerService,WindowManagerService,这些binder service分别运行在不同的线程中,其中ActivityManagerService负责管理Activity栈,应用进程,task。
点击Launcher图标来启动Activity
用户在Launcher程序里点击应用图标时,会通知ActivityManagerService启动应用的入口Activity,ActivityManagerService发现这个应用还未启动,则会通知Zygote进程孵化出应用进程,然后在这个dalvik应用进程里执行ActivityThread的main方法。应用进程接下来通知ActivityManagerService应用进程已启动,ActivityManagerService保存应用进程的一个代理对象,这样ActivityManagerService可以通过这个代理对象控制应用进程,然后ActivityManagerService通知应用进程创建入口Activity的实例,并执行它的生命周期方法。
Android绘制流程窗口启动流程分析
4.2、Activity生命周期
(1)Activity的形态
Active/Running:
Activity处于活动状态,此时Activity处于栈顶,是可见状态,可与用户进行交互。
Paused:
当Activity失去焦点时,或被一个新的非全屏的Activity,或被一个透明的Activity放置在栈顶时,Activity就转化为Paused状态。但我们需要明白,此时Activity只是失去了与用户交互的能力,其所有的状态信息及其成员变量都还存在,只有在系统内存紧张的情况下,才有可能被系统回收掉。
Stopped:
当一个Activity被另一个Activity完全覆盖时,被覆盖的Activity就会进入Stopped状态,此时它不再可见,但是跟Paused状态一样保持着其所有状态信息及其成员变量。
Killed:
当Activity被系统回收掉时,Activity就处于Killed状态。
Activity会在以上四种形态中相互切换,至于如何切换,这因用户的操作不同而异。了解了Activity的4种形态后,我们就来聊聊Activity的生命周期。
Activity的生命周期
所谓的典型的生命周期就是在有用户参与的情况下,Activity经历从创建,运行,停止,销毁等正常的生命周期过程。
onCreate
该方法是在Activity被创建时回调,它是生命周期第一个调用的方法,我们在创建Activity时一般都需要重写该方法,然后在该方法中做一些初始化的操作,如通过setContentView设置界面布局的资源,初始化所需要的组件信息等。
onStart
此方法被回调时表示Activity正在启动,此时Activity已处于可见状态,只是还没有在前台显示,因此无法与用户进行交互。可以简单理解为Activity已显示而我们无法看见摆了。
onResume
当此方法回调时,则说明Activity已在前台可见,可与用户交互了(处于前面所说的Active/Running形态),onResume方法与onStart的相同点是两者都表示Activity可见,只不过onStart回调时Activity还是后台无法与用户交互,而onResume则已显示在前台,可与用户交互。当然从流程图,我们也可以看出当Activity停止后(onPause方法和onStop方法被调用),重新回到前台时也会调用onResume方法,因此我们也可以在onResume方法中初始化一些资源,比如重新初始化在onPause或者onStop方法中释放的资源。
onPause
此方法被回调时则表示Activity正在停止(Paused形态),一般情况下onStop方法会紧接着被回调。但通过流程图我们还可以看到一种情况是onPause方法执行后直接执行了onResume方法,这属于比较极端的现象了,这可能是用户操作使当前Activity退居后台后又迅速地再回到到当前的Activity,此时onResume方法就会被回调。当然,在onPause方法中我们可以做一些数据存储或者动画停止或者资源回收的操作,但是不能太耗时,因为这可能会影响到新的Activity的显示——onPause方法执行完成后,新Activity的onResume方法才会被执行。
onStop
一般在onPause方法执行完成直接执行,表示Activity即将停止或者完全被覆盖(Stopped形态),此时Activity不可见,仅在后台运行。同样地,在onStop方法可以做一些资源释放的操作(不能太耗时)。
onRestart
表示Activity正在重新启动,当Activity由不可见变为可见状态时,该方法被回调。这种情况一般是用户打开了一个新的Activity时,当前的Activity就会被暂停(onPause和onStop被执行了),接着又回到当前Activity页面时,onRestart方法就会被回调。
onDestroy
此时Activity正在被销毁,也是生命周期最后一个执行的方法,一般我们可以在此方法中做一些回收工作和最终的资源释放。
小结
到这里我们来个小结,当Activity启动时,依次会调用onCreate(),onStart(),onResume(),而当Activity退居后台时(不可见,点击Home或者被新的Activity完全覆盖),onPause()和onStop()会依次被调用。当Activity重新回到前台(从桌面回到原Activity或者被覆盖后又回到原Activity)时,onRestart(),onStart(),onResume()会依次被调用。当Activity退出销毁时(点击back键),onPause(),onStop(),onDestroy()会依次被调用,到此Activity的整个生命周期方法回调完成。现在我们再回头看看之前的流程图,应该是相当清晰了吧。嗯,这就是Activity整个典型的生命周期过程。
ANR是怎么触发的?
重要
ActivityManagerService和WindowManagerService会检测APP的响应时间,在应用进程的主线程处理特定的事件之前,用AMS/BroadcastQueue等相关的Handler像系统进程的Looper发送一个延时消息,在延时的时间之内,如果特定事件被执行完,则会移除掉MessageQueue中加入的那个延时消息;否则,如果特定的事件没有执行完,则不会移除那个消息,相应的Looper会取出该消息进行处理,从而触发ANR。这就是触发ANR的原理。
android anr之后如何导出traces.txt
重要
Anr Application not responding 即程序无响应
Anr的类型
1> 按键或触摸事件无响应 5s
2>BroadCastTimeout在特定时间无响应 10s
3>小概率 ServicesTimeout servicez在特定时间无法处理 20s
产生anr的原因
1>当前事件没有机会处理 (当ui线程正在处理其他的工作,没空处理当前事件,也就是当前事件被阻塞啦)
2>正在处理当前事件,但是没有处理完(即没有反馈)
导出traces.txt 文件
adb pull /data/anr/traces.txt d:/ 《导出文件至某盘
adb shell ls /data/anr/ 《查看文件
1、adb shell
2、cat /data/anr/xxx >/mnt/sdcard/yy/zz.txt
3、exit
4、adb pull /mnt/sdcard/yy/zz.txt d: ,即可将文件导出到了d盘。
AsyncTask原理
非常重要
AsyncTask工作组成:
是由俩个线程池+Hanlder构成
任务队列SerialExecutor线程池
执行 THREAD_POOL_EXECUTOR线程池
内部Handler 异步通信+消息传递
如果不设置执行线程数那么默认是4,如果有6个线程那么有两个就会在任务队列等待,执行线程中执行4个
具体去看源码这个说不清楚
LRUCache原理
重要
LRUCache的核心思想就是维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没有访问的对象放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰。
缺图片
Bitmap如何处理大图,如一张30M的大图,如何预防OOM
重要
需要将这张大图进行压缩。
使用图片缓存。
https://blog.csdn.net/guolin_blog/article/details/9316683
自定义View注意事项
重要
减少不必要的invalidate()方法。
(1)、降低刷新频率
(2)、使用硬件加速
(3)、初始化时创建对象;不要在onDraw方法内创建绘制对象,一般都在构造函数里初始化对象。
(4)、状态存储和恢复:如果内存不足时,而恰好我们的Activity置于后台,不行被重启,或者用户旋转屏幕造成Activity重启,我们的View也应该尽量的去保存自己的属性。
App启动崩溃异常捕捉
重要
由于我们写的代码难免会出现一些bug,以及由于测试环境和生产环境差异导致出现的一些异常,在测试过程中没有发现,而在app上线之后会偶然出现的一些bug,以至于app在使用过程中出现ANR,这是个令人蛋疼的现象,app卡死、出现黑屏等,总之当app出现异常时的用户体验不友好,我们开发者需要去捕获这些异常,收集这些异常信息,并且上传到服务器,利于开发人员去解决这些问题,同时,我们还需要给用户一个友好的交互体验。
我也查找了许多捕获崩溃异常的文章来看,但大多都千篇一律,只说了怎么捕获异常,处理异常,并没有对捕获异常后的界面交互做出很好的处理,系统出现ANR时会先卡一段时间(这个时间还有点长)然后弹出一个系统默认的对话框,但是我现在需要的是当出现异常情况时,立马弹出对话框,并且对话框我想自定义界面。
最终实现的效果如图:
<img src="http://upload-images.jianshu.io/upload_images/1159224-cdf0b4169183ecaf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="40%" alt="最终效果图" align="center" />
首先需要自定义Application,并且在AndroidManifest.xml中进行配置
AndroidManifest.xml.png
还需要自定义一个应用异常捕获类AppUncaughtExceptionHandler,它必须得实现Thread.UncaughtExceptionHandler接口,另外还需要重写uncaughtException方法,去按我们自己的方式来处理异常
在Application中我们只需要初始化自定义的异常捕获类即可:
@OverridepublicvoidonCreate(){super.onCreate();mInstance=this;// 初始化文件目录SdcardConfig.getInstance().initSdcard();// 捕捉异常AppUncaughtExceptionHandlercrashHandler=AppUncaughtExceptionHandler.getInstance();crashHandler.init(getApplicationContext());}
其中文件目录是异常信息保存在sd卡中的目录,还有我们是整个app中全局捕获异常,所以我们自定义的捕获类是单例。
/**
* 初始化捕获类
*
* @param context
*/publicvoidinit(Contextcontext){applicationContext=context.getApplicationContext();crashing=false;//获取系统默认的UncaughtException处理器mDefaultHandler=Thread.getDefaultUncaughtExceptionHandler();//设置该CrashHandler为程序的默认处理器Thread.setDefaultUncaughtExceptionHandler(this);}
完成以上过程后,接着需要重写uncaughtException方法:
@OverridepublicvoiduncaughtException(Threadthread,Throwableex){if(crashing){return;}crashing=true;// 打印异常信息ex.printStackTrace();// 我们没有处理异常 并且默认异常处理不为空 则交给系统处理if(!handlelException(ex)&&mDefaultHandler!=null){// 系统处理mDefaultHandler.uncaughtException(thread,ex);}byebye();}privatevoidbyebye(){android.os.Process.killProcess(android.os.Process.myPid());System.exit(0);}
既然是我们自己处理异常,所以会先执行handlelException(ex)方法:
privatebooleanhandlelException(Throwableex){if(ex==null){returnfalse;}try{// 异常信息StringcrashReport=getCrashReport(ex);// TODO: 上传日志到服务器// 保存到sd卡saveExceptionToSdcard(crashReport);// 提示对话框showPatchDialog();}catch(Exceptione){returnfalse;}returntrue;}
获取的异常信息包括系统信息,app版本信息,以及手机制造商信息等:
/**
* 获取异常信息
* @param ex
* @return
*/privateStringgetCrashReport(Throwableex){StringBufferexceptionStr=newStringBuffer();PackageInfopinfo=CrashApplication.getInstance().getLocalPackageInfo();if(pinfo!=null){if(ex!=null){//app版本信息exceptionStr.append("App Version:"+pinfo.versionName);exceptionStr.append("_"+pinfo.versionCode+"\n");//手机系统信息exceptionStr.append("OS Version:"+Build.VERSION.RELEASE);exceptionStr.append("_");exceptionStr.append(Build.VERSION.SDK_INT+"\n");//手机制造商exceptionStr.append("Vendor: "+Build.MANUFACTURER+"\n");//手机型号exceptionStr.append("Model: "+Build.MODEL+"\n");StringerrorStr=ex.getLocalizedMessage();if(TextUtils.isEmpty(errorStr)){errorStr=ex.getMessage();}if(TextUtils.isEmpty(errorStr)){errorStr=ex.toString();}exceptionStr.append("Exception: "+errorStr+"\n");StackTraceElement[]elements=ex.getStackTrace();if(elements!=null){for(inti=0;i<elements.length;i++){exceptionStr.append(elements[i].toString()+"\n");}}}else{exceptionStr.append("no exception. Throwable is null\n");}returnexceptionStr.toString();}else{return"";}}
将异常信息保存到sd卡这个我觉得可选吧,但是上传到服务端还是很有必要的:
/**
* 保存错误报告到sd卡
* @param errorReason
*/privatevoidsaveExceptionToSdcard(StringerrorReason){try{Log.e("CrashDemo","AppUncaughtExceptionHandler执行了一次");Stringtime=mFormatter.format(newDate());StringfileName="Crash-"+time+".log";if(SdcardConfig.getInstance().hasSDCard()){Stringpath=SdcardConfig.LOG_FOLDER;Filedir=newFile(path);if(!dir.exists()){dir.mkdirs();}FileOutputStreamfos=newFileOutputStream(path+fileName);fos.write(errorReason.getBytes());fos.close();}}catch(Exceptione){Log.e("CrashDemo","an error occured while writing file..."+e.getMessage());}}
保存在sd卡中的异常文件格式:
异常信息
至于上传到服务器,就比较灵活了,可以将整个文件上传,或者上传异常信息的字符串,可以和后端开发人员配合。
因为捕获异常后我要马上关闭掉app即上面的byebye方法,是将app整个进程杀死,如果接着要显示提示对话框,则需要在新的任务栈中打开activity:
publicstaticIntentnewIntent(Contextcontext,Stringtitle,StringultimateMessage){Intentintent=newIntent();intent.setClass(context,PatchDialogActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.putExtra(EXTRA_TITLE,title);intent.putExtra(EXTRA_ULTIMATE_MESSAGE,ultimateMessage);returnintent;}
对话框中给出了重启操作的选项,重启过程的实现:
privatevoidrestart(){Intentintent=getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);startActivity(intent);super.onDestroy();}
源代码地址:https://github.com/shenhuniurou/BlogDemos/tree/master/CrashDemo欢迎star。
作者:shenhuniurou
链接:https://www.jianshu.com/p/fb28a5322d8a
来源:简书
启动太慢怎么解决?
重要
应用启动速度取决于application中做了什么事情,比如继承了很多sdk,并且sdk的init操作都需要在主线程中实现,那自然就慢了。在非必要的情况下,可以把加载延后,或丢给子线程。
统计启动时长,标准
重要
启动类型
(1)、冷启动:application没有被创建,需要先创建进程,然后启动MainActivity。由于这个过程需要fork一个新进程,所以耗时。
(2)、热启动:同上面对照,已经启动过application,并驻留在系统内存内,只是需要唤醒该进程,并启动MainActivity。
启动时长统计:
adb统计:adb shell am start -w pageage/activityname 通过这条命令启动,可以获得启动时间。
从0设计一款App整体架构,如何去做?
重要
(1)、构建方式:Gradle或Maven
(2)、网络框架:包括网络请求,图片异步加载,图片缓存,网络缓存,力求简单易用。
(3)、适配资源。
(4)、设计模式:MVC、MVP、MVVM
说说EventBus作用,实现方式,代替EventBus的方式
重要
EventBus作用:简化各组件间的通信,让我们书写代码更简单,能有效的分离事件发送方和事件接收方(也就是解耦合),能避免复杂和容易出错的依赖性和生命周期的问题。
RxJava可以替代EventBus,也可以使用Handler或AsyncTask
谈谈对RxJava的理解
重要
RxJava说到底,本质可以压缩为异步一个词。它就是一个异步操作的库,其他定于都是基于这之上的。
RxJava的异步实现是通过一种扩展的观察者模式来实现的。
RxJava的功能与原理实现
RxJava原理就是创建一个Observable对象来干活,然后使用各种操作符建立起来的链式操作,就如同流水线一样,把你想要处理的数据一步一步加工成你想要的成品,然后发射给Subscriber处理。
RxJava的作用,与平时使用的异步操作来比的优缺点
异步操作有Handler、AsyncTask等,但使用Rxjava,就算再多的异步操作,代码逻辑越来越复杂,RxJava依然可以保持清晰的逻辑。
https://www.jianshu.com/p/57b5c6567791
适配器模式,装饰者模式,外观模式的异同?
重要
装饰器模式:能动态的新增或组合对象的行为。
适配器模式:是对其他对象接口的一种转换行为,将原接口转换为目标接口,达到适配的效果。
外观模式:外观对象提供对子系统各元件功能的简化为共同层次的调用接口,它主要起简化作用。
装饰者是“新增行为”,适配器模式是“转换行为”,外观者模式是“简化行为”。
写出观察者模式的代码
重要
缺图片
从上边的例子可以看出,定义了四个订阅者,一个发布者,当发布者更新一个消息时,四个订阅者都收到消息,根据发布者更新的信息执行对应的更新操作。
手写生产者/消费者模式
重要
生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。
实现生产者消费者模式有三点:
(1)、一般使用队列作为缓冲区,给生产者和消费者解耦,平衡了生产者和消费者的处理能力。
(2)、构建生产者,队列满使得生产者线程阻塞。
(3)、构建消费者,队列空使得消费者线程阻塞。
缺图片
BlockingQueue是一个阻塞队列,它的存取可以保证只有一个线程在进行,所以根据逻辑,生产者在内存满的时候进行等待,并唤醒消费者队列,反过来消费者在饥饿状态下,等待并唤醒生产者生产。
项目中常用的设计模式
重要
(1)、模板方法模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,如jdbcTemplate
(2)、代理模式
spring的Proxy模式在AOP中有体现
(3)、观察者模式
定义对象的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
(4)、适配器模式
MethodBeforeAdviceAdapter类。
(5)、策略模式
使用了java的继承和多态
(6)、单例模式
解决了一个全局使用的类频繁的创建与销毁。
(7)、工厂模式
分为三种:简单工厂,工厂方法,抽象工厂。
你所知道的设计模式有哪些?
重要
MVC MVP MVVM原理和区别
重要
MVC是指Modle,View和Controller,将界面,业务逻辑和控制器分开,是一种低耦合的设计方式,适用于简单的应用开发。
这种设计模式最简单,但问题有三:
(1)、View和Model相互可见,耦合度高。
(2)、如果程序复杂,那么Activity这个Controller将十分繁琐复杂,不容易维护。
(3)、Activity角色模糊,View或Model。
MVP:P是指Presenter,即实现者,功能与Controller类似。Presenter实质为Interface类的运用,用于降低M、V、C间的耦合度,主旨是为Activity或Fragment瘦身。
MVP模式容易维护,可拆卸,可扩展,耦合性叫MVC较小,结构清晰。
MVP的缺点,在于开发开销相对较大。与MVC相比,需要维护更多的接口。
MVVM:MVVM由三部分组成,M,V,VM,分别代表Modle,View,ViewModle。谈论MVVM的前提是需要了解DataBinding,即Modle与View的进行绑定,包括单向绑定(Modle影响View),双向绑定(Modle与View相互影响)。
MVVM的侧重点在于数据与UI的联动,自动更新,而非降低耦合度。对于耦合度的问题,其实还是需要结合MVP模式来解决。
模块化实现(好处,原因)
重要
(1)、结构清晰,各个模块的代码实现分离,不会搅在一起。在代码review或者二次开发的时候一目了然,不会满世界去找代码。
(2)、协同开发的时候更灵活,不用再等同组的其他同事的模块开发完成后才能运行app,自己负责的模块稍加修改就可以当做主App直接跑起来。
(3)、便于维护。每个模块的代码、布局文件、资源文件可以随时从项目中通过gradle配置去掉。
数据库框架对比和源码分析
重要
greenDao是一种Android数据库ORM框架,与OrmLite、ActiveOrm、LitePal等数据库相比,单位时间内可以插入、更新和查询更多数据,而且提供了大量的灵活通用的接口。
https://blog.csdn.net/u012702547/article/details/52226163
自己去设计网络请求框架,怎么做?
重要
网络请求框架的核心就是封装了网络请求+异步+数据处理
其中网络请求一般使用Android的网络请求原生方法(HttpClient或HttpURLConnection)
一般而言,我们需要考虑的方面有:
1、构建我们的请求Request:包含url,请求方法,请求参数,请求头,编码,请求体,编码格式,超时时间,代理端口,代理主机等。
2、由Dispatcher分发器并行分发我们的Request请求,这里的分发器实际是一个线程池。
3、准备开始连接服务器,连接http获取https服务器地址,https需要考虑自签名证书、双向SSL验证等安全问题。
4、Response得到服务器的回调,考虑输入流关闭的问题,大文件传输的问题,线程切换问题,缓存问题。
数据库的优化
重要
数据库性能上:
(1)、批量事务插入,提升数据插入的性能。
(2)、单条sql由于多条sql
(3)、读和写操作是互斥的,写操作过程中可以休眠让读操作进行。
(4)、使用索引。
(5)、使用联合索引
(6)、勿使用过多索引
(7)、增加查询条件
(8)、提前将字段的index映射好。
数据库设计上:
(1)、通过冗余换取查询速度
(2)、减少数据来提升查询速度。
(3)、避免大数据多表的联合查询。
4、数据库数据迁移问题
(1)、将表A重命名,重命名为A_temp。
(2)、创建新表A。
(3)、将表A_temp中的数据插入到新表A中。
(4)、删除表A_temp。
apk安装流程
一般
复制APK到/data/app目录下,解压并扫描安装包。
资源管理器解析APK里的资源文件。
解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录。
然后对dex文件进行优化,并保存在dalvik-cache目录下。
将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
安装完成后,发送广播。
apk打包流程
一般
aapt工具打包资源文件,生成R.java文件
aidl工具处理AIDL文件,生成对应的.java文件
javac工具编译Java文件,生成对应的.class文件
把.class文件转化成Davik VM支持的.dex文件
apkbuilder工具打包生成未签名的.apk文件
jarsigner对未签名.apk文件进行签名
zipalign工具对签名后的.apk文件进行对齐处理
序列化的方式
重要
Serializable是Java提供的一个序列化接口,是一个空接口,用于标示对象是否可以支持序列化,通过ObjectOutputStrean及ObjectInputStream实现序列化和反序列化的过程。注意可以为需要序列化的对象设置一个serialVersionUID,在反序列化的时候系统会检测文件中的serialVersionUID是否与当前类的值一致,如果不一致则说明类发生了修改,反序列化失败。因此对于可能会修改的类最好指定serialVersionUID的值。
Parcelable是Android特有的一个实现序列化的接口,在Parcel内部包装了可序列化的数据,可以在Binder中自由传输。序列化的功能由writeToParcel方法来完成,最终通过Parcel的一系列write方法完成。反序列化功能由CREAOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化的过程。
同步屏障机制(sync barrier)
一般
同步屏障可以通过MessageQueue.postSyncBarrier函数来设置。该方法发送了一个没有target的Message到Queue中,在next方法中获取消息时,如果发现没有target的Message,则在一定的时间内跳过同步消息,优先执行异步消息。再换句话说,同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。在创建Handler时有一个async参数,传true表示此handler发送的时异步消息。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保证UI绘制优先执行。
图片加载框架有哪些?他们之间的区别是什么?
重要
ImageLoader :
优点:
① 支持下载进度监听;
② 可以在 View 滚动中暂停图片加载;
③ 默认实现多种内存缓存算法这几个图片缓存都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等;
④ 支持本地缓存文件名规则定义;
缺点:
缺点在于不支持GIF图片加载, 缓存机制没有和http的缓存很好的结合, 完全是自己的一套缓存机制
Picasso:
优点:
① 自带统计监控功能,支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。
② 支持优先级处理
③ 支持延迟到图片尺寸计算完成加载
④ 支持飞行模式、并发线程数根据网络类型而变,手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数。
⑤ “无”本地缓存。Picasso 自己没有实现本地缓存,而由okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。
缺点:
于不支持GIF,默认使用ARGB_8888格式缓存图片,缓存体积大。
Glide:
优点:
① 图片缓存->媒体缓存 ,支持 Gif、WebP、缩略图。甚至是 Video。
② 支持优先级处理
③ 与 Activity/Fragment 生命周期一致,支持 trimMemory
④ 支持 okhttp、Volley。Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。
⑤ 内存友好,内存缓存更小图片,图片默认使用默认 RGB565 而不是 ARGB888
缺点:
清晰度差,但可以设置
Fresco:
优点:
① 图片存储在安卓系统的匿名共享内存, 而不是虚拟机的堆内存中,所以不会因为图片加载而导致oom, 同时也减少垃圾回收器频繁调用回收Bitmap导致的界面卡顿,性能更高.
② 渐进式加载JPEG图片, 支持图片从模糊到清晰加载
③ 图片可以以任意的中心点显示在ImageView, 而不仅仅是图片的中心.
④ JPEG图片改变大小也是在native进行的, 不是在虚拟机的堆内存, 同样减少OOM
⑤ 很好的支持GIF图片的显示
缺点:
框架较大, 影响Apk体积,使用较繁琐
平时开发中设计到哪些性能优化,你是从哪些地方来优化,你是通过什么工具来分析的?
重要
笼统的说:就是让App反应更快,使用更稳,流量、电量更省,apk更小。
具体的说:省电优化、内存优化、网络优化、图片优化、UI优化。
更快:使用时避免出现卡顿,响应速度快,减少用户等待的时间,满足用户期望。
UI优化:
分析工具:Systrace
(1)减少层级,合理使用 RelativeLayout 和 LinerLayout,合理使用Merge,Include。
(2)提高显示速度,使用 ViewStub,它是一个看不见的、不占布局位置、占用资源非常小的视图对象。
(3)布局复用,可以通过标签来提高复用。
(4)尽可能少用wrap_content,wrap_content 会增加布局 measure 时计算成本,在已知宽高为固定值时,不用wrap_content 。
(5)删除控件中无用的属性。
更稳:减低 Crash 率和 ANR 率,不要在用户使用过程中崩溃和无响应。
(1)增加相应的判断,以及异常处理。
(2)避免在主线程做耗时操作。
更省:节省流量和耗电,节约内存,减少用户使用成本,避免使用时导致手机发烫。
耗电分析工具:Battery Historian
(1)避免浮点运算。
(2)根据客户端图片的大小要求叫UI做相应大小的图提供给服务器,避免过大消耗更多流量和电量。
(3)不用的广播,服务记得及时关闭。
内存分析工具:Memory Monitor
(1)对象引用:强引用、软引用、弱引用、虚引用四种引用类型,根据业务需求合理使用不同,选择不同的引用类型。
(2)减少不必要的内存开销:注意自动装箱,增加内存复用,比如有效利用系统自带的资源、视图复用、对象池、Bitmap对象的复用。
(3)使用最优的数据类型:比如针对数据类容器结构,可以使用ArrayMap数据结构,避免使用枚举类型,使用缓存Lrucache等。
(4)图片内存优化:点9图减少图片大小以及可以设置位图规格,根据采样因子做压缩,用一些图片缓存方式对图片进行管理等。
更小:安装包小可以降低用户的安装成本。
(1)做混淆优化代码。
(2)删除无用的代码及图片相应的本地库。
(3)Lint优化。
(4)zip压缩。
Xutils, OKhttp, Volley, Retrofit对比
重要
Xutils这个框架非常全面,可以进行网络请求,可以进行图片加载处理,可以数据储存,还可以对view进行注解,使用这个框架非常方便,但是缺点也是非常明显的,使用这个项目,会导致项目对这个框架依赖非常的严重,一旦这个框架出现问题,那么对项目来说影响非常大的。、
OKhttp:Android开发中是可以直接使用现成的api进行网络请求的。就是使用HttpClient,HttpUrlConnection进行操作。okhttp针对Java和Android程序,封装的一个高性能的http请求库,支持同步,异步,而且okhttp又封装了线程池,封装了数据转换,封装了参数的使用,错误处理等。API使用起来更加的方便。但是我们在项目中使用的时候仍然需要自己在做一层封装,这样才能使用的更加的顺手。
Volley:Volley是Google官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley里面也封装了ImageLoader,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,稍复杂点的需求还是需要用到专门的图片加载框架。Volley也有缺陷,比如不支持post大数据,所以不适合上传文件。不过Volley设计的初衷本身也就是为频繁的、数据量小的网络请求而生。
Retrofit:Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格, 并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。
Volley VS OkHttp
Volley的优势在于封装的更好,而使用OkHttp你需要有足够的能力再进行一次封装。而OkHttp的优势在于性能更高,因为 OkHttp基于NIO和Okio ,所以性能上要比 Volley更快。IO 和 NIO这两个都是Java中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作这种是最简单的也叫阻塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是 NIO 的方式,非阻塞式, 所以NIO当然要比IO的性能要好了,而 Okio是 Square 公司基于IO和NIO基础上做的一个更简单、高效处理数据流的一个库。理论上如果Volley和OkHttp对比的话,更倾向于使用 Volley,因为Volley内部同样支持使用OkHttp,这点OkHttp的性能优势就没了, 而且 Volley 本身封装的也更易用,扩展性更好些。
OkHttp VS Retrofit
毫无疑问,Retrofit 默认是基于 OkHttp 而做的封装,这点来说没有可比性,肯定首选 Retrofit。
Volley VS Retrofit
这两个库都做了不错的封装,但Retrofit解耦的更彻底,尤其Retrofit2.0出来,Jake对之前1.0设计不合理的地方做了大量重构, 职责更细分,而且Retrofit默认使用OkHttp,性能上也要比Volley占优势,再有如果你的项目如果采用了RxJava ,那更该使用 Retrofit 。所以这两个库相比,Retrofit更有优势,在能掌握两个框架的前提下该优先使用 Retrofit。但是Retrofit门槛要比Volley稍高些,要理解他的原理,各种用法,想彻底搞明白还是需要花些功夫的,如果你对它一知半解,那还是建议在商业项目使用Volley吧。
Universal-ImageLoader,Picasso,Fresco,Glide对比
重要
Fresco 是 Facebook 推出的开源图片缓存工具,主要特点包括:两个内存缓存加上 Native 缓存构成了三级缓存,
优点:
1. 图片存储在安卓系统的匿名共享内存, 而不是虚拟机的堆内存中, 图片的中间缓冲数据也存放在本地堆内存, 所以, 应用程序有更多的内存使用, 不会因为图片加载而导致oom, 同时也减少垃圾回收器频繁调用回收 Bitmap 导致的界面卡顿, 性能更高。
2. 渐进式加载 JPEG 图片, 支持图片从模糊到清晰加载。
3. 图片可以以任意的中心点显示在 ImageView, 而不仅仅是图片的中心。
4. JPEG 图片改变大小也是在 native 进行的, 不是在虚拟机的堆内存, 同样减少 OOM。
5. 很好的支持 GIF 图片的显示。
缺点:
1. 框架较大, 影响 Apk 体积
2. 使用较繁琐
Universal-ImageLoader:(估计由于HttpClient被Google放弃,作者就放弃维护这个框架)
优点:
1.支持下载进度监听
2.可以在 View 滚动中暂停图片加载,通过 PauseOnScrollListener 接口可以在 View 滚动中暂停图片加载。
3.默认实现多种内存缓存算法 这几个图片缓存都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等。
4.支持本地缓存文件名规则定义
Picasso 优点
1. 自带统计监控功能。支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。
2.支持优先级处理。每次任务调度前会选择优先级高的任务,比如 App 页面中 Banner 的优先级高于 Icon 时就很适用。
3.支持延迟到图片尺寸计算完成加载
4.支持飞行模式、并发线程数根据网络类型而变。 手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4,4g 为 3,3g 为 2。 这里 Picasso 根据网络类型来决定最大并发数,而不是 CPU 核数。
5.“无”本地缓存。无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。
Glide 优点
1. 不仅仅可以进行图片缓存还可以缓存媒体文件。Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存。
2. 支持优先级处理。
3. 与 Activity/Fragment 生命周期一致,支持 trimMemory。Glide 对每个 context 都保持一个 RequestManager,通过 FragmentTransaction 保持与 Activity/Fragment 生命周期一致,并且有对应的 trimMemory 接口实现可供调用。
4. 支持 okhttp、Volley。Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。
5. 内存友好。Glide 的内存缓存有个 active 的设计,从内存缓存中取数据时,不像一般的实现用 get,而是用 remove,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。内存缓存更小图片,Glide 以 url、view_width、view_height、屏幕的分辨率等做为联合 key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小与 Activity/Fragment 生命周期一致,支持 trimMemory。图片默认使用默认 RGB_565 而不是 ARGB_888,虽然清晰度差些,但图片更小,也可配置到 ARGB_888。
6.Glide 可以通过 signature 或不使用本地缓存支持 url 过期
ANR是什么?怎样避免和解决ANR
重要
Application Not Responding,即应用无响应
出现的原因有三种:
a)KeyDispatchTimeout(5 seconds)主要类型按键或触摸事件在特定时间内无响应
b)BroadcastTimeout(10 seconds)BoradcastReceiver在特定的时间内无法处理
c)ServiceTimeout(20 seconds)小概率类型Service在特定的时间内无法处理完成
避免ANR最核心的一点就是在主线程减少耗时操作。通常需要从那个以下几个方案下手:
a)使用子线程处理耗时IO操作
b)降低子线程优先级,使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同
c)使用Handler处理子线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程
d)Activity的onCreate和onResume回调中尽量避免耗时的代码
e)BroadcastReceiver中onReceiver代码也要尽量减少耗时操作,建议使用intentService处理。intentService是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题
图片优化
重要
(1)对图片本身进行操作。尽量不要使用setImageBitmap、setImageResource、BitmapFactory.decodeResource来设置一张大图,因为这些方法在完成decode后,
最终都是通过java层的createBitmap来完成的,需要消耗更多内存.
(2)图片进行缩放的比例,SDK中建议其值是2的指数值,值越大会导致图片不清晰。
(3)不用的图片记得调用图片的recycle()方法
Android内存泄露及管理
非常重要
(1)内存溢出(OOM)和内存泄露(对象无法被回收)的区别。
(2)引起内存泄露的原因
(3) 内存泄露检测工具 ------>LeakCanary
内存溢出 out of memory:是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。内存溢出通俗的讲就是内存不够用。
内存泄露 memory leak:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光
内存泄露原因:
一、Handler 引起的内存泄漏。
解决:将Handler声明为静态内部类,就不会持有外部类SecondActivity的引用,其生命周期就和外部类无关,
如果Handler里面需要context的话,可以通过弱引用方式引用外部类
二、单例模式引起的内存泄漏。
解决:Context是ApplicationContext,由于ApplicationContext的生命周期是和app一致的,不会导致内存泄漏
三、非静态内部类创建静态实例引起的内存泄漏。
解决:把内部类修改为静态的就可以避免内存泄漏了
四、非静态匿名内部类引起的内存泄漏。
解决:将匿名内部类设置为静态的。
五、注册/反注册未成对使用引起的内存泄漏。
注册广播接受器、EventBus等,记得解绑。
六、资源对象没有关闭引起的内存泄漏。
在这些资源不使用的时候,记得调用相应的类似close()、destroy()、recycler()、release()等方法释放。
七、集合对象没有及时清理引起的内存泄漏。
通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。
热修复的原理
重要
我们知道Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件,
而他们加载类的时候都需要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个
数组——DexPathList,是用来存放dex文件,当BaseDexClassLoader通过调用findClass方法时,实际上就是遍历数组,
找到相应的dex文件,找到,则直接将它return。而热修复的解决方法就是将新的dex添加到该集合中,并且是在旧的dex的前面,
所以就会优先被取出来并且return返回。
AIDL理解
重要
AIDL:
每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。而aidl就类似与两个进程之间的桥梁,使得两个进程之间可以进行数据的传输,跨进程通信有多种选择,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行。
Binde机制简单理解:
在Android系统的Binder机制中,是有Client,Service,ServiceManager,Binder驱动程序组成的,其中Client,service,Service Manager运行在用户空间,Binder驱动程序是运行在内核空间的。而Binder就是把这4种组件粘合在一块的粘合剂,其中核心的组件就是Binder驱动程序,Service Manager提供辅助管理的功能,而Client和Service正是在Binder驱动程序和Service Manager提供的基础设施上实现C/S 之间的通信。其中Binder驱动程序提供设备文件/dev/binder与用户控件进行交互,
Client、Service,Service Manager通过open和ioctl文件操作相应的方法与Binder驱动程序进行通信。而Client和Service之间的进程间通信是通过Binder驱动程序间接实现的。而Binder Manager是一个守护进程,用来管理Service,并向Client提供查询Service接口的能力。
APK打包及安装流程
重要
打包原理
Android的包文件APK分为两个部分:代码和资源,所以打包方面也分为资源打包和代码打包两个方面,这篇文章就来分析资源和代码的编译打包原理。
具体说来:
通过AAPT工具进行资源文件(包括AndroidManifest.xml、布局文件、各种xml资源等)的打包,生成R.java文件。
通过AIDL工具处理AIDL文件,生成相应的Java文件。
通过Javac工具编译项目源码,生成Class文件。
通过DX工具将所有的Class文件转换成DEX文件,该过程主要完成Java字节码转换成Dalvik字节码,压缩常量池以及清除冗余信息等工作。
通过ApkBuilder工具将资源文件、DEX文件打包生成APK文件。
利用KeyStore对生成的APK文件进行签名。
如果是正式版的APK,还会利用ZipAlign工具进行对齐处理,对齐的过程就是将APK文件中所有的资源文件举例文件的起始距离都偏移4字节的整数倍,这样通过内存映射访问APK文件的速度会更快。
3.2、安装流程
Android apk的安装过程主要氛围以下几步:
复制APK到/data/app目录下,解压并扫描安装包。
资源管理器解析APK里的资源文件。
解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录。
然后对dex文件进行优化,并保存在dalvik-cache目录下。
将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
安装完成后,发送广播。
可以使用下面的图表示: